Skip to content

Commit cc45b0d

Browse files
authored
Merge pull request #878 from Dmao233/cursor/fix-866-threshold-zero-nan-6b9b
BUG: return 0 not NaN when thresholding zeros at value 0
2 parents 65f19a7 + 972f39d commit cc45b0d

2 files changed

Lines changed: 45 additions & 3 deletions

File tree

‎pywt/_thresholding.py‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@ def soft(data, value, substitute=0):
1717
data = np.asarray(data)
1818
magnitude = np.absolute(data)
1919

20-
with np.errstate(divide='ignore'):
20+
with np.errstate(divide='ignore', invalid='ignore'):
2121
# divide by zero okay as np.inf values get clipped, so ignore warning.
22+
# 0/0 (value==0 and data==0) is invalid and yields NaN; zero those below.
2223
thresholded = (1 - value/magnitude)
2324
thresholded.clip(min=0, max=None, out=thresholded)
2425
thresholded = data * thresholded
26+
# Keep dtype; sign(0) is undefined, but soft-threshold(0) is 0 for any λ≥0.
27+
thresholded[magnitude == 0] = 0
2528

2629
if substitute == 0:
2730
return thresholded
@@ -35,11 +38,13 @@ def nn_garrote(data, value, substitute=0):
3538
data = np.asarray(data)
3639
magnitude = np.absolute(data)
3740

38-
with np.errstate(divide='ignore'):
41+
with np.errstate(divide='ignore', invalid='ignore'):
3942
# divide by zero okay as np.inf values get clipped, so ignore warning.
43+
# 0/0 (value==0 and data==0) is invalid and yields NaN; zero those below.
4044
thresholded = (1 - value**2/magnitude**2)
4145
thresholded.clip(min=0, max=None, out=thresholded)
4246
thresholded = data * thresholded
47+
thresholded[magnitude == 0] = 0
4348

4449
if substitute == 0:
4550
return thresholded
@@ -235,12 +240,14 @@ def threshold_firm(data, value_low, value_high):
235240

236241
data = np.asarray(data)
237242
magnitude = np.absolute(data)
238-
with np.errstate(divide='ignore'):
243+
with np.errstate(divide='ignore', invalid='ignore'):
239244
# divide by zero okay as np.inf values get clipped, so ignore warning.
245+
# 0/0 (value_low==0 and data==0) is invalid and yields NaN; zero those below.
240246
vdiff = value_high - value_low
241247
thresholded = value_high * (1 - value_low/magnitude) / vdiff
242248
thresholded.clip(min=0, max=None, out=thresholded)
243249
thresholded = data * thresholded
250+
thresholded[magnitude == 0] = 0
244251

245252
# restore hard-thresholding behavior for values > value_high
246253
large_vals = np.where(magnitude > value_high)

‎pywt/tests/test_thresholding.py‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,38 @@ def test_threshold_firm():
165165
mt_abs_firm = np.abs(d_firm[mt])
166166
assert_(np.all(mt_abs_firm < np.abs(d_hard[mt])))
167167
assert_(np.all(mt_abs_firm > np.abs(d_soft[mt])))
168+
169+
170+
def test_threshold_zero_value_with_zeros():
171+
# Issue 866: value==0 and exact-zero data used to yield NaN (0/0).
172+
data = np.array([0.0, 1.0, -2.0])
173+
expected = np.array([0.0, 1.0, -2.0])
174+
175+
assert_allclose(pywt.threshold(data, 0.0, 'soft'), expected, rtol=1e-12)
176+
assert_allclose(pywt.threshold(data, 0.0, 'garrote'), expected, rtol=1e-12)
177+
assert_allclose(pywt.threshold_firm(data, 0.0, 0.0), expected, rtol=1e-12)
178+
179+
# all zeros remain zeros (and must not warn: pytest treats warnings as errors)
180+
zeros = np.zeros(8)
181+
assert_allclose(pywt.threshold(zeros, 0.0, 'soft'), zeros, rtol=1e-12)
182+
assert_allclose(pywt.threshold(zeros, 0.0, 'garrote'), zeros, rtol=1e-12)
183+
assert_allclose(pywt.threshold_firm(zeros, 0.0, 0.0), zeros, rtol=1e-12)
184+
185+
# complex: same 0/0 path, result must be 0+0j not nan+nanj
186+
cdata = np.array([0.0, 1.0 + 1.0j])
187+
cexpected = np.array([0.0 + 0.0j, 1.0 + 1.0j])
188+
assert_allclose(pywt.threshold(cdata, 0.0, 'soft'), cexpected, rtol=1e-12)
189+
assert_allclose(pywt.threshold(cdata, 0.0, 'garrote'), cexpected, rtol=1e-12)
190+
assert_allclose(pywt.threshold_firm(cdata, 0.0, 0.0), cexpected, rtol=1e-12)
191+
192+
for dtype in float_dtypes:
193+
typed = np.asarray(data if dtype in real_dtypes else cdata, dtype=dtype)
194+
out_soft = pywt.threshold(typed, 0.0, 'soft')
195+
out_garrote = pywt.threshold(typed, 0.0, 'garrote')
196+
out_firm = pywt.threshold_firm(typed, 0.0, 0.0)
197+
assert_equal(out_soft.dtype, typed.dtype)
198+
assert_equal(out_garrote.dtype, typed.dtype)
199+
assert_equal(out_firm.dtype, typed.dtype)
200+
assert_(not np.isnan(out_soft).any())
201+
assert_(not np.isnan(out_garrote).any())
202+
assert_(not np.isnan(out_firm).any())

0 commit comments

Comments
 (0)