Beispiel #1
0
 private static void Debug_DumpFilteringResults(PeaksFilterOutput r_min)
 {
     for (int ii = 0; ii < r_min.rated_heart_contraction_marks.Count; ++ii)
     {
         if (r_min.rated_heart_contraction_marks[ii].IntervalsCount < 2)
         {
             log4net.LogManager.GetLogger(typeof(PeaksFilter)).Debug("not so good heart contraction mark");
         }
     }
 }
Beispiel #2
0
        private static PeaksFilterOutput TryConvertAndRatePeaks(List <RatedContractionMark> src_hr_marks, double data_rate, double min_interval_length, double max_interval_length, double max_relative_delta, double ave, int i_first)
        {
            // начало трехшагового алгоритма извлечения качественных интервалов
            var result = new PeaksFilterOutput(src_hr_marks);

            // для быстрого обращения к полям объекта result...
            var hrMarksToRate = result.rated_heart_contraction_marks;
            var intervals     = result.extracted_intervals;

            double begin = 0, end = 0, interval = 0, delta, rdelta;

            // пусть в самом начале предыдущий интервал будет равен среднему интервалу за весь период оценки
            double prev_interval = ave;

            bool bChecksPassed, rdelta_ok;
            int  i_begin, i_end;

            // количество последовательных отбракованных интервалов по правилу относительного изменения длительности
            int count_of_successive_failures_due_to_relative_change = 0;

            // максимально допустимое количество таких отбраковок
            // (при превышении делается возврат к началу серии и изменяются парметры)
            int max_count_of_successive_failures_due_to_relative_change = 6;

            bool in_step_back_mode = false;

            var i_ends = new List <int>(10);
            var i_ends_interval_ends = new Dictionary <int, double>(10);

            // перебираем все обнаруженные метки сердечных сокращений
            for (int i = i_first; i < hrMarksToRate.Count; ++i)
            {
                i_begin = i - 1;
                i_end   = i_begin + 1;

                bChecksPassed = false;

                // из нескольких интервалов, получаемых из сердечных сокращений № i..i+k,
                // выбираем допустимые по абсолютному значению, а из них отбираем интервал,
                // наиболее близкий к предыдущему значению
                //
                // если такой интервал не удовлетворяет ограничению на изменение относительно предыдущего интервала,
                // сравниваем его величину со средним значением при том же ограничении на относительное изменение
                rdelta_ok = true; // важно, чтобы r_delta было true если вылетаем из внутреннего while из-за абсолютного значения интервала

                begin = hrMarksToRate[i_begin].Position / data_rate * 1000.0;

                i_ends.Clear();
                i_ends_interval_ends.Clear();

                if (max_relative_delta < double.PositiveInfinity)
                {
                    for (; i_end < hrMarksToRate.Count; ++i_end)
                    {
                        end      = hrMarksToRate[i_end].Position / data_rate * 1000.0;
                        interval = end - begin;
                        if (interval < min_interval_length)
                        {
                            // пропускаем все отметки, дающие слишком короткие интервалы
                            continue;
                        }
                        if (interval > max_interval_length)
                        {
                            // получаются слишком длинные интервалы, прекращаем
                            break;
                        }
                        i_ends.Add(i_end);
                        i_ends_interval_ends[i_end] = end;
                    }
                }
                else
                {
                    i_ends.Add(i_end);
                    i_ends_interval_ends.Add(i_end, hrMarksToRate[i_end].Position / data_rate * 1000.0);
                }

                if (0 == i_ends.Count)
                {
                    // сердечных сокращений, отстоящих от begin на допустимое расстояние, не обнаружено
                    continue;
                }

                // для каждого из "подозрительных" интервалов считаем разницу между
                // его величиной и величиной предыдущего интервала.
                // интервал с минимальной разницей и есть наиболее "качественный" интервал.
                double min_delta = double.MaxValue;
                int    min_index = -1;
                for (int l = 0; l < i_ends_interval_ends.Count; ++l)
                {
                    interval = i_ends_interval_ends[i_ends[l]] - begin;
                    delta    = System.Math.Abs(prev_interval - interval);
                    if (delta < min_delta)
                    {
                        min_delta = delta;
                        min_index = l;
                    }
                }

                // работаем с наиболее вероятным сердечным сокращением
                i_end = i_ends[min_index];
                // оно соответствует времени (в мс):
                end = i_ends_interval_ends[i_end];
                // и дает интервал длительностью
                interval = end - begin;

                delta = System.Math.Abs(prev_interval - interval);

                rdelta = delta / prev_interval;

                // по приращению по отношению к предыдущему интервалу:
                rdelta_ok = (rdelta <= max_relative_delta);

                // по абсолютному значению оно обязано подходить, ибо получено из этого условия...
                bool abs_val_ok = (interval >= min_interval_length) && (interval <= max_interval_length);

                bChecksPassed = abs_val_ok && rdelta_ok;

                // ok, оценим интервал -- по абсолютной величине и по отношению к предыдущему...
                if (bChecksPassed)
                {
                    // получили достоверный интервал
                    // пометим все сокращения между begin и end как недостоверные!
                    for (int j = i_begin + 1; j < i_end; ++j)
                    {
                        hrMarksToRate[i].Valid          = false;
                        hrMarksToRate[i].IntervalsCount = 0;
                    }
                    hrMarksToRate[i_begin].Valid = true;
                    hrMarksToRate[i_end].Valid   = true;

                    ++hrMarksToRate[i_begin].IntervalsCount;
                    ++hrMarksToRate[i_end].IntervalsCount;

                    count_of_successive_failures_due_to_relative_change = 0;
                    in_step_back_mode = false;

                    prev_interval = interval;
                    intervals.Add(new CardioInterval(begin, end, i_begin, i_end));

                    i = i_end;
                }
                else
                {
                    // наиболее достоверный интервал недостоверен...
                    // что делать?

                    // бросаем (i-1)-ю метку сердечного сокращения и пытаемся сформировать интервал
                    // с i-й по i+1-ю метку и т.д.

                    // не удалось построить интервал из i-1-го сердечного сокращения
                    // помечаем его как недостоверное
                    hrMarksToRate[i_begin].Valid = false;

                    // Интервал был отбракован
                    if (rdelta_ok) // Из-за относительного изменения?
                    {
                        // нет, относительное изменение в порядке
                        count_of_successive_failures_due_to_relative_change = 0;
                        in_step_back_mode = false;
                    }
                    else
                    {
                        // да
                        // еще одно сердечное сокращение отфильтровано по правилу относительного изменения длительности
                        ++count_of_successive_failures_due_to_relative_change;
                    }

                    // проверим количество интервалов отброшенных подряд из-за нарушения ограничений
                    // на относительное изменение длительности интервала
                    if (PeaksFilter.EnableStepBackOnSuccessiveFailures)
                    {
                        if (count_of_successive_failures_due_to_relative_change > max_count_of_successive_failures_due_to_relative_change)
                        {
                            // шаг назад (чтобы не зациклиться, делаем шаг назад только один раз)...
                            if (false == in_step_back_mode)
                            {
                                // запоминаем, что шаг назад уже сделан
                                in_step_back_mode = true;
                                // переходим к оценке изменения по среднему интервалу,
                                // а не по предыдущему, с которого все и началось
                                prev_interval = ave;
                                count_of_successive_failures_due_to_relative_change = 0;
                                // возвращаемся на max_count_of_successive_failures_due_to_relative_change сокращений назад
                                i = System.Math.Max(1, i - max_count_of_successive_failures_due_to_relative_change);
                            }
                            else
                            {
                                // если шаг назад уже сделали, то больше его не повторяем, чтобы не зациклиться!
                            }
                        }
                    }
                }
            } // for( i = ...

            result.Update();
            return(result);
        }
Beispiel #3
0
        /// <summary>
        /// Создает из массива сердечных сокращений массив кардио-интервалов с указанием положения интервала
        /// в записи в секундах.
        /// При анализе учитываются возможные пропуски сердечных сокращений в областях некачественного сигнала.
        ///
        /// После построения ряда сердечных сокращений с
        /// </summary>
        /// <param name="hr_marks_to_rate"></param>
        /// <param name="data_rate"></param>
        /// <param name="min_interval_length"></param>
        /// <param name="max_interval_length"></param>
        /// <param name="max_relative_delta">
        /// Максимальное приращение относительно предыдущего или приближенного усредненного интервала, %%
        /// Если значение параметра равно double.PositiveInfinity, фильтрация по относительному приращению не производится
        /// </param>
        /// <param name="signal">Сигнал, на основе которого определены моменты сердечных сокращений.
        /// Может использоваться для оценки "качества" найденных сердечных сокращений</param>
        /// <returns></returns>
        /// <remarks>
        ///
        /// 0. Предложние:
        /// Выбрасываем сразу:
        /// Все СС (сердечные сокращения), расстояние от которых до соседних сокращений меньше 1/3
        /// минимально допустимого кардио-интервала?????
        ///
        /// Зная средний интервал, можем строить предположения о качестве обнаруженных сердечных сокращений (СС).
        ///
        /// 1. Например, если обнаружено (СС), образующее с
        /// предыдущим СС интервал длительностью менее чем в 0.65 среднего интервала
        /// и более чем на 30% короче предыдущего интервала, то можем предполагать,
        /// что данное СС обнаружено в районе инцезуры (дикротической волны)
        /// и его нужно "выбросить" из ряда сокращений, используемых для построения
        /// ряда интервалов.
        /// 1а. Если значение предыдущего интервала неизвестно (например, для первого интервала в ряду),
        /// используем для оценки величину среднего интервала (получаем ее из всех интервалов в течение
        /// записи, как "качественных", таки "некачественных" по критерию скрости изменения
        /// (т.е. учитываем только интервалы, удовлетворяющие ограничениям на абсолютную величину интервала).
        ///
        /// 2. Если некоторое сердечное сокращение было пропущено, то пытаемся найти
        /// "валидный" интервал, используя в качестве начального и конечного СС
        /// СС, предшествующий отброшенному и СС, следующие за отброшенным, предполагая
        /// их "валидными" СС и проводя оценку валидности каждого из получившехся интервалов
        /// с учетом правил отбраковки кардио-интервалов.
        ///
        /// 3. Если по правилу (2) валидных интервалов получить не удается (величина получающихся
        /// интервалов начинает превышать максимально допустимую), начинаем строить новый кардио-интервал,
        /// считая моментом начального СС "отброшенное" СС, и переходим к пункту 1.
        ///
        /// Предложение:
        /// 4. Если фильтрация СС с учетом ограничений на относительное изменение длительности
        /// интервалов дает слишком много (более T=6) недостоверных СС и (T-1) кардио-интервалов подряд,
        /// предлагается делать T шагов назад (к первому СС из отброшенной серии) и оценить относительное
        /// изменение длительности первого из серии отброшенных интервалов не по сравнению с предыдущим,
        /// а по сравнению со средним значением за всю запись без отбраковки.
        ///
        /// Для получения корректных результатов должно выполняться предположение о том, что количество
        /// неотсеянных инцезур в исходном списке сердечных сокращений много меньше количества правильно
        /// обнаруженных СС.
        /// ///////////////////////////////////////////
        /// </remarks>
        public static PeaksFilterOutput ConvertPeaksToIntervalsWithRejectionAndRatePeaks(
            List <RatedContractionMark> hr_marks_to_rate,
            double data_rate,
            double min_interval_length,
            double max_interval_length,
            double max_relative_delta,
            ChannelData signal
            )
        {
            if (hr_marks_to_rate.Count < 2)
            {
                throw new ArgumentException(strings.too_few_heart_contractions_detected_in_signal);
            }

            // перейдем от %% к долям от 1
            max_relative_delta /= 100.0;

            // вычисляем (приближенно) усредненную величину достоверного интервал с учетом ограничений
            double ave = Estimate_AverageInterval(
                hr_marks_to_rate,
                data_rate,
                min_interval_length,
                max_interval_length,
                max_relative_delta);

            List <PeaksFilterOutput> results = new List <PeaksFilterOutput>(5);

            int start       = 1;
            int max_retries = 1; // 5 -- этот подход подлежит дальнейшему изучению и уточнению, пока отключено

            for (int i_first = start; i_first < System.Math.Min(start + max_retries, hr_marks_to_rate.Count); ++i_first)
            {
                PeaksFilterOutput r = TryConvertAndRatePeaks(hr_marks_to_rate, data_rate, min_interval_length, max_interval_length, max_relative_delta, ave, i_first);
                results.Add(r);

                double delta_ave = System.Math.Abs(r.average_cardio_interval - ave) / (0.5 * (r.average_cardio_interval + ave));
                if ((delta_ave <= 0.1) || (double.PositiveInfinity == max_relative_delta))
                {
                    Debug_DumpFilteringResults(r);
                    return(r);
                }
            }

            // Если попали сюда, значит ни один из полученных наборов интервалов
            // не укладывается в погрешность среднего в 10%. Необходимо выбрать
            // наилучший результат из полученного набора

            PeaksFilterOutput r_min       = results[0];
            double            delta_r_min = System.Math.Abs(r_min.average_cardio_interval - ave) / (0.5 * (r_min.average_cardio_interval + ave));

            for (int i = 1; i < results.Count; ++i)
            {
                double delta_r_i = System.Math.Abs(results[i].average_cardio_interval - ave) / (0.5 * (results[i].average_cardio_interval + ave));
                if (delta_r_i < delta_r_min)
                {
                    r_min       = results[i];
                    delta_r_min = delta_r_i;
                }
            }

            Debug_DumpFilteringResults(r_min);

            return(r_min);
        }