예제 #1
0
        /// <summary>
        /// Возвращает фрагмент сигнала, соответствующий периоду первого стимула;
        /// по возможности сглаженный со стабилизированной амплитудой,
        /// </summary>
        /// <param name="ismid"></param>
        /// <param name="all_data">data package to which returned channel data belongs</param>
        /// <param name="begin_mark">beginning of the relevant signal</param>
        /// <param name="end_mark">end of the relevant signal in the returned signal's buffer</param>
        /// <returns></returns>
        private ChannelData GetSignal(
            HrvRawData ismid,
            ref PatientPhysioData all_data,
            ref long begin_mark,
            ref long end_mark)
        {
            var data = ismid.PhysioData;

            // and we must have at least some physio data
            System.Diagnostics.Debug.Assert(data != null);

            all_data = data;

            ChannelData result = null;

            // first, we take all the data
            var ecg_data = data.GetChannelDataBySignalType(SignalType.ECG);
            var ppg_data = data.GetChannelDataBySignalType(SignalType.PPG);

            // select appropriate signal using 'a priori' information about priority of signal types
            result = GetPreferredSignal(ecg_data, ppg_data);

            if (result == null)
            {
                return(null);
            }

            begin_mark = ismid.PhSyncBegin;
            end_mark   = ismid.PhSyncEnd;

            // Возвращаем результат
            return(result);
        }
        /// <summary>
        /// throws exception if something is wrong with the input data
        /// </summary>
        private void TestIfDataAreAcceptable(
            HrvRawData hrvData,
            SvmrRawData svmrData,
            out TestInfo hrvTestInfo,
            out TestInfo svmrTestInfo)
        {
            if (null == hrvData)
            {
                throw new ArgumentOutOfRangeException(
                          "This summary generator requires HRV & SVMR data. HRV test data not found."
                          );
            }
            if (null == svmrData)
            {
                throw new ArgumentOutOfRangeException(
                          "This summary generator requires HRV & SVMR data. SVMR test data not found."
                          );
            }
            if (ReferenceEquals(hrvData, svmrData))
            {
                throw new Exception("Logic error. Please report the error to solution vendor!");
            }

            svmrTestInfo = svmrData.TestInfo;
            hrvTestInfo  = hrvData.TestInfo;
            if (ReferenceEquals(svmrTestInfo, hrvTestInfo))
            {
                throw new Exception(
                          "2 methods must have different result instances. Please report the error to developers!"
                          );
            }

            if (svmrTestInfo.Patient == null)
            {
                throw new Exception("SVMR data doesn't have employee/patient information!");
            }

            if (hrvTestInfo.Patient == null)
            {
                throw new Exception("HRV data doesn't have employee/patient information!");
            }

            if (svmrTestInfo.Patient.Id != hrvTestInfo.Patient.Id)
            {
                throw new Exception(
                          "Summary report will only be generated based on results belonging to a single employee!"
                          );
            }
        }
        public SummaryDocument GenerateSummary(string databaseId, Guid inspectionId, Employee employee, SvmrRawData svmrData, HrvRawData hrvData)
        {
            TestIfDataAreAcceptable(hrvData, svmrData, out TestInfo hrvTestInfo, out TestInfo _);

            _log.Info($"Input data for employee '{employee}' seem to be accessible");

            DateTimeOffset completionDate = GetCompletionDate(hrvData, svmrData);

            var hostName = hrvTestInfo.MachineName;

            new RusHydroHrvAnalyzer().GetHrvConclusion(
                hrvData, hrvData.TestInfo.TestId, out PreShiftHrvConclusion hrvConclusion);
            new RusHydroSvmrAnalyzer().GetSvmrConclusion(
                svmrData, svmrData.TestInfo.TestId, out PreShiftSvmrConclusion svmrConclusion);

            var finalConclusion = FinalStatusProvider.GetFinalConclusion(hrvConclusion, svmrConclusion);

            finalConclusion.InspectionId = inspectionId;

            _log.Info($"Summary prepared for employee {employee}.");

            return(new SummaryDocument
            {
                Employee = employee,
                CompletionTime = completionDate,
                HrvConclusion = hrvConclusion,
                SvmrConclusion = svmrConclusion,
                FinalConclusion = finalConclusion,
                HostName = hostName,
                InspectionId = inspectionId
            });
        }
예제 #4
0
        public HrvResults ProcessData(HrvRawData rawHrvData)
        {
            base.ProcessData(rawHrvData);

            double[] valid_intervals_durations    = null;
            List <CardioInterval> valid_intervals = null;

            // recorded data contains cardio intervals
            // this is the case when the inspection was performed
            // using a cardio intervals sensor
            // (e.g. Bluetooth HR sensor with RR intervals)
            if (InputDataContainsCardioIntervals(rawHrvData))
            {
                var heartRythmData = ReadCardioIntervalsFromResultSet(rawHrvData);
                valid_intervals           = heartRythmData.Item1;
                HrvResults.RATED_HR_MARKS = heartRythmData.Item2.ToArray();
            }
            else
            {
                ChannelData data = null, adc_data = null;
                double[]    hrv_marks = null;

                SelectAndProcessSignal(m_settings.RejectLowQualitySignalAreas, rawHrvData, out hrv_marks, out data, out adc_data);

                if ((null == data) || (null == adc_data))
                {
                    throw new DataProcessingException(strings.NoSignalInSourceData);
                }

                HrvResults.CRV_CARDIO_SIGNAL = new PhysioSignalView(
                    data.GetDataAsFloat(),
                    (int)data.BitsPerSample,
                    (float)data.SamplingRate,
                    data.PhysioSignalType
                    );

                HrvResults.CRV_CARDIO_SIGNAL_ADC = new PhysioSignalView(
                    adc_data.GetDataAsFloat(),
                    (int)adc_data.BitsPerSample,
                    (float)adc_data.SamplingRate,
                    adc_data.PhysioSignalType
                    );

                // отметки пульса с оценкой достоверности
                var hc_marks_to_rate = new List <RatedContractionMark>(hrv_marks.Length);
                for (int i = 0; i < hrv_marks.Length; ++i)
                {
                    var mark = new RatedContractionMark
                    {
                        Position = hrv_marks[i],
                        Valid    = false
                    };
                    hc_marks_to_rate.Add(mark);
                }

                // получаем достоверные интервалы в миллисекундах,
                // "хорошие" сокращения будут помечены как хорошие (по обоим параметрам),
                var converted_peaks =
                    PeaksFilter.ConvertPeaksToIntervalsWithRejectionAndRatePeaks(
                        hc_marks_to_rate,
                        data.SamplingRate,
                        m_settings.GetApplicableMinIntervalLength(),
                        m_settings.GetApplicableMaxIntervalLength(),
                        m_settings.GetApplicableMaxIntervalDeltaRelative(),
                        adc_data);

                valid_intervals = converted_peaks.extracted_intervals;
                if (converted_peaks.extracted_intervals.Count < 5)
                {
                    throw new DataProcessingException(string.Format(strings.too_few_intervals_detected_in_signal, valid_intervals.Count));
                }

                // это все отметки пульса
                HrvResults.CRV_HR_MARKS = hrv_marks;

                // это отметки пульса с оценками качества
                HrvResults.RATED_HR_MARKS = converted_peaks.rated_heart_contraction_marks.ToArray();

                // Спектр сигнала ФПГ
                HrvResults.SignalSpectrum = PpgSpectrumAnalyzer.CalculateSpectrum(data.Data, data.SamplingRate);
            }

            // только длительности интервалов
            valid_intervals_durations = (from interval in valid_intervals select interval.duration).ToArray();

            var peakAmoDistrib = default(MinMaxModeDescriptor);

            // кардио-интервалы подготовлены
            try
            {
                var mathStatData = new StatData();
                // считаем статистику по кардио-интервалам
                Calculator.CalcStatistics(valid_intervals_durations, mathStatData);
                Calculator.MakeProbabilityDensity(valid_intervals_durations, mathStatData, 440, 2000, 4);

                // The standard approach
                Calculator.MakeDistribution(valid_intervals_durations, mathStatData, 0, 2500, 50);

                // Find the maximized & minimized mode amplitude values
                peakAmoDistrib = MinMaxModeAmpFinder.GetPeakAmoDistrib(valid_intervals_durations, 50, 0, 2500, 1);

                HrvResults.CRV_STAT = _mapper.Map <Methods.ObjectModel.Statistics.StatData>(mathStatData);
            }
            catch (ArgumentException)
            {
                throw new DataProcessingException(
                          string.Format(strings.too_few_valid_intervals_detected_in_signal, valid_intervals_durations.Length));
            }

            HrvResults.Indicators.HRV_Triangular_Index = CalcHrvTriangularIndex(valid_intervals_durations);

            double AMo = Statistics.distribution.mode_amplitude; // Безразмерная -- в долях от 1
            double Mo  = Statistics.distribution.mode / 1000;    // В секундах!!!
            //double AMo = peakAmoDistrib.ModeAmp;   // Безразмерная -- в долях от 1
            //double Mo = peakAmoDistrib.Mode / 1000;       // В секундах!!!
            double M     = Statistics.m / 1000;                        // В секундах!!!
            double dNN   = Statistics.varRange / 1000;                 // В секундах!!!
            double Sigma = Statistics.sigma / 1000;                    // В секундах!!!

            var _AMo = Statistics.distribution.mode_amplitude;         // Безразмерная -- в долях от 1
            var _Mo  = Statistics.distribution.mode / 1000;            // В секундах!!!

            var AmoMax = peakAmoDistrib.MaxModeDescriptor.ModeAmp;     // maximized Mode Amplitude
            var MoMax  = peakAmoDistrib.MaxModeDescriptor.Mode / 1000; // Mode when the Mode amplitude is maximized

            var AmoMin = peakAmoDistrib.MinModeDescriptor.ModeAmp;     // minimized Mode Amplitude
            var MoMin  = peakAmoDistrib.MinModeDescriptor.Mode / 1000; // Mode when the Mode amplitude is minimized

            var AmoMid = peakAmoDistrib.MidModeDescriptor.ModeAmp;     // mean Mode Amplitude
            var MoMid  = peakAmoDistrib.MidModeDescriptor.Mode / 1000; // Mode when the Mode amplitude is close to mean

            // Вычислим индексы Баевского
            // умножаем на 100, т.к. "по Баевскому" АМо указывается в %%.
            HrvResults.Indicators.IN = AMo * 100 / (2.0 * Mo * dNN);

            HrvResults.Indicators.IN_Max     = AmoMax * 100 / (2.0 * MoMax * dNN);
            HrvResults.Indicators.IN_MaxMode = MoMax;

            HrvResults.Indicators.IN_Min     = AmoMin * 100 / (2.0 * MoMin * dNN);
            HrvResults.Indicators.IN_MinMode = MoMin;

            HrvResults.Indicators.IN_Mid     = AmoMid * 100 / (2.0 * MoMid * dNN);
            HrvResults.Indicators.IN_MidMode = MoMid;

            // TODO: выяснить, что правильно, что нет: m_BaevskyStatistics._VPR = 1.f / (fM * fX);
            HrvResults.Indicators.VPR  = 1.0 / (Mo * dNN);
            HrvResults.Indicators.PAPR = AMo * 100 / Mo;
            HrvResults.Indicators.IVR  = AMo * 100 / dNN;

            // Психофизиологическая цена [адаптации]
            // АМ [%%] / ( Мат. ожидание [с] * сигма [с] )
            // умножаем на 100, т.к. АМо д.б. в %%.
            HrvResults.Indicators.PPPA = AMo * 100 / (M * Sigma);

            // Передать в результаты теста также и сами кардиоинтервалы
            // с моментами их появления в записи
            HrvResults.CRV_INTERVALS = valid_intervals.ToArray();

            // Спектр кардиоинтервалов
            HrvResults.IntervalsSpectrum = HrvSpectrumAnalyzer.MakeCardioIntervalsSpectrum(valid_intervals);

            double sum = 0.0;
            double n50 = (valid_intervals[0].duration > 50) ? 1 : 0;

            for (int i = 1; i < valid_intervals.Count; i++)
            {
                double diff = valid_intervals[i].duration - valid_intervals[i - 1].duration;
                sum += Math.Pow(diff, 2);
                if (diff > 50)
                {
                    n50++;
                }
            }

            HrvResults.Indicators.pNN50 = 100.0 * n50 / (valid_intervals.Count - 1);
            HrvResults.Indicators.RMSSD = Math.Sqrt(sum);

            HrvResults.CRV_SCATTEROGRAMM_PARAMETERS = GetScatterParams(valid_intervals_durations);

            HrvResults.ResultsReliability = GetResultsReliability(HrvResults);

            return((HrvResults)ProcessorOutputData);
        }
예제 #5
0
        private void SelectAndProcessSignal(
            bool rejectLowQualitySignalAreas,
            HrvRawData ismid,
            out double[] out_hrv_marks,
            out ChannelData out_channel_data,
            out ChannelData out_adc_data)
        {
            long begin = 0;
            long end   = 0;

            out_hrv_marks    = null;
            out_channel_data = null;

            PatientPhysioData all_data = null;
            ChannelData       data     = GetSignal(ismid, ref all_data, ref begin, ref end);

            if (rejectLowQualitySignalAreas)
            {
                // BUG: this is for Rushydro only, until there is a solution
                // to mask lower-quality portions of the signal at the beginning
                all_data.MoveMarker(begin, 12000000L);
            }

            out_adc_data = new ChannelData(data);

            if (data == null)
            {
                return;
            }

            if (data.PhysioSignalType == SignalType.PPG)
            {
                //int[] unfiltered_data_array = data.GetData();
                int[] data_buffer = data.Data;

                // обрабатываем данные с помощью стандартного процессора
                // (фильтруем, стабилизируем амплитуду и находим фронты)
                // обрабатываем все данные, и даже нерелевантные, чтобы не повредить релевантные данные
                // (в процессе инициализации фильтра сигнал выглядит довольно страшно)
                double[] hrv_marks = Pulse.PpgPulseDetectorHelper.ProcessPpgData(
                    data.SamplingRate, data_buffer, (int)data.BitsPerSample, data.DeviceTypeName);

                // избавляемся от лишних данных и лишних отметок пульса,
                // корректируя положение релевантных отметок
                ChannelData relevant_data = all_data.GetChannelDataFromLeftToRight(data, begin, end);

                long left_marker_data_count = all_data.GetChannelDataCountForMarker(data, begin);
                if (left_marker_data_count < 0)
                {
                    left_marker_data_count = 0;
                }
                long right_marker_data_count = all_data.GetChannelDataCountForMarker(data, end);
                if (right_marker_data_count < 0)
                {
                    right_marker_data_count = data.Data.Length;
                }

                var relevant_hrv_marks = new List <double>(hrv_marks.Length);
                for (int i = 0; i < hrv_marks.Length; ++i)
                {
                    // добавляем в список только те отметки, которые попали между левым и правым маркером
                    if ((hrv_marks[i] >= left_marker_data_count) && (hrv_marks[i] <= right_marker_data_count))
                    {
                        relevant_hrv_marks.Add(hrv_marks[i] - ((double)left_marker_data_count));
                    }
                }

                out_hrv_marks    = relevant_hrv_marks.ToArray();
                out_channel_data = relevant_data;
            }
            else if (data.PhysioSignalType == SignalType.ECG)
            {
                // not implemented so far
                return;
            }
            else
            {
                // other signal types not supported
                return;
            }
        }
예제 #6
0
        private Tuple <List <CardioInterval>, List <RatedContractionMark> > ReadCardioIntervalsFromResultSet(HrvRawData rawData)
        {
            var data_for_all = rawData.PhysioData;

            System.Diagnostics.Debug.Assert(data_for_all != null);

            // in this method we always have only one patient
            var all_data = rawData.PhysioData;

            // and we must have at least some physio data
            System.Diagnostics.Debug.Assert(all_data != null);

            // first, we take all the data
            var intervals = all_data.GetChannelDataBySignalType(SignalType.CardioIntervals);

            if (intervals == null)
            {
                // there are no intervals stored in the result set in a known format...
                // use InputDataContainsCardioIntervals to check before calling Read...
                throw new InvalidOperationException(
                          "The test data don't contain cardio intervals. Did you check the cardio interval data are present?");
            }

            long begin_mark = rawData.PhSyncBegin;
            long end_mark   = rawData.PhSyncEnd;

            // Читаем данные из канала и заполняем массив интервалов и меток сердечных сокращений
            var signal = intervals.First();

            logger.Info($"Using pre-recorded cardio intervals from {signal.ChannelId}");

            var valid_intervals = new List <CardioInterval>(signal.Data.Length);
            var hc_marks        = new List <RatedContractionMark>(signal.Data.Length);

            // запоминаем количество сэмплов для первой метки времени
            long startCount = (begin_mark == -1) ? 0 :
                              all_data.GetChannelDataCountForMarker(signal, begin_mark);
            var intervalsMSec = signal.Data;

            var          lastTimestampId    = signal.Timestamps.Keys.Max();
            var          lastTimestamp_uSec = signal.Timestamps[lastTimestampId];
            const double oneMillion         = 1000000.0;

            hc_marks.Add(new RatedContractionMark()
            {
                // здесь: секунды (канал Кардио Интервалы работает с декларированной частотой 1)
                Position       = (lastTimestamp_uSec) / oneMillion,
                IntervalsCount = 1, // последняя метка всегда участвует только в 1 интервале
                Valid          = true
            });
            // пока просто возьмем все сэмплы, и расположим их начиная от последней метки времени
            // считая, что все интервалы поступали непрерывно
            // это приемлемо для статистической обработки,
            // но не всегда приемлемо при использовании time-domain и frequency-domain методов
            for (var i = intervalsMSec.Length - 1; i >= (int)startCount; --i)
            {
                var intervalDuration_uSec = intervalsMSec[i] * 1000;

                hc_marks.Insert(0, new RatedContractionMark()
                {
                    // здесь: секунды (канал Кардио Интервалы работает с декларированной частотой 1)
                    Position       = (lastTimestamp_uSec - intervalDuration_uSec) / oneMillion,
                    IntervalsCount = 2,
                    Valid          = true
                });

                valid_intervals.Insert(0, new CardioInterval(
                                           (lastTimestamp_uSec - intervalDuration_uSec) / 1000,
                                           lastTimestamp_uSec / 1000.0,
                                           i,
                                           i + 1)
                                       );

                lastTimestamp_uSec = lastTimestamp_uSec - intervalDuration_uSec;
            }

            if (hc_marks.Count != 0)
            {
                hc_marks.First().IntervalsCount = 1;
                hc_marks.Last().IntervalsCount  = 1;
            }

            // потом сравним первую метку времени и вычисленную метку времени
            // при больших расхождениях, результат частотного анализа ритма сердца
            // может быть недостоверным

            return(Tuple.Create(valid_intervals, hc_marks));
        }