MathGroup Archive 2010

[Date Index] [Thread Index] [Author Index]

Search the Archive

Re: Finding local maxima in multidimensional array (efficiently)

  • To: mathgroup at smc.vnet.net
  • Subject: [mg114327] Re: Finding local maxima in multidimensional array (efficiently)
  • From: "Carl K. Woll" <carlw at wolfram.com>
  • Date: Wed, 1 Dec 2010 02:11:20 -0500 (EST)

On 11/28/2010 5:55 AM, Ray Koopman wrote:
> On Nov 27, 12:48 am, Ray Koopman<koop... at sfu.ca>  wrote:
>> On Nov 26, 2:52 am, Julian Francis<julian.w.fran... at gmail.com>  wrote:
>>> Dear all,
>>>
>>> How do I find the local maxima in a 4 Dimensional array.
>>>
>>> e.g.
>>>
>>> randTable = Table[Random[], {a, 1, 40}, {b, 1, 40}, {c, 1, 40}, {d, 1,
>>> 40}];
>>>
>>> in this case there are 80 neighbours for each point (except at the
>>> edges)
>>>
>>> This is my best attempt, NB I take the highest 0.1% to reduce the
>>> computational load:
>>>
>>> In[11]:= threshold = Quantile[Flatten[randTable], .999]
>>>
>>> Out[11]= 0.998989
>>>
>>> In[13]:= top = Position[randTable, x_ /; x>  threshold];
>>>
>>> In[5]:= arrayBoundsCheck[array_, index_] :=
>>>   If[index == {}, True,
>>>    First[index]>  0&&  First[index]<= Length[array]&&
>>>     arrayBoundsCheck[array[[First[index]]], Rest[index]]]
>>>
>>> In[6]:= maxKernel =
>>>    DeleteCases[
>>>     Flatten[Table[{h, w, y, x}, {h, -1, 1}, {w, -1, 1}, {y, -1,
>>>        1}, {x, -1, 1}], 3], {0, 0, 0, 0}];
>>>
>>> In[8]:= localMax[line_, array_] :=
>>>   Extract[array, line]>
>>>    Max[Extract[array,
>>>      Select[Map[Function[s, s + line], maxKernel],
>>>       arrayBoundsCheck[array, #]&]]]
>>>
>>> In[15]:= Timing[results = Select[top, localMax[#, randTable]&];]
>>>
>>> Out[15]= {80.59, Null}
>>>
>>> Taking the top 0.1% as I have done is just done to try to bring the
>>> compute time down, but isn't necessary to the problem.
>>> I'm really just interested in finding a list of all the local maxima.
>>> If someone has a much better solution, I would love to see it.
>>>
>>> Thanks very much,
>>> Julian.
>> Here are two routines that find local maxima.
>>
>> 'localMaxima' is a recoded version of your algorithm.
>>
>> localMaxima[data_,p_] := Module[{dims,threshold,maxKernel,localMaxQ},
>> dims = Dimensions@data; threshold = Quantile[Flatten@data,p];
>> maxKernel = Transpose@Delete[Tuples[{-1,0,1},4],41];
>> localMaxQ[index_] := Extract[data, index]>  Max@Extract[data,
>>       Select[Transpose[index + maxKernel],
>>              (And@@LessEqual@@@Transpose@{{1,1,1,1},#,dims}&)]];
>> Select[Position[data, x_ /; x>  threshold], localMaxQ]]
>>
>> 'localMaxima2' takes a different approach. There is no thresholding,
>> and the window size is variable. The second parameter, 'h',
>> controls the window size: j +/- h on each axis.
>> A local maximum must be in the center of a full window.
>> ('localMaxima' uses h = 1 implicitly, and does not
>> require maxima to be in the centers of full windows.)
>>
>> localMaxima2[data_,h_] := Module[{dims = Dimensions@data,
>> r = Range[-h,h], s = (1+(1+2h)^4)/2}, Reap[
>> Do[If[#[[s]]>  Max@Delete[#,s], Sow@{j1,j2,j3,j4}]&  @
>>     Flatten@data[[j1+r,j2+r,j3+r,j4+r]],
>>     {j1,1+h,dims[[1]]-h},
>>     {j2,1+h,dims[[2]]-h},
>>     {j3,1+h,dims[[3]]-h},
>>     {j4,1+h,dims[[4]]-h}]][[2,1]]]
>>
>> Dimensions[randata = With[{n = 40}, Table[Random[],{n},{n},{n},{n}]]]
>> {40,40,40,40}
>>
>> Timing@Length[result = localMaxima[randata,.999]]
>> {18.53 Second, 2448}
>>
>> Timing@Length[result1 = localMaxima2[randata,1]]
>> {80.35 Second, 25788}
>>
>> Timing@Length[result2 = localMaxima2[randata,2]]
>> {84.68 Second, 2629}
>>
>> Timing@Length[result3 = localMaxima2[randata,3]]
>> {360.38 Second, 559}
> 'localMaxima3' defines the window around each test point by ANDing
> a hypersphere of squared radius 'rr' with the hypercube used by
> localMaxima2: only points that are in both the sphere and the cube
> are in the window.
>
> The minimum distance of the test point from each edge of the data
> is still h, regardless of rr.
>
> If rr>= 4 h^2 then localMaxima3 will give the same result as
> localMaxima2 but will take more time to do it.
>
> localMaxima3[data_,h_,rr_] := Module[{dims,g,sel,s},
> dims = Dimensions@data; g = Range[-h,h];
> sel = Flatten@Position[Tuples[g,4],x_/;x.x<= rr,{1}];
> s = (1 + Length@sel)/2; Reap[
> Do[If[#[[s]]>  Max@Delete[#,s], Sow@{j1,j2,j3,j4}]&  @
>     Flatten[data[[j1+g,j2+g,j3+g,j4+g]]][[sel]],
>     {j1,1+h,dims[[1]]-h},
>     {j2,1+h,dims[[2]]-h},
>     {j3,1+h,dims[[3]]-h},
>     {j4,1+h,dims[[4]]-h}]][[2,1]]]
>
> Dimensions[randata = With[{n = 30},Table[Random[],{n},{n},{n},{n}]]]
> {30,30,30,30}
>
> Timing@Length[result = localMaxima[randata,.999]]
> {5.45 Second, 772}
>
> Timing@Length[res1   = localMaxima2[randata,1  ]]
> Timing@Length[res104 = localMaxima3[randata,1,4]]
> res1 == res104
>
> {23.06 Second, 7614}
> {25.66 Second, 7614}
> True
>
> Timing@Length[res2   = localMaxima2[randata,2   ]]
> Timing@Length[res216 = localMaxima3[randata,2,16]]
> Timing@Length[res204 = localMaxima3[randata,2, 4]]
> res2 == res216
>
> {22.69 Second, 701}
> {31.02 Second, 701}
> {22.98 Second, 5161}
> True
>
> Timing@Length[res3   = localMaxima2[randata,3   ]]
> Timing@Length[res336 = localMaxima3[randata,3,36]]
> Timing@Length[res325 = localMaxima3[randata,3,25]]
> Timing@Length[res316 = localMaxima3[randata,3,16]]
> Timing@Length[res309 = localMaxima3[randata,3, 9]]
> res3 == res336
>
> { 89.30 Second, 142}
> {144.59 Second, 142}
> {141.58 Second, 155}
> { 80.03 Second, 254}
> { 65.54 Second, 776}
> True
>
> Timing@Length[res4   = localMaxima2[randata,4   ]]
> Timing@Length[res464 = localMaxima3[randata,4,64]]
> Timing@Length[res436 = localMaxima3[randata,4,36]]
> Timing@Length[res425 = localMaxima3[randata,4,25]]
> Timing@Length[res416 = localMaxima3[randata,4,16]]
> res4 == res464
>
> {143.83 Second, 40}
> {265.62 Second, 40}
> {242.99 Second, 47}
> {174.37 Second, 86}
> {105.37 Second,196}
> True

If one only cares about rectangular blocks, then an alternative is to 
use Partition, or even better, Developer`PartitionMap. For example, the 
following finds the max value of each hypercube of length 2:

blockMax[data_] :=  Developer`PartitionMap[Max, data, ConstantArray[3, 
Length[Dimensions[data]]], 1]

The following finds out the locations where the center of each hypercube 
is at least as large as the max:

lm[data_] :=  Position[UnitStep[ArrayPad[data, -1] - blockMax[data]], 1] + 1

Comparing:

In[25]:= n=40;
data=RandomReal[1,{n,n,n,n}];

In[27]:= r1=lm[data];//Timing
r2=localMaxima2[data,1];//Timing
r1===r2

Out[27]= {3.385,Null}
Out[28]= {37.206,Null}
Out[29]= True

The above works because UnitStep[0.] is 1. If local max means that the 
center value is strictly greater than all neighbors, then the above 
approach can return incorrect results.

Carl Woll
Wolfram Research



  • Prev by Date: Re: How to short-circuit match failure?
  • Next by Date: Re: Re-virginating Manipulates?
  • Previous by thread: Re: How to short-circuit match failure?
  • Next by thread: Re: Finding local maxima in multidimensional array (efficiently)