common.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. """
  2. Functions which are common and require SciPy Base and Level 1 SciPy
  3. (special, linalg)
  4. """
  5. from __future__ import division, print_function, absolute_import
  6. from numpy import arange, newaxis, hstack, product, array, frombuffer, load
  7. __all__ = ['central_diff_weights', 'derivative', 'ascent', 'face',
  8. 'electrocardiogram']
  9. def central_diff_weights(Np, ndiv=1):
  10. """
  11. Return weights for an Np-point central derivative.
  12. Assumes equally-spaced function points.
  13. If weights are in the vector w, then
  14. derivative is w[0] * f(x-ho*dx) + ... + w[-1] * f(x+h0*dx)
  15. Parameters
  16. ----------
  17. Np : int
  18. Number of points for the central derivative.
  19. ndiv : int, optional
  20. Number of divisions. Default is 1.
  21. Notes
  22. -----
  23. Can be inaccurate for large number of points.
  24. """
  25. if Np < ndiv + 1:
  26. raise ValueError("Number of points must be at least the derivative order + 1.")
  27. if Np % 2 == 0:
  28. raise ValueError("The number of points must be odd.")
  29. from scipy import linalg
  30. ho = Np >> 1
  31. x = arange(-ho,ho+1.0)
  32. x = x[:,newaxis]
  33. X = x**0.0
  34. for k in range(1,Np):
  35. X = hstack([X,x**k])
  36. w = product(arange(1,ndiv+1),axis=0)*linalg.inv(X)[ndiv]
  37. return w
  38. def derivative(func, x0, dx=1.0, n=1, args=(), order=3):
  39. """
  40. Find the n-th derivative of a function at a point.
  41. Given a function, use a central difference formula with spacing `dx` to
  42. compute the `n`-th derivative at `x0`.
  43. Parameters
  44. ----------
  45. func : function
  46. Input function.
  47. x0 : float
  48. The point at which `n`-th derivative is found.
  49. dx : float, optional
  50. Spacing.
  51. n : int, optional
  52. Order of the derivative. Default is 1.
  53. args : tuple, optional
  54. Arguments
  55. order : int, optional
  56. Number of points to use, must be odd.
  57. Notes
  58. -----
  59. Decreasing the step size too small can result in round-off error.
  60. Examples
  61. --------
  62. >>> from scipy.misc import derivative
  63. >>> def f(x):
  64. ... return x**3 + x**2
  65. >>> derivative(f, 1.0, dx=1e-6)
  66. 4.9999999999217337
  67. """
  68. if order < n + 1:
  69. raise ValueError("'order' (the number of points used to compute the derivative), "
  70. "must be at least the derivative order 'n' + 1.")
  71. if order % 2 == 0:
  72. raise ValueError("'order' (the number of points used to compute the derivative) "
  73. "must be odd.")
  74. # pre-computed for n=1 and 2 and low-order for speed.
  75. if n == 1:
  76. if order == 3:
  77. weights = array([-1,0,1])/2.0
  78. elif order == 5:
  79. weights = array([1,-8,0,8,-1])/12.0
  80. elif order == 7:
  81. weights = array([-1,9,-45,0,45,-9,1])/60.0
  82. elif order == 9:
  83. weights = array([3,-32,168,-672,0,672,-168,32,-3])/840.0
  84. else:
  85. weights = central_diff_weights(order,1)
  86. elif n == 2:
  87. if order == 3:
  88. weights = array([1,-2.0,1])
  89. elif order == 5:
  90. weights = array([-1,16,-30,16,-1])/12.0
  91. elif order == 7:
  92. weights = array([2,-27,270,-490,270,-27,2])/180.0
  93. elif order == 9:
  94. weights = array([-9,128,-1008,8064,-14350,8064,-1008,128,-9])/5040.0
  95. else:
  96. weights = central_diff_weights(order,2)
  97. else:
  98. weights = central_diff_weights(order, n)
  99. val = 0.0
  100. ho = order >> 1
  101. for k in range(order):
  102. val += weights[k]*func(x0+(k-ho)*dx,*args)
  103. return val / product((dx,)*n,axis=0)
  104. def ascent():
  105. """
  106. Get an 8-bit grayscale bit-depth, 512 x 512 derived image for easy use in demos
  107. The image is derived from accent-to-the-top.jpg at
  108. http://www.public-domain-image.com/people-public-domain-images-pictures/
  109. Parameters
  110. ----------
  111. None
  112. Returns
  113. -------
  114. ascent : ndarray
  115. convenient image to use for testing and demonstration
  116. Examples
  117. --------
  118. >>> import scipy.misc
  119. >>> ascent = scipy.misc.ascent()
  120. >>> ascent.shape
  121. (512, 512)
  122. >>> ascent.max()
  123. 255
  124. >>> import matplotlib.pyplot as plt
  125. >>> plt.gray()
  126. >>> plt.imshow(ascent)
  127. >>> plt.show()
  128. """
  129. import pickle
  130. import os
  131. fname = os.path.join(os.path.dirname(__file__),'ascent.dat')
  132. with open(fname, 'rb') as f:
  133. ascent = array(pickle.load(f))
  134. return ascent
  135. def face(gray=False):
  136. """
  137. Get a 1024 x 768, color image of a raccoon face.
  138. raccoon-procyon-lotor.jpg at http://www.public-domain-image.com
  139. Parameters
  140. ----------
  141. gray : bool, optional
  142. If True return 8-bit grey-scale image, otherwise return a color image
  143. Returns
  144. -------
  145. face : ndarray
  146. image of a racoon face
  147. Examples
  148. --------
  149. >>> import scipy.misc
  150. >>> face = scipy.misc.face()
  151. >>> face.shape
  152. (768, 1024, 3)
  153. >>> face.max()
  154. 255
  155. >>> face.dtype
  156. dtype('uint8')
  157. >>> import matplotlib.pyplot as plt
  158. >>> plt.gray()
  159. >>> plt.imshow(face)
  160. >>> plt.show()
  161. """
  162. import bz2
  163. import os
  164. with open(os.path.join(os.path.dirname(__file__), 'face.dat'), 'rb') as f:
  165. rawdata = f.read()
  166. data = bz2.decompress(rawdata)
  167. face = frombuffer(data, dtype='uint8')
  168. face.shape = (768, 1024, 3)
  169. if gray is True:
  170. face = (0.21 * face[:,:,0] + 0.71 * face[:,:,1] + 0.07 * face[:,:,2]).astype('uint8')
  171. return face
  172. def electrocardiogram():
  173. """
  174. Load an electrocardiogram as an example for a one-dimensional signal.
  175. The returned signal is a 5 minute long electrocardiogram (ECG), a medical
  176. recording of the heart's electrical activity, sampled at 360 Hz.
  177. Returns
  178. -------
  179. ecg : ndarray
  180. The electrocardiogram in millivolt (mV) sampled at 360 Hz.
  181. Notes
  182. -----
  183. The provided signal is an excerpt (19:35 to 24:35) from the `record 208`_
  184. (lead MLII) provided by the MIT-BIH Arrhythmia Database [1]_ on
  185. PhysioNet [2]_. The excerpt includes noise induced artifacts, typical
  186. heartbeats as well as pathological changes.
  187. .. _record 208: https://physionet.org/physiobank/database/html/mitdbdir/records.htm#208
  188. .. versionadded:: 1.1.0
  189. References
  190. ----------
  191. .. [1] Moody GB, Mark RG. The impact of the MIT-BIH Arrhythmia Database.
  192. IEEE Eng in Med and Biol 20(3):45-50 (May-June 2001).
  193. (PMID: 11446209); :doi:`10.13026/C2F305`
  194. .. [2] Goldberger AL, Amaral LAN, Glass L, Hausdorff JM, Ivanov PCh,
  195. Mark RG, Mietus JE, Moody GB, Peng C-K, Stanley HE. PhysioBank,
  196. PhysioToolkit, and PhysioNet: Components of a New Research Resource
  197. for Complex Physiologic Signals. Circulation 101(23):e215-e220;
  198. :doi:`10.1161/01.CIR.101.23.e215`
  199. Examples
  200. --------
  201. >>> from scipy.misc import electrocardiogram
  202. >>> ecg = electrocardiogram()
  203. >>> ecg
  204. array([-0.245, -0.215, -0.185, ..., -0.405, -0.395, -0.385])
  205. >>> ecg.shape, ecg.mean(), ecg.std()
  206. ((108000,), -0.16510875, 0.5992473991177294)
  207. As stated the signal features several areas with a different morphology.
  208. E.g. the first few seconds show the electrical activity of a heart in
  209. normal sinus rhythm as seen below.
  210. >>> import matplotlib.pyplot as plt
  211. >>> fs = 360
  212. >>> time = np.arange(ecg.size) / fs
  213. >>> plt.plot(time, ecg)
  214. >>> plt.xlabel("time in s")
  215. >>> plt.ylabel("ECG in mV")
  216. >>> plt.xlim(9, 10.2)
  217. >>> plt.ylim(-1, 1.5)
  218. >>> plt.show()
  219. After second 16 however, the first premature ventricular contractions, also
  220. called extrasystoles, appear. These have a different morphology compared to
  221. typical heartbeats. The difference can easily be observed in the following
  222. plot.
  223. >>> plt.plot(time, ecg)
  224. >>> plt.xlabel("time in s")
  225. >>> plt.ylabel("ECG in mV")
  226. >>> plt.xlim(46.5, 50)
  227. >>> plt.ylim(-2, 1.5)
  228. >>> plt.show()
  229. At several points large artifacts disturb the recording, e.g.:
  230. >>> plt.plot(time, ecg)
  231. >>> plt.xlabel("time in s")
  232. >>> plt.ylabel("ECG in mV")
  233. >>> plt.xlim(207, 215)
  234. >>> plt.ylim(-2, 3.5)
  235. >>> plt.show()
  236. Finally, examining the power spectrum reveals that most of the biosignal is
  237. made up of lower frequencies. At 60 Hz the noise induced by the mains
  238. electricity can be clearly observed.
  239. >>> from scipy.signal import welch
  240. >>> f, Pxx = welch(ecg, fs=fs, nperseg=2048, scaling="spectrum")
  241. >>> plt.semilogy(f, Pxx)
  242. >>> plt.xlabel("Frequency in Hz")
  243. >>> plt.ylabel("Power spectrum of the ECG in mV**2")
  244. >>> plt.xlim(f[[0, -1]])
  245. >>> plt.show()
  246. """
  247. import os
  248. file_path = os.path.join(os.path.dirname(__file__), "ecg.dat")
  249. with load(file_path) as file:
  250. ecg = file["ecg"].astype(int) # np.uint16 -> int
  251. # Convert raw output of ADC to mV: (ecg - adc_zero) / adc_gain
  252. ecg = (ecg - 1024) / 200.0
  253. return ecg