;+ ; ; see http://www.physics.emory.edu/~weeks/idl ; for more information ; ; NAME: ; Feature ; PURPOSE: ; Finds and measures roughly circular 'features' within ; an image. ; CATEGORY: ; Image Processing ; CALLING SEQUENCE: ; f = feature( image, diameter [, separation, masscut = masscut, ; min = min, iterate = iterate, /field, /quiet ] ) ; INPUTS: ; image: (nx,ny) array which presumably contains some ; features worth finding ; diameter: a parameter which should be a little greater than ; the diameter of the largest features in the image. ; Diameter MUST BE ODD valued. ; separation: an optional parameter which specifies the ; minimum allowable separation between feature ; centers. The default value is diameter+1. ; masscut:Setting this parameter saves runtime by reducing the ; runtime wasted on low mass 'noise' features. ; min: Set this optional parameter to the minimum allowed ; value for the peak brightness of a feature. Useful ; for limiting the number of spurious features in ; noisy images. ; field: Set this keyword if image is actually just one field ; of an interlaced (e.g. video) image. All the masks ; will then be constructed with a 2:1 aspect ratio. ; quiet: Supress printing of informational messages. ; iterate: if the refined centroid position is too far from ; the initial estimate, iteratively recalc. the centroid ; using the last cetroid to position the mask. This ; can be useful for really noisy data, or data with ; flat (e.g. saturated) peaks. Use with caution- it ; may 'climb' hills and give you multiple hits. ; OUTPUTS: ; f(0,*): this contains the x centroid positions, in pixels. ; f(1,*): this contains the y centroid positions, in pixels. ; f(2,*): this contains the integrated brightness of the ; features. ; f(3,*): this contains the square of the radius of gyration ; of the features. ; f(4,*): this contains the eccentricity, which should be ; zero for circularly symmetric features and order ; one for very elongated images. ; SIDE EFFECTS: ; Displays the number of features found on the screen. ; RESTRICTIONS: ; To work properly, the image must consist of bright, ; circularly symmetric regions on a roughly zero-valued ; background. To find dark features, the image should be ; inverted and the background subtracted. If the image ; contains a large amount of high spatial frequency noise, ; performance will be improved by first filtering the image. ; BPASS will remove high spatial frequency noise, and ; subtract the image background and thus provides a good ; complement to using this program. Individual features ; should NOT overlap. ; PROCEDURE: ; First, identify the positions of all the local maxima in ; the image ( defined in a circular neighborhood with diameter ; equal to 'diameter' ). Around each of these maxima, place a ; circular mask, of diameter 'diameter', and calculate the x & y ; centroids, the total of all the pixel values, and the radius ; of gyration and the 'eccentricity' of the pixel values within ; that mask. If the initial local maximum is found to be more ; than 0.5 pixels from the centroid and iterate is set, the mask ; is moved and the data are re-calculated. This is useful for ; noisy data. If the restrictions above are adhered to, and the ; features are more than about 5 pixels across, the resulting x ; and y values will have errors of order 0.1 pixels for ; reasonably noise free images. ; ; ********* READ THE FOLLOWING IMPORTANT CAVEAT! ********** ; 'feature' is capable of finding image features with sub-pixel ; accuracy, but only if used correctly- that is, if the ; background is subtracted off properly and the centroid mask ; is larger than the feature, so that clipping does not occur. ; It is an EXCELLENT idea when working with new data to plot ; a histogram of the x-positions mod 1, that is, of the ; fractional part of x in pixels. If the resulting histogram ; is flat, then you're ok, if its strongly peaked, then you're ; doing something wrong- but probably still getting 'nearest ; pixel' accuracy. ; ; For a more quantitative treatment of sub-pixel position ; resolution see: ; J.C. Crocker and D.G. Grier, J. Colloid Interface Sci. ; *179*, 298 (1996). ; ; MODIFICATION HISTORY: ; This code is inspired by feature_stats2 written by ; David G. Grier, U of Chicago, 1992. ; Written by John C. Crocker, U of Chicago, optimizing ; runtime and measurement error, 10/93. ; Added field keyword, 4/94. ; Added eccentricity parameter, 5/95. ; Added quiet keyword 12/95. ; Added iteration, fixed up the radius/diameter fiasco and ; did some debugging which improves non-centroid data. 4/96. ; ; This code 'feature.pro' is copyright 1997, John C. Crocker and ; David G. Grier. It should be considered 'freeware'- and may be ; distributed freely in its original form when properly attributed. ;- ; ; produce a parabolic mask ; function rsqd,w,h if n_params() eq 1 then h = w r2 = fltarr(w,h,/nozero) xc = float(w-1) / 2. yc = float(h-1) / 2. x = (findgen(w) - xc) x = x^2 y = (findgen(h)- yc) y = y^2 for j = 0, h-1 do begin r2(*,j) = x + y(j) endfor return,r2 end ; ; produce a 'theta' mask ; function thetarr,w theta = fltarr(w,w,/nozero) xc = float(w-1) / 2. yc = float(w-1) / 2. x = (findgen(w) - xc) x(xc) = 1e-5 y = (findgen(w) - yc) for j = 0, w-1 do begin theta(*,j) = atan( y(j),x ) endfor return,theta end ; ; This routine returns the even or odd field of an image ; function fieldof,array,odd=odd,even=even sz = size(array) if sz(0) ne 2 then message,"Argument must be a two-dimensional array!" if keyword_set(odd) then f=1 else f=0 ny2 = fix( (sz(2)+(1-f))/2 ) rows = indgen(ny2)*2 + f return,array(*,rows) end ; ; barrel "shifts" a floating point arr by a fractional pixel amount, ; by using a 'lego' interpolation technique. ; function fracshift,im,shiftx,shifty ipx = fix( shiftx ) ipy = fix( shifty ) fpx = shiftx - ipx fpy = shifty - ipy if fpx lt 0 then begin fpx=fpx+1 & ipx=ipx-1 endif if fpy lt 0 then begin fpy=fpy+1 & ipy=ipy-1 endif image = im imagex = shift( image,ipx+1,ipy ) imagey = shift( image,ipx ,ipy+1 ) imagexy = shift( image,ipx+1,ipy+1 ) image = shift( image,ipx ,ipy ) res = ( (1. - fpx) * (1. - fpy) * image ) + $ ( ( fpx) * (1. - fpy) * imagex ) + $ ( (1. - fpx) * ( fpy) * imagey ) + $ ( ( fpx) * ( fpy) * imagexy ) return,res end ; ; John's version of local_max2, which supports the field keyword ; and is otherwise identical. ; function lmx, image, sep, min = min, field = field range = fix(sep/2) a = bytscl(image) w = round( 2 * range + 1 ) ; width of sample region s = rsqd( w ) ; sample region is circular good = where( s le range^2 ) mask = bytarr( w, w ) mask(good) = 1b yrange = range if keyword_set( field ) then begin mask = fieldof( mask, /even ) yrange = fix(range/2.) +1 endif b = dilate( a, mask, /gray ) ; find local maxima in given range ; but don't include pixels from the ; background which will be too dim if not keyword_set( min ) then begin h = histogram( a ) for i = 1, n_elements(h) - 1 do $ h(i) = h(i) + h(i-1) h = float( h ) / max( h ) min = 0 while h(min) lt 0.64 do $ min = min + 1 min = min + 1 endif r = where( a eq b and a ge min ) ; Discard maxima within range of the edge sz = size( a ) nx = sz(1) & ny = sz(2) x = r mod nx & y = r / nx x0 = x - range & x1 = x + range y0 = y - yrange & y1 = y + yrange good = where( x0 ge 0 and x1 lt nx and y0 ge 0 and y1 lt ny,ngood ) if ngood lt 1 then $ return,[-1] r = r(good) x = x(good) & y = y(good) x0 = x0(good) & x1 = x1(good) y0 = y0(good) & y1 = y1(good) ; There may be some features which get ; found twice or which have flat peaks ; and thus produce multiple hits. Find ; and clear such spurious points. c = 0b * a c(r) = a(r) center = w * yrange + range for i = 0D, n_elements(r) - 1D do begin b = c(x0(i):x1(i),y0(i):y1(i)) b = b * mask ; look only in circular region m = max( b, location ) if location ne center then $ c(x(i),y(i)) = 0b endfor ; Ideally, the above routine would shrink ; clusters of points down to their center. ; As written, this will leave the lower ; right (?) pixel of a cluster. r = where( c ne 0 ) ; What's left are valid maxima. return,r ; return their locations end ; ; John's version of DGG's feature_stats2. ; which: a) avoids some unnecessary computation (convolutions) ; b) uses fractional shift techniques to reduce pixel bias in m and Rg ; c) has the field keyword ; function feature, image, extent, sep, min=min, masscut = masscut, field = field,$ quiet = quiet,iterate = iterate extent = fix(extent) if (extent mod 2) eq 0 then begin message,'Requires an odd extent. Adding 1...',/inf extent = extent + 1 endif sz = size( image ) nx = sz(1) ny = sz(2) if n_params() eq 2 then sep = extent+1 ; Put a border around the image to prevent mask out-of-bounds a = fltarr( nx + extent, ny + extent ) a(extent/2:(extent/2)+nx-1,extent/2:(extent/2)+ny-1) = float( image ) nx = nx + extent ; Finding local maxima if keyword_set( field ) then $ if not keyword_set( min ) then loc = lmx(a,sep,/field) else $ loc=lmx(a,sep,min=min,/field) $ else $ if not keyword_set( min ) then loc = lmx(a,sep) else $ loc=lmx(a,sep,min=min) if loc(0) eq -1 then return,-1 x = float( loc mod nx ) y = float( loc / nx ) nmax=n_elements(loc) xl = x - fix(extent/2) xh = xl + extent -1 m = fltarr(nmax) ; Set up some masks rsq = rsqd( extent ) t = thetarr( extent ) mask = rsq le (float(extent)/2.)^2 mask2 = make_array( extent , extent , /float, /index ) mod (extent ) + 1. mask2 = mask2 * mask mask3= (rsq * mask) + (1./6.) cen = float(extent-1)/2. cmask = cos(2*t) * mask smask = sin(2*t) * mask cmask(cen,cen) = 0. smask(cen,cen) = 0. ; Extract fields of the masks, if necessary if keyword_set( field ) then begin suba = fltarr(extent , fix(extent/2) , nmax) mask = fieldof(mask,/odd) xmask = fieldof(mask2,/odd) ymask = fieldof(transpose(mask2),/odd) mask3 = fieldof(mask3,/odd) cmask = fieldof(cmask,/odd) smask = fieldof(smask,/odd) halfext = fix( extent /2 ) yl = y - fix(halfext/2) yh = yl + halfext -1 yscale = 2 ycen = cen/2 endif else begin suba = fltarr(extent, extent, nmax) xmask = mask2 ymask = transpose( mask2 ) yl = y - fix(extent/2) yh = yl + extent -1 yscale = 1 ycen = cen endelse ; Estimate the mass for i=0,nmax-1 do m(i) = total( a(xl(i):xh(i),yl(i):yh(i)) * mask ) if keyword_set( masscut ) then begin w = where( m gt masscut, nmax ) if nmax eq 0 then begin message,'No features found!',/inf return,-1 endif xl = xl(w) xh = xh(w) yl = yl(w) yh = yh(w) x = x(w) y = y(w) m = m(w) endif if not keyword_set(quiet) then message, strcompress( nmax ) + ' features found.',/inf ; Setup some result arrays xc = fltarr(nmax) yc = fltarr(nmax) rg = fltarr(nmax) e = fltarr(nmax) ; Calculate feature centers for i=0,nmax-1 do begin xc(i) = total( a(xl(i):xh(i),yl(i):yh(i)) * xmask ) yc(i) = total( a(xl(i):xh(i),yl(i):yh(i)) * ymask ) endfor ; Correct for the 'offset' of the centroid masks xc = xc / m - ((float(extent)+1.)/2.) yc = (yc / m - ((float(extent)+1.)/2.)) / yscale ; Iterate any bad initial estimate. if keyword_set( iterate ) then begin counter = 0 repeat begin counter = counter + 1 w = where( abs(xc) gt 0.6, nbadx ) if nbadx gt 0 then begin dx = round( xc(w) ) xl(w) = xl(w) + dx xh(w) = xh(w) + dx x(w) = x(w) + dx endif w = where( abs(yc) gt 0.6, nbady ) if nbady gt 0 then begin dy = round( yc(w) ) yl(w) = yl(w) + dy yh(w) = yh(w) + dy y(w) = y(w) + dy endif w = where( (abs(xc) gt 0.6) or (abs(yc) gt 0.6), nbad ) if nbad gt 0 then begin ; recalculate the centroids for the guys we're iterating for i=0,nbad-1 do m(w(i)) = total( a(xl(w(i)):xh(w(i)),$ yl(w(i)):yh(w(i))) * mask ) for i=0,nbad-1 do begin xc(w(i)) = total( a(xl(w(i)):xh(w(i)),yl(w(i)):yh(w(i))) * xmask ) yc(w(i)) = total( a(xl(w(i)):xh(w(i)),yl(w(i)):yh(w(i))) * ymask ) endfor xc(w) = xc(w) / m(w) - ((float(extent)+1.)/2.) yc(w) = ( yc(w) / m(w) - ((float(extent)+1.)/2.) ) / yscale endif endrep until (nbad eq 0) or (counter eq 10) endif ; Update the positions and correct for the width of the 'border' x = x + xc - extent/2 y = ( y + yc - extent/2 ) * yscale ; Construct the subarray for i=0,nmax-1 do suba(*,*,i) = fracshift( a(xl(i):xh(i),yl(i):yh(i)), -xc(i) , -yc(i) ) ; Calculate the 'mass' for i=0,nmax-1 do m(i) = total( suba(*,*,i) * mask ) ; Calculate radii of gyration squared for i=0,nmax-1 do rg(i) = total( suba(*,*,i) * mask3 ) / m(i) ; Calculate the 'eccentricity' for i=0,nmax-1 do e(i) = sqrt(( total( suba(*,*,i) * cmask )^2 ) +$ ( total( suba(*,*,i) * smask )^2 )) / (m(i)-suba(cen,ycen,i)+1e-6) params = [transpose(x),transpose(y),transpose(m),transpose(rg),transpose(e)] return,params end