|
|
|
@@ -0,0 +1,69 @@ |
|
|
|
package classifier |
|
|
|
|
|
|
|
import ( |
|
|
|
"math" |
|
|
|
) |
|
|
|
|
|
|
|
// ExtractTemporalFeatures computes simple time-domain features from IQ. |
|
|
|
func ExtractTemporalFeatures(iq []complex64) (envVar float64, zeroCross float64, instFreqStd float64, crest float64) { |
|
|
|
if len(iq) == 0 { |
|
|
|
return 0, 0, 0, 0 |
|
|
|
} |
|
|
|
env := make([]float64, len(iq)) |
|
|
|
var mean, rms float64 |
|
|
|
for i, v := range iq { |
|
|
|
a := math.Hypot(float64(real(v)), float64(imag(v))) |
|
|
|
env[i] = a |
|
|
|
mean += a |
|
|
|
rms += a * a |
|
|
|
} |
|
|
|
mean /= float64(len(iq)) |
|
|
|
rms = math.Sqrt(rms / float64(len(iq))) |
|
|
|
// env variance |
|
|
|
var sumVar float64 |
|
|
|
for _, v := range env { |
|
|
|
d := v - mean |
|
|
|
sumVar += d * d |
|
|
|
} |
|
|
|
envVar = sumVar / float64(len(iq)) |
|
|
|
if rms > 0 { |
|
|
|
crest = maxFloat(env) / rms |
|
|
|
} |
|
|
|
// zero-crossing on real part |
|
|
|
zc := 0 |
|
|
|
for i := 1; i < len(iq); i++ { |
|
|
|
p := real(iq[i-1]) |
|
|
|
c := real(iq[i]) |
|
|
|
if (p >= 0 && c < 0) || (p < 0 && c >= 0) { |
|
|
|
zc++ |
|
|
|
} |
|
|
|
} |
|
|
|
zeroCross = float64(zc) / float64(len(iq)) |
|
|
|
// instantaneous frequency std |
|
|
|
if len(iq) > 1 { |
|
|
|
var sum, sumSq float64 |
|
|
|
for i := 1; i < len(iq); i++ { |
|
|
|
p := iq[i-1] |
|
|
|
c := iq[i] |
|
|
|
num := float64(real(p))*float64(imag(c)) - float64(imag(p))*float64(real(c)) |
|
|
|
den := float64(real(p))*float64(real(c)) + float64(imag(p))*float64(imag(c)) |
|
|
|
v := math.Atan2(num, den) |
|
|
|
sum += v |
|
|
|
sumSq += v * v |
|
|
|
} |
|
|
|
n := float64(len(iq) - 1) |
|
|
|
mean := sum / n |
|
|
|
instFreqStd = math.Sqrt(sumSq/n - mean*mean) |
|
|
|
} |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
func maxFloat(vals []float64) float64 { |
|
|
|
m := vals[0] |
|
|
|
for _, v := range vals { |
|
|
|
if v > m { |
|
|
|
m = v |
|
|
|
} |
|
|
|
} |
|
|
|
return m |
|
|
|
} |