Skip to content

Commit 93bb8ac

Browse files
committed
irfft: Restore (true) asymmetric apodization, move ramp into apodize()
1 parent 7a68611 commit 93bb8ac

File tree

2 files changed

+72
-38
lines changed

2 files changed

+72
-38
lines changed

orangecontrib/spectroscopy/irfft.py

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,14 @@ def find_zpd(ifg, peak_search):
5656
raise NotImplementedError
5757

5858
def wing_size(ifg, zpd):
59-
"""Calculate negative and positive wing size (including zpd point)"""
59+
"""Calculate negative and positive wing size (including zpd point in both)"""
6060
wing_n = zpd + 1
6161
wing_p = ifg.shape[-1] - zpd
6262
return wing_n, wing_p
6363

64-
def apodize(ifg, zpd, apod_func):
64+
def apodize(ifg, zpd, apod_func, apod_asym):
6565
"""
66-
Perform apodization of asymmetric interferogram using selected apodization
67-
function
66+
Perform apodization of interferogram using selected apodization function
6867
6968
Args:
7069
ifg (np.array): interferogram array (1D or 2D row-wise)
@@ -74,6 +73,8 @@ def apodize(ifg, zpd, apod_func):
7473
<ApodFunc.BLACKMAN_HARRIS_3: 1> : Blackman-Harris (3-term)
7574
<ApodFunc.BLACKMAN_HARRIS_4: 2> : Blackman-Harris (4-term)
7675
<ApodFunc.BLACKMAN_NUTTALL: 3> : Blackman-Nuttall (Eric Peach implementation)
76+
apod_asym (bool): Apply asymmetric apodization for asymmetric interferometers (s-SNOM, DFTS)
77+
Otherwise, truncate symmetric apodization function as required and apply ramp
7778
7879
Returns:
7980
ifg_apod (np.array): apodized interferogram(s)
@@ -87,8 +88,6 @@ def apodize(ifg, zpd, apod_func):
8788
# correcting zpd from 0-based index
8889
ifg_N = ifg.shape[-1]
8990
wing_n, wing_p = wing_size(ifg, zpd)
90-
# Use larger wing to define apodization window width
91-
M = max(wing_n, wing_p)
9291

9392
# Generate apodization function
9493
if apod_func == ApodFunc.BLACKMAN_HARRIS_3:
@@ -118,17 +117,45 @@ def apodize(ifg, zpd, apod_func):
118117
else:
119118
raise ValueError(f"Invalid apodization function {apod_func}")
120119

121-
n = np.arange(1-M, M, 1)
122-
Bs = A0 \
123-
+ A1 * np.cos(np.pi*n/(M-1)) \
124-
+ A2 * np.cos(np.pi*2*n/(M-1)) \
125-
+ A3 * np.cos(np.pi*3*n/(M-1))
126-
127-
# Select appropriate subsection of apodization function
128-
if wing_n > wing_p:
129-
Bs = Bs[:ifg_N]
120+
if apod_asym:
121+
# Scale window to each wing length (asymmetric interferometer)
122+
n_n = np.arange(wing_n)
123+
n_p = np.arange(wing_p)
124+
Bs_n = A0 \
125+
+ A1 * np.cos(np.pi*n_n/wing_n) \
126+
+ A2 * np.cos(np.pi*2*n_n/wing_n) \
127+
+ A3 * np.cos(np.pi*3*n_n/wing_n)
128+
Bs_p = A0 \
129+
+ A1 * np.cos(np.pi*n_p/wing_p) \
130+
+ A2 * np.cos(np.pi*2*n_p/wing_p) \
131+
+ A3 * np.cos(np.pi*3*n_p/wing_p)
132+
# Flip Bs_n and overlap at zpd (Bs_n[0] == Bs_p[0])
133+
Bs = np.hstack((Bs_n[::-1], Bs_p[1:]))
130134
else:
131-
Bs = Bs[2 * M - 1 - ifg_N:]
135+
# Use larger wing to define apodization window width (symmetric interferometer)
136+
M = max(wing_n, wing_p)
137+
n = np.arange(1-M, M, 1)
138+
Bs = A0 \
139+
+ A1 * np.cos(np.pi*n/(M-1)) \
140+
+ A2 * np.cos(np.pi*2*n/(M-1)) \
141+
+ A3 * np.cos(np.pi*3*n/(M-1))
142+
143+
# Select appropriate subsection of apodization function
144+
if wing_n > wing_p:
145+
Bs = Bs[:ifg_N]
146+
else:
147+
Bs = Bs[2 * M - 1 - ifg_N:]
148+
149+
# Add ramp to avoid uneven counting of truncated section
150+
# Use smaller wing to define symmetric subset size
151+
# Skip if fully symmetric (phase data)
152+
wing_min = min(wing_n, wing_p)
153+
ramp = np.linspace(0, 1, 2 * wing_min - 1)
154+
if wing_n < wing_p:
155+
Bs[..., :ramp.shape[0]] *= ramp
156+
elif wing_n > wing_p:
157+
Bs[..., -ramp.shape[0]:] *= ramp[::-1]
158+
132159
# Apodize the sampled Interferogram
133160
try:
134161
ifg_apod = ifg * Bs
@@ -231,14 +258,13 @@ def __call__(self, ifg, zpd=None, phase=None):
231258
else:
232259
# Select phase interferogram as copy
233260
# Note that L is now the zpd index
234-
Ixs = ifg[self.zpd - L : self.zpd + L].copy()
235-
ifg = apodize(ifg, self.zpd, self.apod_func)
236-
ifg = ramp(ifg, self.zpd)
261+
Ixs = ifg[self.zpd - L : self.zpd + 1 + L].copy()
262+
ifg = apodize(ifg, self.zpd, self.apod_func, self.apod_asym)
237263
ifg = zero_fill(ifg, self.zff)
238264
ifg = np.hstack((ifg[self.zpd:], ifg[0:self.zpd]))
239265
Nzff = ifg.shape[0]
240266

241-
Ixs = apodize(Ixs, L, self.apod_func)
267+
Ixs = apodize(Ixs, L, self.apod_func, self.apod_asym)
242268
# Zero-fill Ixs to same size as ifg (instead of interpolating later)
243269
Ixs = _zero_fill_pad(Ixs, Nzff - Ixs.shape[0])
244270
Ixs = np.hstack((Ixs[L:], Ixs[0:L]))
@@ -306,7 +332,7 @@ def __call__(self, ifg, zpd=None, phase=None):
306332
# Calculate phase on interferogram of specified size 2*L
307333
L = self.phase_ifg_size(ifg.shape[1])
308334
if L == 0: # Use full ifg for phase #TODO multi is this code tested
309-
ifg = apodize(ifg, self.zpd, self.apod_func)
335+
ifg = apodize(ifg, self.zpd, self.apod_func, self.apod_asym)
310336
ifg = zero_fill(ifg, self.zff)
311337
# Rotate the Complete IFG so that the centerburst is at edges.
312338
ifg = np.hstack((ifg[self.zpd:], ifg[0:self.zpd]))
@@ -318,13 +344,12 @@ def __call__(self, ifg, zpd=None, phase=None):
318344
# Select phase interferogram as copy
319345
# Note that L is now the zpd index
320346
Ixs = ifg[:, self.zpd - L : self.zpd + L].copy()
321-
ifg = apodize(ifg, self.zpd, self.apod_func)
322-
ifg = ramp(ifg, self.zpd)
347+
ifg = apodize(ifg, self.zpd, self.apod_func, self.apod_asym)
323348
ifg = zero_fill(ifg, self.zff)
324349
ifg = np.hstack((ifg[:, self.zpd:], ifg[:, 0:self.zpd]))
325350
Nzff = ifg.shape[1]
326351

327-
Ixs = apodize(Ixs, L, self.apod_func)
352+
Ixs = apodize(Ixs, L, self.apod_func, self.apod_asym)
328353
# Zero-fill Ixs to same size as ifg (instead of interpolating later)
329354
Ixs = _zero_fill_pad(Ixs, Nzff - Ixs.shape[1])
330355
Ixs = np.hstack((Ixs[:, L:], Ixs[:, 0:L]))
@@ -357,7 +382,7 @@ def __call__(self, ifg, zpd=None, phase=None):
357382
else:
358383
self.zpd = find_zpd(ifg, self.peak_search)
359384

360-
ifg = apodize(ifg, self.zpd, self.apod_func)
385+
ifg = apodize(ifg, self.zpd, self.apod_func, self.apod_asym)
361386
ifg = zero_fill(ifg, self.zff)
362387
# Rotate the Complete IFG so that the centerburst is at edges.
363388
ifg = np.hstack((ifg[self.zpd:], ifg[0:self.zpd]))

orangecontrib/spectroscopy/tests/test_irfft.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from orangecontrib.spectroscopy.irfft import (IRFFT, zero_fill, PhaseCorrection,
99
find_zpd, PeakSearch, ApodFunc,
10-
MultiIRFFT, apodize, ramp,
10+
MultiIRFFT, apodize,
1111
)
1212

1313
dx = 1.0 / 15797.337544 / 2.0
@@ -26,6 +26,7 @@ def setUp(self):
2626
'odd_trunc': self.ifg_seq_ref.X[0],
2727
'even_trunc_reverse': self.ifg_seq_ref.X[0][1:][::-1],
2828
'odd_trunc_reverse': self.ifg_seq_ref.X[0][::-1],
29+
'fake_asym': self.ifg_seq_ref.X[0],
2930
}
3031

3132
def test_zero_fill(self):
@@ -97,6 +98,7 @@ def test_agilent_fft_ab(self):
9798
dx_ag = (1 / 1.57980039e+04 / 2) * 4
9899
fft = IRFFT(dx=dx_ag,
99100
apod_func=ApodFunc.BLACKMAN_HARRIS_4,
101+
apod_asym=False,
100102
zff=1,
101103
phase_res=None,
102104
phase_corr=PhaseCorrection.MERTZ,
@@ -133,6 +135,7 @@ def test_multi_ab(self):
133135
dx_ag = (1 / 1.57980039e+04 / 2) * 4
134136
fft = MultiIRFFT(dx=dx_ag,
135137
apod_func=ApodFunc.BLACKMAN_HARRIS_4,
138+
apod_asym=False,
136139
zff=1,
137140
phase_res=None,
138141
phase_corr=PhaseCorrection.MERTZ,
@@ -153,18 +156,24 @@ def test_multi_ab(self):
153156

154157
def test_apodization(self):
155158
for apod_func in ApodFunc:
159+
if apod_func == ApodFunc.BOXCAR:
160+
ifg = self.ifgs['even_sym']
161+
zpd = find_zpd(ifg, PeakSearch.ABSOLUTE)
162+
out = apodize(ifg, zpd, apod_func, False)
163+
np.testing.assert_array_equal(ifg, out)
164+
continue
156165
for k, ifg in self.ifgs.items():
157-
with self.subTest(apod_func=apod_func.name, ifg=k):
166+
if 'asym' in k:
167+
# Test asymmetric ifgs
168+
apod_asym = True
169+
ramp_value = 1
170+
else:
171+
# Test symmetric and truncated ifgs
172+
apod_asym = False
173+
ramp_value = 0.5
174+
with self.subTest(apod_func=apod_func.name, ifg=k, asym=apod_asym):
158175
zpd = find_zpd(ifg, PeakSearch.ABSOLUTE)
159-
out = apodize(ifg, zpd, apod_func)
176+
out = apodize(ifg, zpd, apod_func, apod_asym)
160177
# Apodization should not change value at zpd
161-
self.assertAlmostEqual(ifg[zpd], out[zpd])
162-
163-
def test_ramp(self):
164-
ifg = self.ifgs['odd_trunc']
165-
zpd = find_zpd(ifg, PeakSearch.ABSOLUTE)
166-
ramp_fwd = ramp(ifg, zpd)
167-
ifg_rev = self.ifgs['odd_trunc_reverse']
168-
zpd_rev = find_zpd(ifg_rev, PeakSearch.ABSOLUTE)
169-
ramp_rev = ramp(ifg_rev, zpd_rev)
170-
np.testing.assert_array_equal(ramp_fwd, ramp_rev[::-1])
178+
# Ramp should reduce zpd value by half
179+
self.assertAlmostEqual(ifg[zpd] * ramp_value, out[zpd])

0 commit comments

Comments
 (0)