Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: Phase unwrapping generalized to arbitrary interval size #16987

Merged
merged 30 commits into from
May 19, 2021
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
29c0bb3
Phase unwrapping generalized
scimax Aug 1, 2020
f080593
Update numpy/lib/function_base.py
scimax Aug 2, 2020
dfee885
Tests added according to #14877, obsolete comments removed.
scimax Aug 2, 2020
6131310
Release Note
scimax Aug 3, 2020
0b7ad2c
dispatcher fixed
scimax Aug 3, 2020
2c0d4b8
Hybrid solution including interval boundaries, and keeping backward c…
scimax Aug 3, 2020
24afdab
shorter slice
scimax Aug 17, 2020
16e3bec
Rolling back to only. Improved documentation
scimax Aug 20, 2020
de7aa73
Minor code clean up
scimax Aug 20, 2020
11ae434
Renamed new argument for unwraping. Extended release note
scimax Aug 20, 2020
8cf0872
Update numpy/lib/tests/test_function_base.py
scimax Aug 20, 2020
c3ea9b6
Integer input returning integer output
scimax Aug 20, 2020
3da0506
Merge branch 'master' of github.com:scimax/numpy
scimax Aug 20, 2020
00dcda2
Updated incorrect argument in tests. boundary correction for int and …
scimax Aug 21, 2020
8755c7f
Code cleanup
scimax Aug 21, 2020
fde3fdb
Minor corrections in unwrapping docstrings
scimax Aug 23, 2020
7c56c83
Apply suggestions from code review
scimax Aug 23, 2020
a84b28a
Comment in docs on discontinuity in unwrap
scimax Aug 23, 2020
d31cc73
Update numpy/lib/function_base.py
eric-wieser Sep 2, 2020
dbb3472
fix CI fails by blank lines
scimax Sep 11, 2020
e6bea5f
Apply suggestions from code review
eric-wieser Sep 14, 2020
ba6fc3a
Cleanup whitespace
eric-wieser Sep 14, 2020
985c2a6
Add missing newline
eric-wieser Sep 14, 2020
d7322c7
Cleanup whitespace
eric-wieser Sep 14, 2020
e54a06c
Add missing whitespace
eric-wieser Sep 14, 2020
fa7f79d
Update numpy/lib/function_base.py
eric-wieser Feb 16, 2021
f80fe62
Update numpy/lib/function_base.py
eric-wieser Feb 16, 2021
f4c2d09
Apply suggestions from code review
eric-wieser Feb 16, 2021
5bc6926
STY: Break long lines
charris May 19, 2021
8bdeaeb
BUG: Fix missing "np." in docstring examples.
charris May 19, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 17 additions & 0 deletions doc/release/upcoming_changes/16987.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Arbitrary `period` option for ``numpy.unwrap``
-----------------------------------------------------
eric-wieser marked this conversation as resolved.
Show resolved Hide resolved
The size of the interval over which phases are unwrapped is no longer restricted to `2 * pi`.
eric-wieser marked this conversation as resolved.
Show resolved Hide resolved
This is especially useful for unwrapping degrees, but can also be used for other intervals.

.. code:: python

>>> phase_deg = np.mod(np.linspace(0,720,19), 360) - 180
scimax marked this conversation as resolved.
Show resolved Hide resolved
>>> phase_deg
array([-180., -140., -100., -60., -20., 20., 60., 100., 140.,
-180., -140., -100., -60., -20., 20., 60., 100., 140.,
-180.])

>>> unwrap(phase_deg, period=360)
array([-180., -140., -100., -60., -20., 20., 60., 100., 140.,
180., 220., 260., 300., 340., 380., 420., 460., 500.,
540.])
61 changes: 46 additions & 15 deletions numpy/lib/function_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1482,27 +1482,35 @@ def angle(z, deg=False):
return a


def _unwrap_dispatcher(p, discont=None, axis=None):
def _unwrap_dispatcher(p, discont=None, axis=None, *, period=None):
return (p,)


@array_function_dispatch(_unwrap_dispatcher)
def unwrap(p, discont=pi, axis=-1):
"""
Unwrap by changing deltas between values to 2*pi complement.
def unwrap(p, discont=None, axis=-1, *, period=2*pi):
r"""
Unwrap by taking the complement of large deltas with respect to the period.

This unwraps a signal `p` by changing elements which have an absolute
difference from their predecessor of more than ``max(discont, period/2)``
to their `period`-complementary values.

Unwrap radian phase `p` by changing absolute jumps greater than
`discont` to their 2*pi complement along the given axis.
For the default case where `period` is :math:`2\pi` and is `discont` is :math:`\pi`,
this unwraps a radian phase `p` such that adjacent differences are never
greater than :math:`\pi` by adding :math:`2k\pi` for some integer :math:`k`.

Parameters
----------
p : array_like
Input array.
discont : float, optional
Maximum discontinuity between values, default is ``pi``.
Maximum discontinuity between values, default is ``period/2``.
Values below ``period/2`` are treated as if they were ``period/2``. To have an effect
different from the default, `discont` should be larger than ``period/2``.
axis : int, optional
Axis along which unwrap will operate, default is the last axis.

period: float, optional
Size of the range over which the input wraps. By default, it is ``2 pi``.
eric-wieser marked this conversation as resolved.
Show resolved Hide resolved
Returns
-------
out : ndarray
Expand All @@ -1514,9 +1522,9 @@ def unwrap(p, discont=pi, axis=-1):

Notes
-----
If the discontinuity in `p` is smaller than ``pi``, but larger than
`discont`, no unwrapping is done because taking the 2*pi complement
would only make the discontinuity larger.
If the discontinuity in `p` is smaller than ``period/2``,
but larger than `discont`, no unwrapping is done because taking
the complement would only make the discontinuity larger.
eric-wieser marked this conversation as resolved.
Show resolved Hide resolved

eric-wieser marked this conversation as resolved.
Show resolved Hide resolved
Examples
--------
Expand All @@ -1526,19 +1534,42 @@ def unwrap(p, discont=pi, axis=-1):
array([ 0. , 0.78539816, 1.57079633, 5.49778714, 6.28318531]) # may vary
>>> np.unwrap(phase)
array([ 0. , 0.78539816, 1.57079633, -0.78539816, 0. ]) # may vary

>>> unwrap([0, 1, 2, -1, 0], period=4)
array([0, 1, 2, 3, 4])
>>> unwrap([ 1, 2, 3, 4, 5, 6, 1, 2, 3], period=6)
array([1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> unwrap([2, 3, 4, 5, 2, 3, 4, 5], period=4)
array([2, 3, 4, 5, 6, 7, 8, 9])
>>> phase_deg = np.mod(np.linspace(0,720,19), 360) - 180
eric-wieser marked this conversation as resolved.
Show resolved Hide resolved
>>> unwrap(phase_deg, period=360)
array([-180., -140., -100., -60., -20., 20., 60., 100., 140.,
180., 220., 260., 300., 340., 380., 420., 460., 500.,
540.])
"""
p = asarray(p)
nd = p.ndim
dd = diff(p, axis=axis)
if discont is None:
discont = period/2
slice1 = [slice(None, None)]*nd # full slices
slice1[axis] = slice(1, None)
slice1 = tuple(slice1)
ddmod = mod(dd + pi, 2*pi) - pi
_nx.copyto(ddmod, pi, where=(ddmod == -pi) & (dd > 0))
dtype = np.result_type(dd, period)
if _nx.issubdtype(dtype, _nx.integer):
interval_high, rem = divmod(period, 2)
boundary_ambiguous = rem == 0
else:
interval_high = period / 2
boundary_ambiguous = True
interval_low = -interval_high
ddmod = mod(dd - interval_low, period) + interval_low
if boundary_ambiguous:
# for `mask = (abs(dd) == period/2)`, the above line made `ddmod[mask] == -period/2`.
# correct these such that `ddmod[mask] == sign(dd[mask])*period/2`.
_nx.copyto(ddmod, interval_high, where=(ddmod == interval_low) & (dd > 0))
eric-wieser marked this conversation as resolved.
Show resolved Hide resolved
ph_correct = ddmod - dd
_nx.copyto(ph_correct, 0, where=abs(dd) < discont)
up = array(p, copy=True, dtype='d')
up = array(p, copy=True, dtype=dtype)
up[slice1] = p[slice1] + ph_correct.cumsum(axis)
return up

Expand Down
18 changes: 18 additions & 0 deletions numpy/lib/tests/test_function_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1756,6 +1756,24 @@ def test_simple(self):
assert_array_equal(unwrap([1, 1 + 2 * np.pi]), [1, 1])
# check that unwrap maintains continuity
assert_(np.all(diff(unwrap(rand(10) * 100)) < np.pi))

def test_period(self):
# check that unwrap removes jumps greater that 255
assert_array_equal(unwrap([1, 1 + 256], period=255), [1, 2])
# check that unwrap maintains continuity
assert_(np.all(diff(unwrap(rand(10) * 1000, period=255)) < 255))
Comment on lines +1763 to +1764
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this diff allowed to be = 255? In general I'd be wary of tests using rand, because a badly written test causes CI failures at an unknown point in the future, rather than immediately.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a test I copy-and-pasted from the old PR and just adopted it to the new parameter names. I have to say, I don't really like it. Let's change that maybe.

My understanding was that this test should ensure that the correction is not too large, e.g. if a correction of 4 pi is needed, there should not be a correction of 6 pi, or so. But not so sure.
Regarding your question of the diff being =255, I think, that should not be the case. Let's say the period is 2 pi, after unwrapping there must not be a jump larger than pi. So jumps of 2 pi are not allowed.

# check simple case
simple_seq = np.array([0, 75, 150, 225, 300])
wrap_seq = np.mod(simple_seq, 255)
assert_array_equal(unwrap(wrap_seq, period=255), simple_seq)
# check custom discont value
uneven_seq = np.array([0, 75, 150, 225, 300, 430])
wrap_uneven = np.mod(uneven_seq, 250)
no_discont = unwrap(wrap_uneven, period=250)
assert_array_equal(no_discont, [0, 75, 150, 225, 300, 180])
sm_discont = unwrap(wrap_uneven, period=250, discont=140)
assert_array_equal(sm_discont, [0, 75, 150, 225, 300, 430])
assert sm_discont.dtype == wrap_uneven.dtype


class TestFilterwindows:
Expand Down