Advertisement
Guest User

Untitled

a guest
Dec 27th, 2020
246
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.66 KB | None | 0 0
  1. # -*- coding: utf-8 -*-
  2. import numpy as np
  3. from numpy.fft import fft, ifft, ifftshift
  4. from ssqueezepy.utils import padsignal, process_scales
  5. from ssqueezepy.algos import find_closest, indexed_sum
  6. from ssqueezepy.visuals import imshow, plot
  7. from ssqueezepy import Wavelet, ssq_cwt
  8.  
  9. #%%# Helpers #################################################################
  10. def _t(min, max, N):
  11.     return np.linspace(min, max, N, endpoint=1)
  12.  
  13. def cos_f(freqs, N=128, phi=0):
  14.     return np.concatenate([np.cos(2 * np.pi * f * (_t(i, i + 1, N) + phi))
  15.                            for i, f in enumerate(freqs)])
  16.  
  17. #%%# Define signal & wavelet #################################################
  18. fs = 32768 // 4 + 1
  19. x = cos_f([320], fs) + cos_f([640], fs)
  20. x /= 2
  21. wavelet = Wavelet(('morlet', {'mu': 4}))
  22.  
  23. #%%# CWT #####################################################################
  24. nv=32; dt=1/fs
  25.  
  26. n = len(x)  # store original length
  27. x, nup, n1, n2 = padsignal(x, padtype='reflect')
  28. x -= x.mean()
  29. xh = fft(x)
  30.  
  31. scales = process_scales(scales='log:maximal', len_x=n, wavelet=wavelet, nv=nv)
  32. pn = (-1)**np.arange(nup)
  33.  
  34. N_orig = wavelet.N
  35. wavelet.N = nup
  36.  
  37. #%%# cwt ####
  38. Psih = (wavelet(scale=scales, nohalf=False)).astype('complex128')
  39. dPsih = (1j * wavelet.xi / dt) * Psih
  40.  
  41. Wx  = ifftshift(ifft(pn * Psih  * xh, axis=-1), axes=-1)
  42. dWx = ifftshift(ifft(pn * dPsih * xh, axis=-1), axes=-1)
  43. #%%#
  44. wavelet.N = N_orig
  45. # shorten to pre-padded size
  46. Wx  = Wx[:,  n1:n1 + n]
  47. dWx = dWx[:, n1:n1 + n]
  48.  
  49. #%%# Phase transform #########################################################
  50. w = np.imag(dWx / Wx) / (2*np.pi)
  51. #%%
  52. # clean up tiny-valued Wx that have large `w` values; removing these makes
  53. # no noticeable difference on `Tx` but allows us to see much better
  54. w[np.abs(Wx) < np.abs(Wx).mean()*.5] = 0
  55.  
  56. #%%# Reassignment frequencies (mapkind='maximal') ############################
  57. na, N = Wx.shape
  58. dT = dt * N
  59. # normalized frequencies to map discrete-domain to physical:
  60. #     f[[cycles/samples]] -> f[[cycles/second]]
  61. # minimum measurable (fundamental) frequency of data
  62. fm = 1 / dT
  63. # maximum measurable (Nyquist) frequency of data
  64. fM = 1 / (2 * dt)
  65.  
  66. ssq_freqs = fm * np.power(fM / fm, np.arange(na) / (na - 1))
  67.  
  68. #%%# Reassignment indices
  69. # This step simply finds the index-equivalent of `w`. E.g., for given
  70. # `ssq_freqs` ranging from 2 to 48, if w[5, 2] == 5, then
  71. # `k[5, 2] = np.where(ssq_freqs == 5)` (or if no exact match, then closest to 5)
  72. # `k` thus ranges from 0 to `len(ssq_freqs) - 1`.
  73. k = find_closest(np.log2(w), np.log2(ssq_freqs))
  74.  
  75. #%%# Synchrosqueeze #########################################################
  76. Tx = indexed_sum(Wx * np.log(2) / nv, k)
  77. Tx = np.flipud(Tx)  # flip for visual aligned with `Wx`
  78.  
  79. #%%# Visualize ##############################################################
  80. kw = dict(abs=1, cmap='jet', show=1, aspect='auto')
  81. imshow(Wx, title="abs(CWT)", **kw)
  82. imshow(Tx, title="abs(SSQ_CWT)", **kw)
  83.  
  84. #%%# Zoom ####
  85. a, b = 65, 155
  86. c, d = 1000, 1400
  87. sidxs, tidxs = np.arange(Wx.shape[0]), np.arange(Wx.shape[1])
  88. tkw = dict(xticks=tidxs[c:d], yticks=sidxs[a:b])
  89.  
  90. imshow(Wx[a:b, c:d], title="abs(CWT), zoomed", **kw, **tkw)
  91. imshow(Tx[a:b, c:d], title="abs(SSQ_CWT), zoomed", **kw, **tkw)
  92.  
  93. #%%# Repeat for `w` ####
  94. imshow(w, title="Phase transform", **kw)
  95. imshow(w[a:b, c:d], title="Phase transform, zoomed", **kw, **tkw)
  96.  
  97. #%%# Show specific row from wavy parts of zoomed
  98. plot(w[80, c:d], title="w[80, 1000:1400]", xticks=tkw['xticks'], show=1)
  99. # We thus confirm a sinusoidal reassignment pattern; but what causes it?
  100. # `w` is `dWx` divided by `Wx`, so let's look to row 80
  101.  
  102. #%%# To compare shapes as opposed to raw magnitudes, `dWx` must be rescaled
  103. r  = np.abs(Wx[80, c:d])
  104. dr = np.abs(dWx[80, c:d])
  105. dr *= (np.abs(r).max() / np.abs(dr).max())
  106.  
  107. plot(dr, title="abs(dWx), abs(Wx), [80, 1000:1400]")
  108. plot(r, show=1)
  109. # We thus observe that magnitudes of `r100` and `dr100` are sines
  110. # of same frequency, but different amplitudes and offsets (means)
  111. # The ratio of such sines is another sine - thus the reassignment pattern
  112. # But WHY are they of same frequency but different amplitudes and offsets?
  113. # and why are magnitudes sinusoidal at all?
  114. #%%
  115. g, h = Wx[80, c:d], dWx[80, c:d]
  116. plot(g, title="Wx[80, 1000:1400]",  complex=1, show=1)
  117. plot(h, title="dWx[80, 1000:1400]", complex=1, show=1)
  118. plot(h / g, title="(dWx / Wx)[80, 1000:1400]", complex=1, show=1)
  119. #%%# Design simpler case to illustrate
  120. def csoid(f, N):  # cos + i*sin
  121.     return cos_f([f], N) + 1j * cos_f([f], N, phi=-1/4/f)
  122.  
  123. def csoid90(f, N):  # -sin + i*cos = cos(+pi/2) + i*sin(+pi/2)
  124.     return cos_f([f], N, phi=+1/4/f) + 1j * cos_f([f], N)
  125.  
  126. def viz_csoids(f1, f2, N, ar, abs_only=0):
  127.     h1 = csoid(f1, N)
  128.     h2 = csoid(f2, N)
  129.     h = h1 / ar + h2
  130.  
  131.     if not abs_only:
  132.         plot(h, complex=1, show=1,
  133.              title="csoid: (f1=%s) + (f1=%s) -- |f2| / |f1| = %s" % (f1, f2, ar))
  134.     plot(h, title="|(f1) + (f2)|", abs=1, ylims=(0, None), show=1)
  135.  
  136. viz_csoids(4, 8, 128, 64)
  137. viz_csoids(4, 8, 128, 8)
  138. viz_csoids(4, 8, 128, 2)
  139. viz_csoids(4, 8, 128, 1)
  140. #%%
  141. h1 = csoid(4, 128)
  142. h2 = csoid(8, 128)
  143. _kw = dict(show=1,
  144.            vlines=([i for i in np.arange(128, step=16)],
  145.                    dict(color='k', linestyle='--')))
  146. plot(np.abs(h1 / 32 + h2))
  147. plot(np.abs(h1 / 1  + h2), title="|f2| / |f1| = 32 / 1", ylims=(0, None), **_kw)
  148. #%%
  149. plot(np.abs(h1 / (32*8) + h2))
  150. plot(np.abs(h1 / 8     + h2), title="|f2| / |f1| = 256 / 8", **_kw)
  151. #%%
  152. _=[plot(w[80 + 4*i, c:d], show=0) for i in range(6)]
  153. plot([], title="w[80+i, 1000:1400], i=[0,4,...,16]", show=1)
  154. #%%
  155. _=[plot(np.abs(w[a-20:b, 1000 + 1*i]), show=0) for i in range(12)]
  156. plot([], title="w[45:155, 1000 + i], i=[0,1,...,11]", show=1)
  157. # Here we see peak amplitudes of earlier sinusoids not changing much toward
  158. # lower scales (in agreement with previous plot), then leveling in an S-shape,
  159. # indicative of regions where wavelet is more centered between the two
  160.  
  161. #%%
  162. def _t(min, max, N):
  163.     return np.linspace(min, max, N, endpoint=False)
  164.  
  165. _kw = dict(color='tab:orange', linestyle='--', xlims=(-3, 130), show=1)
  166. g = cos_f([2], 128)
  167. h = cos_f([32], 128)
  168. plot(np.abs(g) * h)
  169. plot(np.abs(g), title="|cos(f=2)| * cos(f=32)", **_kw)
  170.  
  171. plot((1 + g/5) * h)
  172. plot(1 + g/5, title="|1 + .2*cos(f=2)| * cos(f=32)", **_kw)
  173. #%%
  174. def plot_wavxh_overlap(sidx, b, Psih, name):
  175.     xha = xh.real.max()
  176.     psih = Psih[sidx]
  177.     psih = psih.real if name == 'psih' else psih.imag
  178.     color = 'purple' if name == 'psih' else 'tab:green'
  179.     scale = float(scales[sidx])
  180.  
  181.     plot(xh.real[:b])
  182.     plot(psih[:b] * (xha / psih.max()), color=color, show=1,
  183.          title="xh, %s (rescaled), [:%d], scale=%.2f" % (name, b, scale))
  184.     plot((xh * psih).real[:b], show=1,
  185.          title="xh * %s, [:%d], scale=%.2f" % (name, b, scale))
  186.  
  187. def plot_wav_overlap(sidx, b, Psih, dPsih):
  188.     psih = Psih[sidx].real
  189.     dpsih = dPsih[sidx].imag
  190.     scale = float(scales[sidx])
  191.  
  192.     plot(psih[:b] * (1 / psih.max()), color='purple')
  193.     plot(dpsih[:b] * (1 / dpsih.max()), color='tab:green', show=1,
  194.          title="psih, dpsih, [:2000], rescaled, scale=%.2f" % scale)
  195.  
  196. xha = xh.real.max()
  197. psih = Psih[80].real
  198. dpsih = dPsih[80].imag
  199. #%%
  200. plot_wavxh_overlap(80, 8000, Psih,  'psih')
  201. plot_wavxh_overlap(80, 8000, dPsih, 'dpsih')
  202. #%%
  203. plot_wavxh_overlap(115, 8000, Psih,  'psih')
  204. plot_wavxh_overlap(115, 8000, dPsih, 'dpsih')
  205. #%%
  206. plot_wav_overlap(80, 2000, Psih, dPsih)
  207. plot_wav_overlap(115, 2000, Psih, dPsih)
  208.  
  209. #%%
  210. plot(xh.real[:2000])
  211. plot(psih[:2000] * (xha / psih.max()), color='purple', show=1,
  212.      title="psih (rescaled), xh, [:2000], scale=%.2f" % float(scales[80]))
  213. plot((xh * psih).real[:2000], show=1, title="psih * xh, [:2000]")
  214. #%%
  215. plot(xh.real[:2000])
  216. plot(dpsih[:2000] * (xha / dpsih.max()), color='purple', show=1,
  217.      title="dpsih (rescaled), xh, [:2000], scale=%.2f" % float(scales[80]))
  218. plot((xh * dpsih).real[:2000], show=1, title="dpsih * xh, [:2000]")
  219. #%%
  220. def w_sim(f1, f2, A, B, C, D, N=128, lp=31, rp=32):
  221.     num = (A * csoid90(f1, N) + B * csoid90(f2, N)) / N
  222.     den = (C * csoid(  f1, N) + D * csoid(  f2, N)) / N
  223.     _w = num / den
  224.     return _w.imag[lp:-rp] / (2 * np.pi)
  225.  
  226. def ptimag(f1, f2, A, B, C, D, N=128, lp=31, rp=32):
  227.     num = cos_f([f1 - f2], N) * (A*D + B*C) + (A*C + B*D)
  228.     den = cos_f([f1 - f2], N) * (2*C*D) + C**2 + D**2
  229.     return (num / den)[lp:-rp] / (2 * np.pi)
  230.  
  231. #%%
  232. psih = Psih[80].real
  233. dpsih = dPsih[80].imag
  234.  
  235. out = xh * dpsih
  236. f1, f2 = np.where(out > 10)[0][:2]
  237. A, B = out[f1].real, out[f2].real
  238. out = xh * psih
  239. C, D = out[f1].real, out[f2].real
  240. lp = (len(psih) - fs) // 2 + 1
  241. rp = lp - 1
  242. N = len(dpsih)
  243.  
  244. _args = (f1, f2, A, B, C, D, N, lp, rp)
  245. _w  = w_sim(*_args)
  246. _w2 = ptimag(*_args)
  247.  
  248. print(np.mean(np.abs(_w - _w2)))
  249. #%%
  250. plot(w[80][:200])
  251. plot(-.5*(_w.max() - _w.min()) * cos_f([320], len(w[80]))[:200] + 1.003*_w.mean(),
  252.      show=1, title="w[80][:200] vs sinusoid at same frequency")
  253. #%%
  254. xi = wavelet.xifn(scale=1, N=2048)[:1024]
  255. plot(xi, Wavelet(('morlet', {'mu': 5}) )(scale=3.02,  N=2048)[:1024])
  256. plot(xi, Wavelet(('morlet', {'mu': 20}))(scale=12.1, N=2048)[:1024],
  257.      title="mu: (5, 20); scale: (3.02, 12.1) | Morlet wavelet", show=1)
  258.  
  259. #%%# Double-sinusoidal ssq## #################################################
  260. fs = 32768 // 4 + 1
  261. x = cos_f([320*10.6], fs) + cos_f([320*12], fs)
  262. x /= 2
  263. wavelet = Wavelet(('morlet', {'mu': 12.5}))
  264.  
  265. #%%
  266. Tx, _, Wx, _, w = ssq_cwt(x, wavelet)
  267. Tx = np.flipud(Tx)
  268. kw = dict(abs=1, cmap='jet', show=1, aspect='auto')
  269.  
  270. w[np.isinf(w) | np.isnan(w)] = 0
  271. w[np.where(np.abs(Wx) < np.abs(Wx).mean())] = 0
  272. imshow(Wx[:60, :200],
  273.        title="(N, f1, f2, mu) = (8193, 3392, 3840, 12.5) | zoomed", **kw)
  274. imshow(Tx[:60, :200], **kw)
  275. imshow(w[ :60, :200], **kw)
  276.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement