Mercurial > hg > octave-image
changeset 107:d0c044dbe1f9
Fully recoded: NDArray support, docs, tests, more compatible with MATLAB, changed copyright
author | jmones |
---|---|
date | Wed, 01 Sep 2004 22:51:14 +0000 |
parents | 09103215ed8b |
children | f322c77d1042 |
files | imadjust.m |
diffstat | 1 files changed, 319 insertions(+), 66 deletions(-) [+] |
line wrap: on
line diff
--- a/imadjust.m +++ b/imadjust.m @@ -1,4 +1,4 @@ -## Copyright (C) 1999,2000 Kai Habel +## Copyright (C) 2004 Josep Mones i Teixidor ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -13,88 +13,341 @@ ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +## +## +## Based on old imadjust.m (GPL): +## Copyright (C) 1999,2000 Kai Habel + ## -*- texinfo -*- -## @deftypefn {Function File} @var{J}= imadjust (@var{I},[low high],[bottom top],gamma) -## the values in the range [low high] of the image I (intensity) are transformed -## in range [bottom top] of the resulting intensity image (J). -## A gamma value is applied. -## If gamma is ommitted then a linear mapping (gamma=1) is assumed. +## @deftypefn {Function File} @var{J}= imadjust (@var{I}) +## @deftypefnx {Function File} @var{J}= imadjust (@var{I},[@var{low_in};@var{high_in}]) +## @deftypefnx {Function File} @var{J}= imadjust (@var{I},[@var{low_in};@var{high_in}],[@var{low_out};@var{high_out}]) +## @deftypefnx {Function File} @var{J}= imadjust (..., @var{gamma}) +## @deftypefnx {Function File} @var{newmap}= imadjust (@var{map}, ...) +## @deftypefnx {Function File} @var{RGB_out}= imadjust (@var{RGB}, ...) +## Adjust image or colormap values to a specified range +## +## @code{J=imadjust(I)} adjusts intensity image @var{I} values so that +## 1% of data on lower and higher values (2% in total) of the image is +## saturated; choosing for that the corresponding lower and higher +## bounds (using @code{stretchlim}) and mapping them to 0 and 1. @var{J} +## is an image of the same size as @var{I} which contains mapped values. +## This is equivalent to @code{imadjust(I,stretchlim(I))}. +## +## @code{J=imadjust(I,[low_in;high_in])} behaves as described but uses +## @var{low_in} and @var{high_in} values instead of calculating them. It +## maps those values to 0 and 1; saturates values lower than first limit +## to 0 and values higher than second to 1; and finally maps all values +## between limits linearly to a value between 0 and 1. If @code{[]} is +## passes as @code{[low_in;high_in]} value, then @code{[0;1]} is taken +## as a default value. +## +## @code{J=imadjust(I,[low_in;high_in],[low_out;high_out])} behaves as +## described but maps output values between @var{low_out} and +## @var{high_out} instead of 0 and 1. A default value @code{[]} can also +## be used for this parameter, which is taken as @code{[0;1]}. +## +## @code{J=imadjust(...,gamma)} takes, in addition of 3 parameters +## explained above, an extra parameter @var{gamma}, which specifies the +## shape of the mapping curve between input elements and output +## elements, which is linear (as taken if this parameter is omitted). If +## @var{gamma} is above 1, then function is weighted towards lower +## values, and if below 1, towards higher values. +## +## @code{newmap=imadjust(map,...)} applies a transformation to a +## colormap @var{map}, which output is @var{newmap}. This transformation +## is the same as explained above, just using a map instead of an image. +## @var{low_in}, @var{high_in}, @var{low_out}, @var{high_out} and +## @var{gamma} can be scalars, in which case the same values are applied +## for all three color components of a map; or it can be 1-by-3 +## vectors, to define unique mappings for each component. +## +## @code{RGB_out=imadjust(RGB,...)} adjust RGB image @var{RGB} (a +## M-by-N-by-3 array) the same way as specified in images and colormaps. +## Here too @var{low_in}, @var{high_in}, @var{low_out}, @var{high_out} and +## @var{gamma} can be scalars or 1-by-3 matrices, to specify the same +## mapping for all planes, or unique mappings for each. +## +## The formula used to realize the mapping (if we omit saturation) is: +## +## @code{J = low_out + (high_out - low_out) .* ((I - low_in) / (high_in - low_in)) .^ gamma;} +## +## @strong{Compatibility notes:} +## +## @itemize @bullet +## @item +## Prior versions of imadjust allowed @code{[low_in; high_in]} and +## @code{[low_out; high_out]} to be row vectors. Compatibility with this +## behaviour has been keeped, although preferred form is vertical vector +## (since it extends nicely to 2-by-3 matrices for RGB images and +## colormaps). +## @item +## Previous version of imadjust, if @code{low_in>high_in} it "negated" output. +## Now it is negated if @code{low_out>high_out}, for compatibility with +## MATLAB. +## @item +## Class of @var{I} is not considered, so limit values are not +## modified depending on class of the image, just treated "as is". When +## Octave 2.1.58 is out, limits will be multiplied by 255 for uint8 +## images and by 65535 for uint16 as in MATLAB. +## @end itemize ## ## @end deftypefn +## @seealso{stretchlim, brighten} -## o | -## u ot+ **** -## t | * -## p | * -## u ob+**** -## t | -## -+--+-------+--+- -## 0 il ih 1 -## input range -## -## Author: Kai Habel <kai.habel@gmx.de> -## Date: 17/03/2000 +## Author: Josep Mones i Teixidor <jmones@puntbarra.com> + +## TODO: When Octave 2.1.58 is out multiply indices if input argument is +## TODO: of class int* or uint*. -function ret = imadjust (I, in, out, gamma) +function ret = imadjust (image, in, out, gamma) - if (nargin < 3 || nargin > 4) - usage ("imadjust(...) number of arguments must be 3 or 4"); + if (nargin < 1 || nargin > 4) + usage ("imadjust(...) number of arguments must be between 1 and 4"); endif - if (nargin == 3) - gamma = 1; - else - if !(is_scalar (gamma)) - error ("imadjust(...,gamma) gamma must be a scalar"); - else - if !(gamma >= 0 && gamma < Inf) - error ("gamma range [0,Inf)"); - endif - endif + if (nargin < 4) + gamma = 1; ## default gamma + endif + + if !(ismatrix(image)) + error ("imadjust(image,...) first parameter must be a image matrix or colormap"); endif - if !(is_matrix (I)) - error ("imadjust(I,...) I must be a image matrix or colormap"); + if (nargin==1) + in=stretchlim(image); ## this saturates 1% on lower and 1% on + out=[0;1]; ## higher values + endif + + if (nargin==2) + out=[0;1]; ## default out + endif + + if !((ismatrix(in) || isempty(in)) && (ismatrix(out) || isempty(out)) ) + usage("imadjust(image,[low high],[bottom top],gamma)"); + endif + + if (isempty(in)) + in=[0;1]; ## default in endif - if !((is_vector (in) || isempty (in)) && (is_vector (out) || isempty (out)) ) - usage ("imadjust(I,[low high],[bottom top],gamma)"); - else - if (length (in) == 0) - il = 0; - ih = 1; - elseif (length (in) == 2) - il = min (in); - ih = max (in); + if (isempty(out)) + out=[0;1]; ## default out + endif + + simage=size(image); + if (length(simage)==3 && simage(3)==3) + ## image is rgb + [in, out, gamma]=__imadjust_check_3d_args__(in, out, gamma); + + ## make room + ret=zeros(size(image)); + + ## process each plane + for i=1:3 + ret(:,:,i)=__imadjust_plane__(image(:,:,i),in(1,i),in(2,i),out(1,i),out(2,i),gamma(1,i)); + endfor + + elseif (length(simage)==2) + if(simage(2)==3 && \ + (size(in)==[2,3] || size(out)==[2,3] || size(gamma)==[1,3]) ) + ## image is a colormap + [in, out, gamma]=__imadjust_check_3d_args__(in, out, gamma); + + ret=[]; + ## process each color + for i=1:3 + ret=horzcat(ret,__imadjust_plane__(image(:,i),in(1,i),in(2,i),out(1,i),out(2,i),gamma(i))); + endfor + else - usage ("imadjust(I,[low high],[bottom top],gamma)"); + ## image is a intensity image + if( !isvector(in) || length(in)!=2 || !isvector(out) || length(out)!=2 || !isscalar(gamma) || (gamma<0) || (gamma==Inf) ) + error("imadjust: on an intensity image, in and out must be 2-by-1 and gamma a positive scalar."); + endif + ret=__imadjust_plane__(image,in(1),in(2),out(1),out(2),gamma); endif - if (length (out) == 0) - ob = 0; - ot = 1; - elseif (length (out) == 2) - ob = out (1); - ot = out (2); + else + error("imadjust: first parameter must be a colormap, an intensity image or a RGB image"); + endif +endfunction + + +## This does all the work. I has a plane; li and hi input low and high +## values; and lo and ho, output bottom and top values. +## Image negative is computed if ho<lo although nothing special is +## needed, since formula automatically handles it. +function ret=__imadjust_plane__(I, li, hi, lo, ho, gamma) + ret = (I < li) .* lo; + ret = ret + (I >= li & I < hi) .* (lo + (ho - lo) .* ((I - li) / (hi - li)) .^ gamma); + ret = ret + (I >= hi) .* ho; +endfunction + - if (ob >= ot) - ob = out (1); - ot = out (2); - warning ("bottom greater top"); - endif - else - usage ("imadjust(I,[low high],[bottom top],gamma)"); - endif - endif - - ret = (I < il) .* ob; - ret = ret + (I >= il & I < ih) .* (ob + (ot - ob) .* ((I - il) / (ih - il)) .^ gamma); - ret = ret + (I >= ih) .* ot; - - if (in(1) > in(2)) - # hmm don't know if this is correct for gamma!=1 - ret = il + (ih - ret); +## Checks in, out and gamma to see if they are ok for colormap and RGB +## cases. +function [in, out, gamma]=__imadjust_check_3d_args__(in, out, gamma) + switch(size(in)) + case([2,3]) + ## ok! + case([2,1]) + in=repmat(in,1,3); + case([1,2]) ## Compatibility behaviour! + in=repmat(in',1,3); + otherwise + error("imadjust: in must be 2-by-3 or 2-by-1."); + endswitch + + switch(size(out)) + case([2,3]) + ## ok! + case([2,1]) + out=repmat(out,1,3); + case([1,2]) ## Compatibility behaviour! + out=repmat(out',1,3); + otherwise + error("imadjust: out must be 2-by-3 or 2-by-1."); + endswitch + + switch(size(gamma)) + case([1,3]) + ## ok! + case([1,1]) + gamma=repmat(gamma,1,3); + otherwise + error("imadjust: gamma must be a scalar or a 1-by-3 matrix."); + endswitch + + ## check gamma allowed range + if(!all((gamma>=0)&(gamma<Inf))) + error("imadjust: gamma values must be in the range [0,Inf]"); endif endfunction + + +# bad arguments + +# bad images +%!error(imadjust("bad argument")); +%!error(imadjust(zeros(10,10,3,2))); +%!error(imadjust(zeros(10,10,4))); +%!error(imadjust("bad argument")); + +# bad 2d, 3d or 4th argument +%!error(imadjust([1:100],"bad argument",[0;1],1)); +%!error(imadjust([1:100],[0,1,1],[0;1],1)); +%!error(imadjust([1:100],[0;1],[0,1,1],1)); +%!error(imadjust([1:100],[0;1],[0;1],[0;1])); +%!error(imadjust([1:100],[0;1],[0;1],-1)); + +%!# 1% on each end saturated +%!assert(imadjust([1:100]),[0,linspace(0,1,98),1]); + +%!# test with only input arg +%!assert(sum(abs((imadjust(linspace(0,1,100),[1/99;98/99]) - \ +%! [0,linspace(0,1,98),1] )(:))) < 1e-10); + +%!# a test with input and output args +%!assert(imadjust([1:100],[50;90],[-50;-30]), \ +%! [-50*ones(1,49), linspace(-50,-30,90-50+1), -30*ones(1,10)]); + +%!# a test with input and output args in a row vector (Compatibility behaviour) +%!assert(imadjust([1:100],[50,90],[-50,-30]), \ +%! [-50*ones(1,49), linspace(-50,-30,90-50+1), -30*ones(1,10)]); + +%!# the previous test, "negated" +%!assert(imadjust([1:100],[50;90],[-30;-50]), \ +%! [-30*ones(1,49), linspace(-30,-50,90-50+1), -50*ones(1,10)]); + + +%!shared cm,cmn +%! cm=[[1:10]',[2:11]',[3:12]']; +%! cmn=([[1:10]',[2:11]',[3:12]']-1)/11; + +%!# a colormap +%!assert(imadjust(cmn,[0;1],[10;11]),cmn+10); + +%!# a colormap with params in row (Compatibility behaviour) +%!assert(imadjust(cmn,[0,1],[10,11]),cmn+10); + +%!# a colormap, different output on each +%!assert(imadjust(cmn,[0;1],[10,20,30;11,21,31]),cmn+repmat([10,20,30],10,1)); + +%!# a colormap, different input on each, we need increased tolerance for this test +%!assert(sum(abs((imadjust(cm,[2,4,6;7,9,11],[0;1]) - \ +%! [[0,linspace(0,1,6),1,1,1]', \ +%! [0,0,linspace(0,1,6),1,1]', \ +%! [0,0,0,linspace(0,1,6),1]'] \ +%! ))(:)) < 1e-10 \ +%! ); + +%!# a colormap, different input and output on each +%!assert(sum(abs((imadjust(cm,[2,4,6;7,9,11],[0,1,2;1,2,3]) - \ +%! [[0,linspace(0,1,6),1,1,1]', \ +%! [0,0,linspace(0,1,6),1,1]'+1, \ +%! [0,0,0,linspace(0,1,6),1]'+2] \ +%! ))(:)) < 1e-10 \ +%! ); + +%!# a colormap, different gamma, input and output on each +%!assert(sum(abs((imadjust(cm,[2,4,6;7,9,11],[0,1,2;1,2,3],[1,2,3]) - \ +%! [[0,linspace(0,1,6),1,1,1]', \ +%! [0,0,linspace(0,1,6).^2,1,1]'+1, \ +%! [0,0,0,linspace(0,1,6).^3,1]'+2] \ +%! )(:))) < 1e-10 \ +%! ); + + +%!shared iRGB,iRGBn,oRGB +%! iRGB=zeros(10,1,3); +%! iRGB(:,:,1)=[1:10]'; +%! iRGB(:,:,2)=[2:11]'; +%! iRGB(:,:,3)=[3:12]'; +%! iRGBn=(iRGB-1)/11; +%! oRGB=zeros(10,1,3); +%! oRGB(:,:,1)=[0,linspace(0,1,6),1,1,1]'; +%! oRGB(:,:,2)=[0,0,linspace(0,1,6),1,1]'; +%! oRGB(:,:,3)=[0,0,0,linspace(0,1,6),1]'; + +%!# a RGB image +%!assert(imadjust(iRGBn,[0;1],[10;11]),iRGBn+10); + +%!# a RGB image, params in row (compatibility behaviour) +%!assert(imadjust(iRGBn,[0,1],[10,11]),iRGBn+10); + +%!# a RGB, different output on each +%!test +%! t=iRGBn; +%! t(:,:,1)+=10; +%! t(:,:,2)+=20; +%! t(:,:,3)+=30; +%! assert(imadjust(iRGBn,[0;1],[10,20,30;11,21,31]),t); + + +%!# a RGB, different input on each, we need increased tolerance for this test +%!assert(sum(abs((imadjust(iRGB,[2,4,6;7,9,11],[0;1]) - oRGB)(:))) < 1e-10); + +%!# a RGB, different input and output on each +%!test +%! t=oRGB; +%! t(:,:,2)+=1; +%! t(:,:,3)+=2; +%! assert(sum(abs((imadjust(iRGB,[2,4,6;7,9,11],[0,1,2;1,2,3]) - t)(:))) < 1e-10); + +%!# a RGB, different gamma, input and output on each +%!test +%! t=oRGB; +%! t(:,:,2)=t(:,:,2).^2+1; +%! t(:,:,3)=t(:,:,3).^3+2; +%! assert(sum(abs((imadjust(iRGB,[2,4,6;7,9,11],[0,1,2;1,2,3],[1,2,3]) - t)(:))) < 1e-10); + + +% +% $Log$ +% Revision 1.3 2004/09/01 22:51:14 jmones +% Fully recoded: NDArray support, docs, tests, more compatible with MATLAB, changed copyright +% +%