gusucode.com > vision工具箱matlab源码程序 > vision/+vision/+internal/+ocr/+tool/ImageSet.m

    % This class is for internal use only and may change in the future.

% ImageSet Stores images and associated label data for ocr training
% session.

% Copyright 2015 The MathWorks, Inc.

classdef ImageSet < handle
    
    properties
        % ImageStruct 
        %   Contains following fields:
        %     1. imageFilename
        %     2. objectBoundingBoxes
        %     3. text
        %     4. ImageIcon
        %     5. ImageLabel        
        ImageStruct                       
        
        % AreThumbnailsGenerated 
        %   Logical array that stores whether or not an icon has been
        %   generated for each image in the ImageStruct.        
        AreThumbnailsGenerated         
        AreCharThumbnailsGenerated
        
        % SelectedBoxes 
        %   List of selected boxes per character montage.
        SelectedBoxes
        
        % Selection Map - maps selection to image index and box index. This
        % in only updated when new char montage is drawn so we can remember
        % what's going in current setting.
        SelectionMap
        
        % CharMap
        %   Map relating character label to images and boxes where found.
        CharMap
        CharView = true;
        CharIcons;
        DelayRemoval = {};
        DelayRemovalSelectedBoxes = [];
        
        % RemoveMode Determines whether or not remove callback should move
        % to unknown or delete. First version of app supports move. This is
        % left here to easily add remove later.
        RemoveMode = 'move';
        
        %Font - Choose a font that supports unicode across multiple
        %       platforms. Used for char browser display.
        Font
         
        % TextDetectionParams - Text detetion parameter struct array.
        % Contains ROI, min/max aspect ratio, and area threshold
        % parameters.
        TextDetectionParams
    end
    
    %----------------------------------------------------------------------
    properties(Dependent)
        % Count - Number of images loaded in training session.
        Count
    end
    
    %----------------------------------------------------------------------
    properties(Access=private, Hidden)        
        Version = ver('vision');        
    end
    
    methods        
        %------------------------------------------------------------------
        % Constructor
        %----------------------------------------------------------------        
        function this = ImageSet()            
            this.ImageStruct = [];           
            this.AreThumbnailsGenerated = [];            
            this.AreCharThumbnailsGenerated = []; 
            this.SelectedBoxes = [];
            this.CharMap = containers.Map;
            this.TextDetectionParams = [];
        end
                
        %------------------------------------------------------------------
        % Returns true if the session already has images else returns false
        %------------------------------------------------------------------        
        function ret = hasAnyImages(this)                        
            ret = ~isempty(this.ImageStruct);                        
        end
        
        %------------------------------------------------------------------
        % Returns true if the session characters
        %------------------------------------------------------------------        
        function ret = hasAnyCharacters(this)                       
             ret = ~isempty(this.CharMap);                               
        end
        
        %------------------------------------------------------------------
        function val = get.Count(this)
            val = numel(this.ImageStruct);
        end
        
        %------------------------------------------------------------------
        function imagesWithoutText = boxAndLabelImages(this, start, ocrOptions, autoLabel)
            if this.hasAnyImages                                
                
                progressBar = vision.internal.uitools.ImageSetProgressBar(...
                    this.Count-start+1, ...
                    'vision:ocrTrainer:ExtractSamplesTitle',...
                    'vision:ocrTrainer:ExtractSamplesMsg');
                
                k = 1;
                for i = start:this.Count      
                    update(progressBar);
                    
                    % image may disappear off disk. treat this as a
                    % non-text detection event. an error dialog is issued
                    % earlier.
                    try
                    this.ImageStruct(i).objectBoundingBoxes = detectTextInImage(this, i);                    
                    catch
                        imageHasText(k) = false;
                        k = k + 1;
                        continue
                    end
                    
                    if autoLabel % manual vs. auto label
                        imageHasText(k) = this.autoLabelImage(i, ocrOptions);
                    else
                        nboxes = size(this.getBoxes(i),1);
                        if nboxes > 1
                            imageHasText(k) = true;         
                    else
                            imageHasText(k) = false;
                        end
                        this.ImageStruct(i).text = repmat({char(0)},nboxes,1);                                                                                                               
                    end
                    
                    k = k + 1;
                end   
                
                % remove images without text                
                noText = false(1,this.Count);
                noText(start:end) = ~imageHasText;
                
                % return images that were removed
                imagesWithoutText = {this.ImageStruct(noText).imageFilename};
                
                this.removeImage(noText);                               
                
                % add image text data to char map
                for i = start:this.Count
                    addToCharMap(this, i);                                        
                end                
                               
                this.initialize();
                
                delete(progressBar);
            end
        end
        
        %------------------------------------------------------------------
        % Initialize SelectedBoxes, char icons, and AreCharThumbnailsGenerated
        %------------------------------------------------------------------
        function initialize(this)
            % initialize selected boxes
            this.SelectedBoxes = cell(1, length(this.CharMap));
            k = this.CharMap.keys;
            for i = 1:length(k)
                n = this.getNumCharSamples(k{i});
                d = sparse(false(1,n));
                d(1) = true; % first box selected
                this.SelectedBoxes{i} = d;
            end
            
            % initialize char icons to placeholders
            this.CharIcons = this.generateCharPlaceHolders(length(this.CharMap));
            
            this.AreCharThumbnailsGenerated = false(1,length(this.CharMap));
        end
        
        %------------------------------------------------------------------
        function addToCharMap(this, whichImage)  
            
            % get indices of the non-deleted text (marked as -1);
            idx = getValidIndices(this, whichImage);
            
            % get indices to actual position in the bbox array.
            values = find(idx); 
            
            labels = this.ImageStruct(whichImage).text(idx);
            
            map = containers.Map;
            
            for idx = 1:numel(labels)
                
                key = labels{idx};                                                                    
                if isKey(map, key)   
                    map(key) = [map(key) values(idx)];                    
                else
                    map(key) = values(idx);
                end
                
            end
          
            keys = map.keys;
            for i = 1:numel(keys) 
                key = keys{i};                                                             
                              
                s.imageIndex = whichImage;
                s.bboxIndex  = map(key);
                            
                if isKey(this.CharMap, key)                    
                    this.CharMap(key) = [this.CharMap(key) s];
                else                    
                    this.CharMap(key) = s;
                end
            end               
            
        end
        
        
        %------------------------------------------------------------------
        function resetSelectionMap(this, label)
            % This is reset everytime a new character montage is drawn. It
            % maps the selected boxes back to the image
            value = this.CharMap(label);
            
            this.SelectionMap = {};
            for i = 1:numel(value);
                b = value(i).bboxIndex(:);
                a = repelem(value(i).imageIndex, numel(b), 1);
                oldLabel = repmat({label}, numel(b), 1);
                newLabel = cell(numel(b), 1); 
                data = [num2cell([a b]) oldLabel newLabel];
                this.SelectionMap = [this.SelectionMap; data];
            end  
                        
        end
       
        %------------------------------------------------------------------
        function imageHasText = autoLabel(this, start, ocrOptions)
        
            if this.hasAnyImages                                
                imageHasText = false(1,this.Count-start+1);
                
                progressBar = vision.internal.uitools.ImageSetProgressBar(...
                    this.Count, ...
                    'vision:ocrTrainer:ExtractSamplesTitle',...
                    'vision:ocrTrainer:ExtractSamplesMsg');
                
                for i = start:this.Count
                    update(progressBar);
                    imageHasText(i) = this.autoLabelImage(i, ocrOptions);
                end                                               
                
            end                        
        end
        
        %------------------------------------------------------------------       
        function detectText(this, start)
            
            if this.hasAnyImages                                
                
                for i = start:this.Count                    
                    this.ImageStruct(i).objectBoundingBoxes = detectTextInImage(this, i);                    
                end
                
            end
        end
        
        %------------------------------------------------------------------       
        % Initialize selected boxes to be the first one. 
        %------------------------------------------------------------------
        function initializeSelectedBoxes(this, start)
            if this.hasAnyImages                                
                
                for i = start:this.Count    
                    hasBoxes = ~isempty(this.ImageStruct(i).objectBoundingBoxes);
                    if hasBoxes                         
                        this.SelectedBoxes{i} = 1;
                    else
                        this.SelectedBoxes{i} = [];
                    end
                end
                
            end
        end
        
        %------------------------------------------------------------------
        function [bboxes, out] = findText(this, I, params)
            
            roi = params.ROI;
            
            doCrop = ~isempty(roi);
            
            [m, n, ~] = size(I);
            
            if doCrop % convert to pixel coords
                roi(:, 1:2) = roi(:,1:2) + 0.5;
                
                % clip to within image in case 
                roi(:, 1:2) = max(roi(:,1:2),1);                
                roi(:, 3)   = min(roi(:,3), n);
                roi(:, 4)   = min(roi(:,4), m);
            end
            
            I = vision.internal.detector.cropImageIfRequested(I, roi, doCrop);
            
            bw = binarizeImage(this, I);                     

            if nargout == 2
                returnMask = true;
                stats = {'BoundingBox', 'PixelIdxList'};
            else
                returnMask = false;
                stats = {'BoundingBox'};
            end
            
            cc = bwconncomp(bw);
            stats = regionprops(cc, stats{:});
            bboxes = vertcat(stats(:).BoundingBox);
            
            if isempty(bboxes)
                bboxes = zeros(0,4);
                out = false(m,n);                
                return
            end
                       
            bboxes(:,1:2) = bboxes(:,1:2) + 0.5; % to pixel coords
           
            [M, N] = size(bw);
            
            % Remove bounding boxes that contain the entire image as these don't make sense.                  
            toRemove = all(bsxfun(@eq, round(bboxes(:,[4 3])), [M N]),2);            
            
            % Remove extremely small boxes            
            area = prod(bboxes(:,[3 4]), 2);
            toRemove = toRemove | area < params.MinArea;                       
            
            % Remove boxes with extreme aspect ratios
            aspectRatio = bboxes(:,3)./bboxes(:,4);
            toRemove = toRemove | ...
                aspectRatio > params.MaxAspectRatio | ...
                aspectRatio < params.MinAspectRatio;                       
            
            % merge overlapping bounding boxes            
            overlap = bboxOverlapRatio(bboxes,bboxes, 'min');
            
            % remove boxes that overlap more than 5 other boxes
            overlap(toRemove,:) = 0; % do not count those boxes that are to be removed.
            numChildren = sum(overlap > 0) - 1; % -1 for self
            
            toRemove = toRemove | numChildren' > 5;
           
            bboxes(toRemove, :) = [];
            
            overlap = bboxOverlapRatio(bboxes,bboxes, 'min');
                        
            g = graph(overlap > 0.5,'OmitSelfLoops');
            
            componentIndices = conncomp(g);
            
            xmin = bboxes(:,1);
            ymin = bboxes(:,2);
            xmax = xmin + bboxes(:,3) - 1;
            ymax = ymin + bboxes(:,4) - 1;
            
            % Merge the boxes based on the minimum and maximum dimensions.
            xmin = accumarray(componentIndices', xmin, [], @min);
            ymin = accumarray(componentIndices', ymin, [], @min);
            xmax = accumarray(componentIndices', xmax, [], @max);
            ymax = accumarray(componentIndices', ymax, [], @max);
            
            bboxes = [xmin ymin xmax-xmin+1 ymax-ymin+1];
            
            bboxes = vision.internal.detector.addOffsetForROI(bboxes, roi, doCrop);
            
            bboxes(:,1:2) = bboxes(:,1:2) - 0.5; % back to spatial coords
            
            if returnMask
                allpx = vertcat(stats(~toRemove).PixelIdxList);
                out = false(size(bw));
                out(allpx) = bw(allpx);
                
                if doCrop
                    r = roi(2):(roi(2) + roi(4) - 1);
                    c = roi(1):(roi(1) + roi(3) - 1);
                    
                    bw = false(m,n);
                    bw(r,c) = out;
                    out = bw;
                end
            end
        end
        
        %------------------------------------------------------------------
        % Return bounding boxes around potential text blobs in the image.
        %------------------------------------------------------------------
        function [bboxes, varargout] = detectTextInImage(this, idx)
       
            I = getImages(this, idx);
                  
            params = this.TextDetectionParams(idx);
            
            [bboxes, varargout{1:nargout-1}] = this.findText(I, params);
                                      
                end
        
        %------------------------------------------------------------------
        function I = binarizeImage(~, I)           
            
            if ~ismatrix(I) 
                I = rgb2gray(I);
            end

            if ~islogical(I)
                I = imbinarize(I);
            end
            
            % determine text polarity; dark on light vs. light on dark. For
            % text blob detection we want light on dark.
            c = imhist(I);
            [~,bin] = max(c);
            
            if bin == 2 % light background
                % complement image to switch polarity 
                I = imcomplement(I);
            end
        end
        
        %------------------------------------------------------------------
        function foundText = autoLabelImage(this, idx, ocrOptions)
            
            try
            I = getImages(this, idx);
            catch                
                foundText = false;
                return;
            end
            
            hasBoxes = ~isempty(this.ImageStruct(idx).objectBoundingBoxes);
            
            if hasBoxes
                foundText = true;
                
                % run ocr on boxes
                bboxes = this.getBoxes(idx);
                try
                    
                    results = ocr(I, bboxes, ...
                        'TextLayout', 'Character', ...
                        'Language', ocrOptions.Language, ...
                        'CharacterSet', ocrOptions.CharacterSet);
                catch
                    % OCR issued an error for unknown reasons. Return that
                    % no text was found so that the image is not included
                    % in the session.
                    foundText = false;
                    return
                end
                
                text = {results(:).Text};
                
            else
                foundText = false;
            end
                
            if foundText
            % remove training whitespace and newlines
            text = deblank(text);
            
                % change unlabeled boxes to unknown
            unlabeled = cellfun(@(x)isempty(x),text);
            text(unlabeled) = {char(0)};
            
            this.setText(idx, text);
            end
        end
        
        %------------------------------------------------------------------
        function setText(this, whichImage, text)
            this.ImageStruct(whichImage).text = text;            
        end
        
        %------------------------------------------------------------------
        function addTextROI(this, params, startingIndex)        
            % params has ROI, MinArea, MinAspectRatio, and MaxAspectRatio
            this.TextDetectionParams(startingIndex:this.Count) = params;            
        end        
        
        %------------------------------------------------------------------
        function setBoxes(this, whichImage, boxes)
            this.ImageStruct(whichImage).objectBoundingBoxes = boxes;            
        end
                
        %------------------------------------------------------------------
        % Creates and populates a struct file ImageStruct
        %------------------------------------------------------------------
        % edit: create new struct and then append        
        function [startingIndex, imageFileNames] = addImagesToSession(this, imageFileNames)

            imageFileNames = reshape(imageFileNames,1,[]); % row vector
            
            startingIndex = []; % initialize the index
            
            % Function that eliminates files that are not images
            imageFileNames = this.eliminateNonImages(imageFileNames);
            
            % If there are no images
            if isempty(imageFileNames)
                return;
            end
            
            % look for duplicates and if found, silently remove them
            if this.hasAnyImages() 
                imageFileNames = this.getUniqueFiles(imageFileNames);
                if isempty(imageFileNames)
                    return; % nothing to add
                end                
            end            
            
            labels = generateImageLabels(imageFileNames);
            placeholders = this.generatePlaceHolders(imageFileNames);
            icons = placeholders;
            areThumbnailsGenerated = zeros(1, numel(imageFileNames));
                   
            n = numel(imageFileNames);
            newImageStruct = struct('imageFilename', imageFileNames, ...
                'objectBoundingBoxes', cell(1, n), ...
                'text',                cell(1, n), ...
                'ImageIcon', icons, 'ImageLabel', labels, ...
                'ImagePlaceHolder', placeholders);
            
            % setup default text detection parameters                       
            txtParams = repelem(getDefaultTextDetectionParams(this),1,n);
            
            % by default the first box is selected. 
            selectedBoxesToAdd = num2cell(ones(1,numel(imageFileNames)));                        
            
            if this.hasAnyImages()                
                this.AreThumbnailsGenerated = [this.AreThumbnailsGenerated, ...
                    areThumbnailsGenerated];
                startingIndex = numel(this.ImageStruct)+1;
                this.ImageStruct = [this.ImageStruct newImageStruct];
                this.SelectedBoxes = [this.SelectedBoxes selectedBoxesToAdd];
                this.TextDetectionParams = [this.TextDetectionParams txtParams];
            else
                this.ImageStruct = newImageStruct;
                this.AreThumbnailsGenerated = areThumbnailsGenerated;
                this.SelectedBoxes = selectedBoxesToAdd;
                this.TextDetectionParams = txtParams;
                startingIndex = 1;
            end 
   
        end
        
        %------------------------------------------------------------------
        function defaults = getDefaultTextDetectionParams(~)
            defaults.ROI = [];
            defaults.MinArea = 50;
            defaults.MinAspectRatio = 1/16;
            defaults.MaxAspectRatio = 4;
        end
        
        %------------------------------------------------------------------
        function setTextDetectionParams(this, params, idx)
            % ROI is not updated here. 
            if nargin == 2
                % set all
                for idx = 1:numel(this.TextDetectionParams)
                    this.TextDetectionParams(idx).MinArea = params.MinArea;
                    this.TextDetectionParams(idx).MinAspectRatio = params.MinAspectRatio;
                    this.TextDetectionParams(idx).MaxAspectRatio = params.MaxAspectRatio;
                end
            else
                this.TextDetectionParams(idx).MinArea = params.MinArea;
                this.TextDetectionParams(idx).MinAspectRatio = params.MinAspectRatio;
                this.TextDetectionParams(idx).MaxAspectRatio = params.MaxAspectRatio;
            end
        end
        
        %------------------------------------------------------------------
        % inputs:
        % newImageStruct            - ImageStruct from the newly opened session;
        % newAreThumbnailsGenerated - Logical array to indicate if thumbnails
        %                             are generated;
        %------------------------------------------------------------------        
        function addedImages = addImageStructToCurrentSession(this,newImageSet)
             
            newImageStruct = newImageSet.ImageStruct;
            newAreThumbnailsGenerated = newImageSet.AreThumbnailsGenerated;          
            
            if isempty(newImageStruct)
                addedImages = false;
                return;
            end
            
            % Look for duplicate images in the added session
            imageFilenames = this.getUniqueFiles({newImageStruct.imageFilename});
            if isempty(imageFilenames)
                addedImages = false;
                return;
            end
            addedImages = true;
            [~, indices] = intersect({newImageStruct.imageFilename}, imageFilenames);
            
            start = numel(this.ImageStruct) + 1;            
            
            this.ImageStruct = [this.ImageStruct newImageStruct(indices)];
                        
            this.TextDetectionParams = [this.TextDetectionParams ...
                newImageSet.TextDetectionParams(indices)];
            
            stop = numel(this.ImageStruct);                     
            
            this.AreThumbnailsGenerated = [this.AreThumbnailsGenerated,...
                newAreThumbnailsGenerated(indices)];
            
            % merge char maps. Cannot use vertcat between the current and
            % new session because some images might be duplicates.
            for whichImage = start:stop                
                this.addToCharMap(whichImage);                
            
            end 

            % Initialize everything. Ensures internal states are setup.
            this.initialize(); 
                 
        end
        
        %-----------------------------------------------------------------
        function [imageMatrix, imageLabel] = getImages(this, selectedIndex)
                        
            % If multiple files are selected grab just the first one
            selectedIndex = selectedIndex(1);
            imageMatrix = imread(this.ImageStruct(selectedIndex).imageFilename);
            imageLabel = this.ImageStruct(selectedIndex).imageFilename;
            
        end
        
        %------------------------------------------------------------------
        function name = getImageFilename(this, selectedIndex)
                                   
            name = this.ImageStruct(selectedIndex).imageFilename;
        
        end
        %--------------------------------------------------------------
        function imageIndices = getImagesContainingCharacter(this, character)
            s = this.CharMap(character);
            imageIndices = [s(:).imageIndex];            
        end
        
        %--------------------------------------------------------------
        function [imageIndex, charIndex] = getImageIndex(this, whichBox)           
            imageIndex = this.SelectionMap{whichBox, 1};                        
            charIndex  = this.SelectionMap{whichBox, 2};
        end
        
        %------------------------------------------------------------------
        % Crop out patches that correspond to the input label.
        %------------------------------------------------------------------
        function [patches] = getPatches(this, label)
            
            v = this.CharMap(label);                                    
            
            numImages = numel(v);
            
            patches = {};
            for i = 1:numImages
                
                idx     = v(i).imageIndex;               
                bboxIdx = v(i).bboxIndex;
                
                bboxes = getBoxes(this, idx);
                
                 % convert boxes to pixel coordinates for patch extraction.
                bboxes(:, 1:2) = bboxes(:,1:2) + 0.5;
                    
                    
                % image reading can fail if images no longer exist.
                try
                [~, I] = this.detectTextInImage(idx);
                catch                    
                    % failed to read image. issue error dialog and do not
                    % process this image.                                       
                    errordlg(...
                        vision.getMessage('vision:ocrTrainer:ImageReadError', ...
                        this.ImageStruct(idx).imageFilename),...
                        vision.getMessage('vision:ocrTrainer:ImageReadErrorTitle'), ...
                        'modal');
                    continue
                end
                
                [nrows,ncols] = size(I);
                
                for j = 1:numel(bboxIdx)
                    roi = bboxes(bboxIdx(j),:);
                                        
                    overlap = bboxOverlapRatio(roi, bboxes, 'min');
                    
                    % expanded box overlaps everything. Highly likely
                    % that this bbox spans entire image. Don't merge.
                    if all(overlap)
                        idx    = false(size(overlap));
                        idx(j) = true; 
                    else
                        idx = overlap > 0;
                    end
                    
                    xmin = bboxes(idx,1);
                    ymin = bboxes(idx,2);
                    xmax = xmin + bboxes(idx,3) - 1;
                    ymax = ymin + bboxes(idx,4) - 1;
                    
                    % Get box spanning all nearby chars
                    xmin = min(xmin);
                    ymin = min(ymin);
                    xmax = max(xmax);
                    ymax = max(ymax);
                    
                    box = [xmin ymin xmax-xmin+1 ymax-ymin+1];
                    
                    % make the current character centered box
                    cx = floor(xmin + (xmax - xmin + 1)/2);
                    cy = floor(ymin + (ymax - ymin + 1)/2);
                    
                    cxroi = roi(1) + floor(roi(3)/2);
                    cyroi = roi(2) + floor(roi(4)/2);
                    
                    offsetx = cxroi - cx;
                    offsety = cyroi - cy;
                    
                    box(1) = box(1) + offsetx;
                    box(2) = box(2) + offsety;
                    
                    % expand box around patch a bit to make it look nicer,
                    % then crop out patch.
                    box = vision.internal.detector.expandROI(size(I), box, 1);
                    
                    xmin = max(box(:,1), 1);
                    ymin = max(box(:,2), 1);
                    xmax = min(box(:,1) + box(:,3) - 1, size(I,2));
                    ymax = min(box(:,2) + box(:,4) - 1, size(I,1));
                    
                    box = [xmin ymin xmax-xmin+1 ymax-ymin+1];
                    
                    p = vision.internal.detector.cropImage(I, box);
                    
                    p = im2uint8(p);
                    
                    % Get the box in the cropped image coordinates
                    roi = vision.internal.detector.expandROI(size(I), roi, 1);
                    xx = roi(1) - box(1) + 1;
                    yy = roi(2) - box(2) + 1;
                    
                    c1 = max(xx,1);
                    c2 = min(xx + roi(3) - 1, ncols);
                    
                    r1 = max(yy,1);
                    r2 = min(yy + roi(4) - 1, nrows);
                    
                    % Apply blending to deemphasize surrounding chars
                    [mm,nn] = size(p);
                    
                    mask = true(size(p));
                                        
                    r1 = max(round(r1), 1);
                    r2 = min(round(r2), mm);
                    c1 = max(round(c1), 1);
                    c2 = min(round(c2), nn);
                    mask(r1:r2,c1:c2) = false;
                    
                    p = roifilt2(p,mask,@blend);
                    
                    % colorize background
                    p = repmat(p,1,1,3);
                    
                    patches = [patches p];
                end
            end
            
            %--------------------------------------------------------------
            % Blend function used with roifilt2
            %--------------------------------------------------------------
            function J = blend(I)
                opacity = 0.85;  % is a pleasing opacity value.
                J = 128 * ones(size(I),'like',I);
                J = (1-opacity)*I + opacity*J;
            end
        end
        
        %------------------------------------------------------------------
        function bboxes = getBoxes(this, selectedImageIndex)
            bboxes = this.ImageStruct(selectedImageIndex).objectBoundingBoxes;
        end
        
        %------------------------------------------------------------------
        function idx = getValidIndices(this, selectedImageIndex)
            idx = cellfun(@(x)ischar(x),this.ImageStruct(selectedImageIndex).text);
        end
        
        %------------------------------------------------------------------
        function idx = getUnknownIndices(this, selectedImageIndex)
            idx = cellfun(@(x)ischar(x) && strcmp(x,char(0)), ...
                this.ImageStruct(selectedImageIndex).text);
        end
        
        %------------------------------------------------------------------
        function bboxes = getValidBoxes(this, selectedImageIndex)
            bboxes = this.getBoxes(selectedImageIndex);
            
            isValid = this.getValidIndices(selectedImageIndex);
            
            isValid = isValid & ~this.getUnknownIndices(selectedImageIndex);
            
            bboxes = bboxes(isValid,:);
        end
        
        %------------------------------------------------------------------
        function text = getValidText(this, selectedImageIndex)
            text = this.getText(selectedImageIndex);  
            
            isValid = this.getValidIndices(selectedImageIndex);  
            
            isValid = isValid & ~this.getUnknownIndices(selectedImageIndex);
            
            text = text(isValid);
        end
        
        %------------------------------------------------------------------
        function numSelected = getNumSelectedBoxes(this, selectedImageIndex)
            
            selected = this.SelectedBoxes{selectedImageIndex};
                       
            numSelected = nnz(selected);
        end
        
        %--------------------------------------------------------------
        function selected = getSelectedBoxes(this, selectedImageIndex, ~)
            
            selected = this.SelectedBoxes{selectedImageIndex};
                       
            selected = find(selected);
                       
            selected = min(selected, this.getNumBoxes()); % can't select more than available
        end
        
        %------------------------------------------------------------------
        % Selects one box. Clears existing selection first. 
        %------------------------------------------------------------------
        function setSelectedBoxes(this, selectedChar, selectedBox)
            d = this.SelectedBoxes{selectedChar};           
            d(d) = false; % clear existing selection
            selectedBox = min(selectedBox, max(1,numel(d))); % can't select more than available
            d(selectedBox) = true;
            this.SelectedBoxes{selectedChar} = d;           
        end
        
        %------------------------------------------------------------------
        % Marks selectedBox as selected. Changes nothing else
        %------------------------------------------------------------------
        function selectBox(this, selectedChar, selectedBox)
            d = this.SelectedBoxes{selectedChar};
            selectedBox = min(selectedBox, numel(d));
            d(selectedBox) = true;
            this.SelectedBoxes{selectedChar} = d;
        end
        
        %------------------------------------------------------------------
        % Marks selectedBox as selected. Changes nothing else
        %------------------------------------------------------------------
        function unselectBox(this, selectedChar, selectedBox)
            d = this.SelectedBoxes{selectedChar};
            selectedBox = min(selectedBox, numel(d));
            d(selectedBox) = false;
            this.SelectedBoxes{selectedChar} = d;
        end
        
        %--------------------------------------------------------------
        function charLabel = getCharLabel(this, selectedImageIndex, selectedCharacterIdx)
            if isvector(selectedCharacterIdx)
                selectedCharacterIdx = selectedCharacterIdx(1); % just return first one in case of multiselect
            end
            
            charLabel = this.ImageStruct(selectedImageIndex).text{selectedCharacterIdx};                                                  
        end
        
        %------------------------------------------------------------------
        function txt = getText(this, whichImage)
            txt = this.ImageStruct(whichImage).text;
        end
        
        %------------------------------------------------------------------
        function c = getCharacterByIndex(this, idx)                      
            
            k = this.CharMap.keys();
            
            if isscalar(idx)
                c = k{idx};
            else
                c = k(idx);
            end
        end
        
        %------------------------------------------------------------------
        function charRemoved = removeDeffered(this, idx)
                
            charRemoved = false;
            
            if ~isempty(this.DelayRemovalSelectedBoxes)
                
                charRemoved = true;
                
                if idx >= 1
                    % check idx >= 1. makes sure char is still in browser.
                    % A right-click remove can remove the char from the
                    % list. If that happened, no need to do any deferred
                    % work.
                    
                    % do deferred update for SelectedBox
                    d = this.SelectedBoxes{idx};
                    rmidx = this.DelayRemovalSelectedBoxes;
                    
                    % find what's selected
                    selected = this.getSelectedBoxes(idx);
                    
                    % remove and update SelectedBoxes
                    d(rmidx) = [];
                    this.SelectedBoxes{idx} = d;
                    
                    % check if previously selected were removed. if so we need
                    % to mark something selected. Use original selected.
                    if numel(d) > 0 && any(ismember(selected,rmidx))
                        this.setSelectedBoxes(idx, selected(1));
                    end
                    
                end
                % reset deferred work
                this.DelayRemovalSelectedBoxes = [];
            end
            
            list = this.DelayRemoval;
            charRemoved = charRemoved || numel(list) > 0;
            for i = 1:numel(list)
                removeChar(this, list{i});
            end
            this.DelayRemoval = {};
            
        end
        
        %------------------------------------------------------------------
        % Remove single character from char map. The image and bbox of this
        % character are given by imageIndex and bboxIndex.
        %------------------------------------------------------------------
        function removeFromCharMap(this, old, imageIndex, bboxIndex, shouldDeferRemoval)                       
            
            if nargin == 4
                shouldDeferRemoval = false;
            end
            
            v = this.CharMap(old);
            
            % remove the bbox associated with modified char
            j = [v(:).imageIndex] == imageIndex;
            v(j).bboxIndex(v(j).bboxIndex == bboxIndex) = [];
            
            % If no more bboxes remain, then there are no more
            % samples of this char the image. Remove the image
            % from the char map.
            if isempty(v(j).bboxIndex)
                v(j) = [];
            end
            
            if numel(v) == 0
                if shouldDeferRemoval
                    this.deferRemoval(old, v);
                else
                % No samples left of old char in any of the images,
                % it can be removed completely.                             
                this.removeChar(old);                  
                end
            else
                % update char map with new info.
                this.CharMap(old) = v;
                this.updateIconDescription(old);
            end
            
        end
        
        %------------------------------------------------------------------
        function deferRemoval(this, old, v)
            % Defer removal of char in cases when the
            % current selected character losses all
            % samples. In this case we do not want the char
            % montage to be destroyed until the user has
            % switched to another one. Keep track of which
            % one should be removed and defer removal until
            % the char view is changed.
            if ~ismember(old, this.DelayRemoval)
                this.DelayRemoval{end+1} = old;
                
                % force icon description to 0 so char list
                % display is correct.
                this.updateIconDescription(old, 0);
                this.CharMap(old) = v;
            end
        end
        
        %------------------------------------------------------------------
        % Update char map when changing to new char view. This saves all
        % changes while labeling in the char view. SelectionMap and
        % SelectedBoxes are updated here.
        %------------------------------------------------------------------
        function updated = updateCharMap(this, whichBox, selectedChar)
            
            updated = false;
            
            if isempty(this.SelectionMap)
                % first time                
            else                                
                
                if nargin == 2
                    % update specific box - dynamic update while labeling.
                    modified = whichBox;
                    updated = true;
                else
                    % update on char list selection change
                    
                    % get modified labels. only pick up undeleted chars.
                    modified = cellfun(@(x)~isempty(x) && ischar(x),this.SelectionMap(:,4));
                    
                    modified = find(modified);
                    
                    if any(modified)
                        updated = true;
                    end
                end
                
                % Only update the modified chars
                for i = 1:numel(modified)
                    
                    idx = modified(i);
                                                            
                    % remove old character from char map. then insert new
                    % one.
                    old = this.SelectionMap{idx, 3};
                    new = this.SelectionMap{idx, 4};
                    
                    imageIndex = this.SelectionMap{idx,1};
                    bboxIndex  = this.SelectionMap{idx,2};
                    
                   
                    if strcmp(new, selectedChar)
                        % Remove new char from deferred removal list. This
                        % is required when a box label is change back and
                        % forth between the selected char in the browser and a
                        % different label.
                        this.DelayRemovalSelectedBoxes(...
                            this.DelayRemovalSelectedBoxes == idx) = [];
                        
                    elseif ~ismember(idx, this.DelayRemovalSelectedBoxes)
                        % Mark box for deferred removal from SelectedBoxes
                        % only if it is not already part of the list.
                        this.DelayRemovalSelectedBoxes(end+1) = idx;
                    
                    end
                    
                    v = this.CharMap(old);
                                        
                    % remove the bbox associated with modified char
                    j = [v(:).imageIndex] == imageIndex;
                    v(j).bboxIndex(v(j).bboxIndex == bboxIndex) = [];
                    
                    % If no more bboxes remain, then there are no more
                    % samples of this char the image. Remove the image
                    % from the char map.
                    if isempty(v(j).bboxIndex)
                        v(j) = [];
                    end
                    
                    if numel(v) == 0         
                        % No samples left of old char in any of the images,
                        % it can be removed completely.
                        
                        if nargin == 3 && strcmp(selectedChar,old) 
                            this.deferRemoval(old, v);
                        else
                            this.removeChar(old, false);                                                         
                        end
                    else
                        % update char map with new info.
                        this.CharMap(old) = v;
                        this.updateIconDescription(old);
                    end                                        
                    
                    % Insert new char
                    if isKey(this.CharMap, new)
                        
                        % check if char was marked for deferred removal. If
                        % so, remove it from the deferred removal list.
                        if ismember(new, this.DelayRemoval)
                            this.DelayRemoval(...
                                strcmp(new, this.DelayRemoval)) = [];
                        end
                    
                        % Entry exists for char, append into the existing
                        % entry.
                        v = this.CharMap(new);
                        
                        % Find where we should append. Find location based
                        % on which image the new char is found in.
                        j = find([v(:).imageIndex] == imageIndex,1);
                        if isempty(j)                           
                            % Image isn't part of data entry yet. Add it.
                            s = struct('imageIndex', imageIndex,...
                                       'bboxIndex', bboxIndex); 
                                                                                             
                            this.CharMap(new) = [v s];
                            
                            % Update SelectedBoxes for the newly appended
                            % entry.
                            position = numel([v(:).bboxIndex]);
                            
                        else
                            % Image is already part of the entry. Append
                            % bbox location of new char.
                            position = numel([v(1:j).bboxIndex]);
                            v(j).bboxIndex(end+1) = bboxIndex;
                            this.CharMap(new) = v;
                        end
                            
                        if ~strcmp(new, selectedChar)
                            % place new entries in SelectedBoxes only when
                            % the new char isn't the current selectedChar.
                            % This can happen if box label is change from
                            % curent char to something else and then back
                            % to the current char.
                            this.insertIntoSelectedBoxes(new, position);                            
                        end
                        
                        this.updateIconDescription(new);                                       
                                                
                    else
                        % Brand new char, not in char map yet. Create an
                        % entry for it and populate it's data.
                        
                        s = struct('imageIndex', imageIndex,...
                                   'bboxIndex', bboxIndex);                        
                        this.CharMap(new) = s;  
                        
                        idx = this.getCharacterIndex(new);
                        
                        % insert new character into its place and update
                        % all the state information.
                        
                        v = this.AreCharThumbnailsGenerated;
                        this.AreCharThumbnailsGenerated = [v(1:idx-1) false v(idx:end)];
                        
                        v = this.SelectedBoxes;                      
                        this.SelectedBoxes = [v(1:idx-1) {sparse(true)} v(idx:end)];
                        
                        v = this.CharIcons;
                        this.CharIcons =  [v(1:idx-1) {[]} v(idx:end)];
                        
                        this.updateImageListEntry(idx-1); % minus 1 for java idx                        
                        
                    end
                    
                    % Mark selection map as unmodified to prevent double
                    % updates when switching to a new char montage.
                    this.SelectionMap{modified(i),3} = this.SelectionMap{modified(i),4};
                    this.SelectionMap{modified(i),4} = [];
                end               
            end
                         
        end
        
        %------------------------------------------------------------------
        % Inserts false entry into SelectedBoxes for the new character at
        % the specified position. This required when adding/modifying
        % character labels for keeping track of which boxes are selected
        % across char montage views.
        %------------------------------------------------------------------
        function insertIntoSelectedBoxes(this, new, position)
            cidx = this.getCharacterIndex(new);
            d = this.SelectedBoxes{cidx};
                        
            if position+1 <= numel(d)
                % do insert
                d = [d(1:position) false d(position+1:end)];
            else
                % do append
                d = [d(1:position) false];
                
            end
            
            this.SelectedBoxes{cidx} = d;
        end
        
        %------------------------------------------------------------------
        % Set character label for a box. ImageStruct and SelectionMap are
        % updated.
        %------------------------------------------------------------------
        function setCharLabel(this, whichBox, label)
            
            % update text label in image struct
            for i = 1:numel(whichBox)
                
                bidx = whichBox(i);
                
                [whichImage, whichChar] = this.getImageIndex(bidx);                         
                
                this.ImageStruct(whichImage).text{whichChar} = label;
                
                % also update current selection map
                this.SelectionMap{bidx,4} = label;
            end           
                        
        end
        
        %------------------------------------------------------------------
        % Return the label assigned to a box. This information updated live
        % in the SelectionMap.
        function label = getBoxLabel(this, whichBox)              
            
            label = this.SelectionMap(whichBox,3);
            
            if numel(label) == 2
                label = label{1};
            end
            
        end
        
        
        %------------------------------------------------------------------
        function needsUpdate = updateBoundingBoxes(this, selectedIndex, boundingBoxes, roiselected)
            
            % If multiple files are selected grab just the first one
            selectedIndex = selectedIndex(1);
            needsUpdate = false;
            if ~isequal(this.ImageStruct(selectedIndex).objectBoundingBoxes, ...
                    boundingBoxes)
                this.ImageStruct(selectedIndex).objectBoundingBoxes = boundingBoxes;
                if ~isequal(this.SelectedBoxes{selectedIndex}, roiselected)
                    this.SelectedBoxes{selectedIndex} = roiselected;
                end
                needsUpdate = true;
            else
                if ~isequal(this.SelectedBoxes{selectedIndex}, roiselected)
                    this.SelectedBoxes{selectedIndex} = roiselected;
                    needsUpdate = true;
                end
            end
            
        end
        
        %------------------------------------------------------------------
        function removeImage(this, selectedIndex)            
            this.ImageStruct(selectedIndex)            = [];
            this.AreThumbnailsGenerated(selectedIndex) = [];                        
            this.TextDetectionParams(selectedIndex) = [];
        end
        
        %------------------------------------------------------------------
        function moveCharToUnknown(this, whichOne)

            if ischar(whichOne)
                % remove by char
                character = whichOne;
                idx = this.getCharacterIndex(character);
            else
                % remove by index
                idx = whichOne;
                character = this.getCharacterByIndex(idx);
            end
           
            character = cellstr(character);
            
            % cellstr marks char(0) as '' instead of ' '. correct this.
            character(strcmp('',character)) = {char(0)};
            
            for i = 1:numel(idx)
                c = character{i};
                
                if strcmp(c,char(0))
                    continue
                end
                
                this.resetSelectionMap(c);
                
                whichBox = 1:this.getNumCharSamples(c);                                                
                
                this.setCharLabel(whichBox,char(0));                               
                
                % update the char map
                this.updateCharMap(whichBox, c);
            end
            
        end
        
        %------------------------------------------------------------------
        function removeChar(this, whichOne, invalidateBox)      
            
            if nargin == 2
                invalidateBox = true;
            end
            
            if ischar(whichOne)
                % remove by char
                character = whichOne;
                idx = this.getCharacterIndex(character);
            else
                % remove by index
                idx = whichOne;
                character = this.getCharacterByIndex(idx);
            end
                        
            this.AreCharThumbnailsGenerated(idx) = [];
            this.SelectedBoxes(idx) = [];
            this.CharIcons(idx) = [];

            % remove boxes from image struct
            character = cellstr(character);
            
            % cellstr marks char(0) as '' instead of ' '. correct this.
            character(strcmp('',character)) = {char(0)};
            
            % invalidate a box by marking the text with -1. this is done
            % when removing a character. Otherwise it's not done
            if invalidateBox
                for i = 1:numel(character)
                    key = character{i};
                    if isKey(this.CharMap, key)
                        val = this.CharMap(key);
                        invalidateBoxes(this, val);
                    end
                end                         
            end
                      
            
            % remove from char map
            keysToRemove = isKey(this.CharMap, character);
            remove(this.CharMap, character(keysToRemove));  
            
        end                  
        
        %------------------------------------------------------------------
        function removeCharSample(this, whichBox, shouldDeferRemoval)
          
            [v.imageIndex, v.bboxIndex] = this.getImageIndex(whichBox);
            
            selectedChar = this.getBoxLabel(whichBox);
            
            this.removeFromCharMap(...
                selectedChar{1}, v.imageIndex, v.bboxIndex, ...
                shouldDeferRemoval);
            
            % mark as invalid
            this.invalidateBoxes(v);                      
            
        end
        
        
        %------------------------------------------------------------------
        function invalidateBoxes(this, value)
            
            % mark boxes associated with a character as invalid. invalid
            % boxes are not used for training.
            for i = 1:numel(value)
                imgIdx = value(i).imageIndex;
                boxIdx = value(i).bboxIndex;
                
                % invalid box is marked with -1 text value
                this.ImageStruct(imgIdx).text(boxIdx) = {-1};
            end
        end
                        
        %------------------------------------------------------------------
        function removeItem(this, idx)
            if strcmp(this.RemoveMode, 'move')
                this.moveCharToUnknown(idx);
            else
                if this.CharView
                    this.removeChar(idx);
                else
                    this.removeImage(idx);
                end
            end
        end
        
        %------------------------------------------------------------------
        function reset(this)                
            this.ImageStruct = [];                
            this.SelectedBoxes = {};
            this.AreThumbnailsGenerated = [];
            this.AreCharThumbnailsGenerated = [];
            this.CharMap = containers.Map;
            this.CharIcons = {};
            this.SelectionMap = {};
            this.TextDetectionParams = [];
        end               
        
        
        %------------------------------------------------------------------
        function n = getNumBoxes(this)
            
            n = size(this.SelectionMap,1);            
            
        end
        
        %------------------------------------------------------------------
        function idx = getCharacterIndex(this, whichOne)
            idx = find(strcmp(whichOne, keys(this.CharMap)),1);
        end
        
        %------------------------------------------------------------------
        function updateIconDescription(this, whichOne, numSamples)
            if this.CharView                
                idx = this.getCharacterIndex(whichOne);
                if nargin == 2                                    
                    numSamples = this.getNumCharSamples(whichOne);
                end
                label = vision.internal.ocr.tool.ImageSet.generateCharacterIconDescription(numSamples);
                this.CharIcons{idx}.setDescription(label);
            else
                label = this.ImageStruct(whichOne).ImageLabel;          
                this.ImageStruct(whichOne).ImageIcon.setDescription(label);
            end
            
        end
        
        %------------------------------------------------------------------
        % This method should be called after the Image Session is loaded
        % from a MAT file to check that all the images can be found at
        % their specified locations
        %------------------------------------------------------------------
        function checkImagePaths(this, currentSessionFilePath,...
                origFullSessionFileName)
            
            % verify that all the images are present; adjust path if
            % necessary
            for i=1:numel(this.ImageStruct)
                if ~exist(this.ImageStruct(i).imageFilename,'file')
                    
                    this.ImageStruct(i).imageFilename = ...
                        vision.internal.uitools.tryToAdjustPath(...
                        this.ImageStruct(i).imageFilename, ...
                        currentSessionFilePath, origFullSessionFileName);
                    
                end
            end            
        end 
        
        %------------------------------------------------------------------        
        function ret = areAllImagesLabeled(this)
            
            ret = ~any(cellfun(@isempty, {this.ImageStruct.objectBoundingBoxes}));
            
        end
        
        %------------------------------------------------------------------
        function cellArrayOfIcons = getIcons(this)
            if this.CharView
                cellArrayOfIcons = this.CharIcons;
            else
                cellArrayOfIcons = {this.ImageStruct.ImageIcon};
            end
        end
                
        %------------------------------------------------------------------
        % Return number of elements in the the set. For purposes of
        % rendering image strip list. 
        %------------------------------------------------------------------
        function n = getNumel(this)
            if this.CharView                
                n = length(this.CharMap);
            else
                n = numel(this.ImageStruct);
            end
        end
        
        %------------------------------------------------------------------
        function n = getNumCharSamples(this, c)
            s = this.CharMap(c);
            n = numel([s(:).bboxIndex]);
        end
        
        %------------------------------------------------------------------
        function icon = generateIcon(this, selectedIndex)
            if this.CharView
                k = keys(this.CharMap);
                character = k{selectedIndex};
                n = this.getNumCharSamples(character);
                
                icon = vision.internal.ocr.tool.generateCharacterIcon(...
                    this.Font, character, n);
                
                this.CharIcons{selectedIndex} = icon{1};
            else
                filename = this.ImageStruct(selectedIndex).imageFilename;  
                icon = vision.internal.ocr.tool.ImageSet.generateImageIcon(filename);
                this.ImageStruct(selectedIndex).ImageIcon = icon{1};
            end
        end
        
        %------------------------------------------------------------------
        % edit: change the name to updateImageListEntry(this, selectedIndex)
        function updateMade = updateImageListEntry(this, selectedIndex)
            
            updateMade = true;            
            
            if selectedIndex == -1 % when JList is loading for the first time
                selectedIndex = 1;
            else
                selectedIndex = selectedIndex+1; % making it MATLAB based
            end
                       
            
            if this.getIsThumbnailGenerated(selectedIndex)
                updateMade = false;
                return;
            end
                                           
            this.generateIcon(selectedIndex);            
            this.setIsThumbnailGenerated(selectedIndex,1);
            
        end
                
        %------------------------------------------------------------------
        function tf = getIsThumbnailGenerated(this, idx)
            if this.CharView
                tf = this.AreCharThumbnailsGenerated(idx);
            else
                tf = this.AreThumbnailsGenerated(idx);
            end
        end
        
        %------------------------------------------------------------------
        function setIsThumbnailGenerated(this, idx, value)
            if this.CharView
                this.AreCharThumbnailsGenerated(idx) = value;
            else
                this.AreThumbnailsGenerated(idx) = value;
            end
        end
        
        %------------------------------------------------------------------
        function ret = areAllIconsGenerated(this)
            if this.CharView
                ret = all(this.AreCharThumbnailsGenerated);
            else
                ret = all(this.AreThumbnailsGenerated);
            end
        end
        
        %------------------------------------------------------------------
        function resetIcons(this)
            if this.CharView                
                this.AreCharThumbnailsGenerated = false(1,length(this.CharMap));
            else
                this.AreThumbnailsGenerated = false(1,this.Count);
            end
        end
        
        end
                
    %----------------------------------------------------------------------
    methods (Access=private)      
        function uniqueImageFileNames = getUniqueFiles(this, imageFileNames)
            uniqueImageFileNames = setdiff(unique(...
                [{this.ImageStruct.imageFilename} imageFileNames]),...
                {this.ImageStruct.imageFilename});
        end                
    end

    %----------------------------------------------------------------------
    methods(Hidden, Static)
                
        %------------------------------------------------------------------
        % This function generates place holder icons for N characters
        %------------------------------------------------------------------
        function icons = generatePlaceHolders(imageFileNames)
            icons = cell(1, numel(imageFileNames));
            
            % grab a place holder image from the disk
            placeHolderImage = fullfile(matlabroot,'toolbox','vision',...
                'vision','+vision','+internal','+cascadeTrainer','+tool',...
                'PlaceHolderImage_72.png');
            im = imread(placeHolderImage);
            
            % prapare list data
            javaImage = im2java2d(im);           
            labels = generateImageLabels(imageFileNames);
            
            % populate the icons
            for i = 1:numel(imageFileNames)
                icons{i} = javax.swing.ImageIcon(javaImage);
                icons{i}.setDescription(labels{i});
            end
        end
        
        %------------------------------------------------------------------
        % This function generates place holder icons for N characters
        %------------------------------------------------------------------
        function icons = generateCharPlaceHolders(N)
            
            % grab a place holder image from the disk
            placeHolderImage = fullfile(matlabroot,'toolbox','vision',...
                'vision','+vision','+internal','+ocr','+tool',...
                'iconbg.png');            
            im = imread(placeHolderImage);
            
            % prapare list data
            javaImage = im2java2d(im);       
                        
            icons = repmat({javax.swing.ImageIcon(javaImage)}, 1, N);
            for i = 1:N
                icons{i}.setDescription('');
            end
        end
        
        %------------------------------------------------------------------
        % edit code to return icon as a java icon object and not a cell array
        %------------------------------------------------------------------        
        function icon = generateImageIcon(imageFileName)
            if ~iscell(imageFileName)
                imageFileName = cellstr(imageFileName);
            end
            label = generateImageLabels(imageFileName);
            try
                im = imread(imageFileName{1});
                javaImage = im2java2d(imresize(im, [72 72],'nearest'));
                icon{1} = javax.swing.ImageIcon(javaImage);
                icon{1}.setDescription(label{1});
            catch loadingEx
                errordlg(loadingEx.message,...
                    vision.getMessage('vision:uitools:LoadingImageFailedTitle'),...
                    'modal');
            end
        end
        
        %------------------------------------------------------------------
        function label = generateCharacterIconDescription(numSamples)
             msg = '%d samples';
             if numSamples == 1            
                 msg = msg(1:end-1);
             end             
             label = sprintf(msg,numSamples);
        end
              
        %------------------------------------------------------------------
        function files = eliminateNonImages(imageFileNames)
            isImage = true(1, numel(imageFileNames));
            disableImfinfoWarnings();
            for i = 1:numel(imageFileNames)
                
                try
                    imfinfo(imageFileNames{i});
                catch
                    isImage(i) = false;
                end
            end
            enableImfInfoWarnings();
            files = imageFileNames(isImage);
            
            % Nested functions
            %--------------------------------------------------------------
            function disableImfinfoWarnings()
                imfinfoWarnings('off');
            end
            %--------------------------------------------------------------
            function enableImfInfoWarnings()
                imfinfoWarnings('on');
            end
            %--------------------------------------------------------------
            function imfinfoWarnings(onOff)
                warnings = {'MATLAB:imagesci:tifftagsread:badTagValueDivisionByZero',...
                    'MATLAB:imagesci:tifftagsread:numDirectoryEntriesIsZero',...
                    'MATLAB:imagesci:tifftagsread:tagDataPastEOF'};
                for j = 1:length(warnings)
                    warning(onOff, warnings{j});
                end
            end
            %--------------------------------------------------------------
        end
    end
  
    %----------------------------------------------------------------------
    % saveobj and loadobj are implemented to ensure compatibility across
    % releases even if architecture of Session class changes
    %----------------------------------------------------------------------
    methods (Hidden)
        
        function thisOut = saveobj(this)
            thisOut = this;
        end
        
    end
    %======================================================================
    
    methods (Static, Hidden)
        
        function thisOut = loadobj(this)
            thisOut = this;
        end
        
    end
    %======================================================================
    
end

%------------------------------------------------------------------
% edit: trace where the imageFileNames first come into play and protect it over there.
function labels = generateImageLabels(imageFileNames)

if ~iscell(imageFileNames)
    imageFileNames = cellstr(imageFileNames);
end
[~, labels, ~] = cellfun(@fileparts, imageFileNames, 'UniformOutput', 0);

end