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 }