ssa
1package ssa22
3import (4"fmt"5"log"6"math"7"os"8"slices"9"sort"10
11"github.com/RB-PRO/ssa/pkg/ssa2/pmtm"12)
13
14// Структура загрузчика данных
15type Setup struct {16Cad int // Количество кадров в секунду17NPart int // Количество долей res18Win int // Ширина окна19FMi float64 // Частота среза для 40 уд/мин (0.6667 Гц)20FMa float64 // Частота среза для 240 уд/мин (4.0 Гц)21}
22
23type SSA struct {24set Setup // Структура настройки Расчётов SSA25
26len int // Количество отсчётов pw27
28// Временные интервалы29tim []float64 // Время в секундах30dt float64 // Интервал дискретизации времени, сек31
32col int // Всего колонок33
34// Сигнал фотоплетизмографии35pw []float64 // Сигнал фотоплетизмографии36freq []float6437Pto_fMAX []float6438
39// Оценки СПМ перекрывающихся сегменов pw40iGmin int41iGmax int42
43res int44}
45
46// Создать объект SSA для работы
47func NewSSA(pw []float64, Setting Setup) (*SSA, error) {48var ssa SSA49ssa.pw = pw50ssa.set = Setting51
52// к-во отсчётов пульсовой волны53ssa.len = len(ssa.pw)54
55// интервал дискретизации времени, сек56ssa.dt = 1.0 / float64(ssa.set.Cad)57
58// время в секундах59tim := make([]float64, ssa.len)60for i := 0; i < ssa.len; i++ {61tim[i] = float64(i) * ssa.dt62}63ssa.tim = tim64
65return &ssa, nil66}
67
68// Расчёт параметра col
69func (ssa *SSA) Col() (*SSA, error) {70col := 171Imin := 172Imax := ssa.set.Win73resFloat64 := float64(ssa.len) - float64(ssa.set.Win)*math.Floor(float64(ssa.len)/float64(ssa.set.Win))74res := int(math.Floor(resFloat64 / float64(ssa.set.NPart)))75
76// col - кол-во перекрывающихся сегментов в пределах len77for Imax <= ssa.len {78Imin = Imin + res79Imax = Imax + res80col = col + 181}82col--83
84ssa.col = col85ssa.res = res86return ssa, nil87}
88
89// текущий сегмент pw длинною win
90func (ssa *SSA) Spw(column int) ([]float64, error) {91if ssa.col == 0 {92return nil, fmt.Errorf("spw: ssa.col is 0")93}94if column >= ssa.col {95return nil, fmt.Errorf("spw: Условие column(%d) < ssa.col(%d) не выполнено", column, ssa.col)96}97if len(ssa.pw) == 0 {98return nil, fmt.Errorf("spw: len(pw)=0")99}100return ssa.pw[column*ssa.res : column*ssa.res+ssa.set.Win], nil101}
102
103// Оценки СПМ перекрывающихся сегменов pw
104func (ssa *SSA) SpwEstimation() (*SSA, error) {105df := float64(ssa.set.Cad) / float64(ssa.set.Win-1)106Fmin := ssa.set.FMi - 10*df107Fmax := ssa.set.FMa + 10*df108row := 1 + ssa.set.Win/2109
110tecalF := 0.0111ssa.freq = make([]float64, row)112for i := 0; i < row; i++ {113if math.Abs(tecalF-Fmin) <= df {114ssa.iGmin = i115}116if math.Abs(tecalF-Fmax) <= df {117ssa.iGmax = i118}119ssa.freq[i] = tecalF120tecalF += df121}122SaveTXT("ssafreq.txt", ssa.freq)123
124return ssa, nil125}
126
127// Оценки средних частот основного тона для сегментов pw
128func (ssa *SSA) PwEstimation() (*SSA, error) {129// BlackManHar := blackmanharris.Blackmanharris(ssa.set.Win, blackmanharris.Koef4_74db)130
131filePW, _ := os.Create("tests/pto.txt")132defer filePW.Close()133
134// Выделяем память в слайс135ssa.Pto_fMAX = make([]float64, ssa.col)136for i := 0; i < ssa.col; i++ {137
138// Получение колонки SPW139ColumnSPW, ErrSpw := ssa.Spw(i)140if ErrSpw != nil {141return ssa, fmt.Errorf("SSA: PwEstimation: %w", ErrSpw)142}143// ColumnSPW := make([]float64, len(ColumnSPW2))144// copy(ColumnSPW, ColumnSPW2)145
146// Получение периодограммы колонки SPW147// pg_spw := periodogram.Periodogram(ColumnSPW, BlackManHar, ssa.set.Win)148
149pto_spw := pmtm.Pmtm(ColumnSPW, 3, ssa.set.Win)150
151for j := range pto_spw {152filePW.WriteString(fmt.Sprintf("%.8f;", pto_spw[j]))153}154filePW.WriteString(fmt.Sprintf("\n"))155pto_spw = medianFilter(pto_spw, 30)156// pto_spw = sredFilter(pto_spw, 15)157
158// Сотртируем, чтобы получить по возрастанию порядковвые номера в слайсе по убыванию159// _, SorterIndexts_pg_spw := InsertionSort(pto_spw)160SorterIndexts_pg_spw := InsertionSort2(pto_spw)161// _, SorterIndexts_pg_spw := InsertionSortInt(pto_spw)162
163// fmt.Println(SorterIndexts_pg_spw[0], len(pto_spw))164ssa.Pto_fMAX[i] = ssa.freq[SorterIndexts_pg_spw] // float64(SorterIndexts_pg_spw)165// ssa.Pto_fMAX[i] = float64(SorterIndexts_pg_spw)166}167
168return ssa, nil169}
170
171func SaveTXT(FileName string, data []float64) {172filePW, ErrOpenFile := os.Create(FileName)173if ErrOpenFile != nil {174panic(ErrOpenFile)175}176defer filePW.Close()177for i := range data {178if _, err := filePW.WriteString(fmt.Sprintf("%.8f\n", data[i])); err != nil {179log.Println(err)180}181}182}
183
184// Сортировка с возвратом номеров изначальных элементов
185func InsertionSort(array []float64) ([]float64, []int) {186// var indexArray int187// sort.Slice(array, func(i, j int) bool {188// if array[i] > array[j] {189// indexArray = j190// }191// return array[i] > array[j]192// })193// fmt.Println(indexArray, array[0], array[1], array[2])194
195indexArray := make([]int, len(array))196for ind := range indexArray {197indexArray[ind] = (ind)198}199for i := 1; i < len(array); i++ {200j := i201for j > 0 {202if array[j-1] < array[j] {203array[j-1], array[j] = array[j], array[j-1]204indexArray[j-1], indexArray[j] = indexArray[j], indexArray[j-1]205}206j = j - 1207}208}209return array, indexArray210}
211
212// Сортировка с возвратом номеров изначальных элементов
213func InsertionSortInt(array []float64) ([]float64, int) {214indexArrayint := 0215for i := 1; i < len(array); i++ {216j := i217for j > 0 {218if array[j-1] < array[j] {219array[j-1], array[j] = array[j], array[j-1]220indexArrayint = i221}222j = j - 1223}224}225return array, indexArrayint226}
227
228// Сортировка с возвратом номеров изначальных элементов
229func InsertionSort2(array []float64) int {230return slices.Index(array, slices.Max(array))231}
232
233func medianFilter(x []float64, n int) []float64 {234// Проверка на нечетное значение n235if n%2 == 0 {236n++237}238
239// Длина входного массива240length := len(x)241
242// Результат фильтрации243y := make([]float64, length)244
245for i := 0; i < length; i++ {246// Индексы для сбора значений для медианного фильтра247start := i - n/2248end := i + n/2249
250// Гарантия, что индексы не выходят за пределы массива251if start < 0 {252start = 0253}254if end >= length {255end = length - 1256}257
258// Извлечение значений для медианы259window := x[start : end+1]260
261// Сортировка окна значений и выбор медианы262sortedWindow := make([]float64, len(window))263copy(sortedWindow, window)264sort.Float64s(sortedWindow)265// Хитрый мув. При делении int(5) на int(2), получается int(2),266// т.е. округление в нижнюю сторону, хотя нам нужно в старшую степень.267// Поэтому из нечётного делаем чётное, а в случае получения нечётного не имеет разницы268medianIndex := (len(sortedWindow) + 1) / 2269// fmt.Println("medianIndex", medianIndex, "-", sortedWindow)270y[i] = sortedWindow[medianIndex]271}272
273return y274}
275
276func sredFilter(x []float64, n int) []float64 {277// Проверка на нечетное значение n278if n%2 == 0 {279n++280}281
282// Длина входного массива283length := len(x)284
285// Результат фильтрации286y := make([]float64, length)287
288for i := 0; i < length; i++ {289// Индексы для сбора значений для медианного фильтра290start := i - n/2291end := i + n/2292
293// Гарантия, что индексы не выходят за пределы массива294if start < 0 {295start = 0296}297if end >= length {298end = length - 1299}300
301// Извлечение значений для медианы302window := x[start : end+1]303
304// Сортировка окна значений и выбор медианы305// slises306sum := 0.0307for _, v := range window {308sum += v309}310y[i] = sum / float64(n)311
312}313
314return y315}
316