Code covered by the BSD License  

Highlights from
Digital Vectorscope

image thumbnail

Digital Vectorscope

by

 

03 Jan 2013 (Updated )

Generate a Vectorscope like plot from RGB data.

digitalVectorscope( inRGB, varargin )
function hAxes = digitalVectorscope( inRGB, varargin )
%
%% digitalVectorscope
% The function presents a Vector-scope plot, with multiple options.
%
%% Syntax:
%  hAxes = digitalVectorscope( inRGB )
%  hAxes = digitalVectorscope( inRGB, hAxes )
%  hAxes = digitalVectorscope( inRGB, hAxes, isPixelClr )
%  hAxes = digitalVectorscope( inRGB, hAxes, isPixelClr, plotParamPairs )
%  hAxes = digitalVectorscope( inRGB, hAxes, isPixelClr, is3D, plotParamPairs )
%
%% Description:
% When performing color analysis, Electrical Engineers are frequently using a Vector-scope -
%   usually an analogue device that measures the image color (see Wiki for some details:
%   http://en.wikipedia.org/wiki/Vectorscope ). This function allows the user to present a
%   2D or a 3D plot of the La*b image data.
%
%% Input arguments (defaults exist):
%  inRGB- a 3D matrix with pixels data in RGB. If no data is specified, user will be
%   prompted for an image file via file browser.
%
% The remaining inputs are specified via "Name- Value" pairs, while default values exist
%  hAxes- a handle to the axis where the Vector-scope plot will be placed. If no axis
%       handle is supplied, one will be created.
%  isPixelClr- a logical variable. When enabled, the marker representing each inRGB pixel,
%       will have the pixels color. Note that since it slows down the program, it is
%       disabled by default. It is also disabled in case isBckndImg is enabled, otherwise
%       the line data will not be seen.
%  is3D- a logical variable. When enabled a 3D Vector-scope plot will be presented (a*, b ,
%       chanIntensity). By default it is disabled, as Vector-scope plot is usually 2D.
%  isBckndImg- a logical variable. When enabled, for 2D case a background image will be
%       presented beneath the point location emphasis the appropriate coordinate color.
%       The flag is automatically disabled in case is3D is enabled.
%  colorSpace a string described the color space used in analysis. Currently the following
%       color spaces are supported:{['LAB'], 'YCBCR', 'XYZ', 'XYL'}.
%  borderSize- the size of the rectangul;ar around target points.
%  varargin- a cell array with Lineseries Properties as described in Matlab help (usually
%       pairs of property name and property value). clrChanA default value of a marker
%       line style and color exist.
%
%% Output arguments:
%  hAxes- a handle to the axis where the Vector-scope plot was placed.
%
%% Issues & Comments
% - isPixelClr- when enabled- code runs slowly...
% - isBckndImg- complicates the code, and turns axis and points value to be irrelevant
% - is3D- the plot is not 3D, and seems to be incorrect in case of isBckndImg.
%
%% Example
% (see demo file for more options)
% close all;
%
% inRGB=imread('peppers.png');
% imgFigH=figure;
% imshow(inRGB)
%
% %% Yellow Pepepr
% iRows=185:215;
% iCols=235:265;
%
% figure(imgFigH);
% minCol=min(iCols);
% maxCol=max(iCols);
% minRow=min(iRows);
% maxRow=max(iRows);
% rectangle('Position', [minCol, minRow, maxCol-minCol, maxRow-minRow], 'EdgeColor', 'w',...
%     'LineWidth', 4);
% rectangle('Position', [minCol, minRow, maxCol-minCol, maxRow-minRow], 'EdgeColor', 'b',...
%     'LineWidth', 2);
%
% scopeCLrSpace='LAB';
%
% digitalVectorscope( inRGB(iRows, iCols, :), 'colorSpace', scopeCLrSpace,...
%     'isPixelClr', false, 'is3D', false, 'isBckndImg', true, 'MarkerEdgeColor', 'b');
% title('Background enabled.', 'FontSize', 18);
% legend('Ancor colors', 'Scope axis origin', 'Yellow pepper');
%
% hAxes=digitalVectorscope( inRGB(iRows, iCols, :), 'colorSpace', scopeCLrSpace,...
%     'isPixelClr', false, 'is3D', false, 'isBckndImg', false, 'MarkerEdgeColor', 'y');
%
%% See also:
%  - applycform
%  - makecform
%
%% Revision history:
% First version: Nikolay S. 2013-01-03.
% Last update:   Nikolay S. 2013-02-05.
%
%% *List of Changes*:
% - 2013-02-05- isBckndImg added. Parameter Name- Parameter Value input style adopted.
% - 2013-01-03- first version- issues may arise...

%% Default params- before reading user inputs.
isBckndImg=[];
colorSpace=[];
hAxes=[];
isPixelClr=[];
is3D=[];
borderSize=[];

%% Load uses params, overifding default ones
specificParamsList={'colorSpace', 'hAxes', 'isPixelClr', 'is3D', 'isBckndImg'};
if nargin>1
    nVarargin=length(varargin);
    isSpecificParam=false(1, nVarargin);
    iArg=1;
    while iArg <= (nVarargin-1)
        % automatically get all RELEVANT input pairs and store in local vairables
        if any( strcmpi(varargin{iArg}, specificParamsList) )
            assignin_value(varargin{iArg}, varargin{iArg+1});
            
            isSpecificParam( [iArg, iArg+1] )=true; % parameters come in Name- value pairs
            iArg=iArg+1;
        end
        iArg=iArg+1;
    end % while iArg < (nVarargin-1)
    varargin(isSpecificParam)=[];
end

%% Default params- after reading user inputs.
if isempty(inRGB) || nargin==0
    % force the user to choose input file if inRGB is missing
    imageFormats=imformats;
    imageExtList=cat(2, imageFormats.ext);    % image files extentions
    inRGB=filesFullName([], imageExtList, 'Choose input image file', true);
end

if isempty(colorSpace)
    colorSpace='LAB'; % {['LAB'], 'YCBCR', 'XYZ', 'XYL'}
end

if isempty(isPixelClr)
    isPixelClr=false;   % by default do not set each pixels marker color- it is slow
end

if isempty(is3D)
    is3D=false;         % by default use 2D plot- this is the common case
end

if isempty(isBckndImg)  % default enabled or disabled plot bacground
    isBckndImg=true;
end

if isBckndImg
    if isPixelClr
        % when  isPixelClr enabled, one will not see the points on top of the bacground, so
        % this is a bad combination
        warning( 'digitalVectorscope:paramAmbiguous', ...
            strcat('\nContradictive parameters: isBckndImg & isPixelClr enabled',...
            ' is bad practise.\n Thus isBckndImg is disabled.') );
        isBckndImg=false;
    end
    
    if is3D
        % when  is3D enabled, 3D plot is badly combined with image located in XY plane
        warning( 'digitalVectorscope:paramAmbiguous', ...
            strcat('\nContradictive parameters: isBckndImg & is3D enabled',...
            ' is bad practise.\n Thus isBckndImg is disabled.') );
        isBckndImg=false;
    end
end     % if isBckndImg

if isempty(hAxes)
    % Open a figure if no hAxes was specified (plot- like behaviour)
    figure; % figureFullScreen(figure, true); %
    hAxes=axes;
    %     if is3D
    %         set( hAxes, 'CameraPosition', [-5 -5 5]);
    %     end
end

if isempty(borderSize)
    borderSize=10;
end
%% !!!! Background image is currenlty disabled !!!
% isBckndImg=false;

%% Default marker and line parameters
if isempty(varargin) | ~strcmpi(varargin, 'Marker')
    varargin=cat(2, varargin, {'Marker', '.'});
    if ~isPixelClr
        varargin=cat( 2, varargin, {'Color', 'r'} );
    end
end

if isempty(varargin) | ~strcmpi(varargin, 'LineStyle')
    varargin=cat( 2, varargin, {'LineStyle', 'None'} );
end

if isPixelClr
    if any( cat(2, strcmpi(varargin, 'Color'), strcmpi(varargin, 'MarkerEdgeColor') ) )
        isPixelClr=false;
        warning('digitalVectorscope:paramAmbiguous', ...
            'Contradictive parameters: isPixelClr enabled, while line color specified');
    end
end

%% Program start
% ############################################################## %
% ############################################################## %
holdState=ishold; % the hold {no, off} state at program start

if ischar(inRGB) && exist(inRGB, 'file')==2
    inRGB=imread(inRGB);
end

nClrDims=size(inRGB, 3);
colsInRGB=size(inRGB, 2);
% in case of non 3D matrix (for example is indexing via logicals was used) reshape to
%   proper dimentions
if nClrDims==1
    inRGB=reshape(inRGB, [], colsInRGB, 3);
end
inRGB=reshape(inRGB, [], 3); % convert to 2D matrix

% Convert to chanIntensity*clrChanA*clrChanB* and divide to color channels
% For each input class, set maximal and minimal values
switch( upper(class(inRGB)) )
    case('UINT8')
        maxRGB=2^8-1;
    case('UINT16')
        maxRGB=2^16-1;
    case('DOUBLE')
        maxRGB=1;
end


switch(upper(colorSpace))
    case('LAB')
        cform2CLrSpace = makecform('srgb2lab');
        hFunc2ClrSpace=@(x) applycform(x, cform2CLrSpace);
        
        cform2RGB = makecform('lab2srgb');
        hFunc2RGB=@(x) applycform(x, cform2RGB);
        
        minValClr=-100;
        maxValClr=100;
        minValInt=0;
        maxValInt=100;
        
        xString='A*';
        yString='B*';
        zString='L*';
        
        iInt=1;
        iClrA=2;
        iClrB=3;
        
    case('XYZ')
        cform2CLrSpace = makecform('srgb2xyz');
        hFunc2ClrSpace=@(x) applycform(x, cform2CLrSpace);
        
        cform2RGB = makecform('xyz2srgb');
        hFunc2RGB=@(x) applycform(x, cform2RGB);
        
        minValClr=0;
        maxValClr=1;
        minValInt=0;
        maxValInt=1;
        
        xString='X';
        yString='Y';
        zString='Z';
        
        iInt=1;
        iClrA=2;
        iClrB=3;
        
    case('XYL')
        cform2CLrSpace1 = makecform('srgb2xyz');
        cform2CLrSpace2 = makecform('xyz2xyl');
        hFunc2ClrSpace=@(x) applycform(applycform(x, cform2CLrSpace1) ,cform2CLrSpace2);
        
        cform2RGB1 = makecform('xyl2xyz');
        cform2RGB2 = makecform('xyz2srgb');
        hFunc2RGB=@(x) applycform(applycform(x, cform2RGB1) ,cform2RGB2);
        
        minValClr=0;
        maxValClr=1;
        minValInt=0;
        maxValInt=1;
        
        xString='X';
        yString='Y';
        zString='L';
        
        iInt=3;
        iClrA=1;
        iClrB=2;
        
    case('YCBCR')
        hFunc2ClrSpace=@(x) rgb2ycbcr(x);
        
        hFunc2RGB=@(x) ycbcr2rgb(x);
        
        minValClr=0;
        maxValClr=1;
        minValInt=0;
        maxValInt=1;
        
        xString='Cb';
        yString='Cr';
        zString='Y';
        
        iInt=1;
        iClrA=2;
        iClrB=3;
end


hChildren=get(hAxes, 'Children');
if strcmpi(get(hChildren, 'Type'), 'image')
    hFuncConv2Img=@conv2ImgSpace;
    isBckndImgFound=true;
else
    hFuncConv2Img=@(x) reshape(x, [], 3);
    isBckndImgFound=false;
end

hLines=hChildren( strcmpi(get(hChildren, 'Type'), 'Line') );
isCenterVectorscope=any( strcmpi(get(hLines, 'Tag'), 'centerVectorscope') );
if ~isCenterVectorscope
    if isBckndImg
        %% Generate background image
        nA=200;
        nB=200;
        
        bcndClrSpace=zeros( [nB, nA] );
        [ bcndClrSpace(:, :, 2), bcndClrSpace(:, :, 3) ]=...
            meshgrid( linspace(minValClr, maxValClr, nA), linspace(minValClr, maxValClr, nB) );
        bcndClrSpace(:, :, 1)=maxValInt; % mean( [minValInt, maxValInt] );
        
        bcndRGB=hFunc2RGB(bcndClrSpace);
        imshow(bcndRGB);
        isBckndImgFound=true;
        
        %% Order the resulting axes
        
        %% X axis
        %     set(hAxes, 'XTick',  xTickVals);
        %     set(hAxes, 'XAxisLocation', 'top');     %	[ top | {bottom} ]
        nXTick=length( get(hAxes, 'XTick') )+1;
        xTickLabelVals=linspace(minValClr, maxValClr, nXTick);
        set( hAxes, 'XTickLabel',  xTickLabelVals(2:end) );
        %     set( hAxes, 'XTickLabelMode', 'manual' );
        set( hAxes, 'XTickMode', 'manual' );
        
        %     set(hAxes, 'XTick', linspace(minValClr, maxValClr, nXTickLabel) );
        
        %% Y axis
        set(hAxes, 'YDir', 'normal');
        %     set(hAxes, 'YAxisLocation', 'right'); 	%   [ {left} | right ]
        %     nYTickLabelElems=length( get(hAxes, 'YTickLabel') );
        nYTick=length( get(hAxes, 'YTick') )+1;
        yTickLabelVals=linspace(minValClr, maxValClr, nYTick);
        set(hAxes, 'YTickLabel', yTickLabelVals(2:end) );
        set(hAxes, 'YTickLabelMode', 'manual');
        set(hAxes, 'YTickMode', 'manual');
        
        hFuncConv2Img=@conv2ImgSpace;
    else
        hFuncConv2Img=@(x) reshape(x, [], 3);
    end
    
    %% Title, lables, grids and center point
    title('Digital Vectorscope', 'FontSize', 20);
    grid on;
    hold on;
    xlabel(xString, 'FontSize', 18);
    ylabel(yString, 'FontSize', 18);
    axis on;
    axis equal;
    
    baseColorsText={'M_G', 'B', 'C_Y', 'G', 'Y_L', 'R'};
    dTextDist=[(maxValClr-minValClr)/25, (maxValClr-minValClr)/50, (maxValClr-minValClr)/50];
    nColors=length(baseColorsText);
    
    baseColorsRGB=[...
        168	61	134;...
        31	46	131;...
        0	119	151;...
        64	133	52;...
        232	188	0;...
        154	31	43  ]/255;
    baseColorsSpaceData=hFunc2ClrSpace( baseColorsRGB );
    %% Convert data to imageClrSpace
    baseColorsImgSpaceData=hFuncConv2Img(baseColorsSpaceData);
    
    if is3D
        [sphereX, sphereY, sphereZ] = sphere(11); 
        sphereX=borderSize*sphereX;
        sphereY=borderSize*sphereY;
        sphereZ=borderSize*sphereZ;
        
        zlabel(zString, 'FontSize', 18);
        
        centerPoint=[maxValClr+minValClr, maxValClr+minValClr, maxValInt+minValInt]/2;
        centerPoint=hFuncConv2Img(centerPoint);
        
        [cylinderX, cylinderY, cylinderZ] = cylinder(maxValClr-minValClr);
        cylinderX=cylinderX + centerPoint(1);
        cylinderY=cylinderY + centerPoint(2);
        cylinderZ=cylinderZ*(maxValInt- minValInt);
        cylinderZ=cylinderZ + minValInt;
        
        meshH=mesh(cylinderX, cylinderY, cylinderZ);
        set( meshH, 'FaceAlpha', 0); % make pathces fully transparent
        axis tight;
        
        hCenterLine=plot3(centerPoint(1), centerPoint(2),centerPoint(3),...
            'Marker', 'o', 'MarkerEdgeColor', 'k', 'MarkerFaceColor', 'k',...
            'LineStyle', 'None');
        % text(centerPoint(1), centerPoint(2)-5, centerPoint(3)-5, 'Origin', 'Color', 'r');
        
        pointX=baseColorsImgSpaceData(:, iClrA);
        pointY=baseColorsImgSpaceData(:, iClrB);
        pointZ=baseColorsImgSpaceData(:, iInt);

        plot3( pointZ, pointX, pointY, 'Marker', 's',...
            'MarkerEdgeColor', 'k', 'LineWidth', 2, 'LineStyle', 'None');
        for iBaseColors=1:nColors
            meshH=mesh(sphereZ+pointZ(iBaseColors), sphereX+pointX(iBaseColors),...
                sphereY+pointY(iBaseColors), 'EdgeColor', 'k', 'LineStyle', ':');
            set( meshH, 'FaceAlpha', 0); % make pathces fully transparent

            text(pointZ(iBaseColors)+dTextDist(1), pointX(iBaseColors)+dTextDist(2),...
                pointY(iBaseColors)+dTextDist(3),...
                baseColorsText{iBaseColors}, 'Color', 'k', 'FontSize', 16);
        end
        
    else
        if ~isBckndImgFound
            axis( [minValClr, maxValClr, minValClr, maxValClr] );
        end
        axis tight;
        
        plot( baseColorsImgSpaceData(:, iClrA),...
            baseColorsImgSpaceData(:, iClrB),...
            'Marker', 's', 'MarkerEdgeColor', 'k', 'LineWidth', 2,...
            'LineStyle', 'None');
        for iBaseColors=1:nColors
            pointX=baseColorsImgSpaceData(iBaseColors, iClrA);
            pointY=baseColorsImgSpaceData(iBaseColors, iClrB);
            
            rectangle('Position', [pointX-borderSize/2, pointY-borderSize/2,...
                borderSize, borderSize],...
                'Curvature', [0.8, 0.4], 'EdgeColor', 'k',...
                'LineWidth', 2, 'LineStyle','--');
            
            text(pointX+dTextDist(2), pointY+dTextDist(3), baseColorsText{iBaseColors},...
                'Color', 'k', 'FontSize', 16);
        end
        
        rectOffset=maxValClr/(2*255);
        if isBckndImg
            rectData=[rectOffset, rectOffset, nA, nB];
            centerPoint=[nA, nB]/2;
        else
            rectData=[minValClr+rectOffset, minValClr+rectOffset,...
                maxValClr-minValClr-2*rectOffset, maxValClr-minValClr-2*rectOffset];
            centerPoint=[maxValClr+minValClr, maxValClr+minValClr]/2;
        end
        
        rectangle('Position', rectData, 'Curvature',[1,1],...
            'LineWidth', 2, 'EdgeColor', 'k');
        
        hCenterLine=plot(centerPoint(1), centerPoint(2),...
            'Marker', 'o', 'MarkerEdgeColor', 'k', 'MarkerFaceColor', 'k',...
            'LineStyle', 'None');
        % text(centerPoint(1), centerPoint(2)-5, 'Origin', 'Color', 'r');
    end % if is3D
    
    %     hold off;
    
    set(hCenterLine, 'Tag', 'centerVectorscope');
end % if isempty( get(hAxes, 'UserData') ) || ~get(hAxes, 'UserData')
normInRGB=double(inRGB)/maxRGB;
clrSpaceData=hFunc2ClrSpace( normInRGB );
clrImgSpaceData=hFuncConv2Img(clrSpaceData);

chanIntensity=clrImgSpaceData(:, iInt);
clrChanA=clrImgSpaceData(:, iClrA);
clrChanB=clrImgSpaceData(:, iClrB);

axes(hAxes); % make user selected axes current

nPixels=numel(chanIntensity);
if isPixelClr % each pixel color will be used for it's marker- it is slow...
    for iPixel=1:nPixels
        if is3D % 3D case
            hLine=plot3( clrChanA(iPixel), clrChanB(iPixel), chanIntensity(iPixel),...
                'Color', normInRGB(iPixel, :),...
                'MarkerFaceColor', normInRGB(iPixel, :), varargin{:} );
        else % 2D case
            hLine=plot( clrChanA(iPixel), clrChanB(iPixel),...
                'Color', normInRGB(iPixel, :),...
                'MarkerFaceColor', normInRGB(iPixel, :), varargin{:} );
        end
        if iPixel==1
            hold on;    % Enable "hold" starting from the first pixel
        elseif iPixel==nPixels
            hold off;   % Disable "hold" when finished
        end
    end
else % all markers color are the same
    if is3D
        hLine=plot3( clrChanA(:), clrChanB(:), chanIntensity(:), varargin{:} );
    else
        hLine=plot( clrChanA(:), clrChanB(:), varargin{:} );
    end
end

%% Return to initial hold state at program start
if xor(holdState, ishold) % if current hold state different from the initial one- change it
    hold(hAxes);
end


%% make hLine be the last (first) child, so it will be affected by further operations
% This si good for elgending the plot later on...
hChildren=get(hAxes, 'Children');
iLine=(hChildren==hLine);
% hChildren(iLine)=hChildren(end);
% hChildren(end)=hLine;

% This works fine
hChildren=cat( 1, hLine, hChildren(~iLine) ); % make hLine first

% This brings center and  text lable above the plot data
% hChildren=cat( 1, hChildren(~iLine), hLine ); % make hLine last

set(hAxes, 'Children', hChildren);


%% Internal nested function
    function imgSpaceData=conv2ImgSpace(clrSpaceData)
        is2D=false;
        if size(clrSpaceData, 3)==2
            clrSpaceData=cat( 3, clrSpaceData, clrSpaceData(:, :, 2) );
            is2D=true;
        elseif size(clrSpaceData, 2)==2
            clrSpaceData=cat( 2, clrSpaceData, clrSpaceData(:, 2) );
            is2D=true;
        end
        
        imgSpaceData=reshape(clrSpaceData, [], 3);
        
        imgSpaceData(:, iInt)=( imgSpaceData(:, iInt)-minValInt)/(maxValInt-minValInt );
        imgSpaceData(:, iClrA)=nA*( imgSpaceData(:, iClrA)-minValClr )/(maxValClr-minValClr);
        imgSpaceData(:, iClrB)=nB*( imgSpaceData(:, iClrB)-minValClr )/(maxValClr-minValClr);
        
        if is2D
            imgSpaceData(:, end)=[]; % delete last columnn
        end
    end
end

Contact us