Exemple #1
0
        private void timer1_Tick(object sender, EventArgs e)
        {
            string line;

            if (!m_bPaused && m_bInited && m_nStep >= 0 && m_srLog != null)
            {
                ++m_nStep;
                label7.Text = m_nStep.ToString();

                int count     = 0;
                int nTempSize = 0;

                while ((line = m_srLog.ReadLine()) != null)
                {
                    Match match = Regex.Match(line, @"HR=(\d+), SP=(\d+)");

                    int  time = -1, red = -1, ir = -1, hr = -1, sp = -1;
                    bool found_rawdata = false, found_hrsp = false;

                    if (match.Success && match.Groups.Count == 3)
                    {
                        found_hrsp = true;

                        hr = Convert.ToInt32(match.Groups[1].Value);
                        sp = Convert.ToInt32(match.Groups[2].Value);
                        chart1.Series["HR"].Points.AddXY(m_nLastTimeStamp, hr);
                        chart1.Series["SP"].Points.AddXY(m_nLastTimeStamp, sp);

                        // Original values
                        label4.Text = "HR = " + hr.ToString() + ", SP = " + sp.ToString();
                    }
                    else
                    {
                        match = Regex.Match(line, @"time=(\d+), red=(\d+), ir=(\d+)");

                        if (match.Success && match.Groups.Count == 4)
                        {
                            found_rawdata = true;
                        }
                        else
                        {
                            // CSV type
                            match = Regex.Match(line, @"\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)");

                            if (match.Success && match.Groups.Count == 4)
                            {
                                found_rawdata = true;
                            }
                        }

                        if (found_rawdata)
                        {
                            time = Convert.ToInt32(match.Groups[1].Value);
                            red  = Convert.ToInt32(match.Groups[2].Value);
                            ir   = Convert.ToInt32(match.Groups[3].Value);

                            if (count < MAX_TEMP_SIZE)
                            {
                                m_temp_red_buffer[count]  = red;
                                m_temp_ir_buffer[count]   = ir;
                                m_temp_time_buffer[count] = time;
                                nTempSize = count + 1;
                                chart1.Series["Red"].Points.AddXY(time, red);
                                chart1.Series["IR"].Points.AddXY(time, ir);
                            }

                            m_nLastTimeStamp = time;
                            ++count;
                        }
                    }

                    if (found_hrsp || count > 100)
                    {
                        int nTotalSize = m_nBufferSize + nTempSize;

                        if (nTempSize >= Algorithm30102.BUFFER_SIZE)
                        {
                            // Temp is enough
                            int nLeftShift = nTempSize - Algorithm30102.BUFFER_SIZE;

                            for (int i = 0; i < Algorithm30102.BUFFER_SIZE; ++i)
                            {
                                m_red_buffer[i]  = m_temp_red_buffer[i + nLeftShift];
                                m_ir_buffer[i]   = m_temp_ir_buffer[i + nLeftShift];
                                m_time_buffer[i] = m_temp_time_buffer[i + nLeftShift];
                            }

                            m_nBufferSize = Algorithm30102.BUFFER_SIZE;
                        }
                        else if (nTotalSize > Algorithm30102.BUFFER_SIZE)
                        {
                            // Need to left-shift the most recent data on the system buffer
                            int nKeep      = Algorithm30102.BUFFER_SIZE - nTempSize;
                            int nLeftShift = m_nBufferSize - nKeep;

                            for (int i = 0; i < nKeep; ++i)
                            {
                                m_red_buffer[i]  = m_red_buffer[i + nLeftShift];
                                m_ir_buffer[i]   = m_ir_buffer[i + nLeftShift];
                                m_time_buffer[i] = m_time_buffer[i + nLeftShift];
                            }

                            // Then copy the whole temp to fill the system buffer
                            for (int i = nKeep; i < Algorithm30102.BUFFER_SIZE; ++i)
                            {
                                m_red_buffer[i]  = m_temp_red_buffer[i - nKeep];
                                m_ir_buffer[i]   = m_temp_ir_buffer[i - nKeep];
                                m_time_buffer[i] = m_temp_time_buffer[i - nKeep];
                            }

                            m_nBufferSize = Algorithm30102.BUFFER_SIZE;
                        }
                        else
                        {
                            // Just add the temp to the system buffer
                            for (int i = m_nBufferSize; i < nTotalSize; ++i)
                            {
                                m_red_buffer[i]  = m_temp_red_buffer[i - m_nBufferSize];
                                m_ir_buffer[i]   = m_temp_ir_buffer[i - m_nBufferSize];
                                m_time_buffer[i] = m_temp_time_buffer[i - m_nBufferSize];
                            }

                            m_nBufferSize = nTotalSize;
                        }

                        int window = Convert.ToInt32(textBox1.Text);

                        if (window < 100)
                        {
                            window = 100;
                        }
                        else if (window > Algorithm30102.BUFFER_SIZE)
                        {
                            window = Algorithm30102.BUFFER_SIZE;
                        }

                        textBox1.Text = window.ToString();

                        if (m_nBufferSize >= window)
                        {
                            Debug.Assert(m_nBufferSize <= Algorithm30102.BUFFER_SIZE);

                            int  nNewHR = -1, nNewSP = -1;
                            bool bHRValid, bSPValid;

                            try
                            {
                                int nStart = m_nBufferSize - window;

                                int nSelected = comboBox1.SelectedIndex, sr = 100;

                                if (nSelected == 0)
                                {
                                    sr = 50;
                                }
                                else if (nSelected == 1)
                                {
                                    sr = 100;
                                }
                                else if (nSelected == 2)
                                {
                                    sr = 200;
                                }

                                RData rdata = new RData();

                                m_alg.maxim_heart_rate_and_oxygen_saturation(sr, m_ir_buffer.Skip(nStart), window,
                                                                             m_red_buffer.Skip(nStart), out nNewSP, out bSPValid, out nNewHR,
                                                                             out bHRValid, rdata);

                                for (int i = 0; i < rdata.m_size; ++i)
                                {
                                    chart1.Series["AD for Red"].Points.AddXY(m_time_buffer[nStart + rdata.m_red_indices[i]],
                                                                             rdata.m_red_ratios[i]);
                                    chart1.Series["AD for IR"].Points.AddXY(m_time_buffer[nStart + rdata.m_ir_indices[i]],
                                                                            rdata.m_ir_ratios[i]);
                                }
                            }
                            catch
                            {
                                bHRValid = bSPValid = false;
                            }

                            if (!bHRValid)
                            {
                                nNewHR = -1;
                            }

                            if (!bSPValid)
                            {
                                nNewSP = -1;
                            }

                            if (nNewHR > 20 && nNewHR <= 200)
                            {
                                if (m_hr_filter.AddPoint((double)nNewHR))
                                {
                                    m_bHasHRData = true;
                                }
                                else
                                {
                                    nNewHR = -1;
                                }
                            }
                            else
                            {
                                nNewHR = -1;
                            }

                            if (m_bHasHRData)
                            {
                                nNewHR = (int)Math.Round(m_hr_filter.GetValue());
                            }

                            if (nNewSP > 50 && nNewSP <= 100)
                            {
                                if (m_sp_filter.AddPoint((double)nNewSP))
                                {
                                    m_bHasSPData = true;
                                }
                                else
                                {
                                    nNewSP = -1;
                                }
                            }
                            else
                            {
                                nNewSP = -1;
                            }

                            if (m_bHasSPData)
                            {
                                nNewSP = (int)Math.Round(m_sp_filter.GetValue());
                            }

                            label3.Text = "HR = " + nNewHR.ToString() + ", SP = " + nNewSP.ToString();

                            if (nNewHR > 0)
                            {
                                chart1.Series["newHR"].Points.AddXY(m_nLastTimeStamp, nNewHR);
                            }

                            if (nNewSP > 0)
                            {
                                chart1.Series["newSP"].Points.AddXY(m_nLastTimeStamp, nNewSP);
                            }
                        }
                        else
                        {
                            label3.Text = "Not enough raw data";
                        }

                        break;
                    }
                }

                if (line == null)       // EOF
                {
                    button2.Enabled = true;
                    button3.Enabled = false;
                    button4.Enabled = false;
                    m_srLog.Close();
                    m_srLog = null;
                    m_nStep = -1;
                    timer1.Stop();
                }
            }
        }
Exemple #2
0
        int[] an_y  = new int [BUFFER_SIZE];    //red

        /**
         * \brief        Calculate the heart rate and SpO2 level
         * \par          Details
         *               By detecting  peaks of PPG cycle and corresponding AC/DC of red/infra-red signal, the ratio for the SPO2 is computed.
         *               Since this algorithm is aiming for Arm M0/M3. formaula for SPO2 did not achieve the accuracy due to register overflow.
         *               Thus, accurate SPO2 is precalculated and save longo uch_spo2_table[] per each ratio.
         *
         * \param[in]    sr                      - Sample rate
         * \param[in]    *pun_ir_buffer          - IR sensor data buffer
         * \param[in]    n_ir_buffer_length      - IR sensor data buffer length
         * \param[in]    *pun_red_buffer         - Red sensor data buffer
         * \param[out]    *pn_spo2               - Calculated SpO2 value
         * \param[out]    *pch_spo2_valid        - 1 if the calculated SpO2 value is valid
         * \param[out]    *pn_heart_rate         - Calculated heart rate value
         * \param[out]    *pch_hr_valid          - 1 if the calculated heart rate value is valid
         *
         * \retval       None
         */
        public void maxim_heart_rate_and_oxygen_saturation(int sr, IEnumerable <int> pun_ir_buffer_IEnum, int n_ir_buffer_length, IEnumerable <int> pun_red_buffer_IEnum, out int pn_spo2, out bool pch_spo2_valid,
                                                           out int pn_heart_rate, out bool pch_hr_valid, RData rdata)
        {
            int[] pun_ir_buffer  = pun_ir_buffer_IEnum.ToArray();
            int[] pun_red_buffer = pun_red_buffer_IEnum.ToArray();

            int un_ir_mean, un_only_once;
            int k, n_i_ratio_count;
            int i, s, m, n_exact_ir_valley_locs_count, n_middle_idx;
            int n_th1, n_npks, n_c_min;

            int[] an_ir_valley_locs       = new int[15];
            int[] an_exact_ir_valley_locs = new int[15];
            int[] an_dx_peak_locs         = new int[15];
            int   n_peak_interval_sum;
            int   n_y_ac, n_x_ac;
            int   n_spo2_calc;
            int   n_y_dc_max, n_x_dc_max;
            int   n_y_dc_max_idx = -1, n_x_dc_max_idx = -1;

            int[] an_ratio = new int[5];
            int   n_ratio_average, n_nume, n_denom;

            Debug.Assert(pun_red_buffer.Length <= pun_ir_buffer.Length);
            Debug.Assert(n_ir_buffer_length <= pun_ir_buffer.Length);

            // Remove DC of ir signal
            un_ir_mean = 0;

            for (k = 0; k < n_ir_buffer_length; k++)
            {
                un_ir_mean += pun_ir_buffer[k];
            }

            un_ir_mean = un_ir_mean / n_ir_buffer_length;

            for (k = 0; k < n_ir_buffer_length; k++)
            {
                an_x[k] = pun_ir_buffer[k] - un_ir_mean;
            }

            // 4 pt Moving Average
            for (k = 0; k < n_ir_buffer_length - MA4_SIZE; k++)
            {
                n_denom = (an_x[k] + an_x[k + 1] + an_x[k + 2] + an_x[k + 3]);
                an_x[k] = n_denom / (int)4;
            }

            // Get difference of smoothed IR signal
            for (k = 0; k < n_ir_buffer_length - MA4_SIZE - 1; k++)
            {
                an_dx[k] = (an_x[k + 1] - an_x[k]);
            }

            // 2-pt Moving Average to an_dx
            for (k = 0; k < n_ir_buffer_length - MA4_SIZE - 2; k++)
            {
                an_dx[k] = (an_dx[k] + an_dx[k + 1]) / 2;
            }

            // Hamming window: flip wave form so that we can detect valley with peak detector
            for (i = 0; i < n_ir_buffer_length - HAMMING_SIZE - MA4_SIZE - 2; i++)
            {
                s = 0;

                for (k = i; k < i + HAMMING_SIZE; k++)
                {
                    s -= an_dx[k] * auw_hamm[k - i];
                }

                an_dx[i] = s / (int)1146; // divide by sum of auw_hamm
            }

            n_th1 = 0; // threshold calculation

            for (k = 0; k < n_ir_buffer_length - HAMMING_SIZE; k++)
            {
                n_th1 += ((an_dx[k] > 0) ? an_dx[k] : ((int)0 - an_dx[k]));
            }

            n_th1 = n_th1 / (n_ir_buffer_length - HAMMING_SIZE);

            // Peak location is acutally index for sharpest location of raw signal since we flipped the signal
            maxim_find_peaks(an_dx_peak_locs, out n_npks, an_dx, n_ir_buffer_length - HAMMING_SIZE, n_th1, 8, 5);//peak_height, peak_distance, max_num_peaks

            n_peak_interval_sum = 0;

            if (n_npks >= 2)
            {
                for (k = 1; k < n_npks; k++)
                {
                    n_peak_interval_sum += (an_dx_peak_locs[k] - an_dx_peak_locs[k - 1]);
                }

                n_peak_interval_sum = n_peak_interval_sum / (n_npks - 1);

                // Each data point represent 1/sr second in time so peak internal in seconds is
                // n_peak_interval_sum * (1 / sr).
                // The heart rate is 60 / peak interval = 60 * sr / n_peak_interal_sum
                pn_heart_rate = (int)(60 * sr / n_peak_interval_sum);      // Beats per minutes
                pch_hr_valid  = true;
            }
            else
            {
                pn_heart_rate = -999;
                pch_hr_valid  = false;
            }

            for (k = 0; k < n_npks; k++)
            {
                an_ir_valley_locs[k] = an_dx_peak_locs[k] + HAMMING_SIZE / 2;
            }

            // Raw value : RED(=y) and IR(=X)
            // We need to assess DC and AC value of ir and red PPG.
            for (k = 0; k < n_ir_buffer_length; k++)
            {
                an_x[k] = pun_ir_buffer[k];
                an_y[k] = pun_red_buffer[k];
            }

            // Find precise min near an_ir_valley_locs
            n_exact_ir_valley_locs_count = 0;

            for (k = 0; k < n_npks; k++)
            {
                un_only_once = 1;
                m            = an_ir_valley_locs[k];
                n_c_min      = 16777216; //2^24;

                if (m + 5 < n_ir_buffer_length - HAMMING_SIZE && m - 5 > 0)
                {
                    for (i = m - 5; i < m + 5; i++)
                    {
                        if (an_x[i] < n_c_min)
                        {
                            if (un_only_once > 0)
                            {
                                un_only_once = 0;
                            }
                            n_c_min = an_x[i];
                            an_exact_ir_valley_locs[k] = i;
                        }
                    }

                    if (un_only_once == 0)
                    {
                        n_exact_ir_valley_locs_count++;
                    }
                }
            }

            if (n_exact_ir_valley_locs_count < 2)
            {
                pn_spo2        = -999; // do not use SPO2 since signal ratio is out of range
                pch_spo2_valid = false;

                return;
            }

            // 4 pt MA
            for (k = 0; k < n_ir_buffer_length - MA4_SIZE; k++)
            {
                an_x[k] = (an_x[k] + an_x[k + 1] + an_x[k + 2] + an_x[k + 3]) / (int)4;
                an_y[k] = (an_y[k] + an_y[k + 1] + an_y[k + 2] + an_y[k + 3]) / (int)4;
            }

            // Using an_exact_ir_valley_locs , find ir-red DC andir-red AC for SPO2 calibration ratio
            // Finding AC/DC maximum of raw ir * red between two valley locations
            n_ratio_average = 0;
            n_i_ratio_count = 0;

            for (k = 0; k < 5; k++)
            {
                an_ratio[k] = 0;
            }

            for (k = 0; k < n_exact_ir_valley_locs_count; k++)
            {
                if (an_exact_ir_valley_locs[k] > n_ir_buffer_length)
                {
                    pn_spo2        = -999; // do not use SPO2 since valley loc is out of range
                    pch_spo2_valid = false;

                    return;
                }
            }

            // Find max between two valley locations
            // and use ratio betwen AC compoent of Ir & Red and DC compoent of Ir & Red for SPO2
            for (k = 0; k < n_exact_ir_valley_locs_count - 1; k++)
            {
                n_y_dc_max = -16777216;
                n_x_dc_max = -16777216;

                if (an_exact_ir_valley_locs[k + 1] - an_exact_ir_valley_locs[k] > 10)
                {
                    for (i = an_exact_ir_valley_locs[k]; i < an_exact_ir_valley_locs[k + 1]; i++)
                    {
                        if (an_x[i] > n_x_dc_max)
                        {
                            n_x_dc_max = an_x[i]; n_x_dc_max_idx = i;
                        }
                        if (an_y[i] > n_y_dc_max)
                        {
                            n_y_dc_max = an_y[i]; n_y_dc_max_idx = i;
                        }
                    }

                    n_y_ac = (an_y[an_exact_ir_valley_locs[k + 1]] - an_y[an_exact_ir_valley_locs[k]]) * (n_y_dc_max_idx - an_exact_ir_valley_locs[k]); //red
                    n_y_ac = an_y[an_exact_ir_valley_locs[k]] + n_y_ac / (an_exact_ir_valley_locs[k + 1] - an_exact_ir_valley_locs[k]);

                    n_y_ac  = an_y[n_y_dc_max_idx] - n_y_ac;                                                                                             // subracting linear DC compoenents from raw
                    n_x_ac  = (an_x[an_exact_ir_valley_locs[k + 1]] - an_x[an_exact_ir_valley_locs[k]]) * (n_x_dc_max_idx - an_exact_ir_valley_locs[k]); // ir
                    n_x_ac  = an_x[an_exact_ir_valley_locs[k]] + n_x_ac / (an_exact_ir_valley_locs[k + 1] - an_exact_ir_valley_locs[k]);
                    n_x_ac  = an_x[n_y_dc_max_idx] - n_x_ac;                                                                                             // subracting linear DC compoenents from raw
                    n_nume  = (n_y_ac * n_x_dc_max) >> 7;                                                                                                //prepare X100 to preserve floating value
                    n_denom = (n_x_ac * n_y_dc_max) >> 7;

                    if (n_denom > 0 && n_i_ratio_count < 5 && n_nume != 0)
                    {
                        rdata.m_red_ratios[rdata.m_size]  = 10000 * n_y_ac / n_y_dc_max;
                        rdata.m_red_indices[rdata.m_size] = n_y_dc_max_idx;
                        rdata.m_ir_ratios[rdata.m_size]   = 10000 * n_x_ac / n_x_dc_max;
                        rdata.m_ir_indices[rdata.m_size]  = n_x_dc_max_idx;
                        ++rdata.m_size;

                        an_ratio[n_i_ratio_count] = (n_nume * 100) / n_denom; //formular is ( n_y_ac *n_x_dc_max) / ( n_x_ac *n_y_dc_max) ;
                        n_i_ratio_count++;
                    }
                }
            }

            maxim_sort_ascend(an_ratio, n_i_ratio_count);
            n_middle_idx = n_i_ratio_count / 2;

            if (n_middle_idx > 1)
            {
                n_ratio_average = (an_ratio[n_middle_idx - 1] + an_ratio[n_middle_idx]) / 2; // use median
            }
            else
            {
                n_ratio_average = an_ratio[n_middle_idx];
            }

            if (n_ratio_average > 2 && n_ratio_average < 184)
            {
                n_spo2_calc    = uch_spo2_table[n_ratio_average];
                pn_spo2        = n_spo2_calc;
                pch_spo2_valid = true;//  float_SPO2 =  -45.060*n_ratio_average* n_ratio_average/10000 + 30.354 *n_ratio_average/100 + 94.845 ;  // for comparison with table
            }
            else
            {
                pn_spo2        = -999; // do not use SPO2 since signal ratio is out of range
                pch_spo2_valid = false;
            }
        }