Code covered by the BSD License  

Highlights from
customGray

image thumbnail

customGray

by

 

07 Oct 2013 (Updated )

Create a custom grayscale image of an RGB--specify weightings or select from predefined versions.

customGray(imgin,calledFrom)
function adjfighandle = customGray(imgin,calledFrom)
% CUSTOMGRAY: UI for selecting non-default combination of R,G,and B
% Syntax: CUSTOMGRAY(IMGIN)
%         h = CUSTOMGRAY(IMGIN); (Outputs handle to the GUI figure.)
%
% CUSTOMGRAY launches an interactive, uicontrolled figure
% for creating a "custom RGB2GRAY" image by modifying the linear
% combination of R,G,B.
%
% The function will display two versions of the input image; the version of
% the left will be the original, un-adjusted image. The version on the
% right will change interactively when the slider controls are moved,
% updating the multipliers for R,G,B.
%
% NOTES:
% Function |rgb2gray| creates a grayscale image as a weighted sum of R,G,B,
% given by: GRAY = 0.2989 * R + 0.5870 * G + 0.1140 * B;
% These values were designed to provide a "visually pleasing" grayscale
% representation of the RGB image, and is weighted to reflect "normal"
% human visual acuity. However, there are other models available for
% grayscale conversion, and often other reasons (e.g., facilitating
% segmentation) for converting to grayscale.
%
% This user interface allows you to try different pre-built combinations,
% or to create your own. To visualize a wider dynamic range of intensities,
% all images are converted to type double.
%
% ALL OUTPUT IMAGES ARE TYPE DOUBLE!
%
% *Class Support* The input must be an RGB image of any class supported by
% IMREAD. When the "DONE/EXPORT" button is pressed, the output image will
% be written to the base workspace (AS TYPE DOUBLE) along with the
% parameters used to create it, and the text of the command that will
% recreate it independent of customGray.
%
% EXAMPLE:
% %1) Default (opens with default image; you can browse to load a new one):
%    customGray
% %2) Specify image:
%    customGray('peppers.png')
%
% Written by Brett Shoelson, Ph.D.
% brett.shoelson@mathworks.com
% 08/09/2013
% Thanks to Abhijit Bhattacharjee for his excellent critique.
% Comments and suggestions welcome!
%
% Copyright 2014 MathWorks, Inc.
%
% SEE ALSO: RGB2GRAY, EXPLORERGB

% Modifications:
% 01/2014 Re-architected; multiplier limits are now constrained to [-1,1],
% and the "Clipping" monitor shows (via position) the dynamic range of the
% current grayscale image, and indicates (via color) whether or not the
% image saturates (with values <0 or >1). Also, I removed the "Lightness"
% method, since it doesn't follow the same multiplier-driven workflow as
% the other methods.
%
% 11/06/14 Ensured R14b readiness. Tweaks in layout.

% Setup:
bgc = [0.553 0.659 0.678];
highlightColor = [0.85 0.9 0.9];
%
singleton = true; %Change to false to allow multiple instances
if singleton
	delete(findall(groot,'tag','customGrayFig'))
end
adjfig = figure('NumberTitle','off',...
	'name','Create Custom Grayscale Image from RGB',...
	'windowstyle','normal',...
	'units','normalized',...
	'position',[0.1 0.1 0.8 0.8],...
	'tag','customGrayFig',...
	'menubar','none',...
	'color',bgc,...
	'closeRequestFcn',@customClose);
set(adjfig,...
	'defaultuicontrolunits','normalized',...
	'defaultuicontrolbusyaction','cancel',...
	'defaultuicontrolinterruptible','off');
if nargin < 2
	calledFrom = [];
end
%
ht = uitoolbar(adjfig);
icon = im2double(imread('file_open.png'));
icon(icon==0) = NaN;
uitoggletool(ht,...
	'CData',icon,...
	'oncallback',@GetNewFile,...
	'offcallback','',...
	'Tooltipstring','Load new image.',...
	'Tag','loadImageTool');
%
icon = imread('ExportIcon.png');
uitoggletool(ht,...
	'CData',icon,...
	'oncallback',@finish,...
	'offcallback','',...
	'Tooltipstring','Export Results!',...
	'Tag','exportResults');

if nargin == 0
	imgin = 'autumn.tif';
end
if ischar(imgin)
	imname = imgin;
	imgin = iptImread(imname);
else
	imname = 'Original';
	imgin = im2double(imgin);
end
%
[objpos,objdim] = distributeObjects(2,0.025,0.975,0.025);
ax(1) = axes('parent',adjfig,...
	'position',[objpos(1) 0.3125 objdim 0.615],...
	'xtick',[],...
	'ytick',[]);
imshow(imgin,...
	'parent',ax(1));
title(imname,...
	'parent',ax(1),...
	'interpreter','none',...
	'fontweight','bold');
ax(2) = axes('parent',adjfig,...
	'position',[objpos(2) 0.3125 objdim 0.615],...
	'xtick',[],...
	'ytick',[]);
imgout = rgb2gray(imgin);
imgOutHandle = imshow(imgout,...
	'parent',ax(2));
grayTitle = title('Modified Image',...
	'fontweight','bold');
expandAxes(ax);
set(ax,...
	'fontsize',7,...
	'handlevisibility','callback',...
	'xlimmode','auto',...
	'ylimmode','auto');

annotation('textbox',[0.025 0.96 0.95 0.0225],...
	'string','CLICK ON ANY IMAGE TO VIEW IT IN A LARGER WINDOW; RIGHT-CLICK ON EXPANDED IMAGE TO EXPORT TO BASE WORKSPACE!',...
	'color','k',...
	'horizontalalignment','c',...
	'verticalalignment','m',...	
	'fontweight','b',...
	'fontsize',7,...
	'backgroundcolor',bgc*1.2);

% Histograms
colors = [1 0 0; 0 1 0; 0 0 1; 0 0 0];
[hobjpos,hobjdim] = distributeObjects(3,objpos(1)+0.025,objdim+objpos(1)-0.025,0.0375);
%histax = zeros(4,1);
histax = gobjects(4,1);
for ii = 1:3
	histax(ii) = axes(...%subplot(1,6,ii,...
		'parent',adjfig,...
		'fontsize',7,...
		'position',[hobjpos(ii) 0.2 hobjdim 0.1],...
		'xtick',[],...
		'ytick',[]);

	refreshHistogram(histax(ii),imgin(:,:,ii),ii);
end
[hobjpos,hobjdim] = distributeObjects(3,objpos(2)+0.025,objdim+objpos(2)-0.025,0.0375);
histax(4) = axes(...%subplot(1,6,4,...
	'parent',adjfig,...
	'fontsize',7,...
	'position',[hobjpos(1) 0.2 hobjdim 0.1],...
	'xtick',[],...
	'ytick',[]);
refreshHistogram(histax(4),rgb2gray(imgin),4);
axes('parent',adjfig,...
	'fontsize',7,...
	'position',[hobjpos(2) 0.2 0.9-hobjpos(2) 0.1],...
	'xlim',[0 1],...
	'ylim',[-1.1 1.1],...
	'xtick',[],...
	'ytick',-1:1,...
	'box','on');

[barpos,bardim] = distributeObjects(3,0.05,0.95,0.1);
redLevel = 0.2989;
redBar = patch([barpos(1) barpos(1)+bardim barpos(1)+bardim barpos(1)],...
	[0 0 redLevel redLevel],...
	[1 0 0]);
greenLevel = 0.5870;
greenBar = patch([barpos(2) barpos(2)+bardim barpos(2)+bardim barpos(2)],...
	[0 0 greenLevel greenLevel],...
	[0 1 0]);
blueLevel = 0.1140;
blueBar = patch([barpos(3) barpos(3)+bardim barpos(3)+bardim barpos(3)],...
	[0 0 blueLevel blueLevel],...
	[0 0 1]);
a = uicontrol('style','text',...
	'units','normalized',...
	'position',[hobjpos(2) 0.31 0.9-hobjpos(2) 0.02],...
	'fontweight','bold',...
	'fontsize',7,...
	'backgroundcolor',bgc,...
	'string','Red/Green/Blue Levels');
line(xlim,[0 0],...
	'color',[0.5 0.5 0.5],...
	'linewidth',2);
refreshBarGraph;
axes(...
	'parent',adjfig,...
	'position',[0.925 0.2 0.05 0.1],...
	'xlim',[0 1],...
	'ylim',[-0.5 1.5],...
	'xtick',[],...
	'ytick',[0,1],...
	'box','on');
histline  = gobjects(2,1);
histline(1) = line([0 1],[1 1],...
	'linestyle','--',...
	'linewidth',2);
histline(2) = line([0 1],[0 0],...
	'linestyle','--',...
	'linewidth',2);
minval = min(imgout(:));
maxval = max(imgout(:));
clipString = text(0.5,max(-0.3,min(1,mean([minval,maxval]))),'',...
	'color','w',...
	'fontweight','bold',...
	'horizontalalignment','center',...
	'fontsize',12);
tts = sprintf('Color indicates when the maximum absolute value of the grayscale intensity "clips" (i.e., extends beyond [0 1]).\nRed = clips; Green = does not clip.\nNOTE: If clipping occurs, image will be rescaled to [0,1] on export to match the visualization in the right-hand axes!');
offset = 0.025;
outputRangeString = uicontrol('style','text',...
	'units','normalized',...
	'position',[0.925-offset 0.31 0.05+2*offset 0.02],...
	'fontweight','bold',...
	'fontsize',7,...
	'backgroundcolor',bgc,...
	'horizontalalignment','center',...
	'string',sprintf('Range: [%0.2f, %0.2f]',minval,maxval),...
	'tooltipstring',tts);
if minval < 0 || maxval > 1
	satcolor = [0.85 0 0];
	clips = true;
else
	satcolor = [0 1 0];
	clips = false;
end
saturationBar = patch([0 1 1 0],[minval minval maxval maxval],satcolor);
set(adjfig,...
	'handlevisibility','callback');

processPanel = uipanel('parent',adjfig,...
	'backgroundcolor',highlightColor,...
	'position',[0.025 0.025 0.95 0.15],...
	'visible','on');
%
[objpos,objdim] = distributeObjects(3,0.025,0.6,0.025);
% [sliderHandle,panelHandle,editHandle] =
%     sliderPanel(parent,PanelPVs,SliderPVs,EditPVs,LabelPVs,numFormat);

% rgb2gray (default) converts RGB values to grayscalevalues by forming a
% weighted sum of the R, G,and B components:
%  0.2989 * R + 0.5870 * G + 0.1140 * B
uicontrol('style','text',...
	'string','Adjustment Parameters/Multipliers; Right-click sliders to set to 0.',...
	'parent',processPanel,...
	'position',[0.025 0.75 0.6 0.2],...
	'fontsize',9,...
	'backgroundcolor',highlightColor,...
	'fontweight','bold');
[redLevelSlider,~,redLevelText] = sliderPanel(processPanel,...
	{'Position',[objpos(1),0.075,objdim,0.675],...
	'Title','Red Multiplier',...
	'Backgroundcolor',highlightColor,...
	'Foregroundcolor','r'},...
	{'Min',-1,...
	'Max',1,...
	'Value',0,...
	'tag','redLevel',...
	'callback',@update,...
	'Backgroundcolor',bgc,...
	'SliderStep',[0.1/4 1/4]},...
	{'backgroundcolor',highlightColor},...
	{'Backgroundcolor',highlightColor},...
	'%0.2f');
set(redLevelSlider,...
	'value',0.2989);
set(redLevelText,...
	'string',0.2989);
[greenLevelSlider,~,greenLevelText] = sliderPanel(processPanel,...
	{'Position',[objpos(2),0.075,objdim,0.675],...
	'Title','Green Multiplier',...
	'Backgroundcolor',highlightColor...
	'Foregroundcolor',[0 0.7 0]},...
	{'Min',-1,...
	'Max',1,...
	'Value',0,...
	'tag','greenLevel',...
	'callback',@update,...
	'Backgroundcolor',bgc,...
	'SliderStep',[0.1/4 1/4]},...
	{'backgroundcolor',highlightColor},...
	{'Backgroundcolor',highlightColor},...
	'%0.2f');
set(greenLevelSlider,...
	'value',0.5870);
set(greenLevelText,...
	'string',0.5870);
[blueLevelSlider,~,blueLevelText] = sliderPanel(processPanel,...
	{'Position',[objpos(3),0.075,objdim,0.675],...
	'Title','Blue Multiplier',...
	'Backgroundcolor',highlightColor...
	'Foregroundcolor','b'},...
	{'Min',-1,...
	'Max',1,...
	'Value',0,...
	'tag','blueLevel',...
	'callback',@update,...
	'Backgroundcolor',bgc,...
	'SliderStep',[0.1/4 1/4]},...
	{'backgroundcolor',highlightColor},...
	{'Backgroundcolor',highlightColor},...
	'%0.2f');
set(blueLevelSlider,...
	'value',0.1140);
set(blueLevelText,...
	'string',0.1140);
[objpos,objdim] = distributeObjects(2,0.025,0.975,0.025);
prebuiltOptions = uibuttongroup('parent',processPanel,...
	'units','normalized',...
	'position',[0.625 0.075 1-0.025-0.625 0.675],...
	'title','Pre-Defined Combinations',...
	'Backgroundcolor',highlightColor);
uicontrol('Style','radiobutton',...
	'String','RGB2GRAY ("Luminosity 1")',...
	'units','normalized',...
	'Backgroundcolor',highlightColor,...
	'pos',[objpos(1) 0.6 objdim 0.3],...
	'parent',prebuiltOptions,...
	'HandleVisibility','off',...
	'tooltipstring',sprintf('This is a "normalized-luminosity" approach that uses a weighted average targeted to human visual acuity;\nuses default values internal to RGB2GRAY.\n[ 0.2989 0.5870 0.1140 ]'));
uicontrol('Style','radiobutton',...
	'String','Luminosity 2',...
	'units','normalized',...
	'Backgroundcolor',highlightColor,...
	'pos',[objpos(1) 0.2 objdim 0.3],...
	'parent',prebuiltOptions,...
	'HandleVisibility','off',...
	'tooltipstring',sprintf('An alternate "normalized-luminosity" approach that uses a different standard\nthan that used by RGB2GRAY to optimize for human visual acuity.\n[ 0.21 0.71 0.08 ]'));
uicontrol('Style','radiobutton',...
	'String','Average Method',...
	'units','normalized',...
	'Backgroundcolor',highlightColor,...
	'pos',[objpos(2) 0.6 objdim 0.3],...
	'parent',prebuiltOptions,...
	'HandleVisibility','off',...
	'tooltipstring',sprintf('Simple mean of R,G,B:\n[ Mean2(R) Mean2(G) Mean2(B) ])'));
% uicontrol('Style','radiobutton',...
%		'String','Lightness Method',...
%     'units','normalized',...
%		'Backgroundcolor',highlightColor,...
%     'pos',[objpos(2) 0.2 objdim 0.3],...
%		'parent',prebuiltOptions,...
%     'HandleVisibility','off',...
%     'tooltipstring','Grayscale calculated pixelwise as (max(R, G, B) + min(R, G, B)) / 2;');
%sprintf('For R,G,B, use average of strongest and weakest values:\n[ Max(R) + Min(R) / 2, Max(G) + Min(G) / 2, Max(B) + Min(B) / 2 ]');

% Initialize some button group properties.
set(prebuiltOptions,...
	'SelectionChangeFcn',@selectPredefined);
set(findall(adjfig,'type','axes'),...
	'handlevisibility','callback');
set(adjfig,...
	'handlevisibility','callback');
%
if nargout > 0
	adjfighandle = adjfig;
else
	clear adjfighandle;
end

	function customClose(varargin)
		closereq;
		if ~isempty(calledFrom)
			figure(calledFrom)
		end
	end

	function finish(varargin)
		set(gcbo,'state','off');
		%[redLevel,greenLevel,blueLevel] = updateParameters;
		updateParameters;
		opt = get(get(prebuiltOptions,'selectedObject'),'string');
		%
		fprintf('\n\nCALLING SYNTAX:\n**************************\n');
		fprintf('rgb = im2double(rgb);\n');
		fprintf('grayscale = %0.3f*rgb(:,:,1) + %0.3f*rgb(:,:,2) + %0.3f*rgb(:,:,3);\n',redLevel,greenLevel,blueLevel);
		if strcmp(opt,'RGB2GRAY ("Luminosity 1")')
			fprintf('OR:\ngrayscale = rgb2gray(rgb);\n');
		end
		fprintf('\nTo display:\nimshow(grayscale,[]);');
		fprintf('\n**************************\n\n')
		varname = evalin('base','genvarname(''ImgOut'',who)');
		outputImage = get(imgOutHandle,'cdata');
		% Scale to [0 1]
		outputImage = outputImage - min(outputImage(:));
		outputImage = outputImage/max(outputImage(:));
		% Now imadjust it:
		outputImage = imadjust(outputImage);
		assignin('base',varname,outputImage);
		fprintf('Output image written to variable %s in the base workspace.\n\n',varname);
	end %finish

	function GetNewFile(varargin)
		set(gcbo,'state','off');
		[imgin,cmap,fname,~,userCanceled] = getNewRGBImage(true);
		if userCanceled
			return
		end
		if isempty(fname),fname = 'Original'; end
		imshow(imgin,cmap,...
			'parent',ax(1));
		title(fname,...
			'parent',ax(1),...
			'interpreter','none',...
			'fontweight','bold');
		%imgOutHandle = imshow(imgin,cmap,'parent',ax(2));
		%title('Modified Image','interpreter','none','parent',ax(2));
		%set(ax,'handlevisibility','callback');
		%set(ax,'handlevisibility','callback','climmode','auto','xlimmode','auto','ylimmode','auto');
		set(ax,...
			'handlevisibility','callback',...
			'xlimmode','auto',...
			'ylimmode','auto');
		expandAxes(ax);
		update('all');
	end %GetNewFile

	function imout = iptImread(imname,varargin)
		[~,~,ext] = fileparts(imname);
		switch ext
			case '.dcm'
				imout = dicomread(imname);
			case {'.fits','.fts'}
				imout = fitsread(imname);
			case '.img'
				try
					imout = analyze75read(imname);
				catch
					try
						imout = interfilered(imname);
					catch
						error('Unknown image format.');
					end
				end
			case '.nitf'
				imout = nitfread(imname);
			case '.hdr'
				imout = hdrread(imname);
			otherwise
				imout = imread(imname);
		end
		imout = im2double(imout);
	end %iptImread

	function refreshBarGraph(varargin)
		set(redBar,...
			'ydata',[0 0 redLevel redLevel]);
		set(greenBar,...
			'ydata',[0 0 greenLevel greenLevel]);
		set(blueBar,...
			'ydata',[0 0 blueLevel blueLevel]);
	end %refreshBarGraph

	function refreshHistogram(theAx,thePlane,ind,varargin)
		%
		set(gcf,'currentAxes',theAx);
		imhist(thePlane);
		if verLessThan('matlab','8.4')
			fo = findall(theAx,'type','line');
		else
			fo = findall(theAx,'type','stem');
		end
		set(setdiff(findall(adjfig,'type','axes'),ax),...
			'fontsize',6)
		set(fo,...
			'color',colors(ind,:));
		%
		% This may be a hair faster:
% 		set(theAx,'fontsize',7);
% 		hist(theAx,thePlane(:),256);
% 		fo = findall(theAx,...
% 			'type','patch');
% 		set(fo,...
% 			'facecolor',colors(ind,:),...
% 			'edgecolor',colors(ind,:));
		%
	end %refreshHistogram

	function refreshSaturationAx(varargin)
		minval = min(imgout(:));
		maxval = max(imgout(:));
		if minval < 0 || maxval > 1
			satcolor = [0.85 0 0];
			clips = true;
			set(grayTitle,...
				'string','Modified Image (SCALED TO [0,1])');
			set(clipString,...
				'string','CLIP',...
				'position',[0.5 max(-0.3,min(1,mean([minval,maxval])))])
		else
			satcolor = [0 1 0];
			clips = false;
			set(grayTitle,...
				'string','Modified Image');
			set(clipString,...
				'string','')
		end
		set(saturationBar,...
			'facecolor',satcolor,...
			'ydata',[minval minval maxval maxval]);
		set(outputRangeString,...
			'string',sprintf('Range: [%0.2f, %0.2f]',minval,maxval));
		uistack(histline,'top');
	end %refreshSaturationAx

	function selectPredefined(varargin)
		opt = get(get(prebuiltOptions,'selectedObject'),'string');
		tmpim = im2uint16(imgin);
		R = tmpim(:,:,1);
		G = tmpim(:,:,2);
		B = tmpim(:,:,3);
		switch opt
			case 'RGB2GRAY ("Luminosity 1")'
				rgbVals = [0.2989 0.5870 0.1140];
			case 'Luminosity 2'
				rgbVals = [0.21 0.71 0.08];
			case 'Lightness Method'
				%The lightness method averages the most prominent and least
				%prominent colors: (max(R, G, B) + min(R, G, B)) / 2.
				%                 rgbVals = im2double([(max(R(:))+min(R(:)))/2,...
				%                     (max(G(:))+min(G(:)))/2,...
				%                     (max(B(:))+min(B(:)))/2]);
				%                 rgbVals = round(rgbVals*1000)/1000;
				rgbVals = [0 0 0];
			case 'Average Method'
				rgbVals = mean([R(:),G(:),B(:)])/double(intmax('uint16'));
		end
		set(redLevelSlider,...
			'value',rgbVals(1));
		set(greenLevelSlider,...
			'value',rgbVals(2));
		set(blueLevelSlider,...
			'value',rgbVals(3));
		drawnow;
		update('FromRadioButton');
	end %selectPredefined

	function ii = update(option,varargin)
		if ~nargin | ishandle(option) %#ok
			option = 'output';
		end
		% OPTION:
		%     'output': adjusts only output histograms
		%               (DEFAULT; no need to specify!)
		%     'all'   : adjusts all histograms (as on loading of new image)
		%[redLevel,greenLevel,blueLevel] = updateParameters
		updateParameters
		if ~ismember(option,{'all','FromRadioButton'})
			set(prebuiltOptions,...
				'selectedObject',[]);
		end
		% ACTUAL ADJUSTMENT:
		opt = get(get(prebuiltOptions,'selectedObject'),'string');
		if strcmp(opt,'Lightness Method')
			imgout = (min(imgin,[],3)+max(imgin,[],3))/2;
		else
			imgout = imgin(:,:,1)*redLevel + imgin(:,:,2)*greenLevel + imgin(:,:,3)*blueLevel;
		end
		refreshSaturationAx
		if clips
			imgout = imgout - min(imgout(:));
			imgout = imgout/max(imgout(:));
			% Now imadjust it:
			imgout = imadjust(imgout);
		end
		%
		if ~isa(imgout,'double')
			fprintf('\nNOTE: Display may be limited by class (%s) of working image.\nConsider converting to double...\n',class(imgout));
		end
		set(imgOutHandle,'cdata',imgout);
		%set(ax(2),...
		%'climmode','auto',...
		%'xlimmode','auto',...
		%'ylimmode','auto');
		set(ax(2),...
			'xlimmode','auto',...
			'ylimmode','auto');
		% REFRESH HISTOGRAMS
		% 'ALL'/'OUTPUT';
		if strcmp(option,'all')
			% Update input histograms:
			for ii = 1:3
				refreshHistogram(histax(ii),imgin(:,:,ii),ii);
			end
		end
		% ALWAYS refresh output histograms on update:
		refreshHistogram(histax(4),imgout,4);
		refreshBarGraph
	end %update

	function updateParameters(varargin)
		redLevel = get(redLevelSlider,'value');
		greenLevel = get(greenLevelSlider,'value');
		blueLevel = get(blueLevelSlider,'value');
		set(redLevelText,...
			'string',num2str(redLevel));
		set(greenLevelText,...
			'string',num2str(greenLevel));
		set(blueLevelText,...
			'string',num2str(blueLevel));
		drawnow
	end %updateParameters

end

Contact us