Pytorch Argrelmax (or C++) function

catubc: <p>Hello</p>
<p>I’m trying to find the equivalent pytorch (or C++) for scipy.signal.argrelmax(), which finds the peaks in a 1D array with some padding. <a href="https://docs.scipy.org/doc/scipy-0.14.0/reference/generated/scipy.signal.argrelmax.html" rel="nofollow noopener">https://docs.scipy.org/doc/scipy-0.14.0/reference/generated/scipy.signal.argrelmax.html</a></p>
<p>Here’s what I’ve come up with and it is faster than scipy.signal.argrelmax (especially for longer arrays) - but I’m missing a fast solution to the last step which deletes peaks within some window.</p>
<pre><code class="lang-auto">import torch

# initalize an array array 
gpu_max = torch.rand(100000)

# find peaks and troughs by subtracting shifted versions
gpu_temp1 = gpu_max[1:-1]-gpu_max[:-2]
gpu_temp2 = gpu_max[1:-1]-gpu_max[2:]

# and checking where both shifts are positive;
out1 = torch.where(gpu_temp1&gt;0, gpu_temp1*0+1, gpu_temp1*0)
out2 = torch.where(gpu_temp2&gt;0, out1, gpu_temp2*0)

# argrelmax containing all peaks
argrelmax_gpu = torch.nonzero(out2, out=None)+1
</code></pre>
<p>I posted this on stackoverflow and there’s a picture there that visualize the issue:  <a href="https://stackoverflow.com/questions/54498775/pytorch-argrelmax-function-or-c" rel="nofollow noopener">https://stackoverflow.com/questions/54498775/pytorch-argrelmax-function-or-c</a>.</p>
<p>Any input much appreciated!</p>

tom: <p>I’m not sure I understand the precise definition of argrelmax, but you can use maxpooling to determine the absolute maxima of sliding windows. From those (which may reasonably fall on the window boundaries) you could either filter the local maxima, i.e.</p>
<pre data-code-wrap="python"><code class="lang-python">N = 200
width = 31 # odd
a = torch.randn(100).cumsum(0)
a -= torch.linspace(0, a[-1].item(), a.size(0))
peak_mask = torch.cat([torch.zeros(1, dtype=torch.uint8), (a[:-2]&lt;a[1:-1]) &amp; (a[2:]&lt;a[1:-1]), torch.zeros(1, dtype=torch.uint8)], dim=0)
b = torch.nn.functional.max_pool1d_with_indices(a.view(1,1,-1), width, 1, padding=width//2)[1].unique()
b = b[peak_mask[b].nonzero()]

pyplot.plot(a.numpy())
pyplot.plot(b.numpy(), a[b].numpy(),'.')
</code></pre>
<p><img src="https://discuss.pytorch.org/uploads/default/original/2X/f/f99beb77440d5d52b2974f81ad0f43c96cbb4b5c.png" alt="Figure_1" data-base62-sha1="zC8WHiC7Qhtm5ko3NPdBLd6Iuu0" width="640" height="480"></p>
<p>or you could filter which of the window maxima are maxima for the windows around themselves</p>
<pre data-code-wrap="python"><code class="lang-python">window_maxima = torch.nn.functional.max_pool1d_with_indices(a.view(1,1,-1), width, 1, padding=width//2)[1].squeeze()
candidates = window_maxima.unique()
nice_peaks = candidates[(window_maxima[candidates]==candidates).nonzero()]
pyplot.plot(a.numpy())
pyplot.plot(nice_peaks.numpy(), a[nice_peaks].numpy(),'.')
</code></pre>
<p><img src="https://discuss.pytorch.org/uploads/default/original/2X/a/a1cfb8c5a1671475a3fe7741b0b2be2e728e4549.png" alt="Figure_1-1" data-base62-sha1="n5rUHCfRx6rNi1AHYZN2AwOQnLb" width="640" height="480"></p>
<p>At least for the example here, the latter coincides with argrelmax output.</p>
<p>Best regards</p>
<p>Thomas</p>
<p>P.S.: I must admit that I usually just skip cross-posted questions because I don’t want to waste my time or that of the people on other forums. Unless questions are a scarcer resource than answers, it is not terribly efficient to have different groups of people look at the same question.</p>

catubc: <p>Hi Thomas.  Thanks so much, the second option seems to match scipy.signal.argrelmax() perfectly. It’s not superfast, but definitely faster than scipy and scales better with increased data length.  I’ll also link the stackoverflow answer here - nobody there seemed to know how to do this. Cheers, Cat</p>

soulslicer: <p>Can i request for a simular function but for inverted peaks / valleys?</p>

tom: <p>You apply the same on <code>-input</code>?</p>

soulslicer: <p>Of course</p>
<p>Also, could I request a function that works on a batch if 1D signals…</p>
<p>I wrote something but couldnt paralleize the last part</p>
<pre><code class="lang-auto">    peak_mask = torch.cat([torch.zeros((a.shape[0],1), dtype=torch.uint8).bool(), (a[:, :-2]&lt;a[:, 1:-1]) &amp; (a[:, 2:]&lt;a[:, 1:-1]), torch.zeros((a.shape[0],1), dtype=torch.uint8).bool()], dim=1)
    #peak_mask = peak_mask &amp; (a[:] &gt; 0.1)
    b = torch.nn.functional.max_pool1d_with_indices(a.unsqueeze(1), width, 1, padding=width//2)[1].squeeze(1)

    sets = []
    for i in range(0, a.shape[0]):
        bi = b[i,:].unique()
        bi = bi[peak_mask[i,bi].nonzero()]
        #sets.append(bi.flatten().tolist())
        sets.append(bi)
</code></pre>

Ghostv: <p>Well, assuming you don’t have flats in your peaks, you can easily get them with simple derivates.</p>
<p>Let’s say your input is y, first we get the discrete differences:</p>
<p><code>dy = tt.diff(y)</code></p>
<p>Now, a peak is an inflextion point, hence, dy/dx-1<br>
and dy/dx+1 will have different signs, so, we calculate the product of both forward and backward derivates:</p>
<p><code>sign = dy[:-1]*dy[1:]</code></p>
<p>(Note that i filtered the first and last elements cause they don’t have dy/dx-1 and dy/dx+1 respectively.)</p>
<p>Then, to get the peaks, just do:</p>
<p><code>print(sign&lt;0)</code></p>
<p>If you wanna filter the upper / lower peaks, just add a tt.where() to zero all the non peaks and multiply sign by dy[:-1] or dy[1:] respectively and redo the condition.</p>

Laubeee: <p>In anyone interested in an actual port of <code>argrelmax()</code> (which is just <code>argrelextrema()</code> with <code>comparator=np.greater</code>, which is again just <code>np.nonzero()</code> of <code>_boolrelextrema()</code>) - it’s not crazy code at all and easily portable:</p>
<pre data-code-wrap="python"><code class="lang-python">def _boolrelextrema(data, comparator, axis=0, order=1, mode='clip'):
    if (int(order) != order) or (order &lt; 1):
        raise ValueError('Order must be an int &gt;= 1')

    datalen = data.shape[axis]
    locs = np.arange(0, datalen)

    results = np.ones(data.shape, dtype=bool)
    main = data.take(locs, axis=axis, mode=mode)
    for shift in range(1, order + 1):
        plus = data.take(locs + shift, axis=axis, mode=mode)
        minus = data.take(locs - shift, axis=axis, mode=mode)
        results &amp;= comparator(main, plus)
        results &amp;= comparator(main, minus)
        if ~results.any():
            return results
    return results
</code></pre>
<p>the main challenge is probably that torch.take does not work with clipping as it does in numpy, so you’d have to manually pad the signal with the border values, and then just use slicing. Both of which have already been shown in principle by Tom, who actually did something like an argrelmax with order 1 + max pooling / filtering to get to higher orders, which I suppose is slightly less efficient than the scipy implementation.</p>