classdef colonyCounter < matlab.apps.AppBase

    % Properties that correspond to app components
    properties (Access = public)
        ColonyCounterUIFigure          matlab.ui.Figure
        FilesListBoxLabel              matlab.ui.control.Label
        FilesListBox                   matlab.ui.control.ListBox
        LoadFilesButton                matlab.ui.control.Button
        GreenthresholdLabel            matlab.ui.control.Label
        greenThresholdSlider           matlab.ui.control.Slider
        ColonyNumbersPanel             matlab.ui.container.Panel
        CorrectRedCountEditFieldLabel  matlab.ui.control.Label
        CorrectRedCountEditField       matlab.ui.control.NumericEditField
        RedColoniesTextAreaLabel       matlab.ui.control.Label
        RedColoniesTextArea            matlab.ui.control.TextArea
        TotalColoniesTextAreaLabel     matlab.ui.control.Label
        TotalColoniesTextArea          matlab.ui.control.TextArea
        SavedataButton                 matlab.ui.control.Button
        PreferencesButton              matlab.ui.control.Button
    end

    
    properties (Access = private)
        
        plate_centroid %Center of the detected region
        pathname % Working directory
        
        setDefaultParamValues
    end
    
    properties (Access = public)
        plate_radius = 1000; %radius of a plate in pixels
        boundary_width = 10; %Ignore 50 pixel wide region on plate boundary
        colonySizeMax = 10000; % Maximum area of a colony in pixels
        colonySizeAvg = 1000; % Average colony area in pixels
        sizeMultiplier = 0.5 % Lower Limit on the area of red colonies
        plateImagefigure % Figure handle to display plate image
        L % Top-hat filtered L channeel
        RG % Top-hat filtered rg channeel
        masked_im % Masked RGB image
        masked_lab %L*A*B image
        suppress_green % Threshold value to be subtracted from the green channel
        compiledData
        compiledimData
        redColonies
        allColonies
        redprops
        allprops
    end
    
    methods (Access = public)
        
        function [logicalimages, imageproperties, colonySizeAvg] = findandDisplay(app, L, rg,...
                suppress_green, colonySizeMax, sizeMultiplier, figure_handle)
            % Function to find colonies and plot their outlines on the figure_handle
            tic
            [whiteColonies, redColonies, allColonies, colonySizeAvg] = separateColors(app, L,rg, suppress_green,...
                                                                                      colonySizeMax, sizeMultiplier);
            
            [redColoniesLarge, redColoniesSmall, redColonieslogical] = segmentColonies(app, redColonies, 1.5*colonySizeAvg);
            [allColoniesLarge, allColoniesSmall, allColonieslogical] = segmentColonies(app, allColonies, colonySizeAvg);
            [whiteColoniesLarge, whiteColoniesSmall, whiteColonieslogical] = segmentColonies(app, allColonies, colonySizeAvg);
            toc
            figure(figure_handle);
            hold('on');
            whiteprops = regionprops('table', whiteColonieslogical, 'Centroid', 'MajorAxisLength', 'MinorAxisLength');
            redprops =   regionprops('table', (redColonieslogical), 'Centroid', 'MajorAxisLength', 'MinorAxisLength');
            allprops =   regionprops('table', (allColonieslogical), 'Centroid', 'MajorAxisLength', 'MinorAxisLength');
            
            
            centers = allprops.Centroid;
            diameters = mean([allprops.MajorAxisLength allprops.MinorAxisLength],2);
            radii = diameters/2;
            viscircles(centers,radii, 'Color', 'b', 'linewidth', 0.5);
            centers = redprops.Centroid;
            diameters = mean([redprops.MajorAxisLength redprops.MinorAxisLength],2);
            radii = diameters/2;
            viscircles(centers,radii, 'Color', 'r', 'linewidth', 0.5);
            %             centers = sectoredprops.Centroid;
            %             diameters = mean([sectoredprops.MajorAxisLength sectoredprops.MinorAxisLength],2);
            %             radii = diameters/2;
            %             viscircles(centers,radii, 'Color', 'g', 'linewidth', 0.5);
            hold('off')
            logicalimages = cat(3, redColonieslogical,allColonieslogical);
            imageproperties = {redprops, allprops};

        end
        function [maskedImage, norm_im, binImage] = createMask(app, input_im, colonySizeMax)
            size_low = 400;
            norm_im = input_im - mean(input_im(:));
            norm_im(norm_im<0)=0;
            norm_im = norm_im/max(norm_im(:));
            
            binImage = imbinarize(norm_im); %figure(44); imagesc(binImage)
            binImage = eliminateBoundaryColonies(app, binImage,...
                app.plate_radius, app.plate_centroid,...
                app.boundary_width);
            
            cc = bwconncomp(binImage);
            cc_size = cellfun(@numel, cc.PixelIdxList);
            
            for i=1:cc.NumObjects
                if (cc_size(i) < size_low || cc_size(i) > colonySizeMax)
                    binImage(cc.PixelIdxList{i})= 0;
                end
            end
            
            maskedImage = input_im .* double(binImage);
        end
        
        function [whiteColonies, redColonies, allColonies, colonySizeAvg] = separateColors(app, L, rg, suppress_rg, colonySizeMax, sizeMultiplier)

            [maskL, ~, ~] = createMask(app, L, colonySizeMax);
            allColonies = imbinarize(maskL, 'adaptive');
            
            maskRG = rg  - suppress_rg;
            maskRG(maskRG < 0) = 0;
            redColonies = imbinarize(maskRG);
            
            cc = bwconncomp(allColonies);
            ccstats = regionprops(cc, 'Area');
            colonySizeAvg = mean([ccstats.Area]);
            
            rcc = bwconncomp(redColonies);
            redstats = regionprops(rcc, 'Area', 'MinorAxisLength', 'MajorAxisLength');
            skew = [redstats.MajorAxisLength]./[redstats.MinorAxisLength];
            idx = find([redstats.Area] > (sizeMultiplier*colonySizeAvg) & skew < 5);
            redColonies = ismember(labelmatrix(rcc), idx);
            %             figure(45); imagesc(redColonies);
            
            whiteColonies = (allColonies - redColonies);
            whiteColonies(whiteColonies<0)= 0;
        end
        
        function [segmentedLarge, smallColonies, logicalsegIm] = segmentColonies(app, inputIm, colonySizeAvg)
            % Only large regions should be segmented.
            % Separate connected components by size.
            cc = bwconncomp(inputIm);
            ccstats = regionprops(cc, 'Area', 'MinorAxisLength', 'MajorAxisLength');
            skew = [ccstats.MajorAxisLength]./[ccstats.MinorAxisLength];
            idx = find([ccstats.Area]>colonySizeAvg & (skew < 0.85 | skew > 1.25));
            largeColonies = ismember(labelmatrix(cc), idx);  %figure(43); imagesc(largeColonies)
            smallColonies = xor(inputIm, largeColonies);
            
            adist = bwdist(~largeColonies);
            adist = -adist;
            segmentedLarge = watershed(adist);
            segmentedLarge(~largeColonies)=0;
            seglargeIm = logical(segmentedLarge);
            %             figure(44); imagesc(seglargeIm)
            
            h = strel('disk', 5);
            seglargeIm = imerode(seglargeIm, h);
            logicalsegIm = or(seglargeIm, smallColonies);
            logicalsegIm = imopen(logicalsegIm, h);
            
        end
        
        
        function cleanbinaryIm = eliminateBoundaryColonies(app, binaryIm, plate_radius, center, boundary_width)
            inner_radius = plate_radius - boundary_width;
            imageSizeX = size(binaryIm, 2);
            imageSizeY = size(binaryIm, 1);
            [columnsInImage, rowsInImage] = meshgrid(1:imageSizeX, 1:imageSizeY);
            % Next create the circle in the image.
            mask = (rowsInImage - center(2)).^2 ...
                + (columnsInImage - center(1)).^2 <= plate_radius.^2;
            mask_small = (rowsInImage - center(2)).^2 ...
                + (columnsInImage - center(1)).^2 <= (inner_radius).^2;
            mask = mask - mask_small;
            edgeColonies = mask .* binaryIm;
            cleanbinaryIm = binaryIm - edgeColonies;
            
        end
        
    end
    
    methods (Access = private)
        function [mask, centroid] = createplateMask(app, rgb_im, plateradius)
            rbinary = imbinarize(rgb_im(:,:,3)); % blue channel works the best
            rbinary = imfill(rbinary, 'holes');
            rbinary_cc = bwconncomp(rbinary);
            rbinary_props = regionprops(rbinary_cc);
            idx = find([rbinary_props.Area] > 350000);
            % Create a logical image of a circle with specified
            % diameter, center, and image size.
            % First create the image.
            imageSizeX = size(rgb_im, 2);
            imageSizeY = size(rgb_im, 1);
            [columnsInImage, rowsInImage] = meshgrid(1:imageSizeX, 1:imageSizeY);
            % Next create the circle in the image.
            centerX = rbinary_props(idx(end)).Centroid(1);
            centerY = rbinary_props(idx(end)).Centroid(2);
            centroid = [centerX, centerY];
            mask = (rowsInImage - centerY).^2 ...
                + (columnsInImage - centerX).^2 <= plateradius.^2;
        end
    
        
        
        function [sectoredColonies] = findSectoredColonies(app, whiteColonies, redColonies)
            sectoredColonies = and(whiteColonies, redColonies);
            
        end
    end

    % Callbacks that handle component events
    methods (Access = private)

        % Value changed function: FilesListBox
        function FilesListBoxValueChanged(app, event)
            file_selected = app.FilesListBox.Value;
            full_filename = strcat(app.pathname, '/', file_selected);
            rgb_im = imread(full_filename);
            
            % Threshold the red channel to define a circular roi.
            [plate_mask, app.plate_centroid] = createplateMask(app, rgb_im, app.plate_radius);
            app.masked_im = uint8(plate_mask) .* rgb_im;
            app.plate_centroid = round(app.plate_centroid);
            
            app.masked_im = app.masked_im(app.plate_centroid(2)-app.plate_radius:...
                                          app.plate_centroid(2)+app.plate_radius,...
                                          app.plate_centroid(1)-app.plate_radius:...
                                          app.plate_centroid(1)+app.plate_radius, :);
            
            app.masked_lab = rgb2lab(app.masked_im);
            app.L = app.masked_lab(:,:,1);
            app.RG = app.masked_lab(:,:,2);
            
            if isgraphics(app.plateImagefigure)
                figure(app.plateImagefigure);
            else
                figure('Name', app.FilesListBox.Value);
                app.plateImagefigure = gcf;
            end
            imshow(app.masked_im)
            
            hbkg = strel('disk', 100);
            app.L = medfilt2(app.L);
            app.RG = medfilt2(app.RG);
            app.L = double(imtophat(app.L, hbkg));
            app.RG = double(imtophat(app.RG, hbkg));
            app.suppress_green = 22;
            app.greenThresholdSlider.Value = app.suppress_green;
            app.CorrectRedCountEditField.Value = 0;
            [logicalimages,  imageproperties, app.colonySizeAvg] = findandDisplay(app, app.L, ...
                app.RG, app.suppress_green,...
                app.colonySizeMax, app.sizeMultiplier, app.plateImagefigure);
            
            index = find(ismember(app.FilesListBox.Items, app.FilesListBox.Value));
            app.compiledData(index).fullfilename = full_filename;
            app.compiledimData(index).redColonies = logicalimages(:,:,1);
            app.compiledimData(index).allColonies = logicalimages(:,:,2);
            app.compiledimData(index).redprops = imageproperties{1};
            app.compiledimData(index).allprops = imageproperties{2};
            app.compiledData(index).numredColonies = height(app.compiledimData(index).redprops);
            app.compiledData(index).numallColonies = height(app.compiledimData(index).allprops);
            app.compiledData(index).correction_red = app.CorrectRedCountEditField.Value;
            app.compiledData(index).suppress_green = app.suppress_green;
            app.compiledData(index).sizeMultiplier = app.sizeMultiplier;
            app.RedColoniesTextArea.Value = num2str(app.compiledData(index).numredColonies);
            app.TotalColoniesTextArea.Value = num2str(app.compiledData(index).numallColonies);
            
        end

        % Button pushed function: LoadFilesButton
        function LoadFilesButtonPushed(app, event)
            app.pathname = uigetdir;
            % get what is inside the folder
            Infolder = dir(app.pathname);
            % Initialize the cell of string that will be update in the list box
            filelist = string(length(Infolder));
            % Loop on every element in the folder and update the list
            index = 0;
            for i = 1:length(Infolder)
                index = index +1;
                if Infolder(i).isdir==0 && strcmpi(Infolder(i).name((end-2):end), 'jpg')
                    filelist(index) = Infolder(i).name;
                else
                    index = index -1;
                end
            end
            app.FilesListBox.Items = filelist;
        end

        % Value changed function: greenThresholdSlider
        function greenThresholdSliderValueChanged(app, event)
            figure(app.plateImagefigure);
            imshow(app.masked_im);
            app.suppress_green = app.greenThresholdSlider.Value;
            [logicalimages,  imageproperties, app.colonySizeAvg] = findandDisplay(app, app.L, ...
                app.RG, app.suppress_green,...
                app.colonySizeMax, app.sizeMultiplier, app.plateImagefigure);
            app.redColonies = logicalimages(:,:,1);
            app.allColonies = logicalimages(:,:,2);
            app.redprops = imageproperties{1};
            app.allprops = imageproperties{2};
            
            index = find(ismember(app.FilesListBox.Items, app.FilesListBox.Value));
            app.compiledData(index).fullfilename = app.FilesListBox.Value;
            app.compiledimData(index).redColonies = logicalimages(:,:,1);
            app.compiledimData(index).allColonies = logicalimages(:,:,2);
            app.compiledimData(index).redprops = imageproperties{1};
            app.compiledimData(index).allprops = imageproperties{2};
            app.compiledData(index).numredColonies = height(app.compiledimData(index).redprops);
            app.compiledData(index).numallColonies = height(app.compiledimData(index).allprops);
            app.compiledData(index).suppress_green = app.suppress_green;
            app.RedColoniesTextArea.Value = num2str(app.compiledData(index).numredColonies);
            app.TotalColoniesTextArea.Value = num2str(app.compiledData(index).numallColonies);
        end

        % Button pushed function: SavedataButton
        function SavedataButtonPushed(app, event)
            wdir = pwd;
            cd(app.pathname);
            suggestedName = inputdlg('Save as:', 'Enter a file name', 1);
            mat_filename = suggestedName{1};
            compiledData = app.compiledData;
            compiledimData = app.compiledimData;
            suppress_green = app.suppress_green;
            sizeMultiplier = app.sizeMultiplier;
            correction_red = compiledData.correction_red;
            plate_center = app.plate_centroid;
            save(mat_filename, 'compiledimData', 'compiledData', ...
                'plate_center', 'suppress_green');
            assignin('base', 'data', compiledData);
            assignin('base', 'imdata', compiledimData);
            
            xls_filename = strcat(mat_filename, '.xls');
            xlswrite(xls_filename, {'full_filename', 'suppress_green', 'sizeMultiplier', 'numallColonies', 'numredColonies', ...
                'correction_red'}, 'Analysis', 'A1');
            xlswrite(xls_filename, {app.compiledData.fullfilename}', 'Analysis', 'A2');
            xlswrite(xls_filename, [app.compiledData.suppress_green]', 'Analysis', 'B2');
            xlswrite(xls_filename, [app.compiledData.sizeMultiplier]', 'Analysis', 'C2');
            xlswrite(xls_filename, [app.compiledData.numallColonies]', 'Analysis', 'D2');
            xlswrite(xls_filename, [app.compiledData.numredColonies]', 'Analysis', 'E2');
            xlswrite(xls_filename, [app.compiledData.correction_red]', 'Analysis', 'F2');
            cd(wdir);
        end

        % Value changed function: CorrectRedCountEditField
        function CorrectRedCountEditFieldValueChanged(app, event)
            index = find(ismember(app.FilesListBox.Items, app.FilesListBox.Value));
            app.compiledData(index).correction_red = app.CorrectRedCountEditField.Value;
            
        end

        % Button pushed function: PreferencesButton
        function PreferencesButtonPushed(app, event)
            app.setDefaultParamValues = setDefaultParamValues(app, []);
            app.PreferencesButton.Enable = 'off';
        end
    end

    % Component initialization
    methods (Access = private)

        % Create UIFigure and components
        function createComponents(app)

            % Create ColonyCounterUIFigure and hide until all components are created
            app.ColonyCounterUIFigure = uifigure('Visible', 'off');
            app.ColonyCounterUIFigure.Position = [100 100 252 425];
            app.ColonyCounterUIFigure.Name = 'Colony Counter';

            % Create FilesListBoxLabel
            app.FilesListBoxLabel = uilabel(app.ColonyCounterUIFigure);
            app.FilesListBoxLabel.HorizontalAlignment = 'right';
            app.FilesListBoxLabel.Position = [19 354 30 22];
            app.FilesListBoxLabel.Text = 'Files';

            % Create FilesListBox
            app.FilesListBox = uilistbox(app.ColonyCounterUIFigure);
            app.FilesListBox.ValueChangedFcn = createCallbackFcn(app, @FilesListBoxValueChanged, true);
            app.FilesListBox.Position = [64 248 166 130];

            % Create LoadFilesButton
            app.LoadFilesButton = uibutton(app.ColonyCounterUIFigure, 'push');
            app.LoadFilesButton.ButtonPushedFcn = createCallbackFcn(app, @LoadFilesButtonPushed, true);
            app.LoadFilesButton.Position = [14 391 100 22];
            app.LoadFilesButton.Text = 'Load Files...';

            % Create GreenthresholdLabel
            app.GreenthresholdLabel = uilabel(app.ColonyCounterUIFigure);
            app.GreenthresholdLabel.HorizontalAlignment = 'center';
            app.GreenthresholdLabel.Position = [14 178 56 55];
            app.GreenthresholdLabel.Text = {'Green '; 'threshold'};

            % Create greenThresholdSlider
            app.greenThresholdSlider = uislider(app.ColonyCounterUIFigure);
            app.greenThresholdSlider.Limits = [0 30];
            app.greenThresholdSlider.ValueChangedFcn = createCallbackFcn(app, @greenThresholdSliderValueChanged, true);
            app.greenThresholdSlider.Position = [78 220 150 3];

            % Create ColonyNumbersPanel
            app.ColonyNumbersPanel = uipanel(app.ColonyCounterUIFigure);
            app.ColonyNumbersPanel.Title = 'Colony Numbers';
            app.ColonyNumbersPanel.Position = [1 56 253 123];

            % Create CorrectRedCountEditFieldLabel
            app.CorrectRedCountEditFieldLabel = uilabel(app.ColonyNumbersPanel);
            app.CorrectRedCountEditFieldLabel.HorizontalAlignment = 'center';
            app.CorrectRedCountEditFieldLabel.Position = [141 10 45 42];
            app.CorrectRedCountEditFieldLabel.Text = {'Correct'; 'Red'; 'Count'};

            % Create CorrectRedCountEditField
            app.CorrectRedCountEditField = uieditfield(app.ColonyNumbersPanel, 'numeric');
            app.CorrectRedCountEditField.ValueChangedFcn = createCallbackFcn(app, @CorrectRedCountEditFieldValueChanged, true);
            app.CorrectRedCountEditField.HorizontalAlignment = 'center';
            app.CorrectRedCountEditField.Position = [188 15 57 37];

            % Create RedColoniesTextAreaLabel
            app.RedColoniesTextAreaLabel = uilabel(app.ColonyNumbersPanel);
            app.RedColoniesTextAreaLabel.HorizontalAlignment = 'center';
            app.RedColoniesTextAreaLabel.Position = [137 67 52 28];
            app.RedColoniesTextAreaLabel.Text = {'Red'; 'Colonies'};

            % Create RedColoniesTextArea
            app.RedColoniesTextArea = uitextarea(app.ColonyNumbersPanel);
            app.RedColoniesTextArea.HorizontalAlignment = 'center';
            app.RedColoniesTextArea.Position = [188 62 57 38];
            app.RedColoniesTextArea.Value = {'0'};

            % Create TotalColoniesTextAreaLabel
            app.TotalColoniesTextAreaLabel = uilabel(app.ColonyNumbersPanel);
            app.TotalColoniesTextAreaLabel.HorizontalAlignment = 'center';
            app.TotalColoniesTextAreaLabel.Position = [1 67 52 28];
            app.TotalColoniesTextAreaLabel.Text = {'Total'; 'Colonies'};

            % Create TotalColoniesTextArea
            app.TotalColoniesTextArea = uitextarea(app.ColonyNumbersPanel);
            app.TotalColoniesTextArea.HorizontalAlignment = 'center';
            app.TotalColoniesTextArea.Position = [60 62 61 38];
            app.TotalColoniesTextArea.Value = {'0'};

            % Create SavedataButton
            app.SavedataButton = uibutton(app.ColonyCounterUIFigure, 'push');
            app.SavedataButton.ButtonPushedFcn = createCallbackFcn(app, @SavedataButtonPushed, true);
            app.SavedataButton.Position = [78 15 100 22];
            app.SavedataButton.Text = 'Save data';

            % Create PreferencesButton
            app.PreferencesButton = uibutton(app.ColonyCounterUIFigure, 'push');
            app.PreferencesButton.ButtonPushedFcn = createCallbackFcn(app, @PreferencesButtonPushed, true);
            app.PreferencesButton.Position = [136 391 100 22];
            app.PreferencesButton.Text = 'Preferences';

            % Show the figure after all components are created
            app.ColonyCounterUIFigure.Visible = 'on';
        end
    end

    % App creation and deletion
    methods (Access = public)

        % Construct app
        function app = colonyCounter

            % Create UIFigure and components
            createComponents(app)

            % Register the app with App Designer
            registerApp(app, app.ColonyCounterUIFigure)

            if nargout == 0
                clear app
            end
        end

        % Code that executes before app deletion
        function delete(app)

            % Delete UIFigure when app is deleted
            delete(app.ColonyCounterUIFigure)
        end
    end
end