features.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. """首周期波形特征。"""
  2. from __future__ import annotations
  3. import numpy as np
  4. from scipy.signal import hilbert
  5. EPS = 1e-12
  6. BANDS = [
  7. ("band_0_1", 0.5, 1.5),
  8. ("band_1_2", 1.5, 2.5),
  9. ("band_2_5", 2.5, 5.0),
  10. ("band_5_10", 5.0, 10.0),
  11. ("band_10_20", 10.0, 20.0),
  12. ("band_20_40", 20.0, 40.0),
  13. ("band_40_80", 40.0, 80.0),
  14. ]
  15. def _safe_log(value: float) -> float:
  16. return float(np.log10(max(value, 0.0) + EPS))
  17. def _band_power(freqs: np.ndarray, power: np.ndarray, low: float, high: float) -> float:
  18. mask = (freqs >= low) & (freqs < high)
  19. return float(power[mask].sum())
  20. def _band_amplitude(freqs: np.ndarray, amplitude: np.ndarray, center: float) -> float:
  21. if center <= 0:
  22. return 0.0
  23. low, high = center * 0.8, center * 1.2
  24. mask = (freqs >= low) & (freqs <= high)
  25. if not mask.any():
  26. return 0.0
  27. return float(amplitude[mask].max())
  28. def extract_features(signal: np.ndarray, fs: float, rpm: float) -> dict | None:
  29. x = np.asarray(signal, dtype=float)
  30. x = x[np.isfinite(x)]
  31. if x.size < 32:
  32. return None
  33. fs = float(fs or 0.0)
  34. rpm = float(rpm or 0.0)
  35. if fs <= 0:
  36. return None
  37. x = x - x.mean()
  38. n = x.size
  39. std = float(x.std())
  40. rms = float(np.sqrt(np.mean(x * x)))
  41. peak = float(np.max(np.abs(x)))
  42. p2p = float(x.max() - x.min())
  43. mean_abs = float(np.mean(np.abs(x)))
  44. kurt = float(np.mean((x / std) ** 4)) if std > EPS else 0.0
  45. skew = float(np.mean((x / std) ** 3)) if std > EPS else 0.0
  46. crest = peak / rms if rms > EPS else 0.0
  47. impulse = peak / mean_abs if mean_abs > EPS else 0.0
  48. clearance = peak / (mean_abs ** 2) if mean_abs > EPS else 0.0
  49. shape = rms / mean_abs if mean_abs > EPS else 0.0
  50. zcr = float(np.mean(np.diff(np.signbit(x).astype(np.int8)) != 0))
  51. window = np.hanning(n)
  52. spectrum = np.abs(np.fft.rfft(x * window)) / max(n, 1)
  53. power = spectrum ** 2
  54. freqs = np.fft.rfftfreq(n, d=1.0 / fs)
  55. total_power = float(power[freqs > 0].sum())
  56. if total_power > EPS:
  57. probabilities = power[freqs > 0] / total_power
  58. spec_entropy = float(-np.sum(probabilities * np.log(probabilities + EPS)) / np.log(len(probabilities)))
  59. spec_centroid = float(np.sum(freqs[freqs > 0] * probabilities) / 1000.0)
  60. else:
  61. spec_entropy = 0.0
  62. spec_centroid = 0.0
  63. f0 = rpm / 60.0
  64. band_features: dict[str, float] = {}
  65. for name, low, high in BANDS:
  66. if f0 > 0:
  67. energy = _band_power(freqs, power, low * f0, high * f0)
  68. else:
  69. energy = 0.0
  70. band_features[f"{name}_log"] = _safe_log(energy)
  71. if total_power > EPS:
  72. threshold = 10.0 * f0 if f0 > 0 else 2000.0
  73. hf_ratio = float(power[freqs >= threshold].sum() / total_power)
  74. else:
  75. hf_ratio = 0.0
  76. envelope = np.abs(hilbert(x))
  77. env_centered = envelope - envelope.mean()
  78. env_window = np.hanning(n)
  79. env_spectrum = np.abs(np.fft.rfft(env_centered * env_window)) / max(n, 1)
  80. env_power = env_spectrum ** 2
  81. env_total = float(env_power[freqs > 0].sum())
  82. if env_total > EPS:
  83. env_probabilities = env_power[freqs > 0] / env_total
  84. env_entropy = float(-np.sum(env_probabilities * np.log(env_probabilities + EPS)) / np.log(len(env_probabilities)))
  85. else:
  86. env_entropy = 0.0
  87. env_1x = _band_amplitude(freqs, env_spectrum, f0)
  88. env_2x = _band_amplitude(freqs, env_spectrum, 2.0 * f0)
  89. env_3x = _band_amplitude(freqs, env_spectrum, 3.0 * f0)
  90. return {
  91. "n_samples": int(n),
  92. "rms": rms,
  93. "rms_log": _safe_log(rms),
  94. "peak": peak,
  95. "p2p": p2p,
  96. "std": std,
  97. "skew": skew,
  98. "kurt": kurt,
  99. "crest": crest,
  100. "impulse": impulse,
  101. "clearance": clearance,
  102. "shape": shape,
  103. "zcr": zcr,
  104. "spec_centroid": spec_centroid,
  105. "spec_entropy": spec_entropy,
  106. "hf_ratio": hf_ratio,
  107. "env_rms_log": _safe_log(float(np.sqrt(np.mean(envelope ** 2)))),
  108. "env_peak": float(envelope.max()),
  109. "env_1x_log": _safe_log(env_1x),
  110. "env_2x_log": _safe_log(env_2x),
  111. "env_3x_log": _safe_log(env_3x),
  112. "env_entropy": env_entropy,
  113. **band_features,
  114. }