Click here to Skip to main content
15,885,278 members
Home / Discussions / C#
   

C#

 
AnswerRe: Checking for range of numbers Pin
Luc Pattyn14-Dec-15 11:07
sitebuilderLuc Pattyn14-Dec-15 11:07 
GeneralRe: Checking for range of numbers Pin
Eytukan16-Dec-15 3:59
Eytukan16-Dec-15 3:59 
QuestionCurrent datagrid row not being selected Pin
Member 1144944710-Dec-15 0:46
Member 1144944710-Dec-15 0:46 
AnswerRe: Current datagrid row not being selected Pin
Richard Deeming10-Dec-15 2:36
mveRichard Deeming10-Dec-15 2:36 
GeneralRe: Current datagrid row not being selected Pin
Member 1144944710-Dec-15 3:49
Member 1144944710-Dec-15 3:49 
GeneralRe: Current datagrid row not being selected Pin
Richard Deeming10-Dec-15 4:18
mveRichard Deeming10-Dec-15 4:18 
GeneralRe: Current datagrid row not being selected Pin
Member 1144944710-Dec-15 5:34
Member 1144944710-Dec-15 5:34 
GeneralRe: Current datagrid row not being selected Pin
Richard Deeming10-Dec-15 5:53
mveRichard Deeming10-Dec-15 5:53 
GeneralRe: Current datagrid row not being selected Pin
Member 1144944714-Dec-15 6:25
Member 1144944714-Dec-15 6:25 
GeneralRe: Current datagrid row not being selected Pin
Member 1144944714-Dec-15 7:08
Member 1144944714-Dec-15 7:08 
GeneralRe: Current datagrid row not being selected Pin
Eddy Vluggen10-Dec-15 4:43
professionalEddy Vluggen10-Dec-15 4:43 
Questionwhy do not SetFocus row for the GridView when using the Thread ? Pin
Member 24584679-Dec-15 21:20
Member 24584679-Dec-15 21:20 
AnswerRe: why do not SetFocus row for the GridView when using the Thread ? Pin
Simon_Whale9-Dec-15 21:58
Simon_Whale9-Dec-15 21:58 
GeneralC# ECG Analysis Algorithm Pin
Member 121973799-Dec-15 18:45
Member 121973799-Dec-15 18:45 
C#
/****************************************************************************
** $Id: arrmain.cpp



//#define	ENABLE_EXTRA_VFIB_DETECTION

ChannelAutoSelInfoType gChannelAutoSelInfo;
ARRANAINFO 	 gArrAnalysisInfo;
QRSANAINFO    gQrsAnaInfo;
//EcgSettingType         gLastEcgSetting;

//MWI value threshold
UINT	MWI_THRESHOLD= 400;

//minimal slope in noise detecting for different gain
INT	MIN_NOISESLOPE= 30;


//define the arr code's name(string), code, and alarm level
ArrInfoType  sArrInfo[MAX_ARR_TYPE + 5 ]={
				{"ASY",0,1},   // asystole
				{"FIB",1,1},   // fibrillation
				{"VTA",2,1},   // ventricular tachycardia
				{"ROT",3,2},   // VPB R on T
				{"RUN",4,2},   // VPB runs of 3 or 4
				{"TPT",5,2},   // VPB triple. important: not use
				{"CPT",6,2},   // VPB couple
				{"VPB",7,2},   // accidental VPB
				{"BGM",8,2},   // VPB bigeminy
				{"TGM",9,2},   // VPB trigeminy
				{"TAC",10,1},  // supraventricular tachycaridia
				{"BRD",11,2},  // supraventricular bradycaridia
				{"MTI",12,2},  // VPB multiform. important: not use
				{"PNC",13,2},  // pace not capture
				{"PNP",14,2},  // pacer not paced
				{"PNF",15,2},  // pacer not function. important: not use
				{"MIS",16,2},  // missed beat
				{"SPB",17,2},  // supraventricular premature beat. important: not use
				{"IPB",18,2},  // insertion premature beat. important: not use
				{"APB",19,2},  // artial premature beat. important: not use

				{"LRN",20,1},  // learning
				{"OFF",21,1},  // off
				{"NOM",22,0},  // normal qrs
				{"PAC",23,0},  // pace rhythmia. important: not used
				{"NOS",24,0},  // noise beat
};


//the following two arrays are used in ArrhythmiaClassify:
static INT sStateTransfer[MAX_STATE+1][MAX_TYPE+1] ={
								 {21,1},  {2,3},   {4,5},  {6,7},  {8,9},   {10,11},
								 {21,1},  {12,13}, {23,1}, {14,3}, {21,16}, {6,7},
								 {21,1},  {17,18}, {19,1}, {6,7},  {10,3},  {0,1},
								 {0,18},  {0,20},  {14,3}, {22,1}, {23,1},  {24,1},
								 {24,1},  {0,1} };

static INT sStateToArrType[MAX_STATE+1][3] ={
				{NML,0,1}, {NON,-1,0}, {NON,-1,0}, {NON,-1,0}, {NON,-1,0}, {NON,-1,0},
				{CPT,2,3}, {NON,-1,0}, {VPB,3,4},  {NON,-1,0}, {BGM,3,4},  {VPB,3,2},
				{RUN,3,4}, {NON,-1,0}, {TGM,4,5},  {VPB,4,3},  {NON,-1,0}, {RUN,4,5},
				{VTA,4,5}, {NON,-1,0}, {NON,-1,0}, {NML,0,1},  {NML,0,1},  {NML,0,1},
				{NML,0,1} };

static INT sContinueAllowed[26] = {
					 1/*asy*/, 1/*fib*/, 1/*vta*/, 0/*RonT*/, 0/*run*/, 0, 0/*cpt*/,
					 0/*vpb*/, 1/*bgm*/, 1/*tgm*/, 1/*tac*/, 1/*brd*/, 0,  0/*pnc*/,
					 0/*pnp*/, 0, 0/*mis*/, 0, 0, 0, 0, 0, 0, 0, 0, 0};



static INT sIsLastArrValid = 0;

#define	 RRINTERVAL_NUM   12

static INT16 sHeartRate;
static INT16 sRrIntervals[RRINTERVAL_NUM];

static INT sHrOfOneSecond;
static INT sInnerHeartRate, sInnerLastHeartRate;
static INT sPreviousMatched;
static INT sOutHR;

typedef struct{ UCHAR type; INT position; INT isSaved;} ARRSELECTED;

static ARRSELECTED sLastArrSelected;

//samples that not found MWI value greater than threshold
static  UINT32  sBlankLength = 0;

// How many seconds past that average R-R interval has not been updated.
static INT sNotUpdatedTime = 0;

/*
 *
 *	FUNCTION:       call arr_anal to do arrhythmia analysis work. if a new
 *									arrhythmia was detected, save 4 seconds wave before and
 *									after the arrhythmia occurred time
 *
 */
void  ArrhythmiaAnalysis( void )
{
   // pace statistic
   PaceStatistic();

   // R wave detection and arrhythmia classify
   arr_anal( );
   if( 0 < gArrAnalysisInfo.ignoreResult )
   {
      gArrAnalysisInfo.arrCode = NML;
      gStates.current = MAX_STATE;
      gRGlobals.counter = 0;
   }
}


/**
 *
 *	1. if auto-select channel is ON, execute channel auto-select function.
 *	2. check whether relearn is needed or not.
 *	3. QRS complex detecting:
 *			1). do ECG filter, differential, integration.
 *			2). check ECG signal: noise or signal too slight
 *			3). check whether process is in learn state, execute proper function.
 *			4). detect QRS complex if not in learn.
 *
 *	4. if Arrhythmia analysis monitor or ST analysis is ON:
 *			1). Ventricular fibrillation or Ventricular tachycardia detecting.
 *			2). Asystole detecting.
 *			3). if Paced is ON, do pace analysis.
 *					else, call Arrhythmia analysis
 *			4). superventricular tachycardia and bradycarida detecting.
 *			5). arrhythmia selection: select the most serious arrhythmia for
 *					saving and display in this second.
 *			6). PVC statistic.
 *
 *	NOTE:
 *		1.		only if ECG lead is connected, 
 *					can this function be called.
 */
void  arr_anal( void )
{
	INT  i, ptr;
    Frame pack;

	//auto select channel
    if ( gEcgStatus.ecg1Exist
        && gEcgStatus.ecg2Exist  )
    {
        i = ChannelAutoSelect(mod( gEcgBuffer.pos - ARR_SAMPLE_RATE , ECG_BUF_LENGTH));
    }
    else
    {
        if ( gEcgStatus.ecg1Exist )
            i = ECG_CHANNEL_1;
        else
            i = ECG_CHANNEL_2;
    }
    if ( i != gArrConfig.arrChannel )
    {
        gArrConfig.arrChannel = i;
        pack.id = ECGCHANNEL;
        pack.data[0] = gArrConfig.arrChannel;
        gProtocol.SendPack( pack );
    }

	gRDetEcgBuf = gEcgBuffer.channel[gArrConfig.arrChannel];

	//check relearn is needed or not
	if( IsRelearnNeeded() == TRUE )
	{
    gArrAnalysisInfo.ignoreResult = 4;

		EcgVarInit();
		gQrsAnaInfo.inLearning = TRUE;

    ResetSTAnalysis( );
    
		sBlankLength = 0;
	}

  // Maybe the gQrsAnaInfo.inLearning has been set to TRUE,
  // but we must clear gLocalArrConfig.arrSelfLearn
  if(TRUE == gArrConfig.arrSelfLearn)
  {
		gQrsAnaInfo.inLearning = TRUE;
        gArrConfig.arrSelfLearn = FALSE;
  }

	//when arrhythmia analysis or ST analysis is ON, set maxTemplate to MAX_TEMPLATE
    gTemplateSet.maxTemplate = MAX_TEMPLATE;

	//gRDetEcgBufPtr is global variable, it's point to the end of ECG data saved.
	gRDetEcgBufPtr = gEcgBuffer.pos;
	ECGProcess( ARR_SAMPLE_RATE );

#ifdef	ENABLE_EXTRA_VFIB_DETECTION
	CopyDiffData( ARR_SAMPLE_RATE );
#endif

	gRGlobals.qrsDetected = 0;

	if( IsNoise( ARR_SAMPLE_RATE ) )
	{
		// ECG analyzing status: current signal is full of noise.
		gArrAnalysisInfo.arrCode = NOS;
		gArrStatus.ecgAnalysisStatus = ECG_ANA_STATUS_NOISE;

		gQrsAnaInfo.delay = 2;
		gRGlobals.validRRi = 0;
		gRGlobals.resetPeakDetect = 1;
		gRGlobals.timePast = 0;
		gStates.current = MAX_STATE;
		gRGlobals.counter = 0;
		if( gRGlobals.initialized == 0 )
			EcgVarInit();
	}
	else
	if( gQrsAnaInfo.delay > 0 )
	{
		gQrsAnaInfo.delay --;

		// ECG analyzing status: Normal
		gArrAnalysisInfo.arrCode = NML;
	}
	else
	{
		if( IsECGLost( ARR_SAMPLE_RATE ) )
		{
            gArrAnalysisInfo.arrCode = ASY;
			gRGlobals.timePast = 0;
			gQrsAnaInfo.isEcgLost = TRUE;
			gRGlobals.validRRi = 0;
			gRGlobals.resetPeakDetect = 1;
			gStates.current = MAX_STATE;
			gRGlobals.counter = 0;
			if( gRGlobals.initialized == 0 )
				EcgVarInit();
		}
		else
		{
			gQrsAnaInfo.isEcgLost = FALSE;

		}

		if( gRGlobals.initialized == 0 )
		{
			if(	TRUE == gQrsAnaInfo.isEcgLost )
			{
				gRGlobals.initLength = 0;
				gRGlobals.initialized = 0;
				gRGlobals.qrsPtr = 0;
			}
			else
			{
				InitDetect( );
				if( gRGlobals.initialized == 1 )
				{
						gArrStatus.ecgAnalysisStatus = ECG_ANA_STATUS_ARR_LRN;
                }
                else
                    // ECG analyzing status: QRS detection learning.
					gArrStatus.ecgAnalysisStatus = ECG_ANA_STATUS_QRS_LEARN;
			}
		}
		else
		{
				if( gQrsAnaInfo.inLearning == TRUE && gTemplateSet.ready == 1 )
				{
					TemplateVarInit();//if relearn, initialize variables

          // ECG analyzing status: arrhythmia analysis learning.
					gArrStatus.ecgAnalysisStatus = ECG_ANA_STATUS_ARR_LRN;
				}

			if( FALSE == gQrsAnaInfo.isEcgLost)
				QRSDetector( ARR_SAMPLE_RATE );
			else
				gRGlobals.qrsDetected = 0;

				i = gRGlobals.qrsDetected - 1;

				while( i >= 0 )
				{
					ptr = mod( gRGlobals.qrsPtr - i, MAX_QRS_NUM);
					if( gQrsComplex[ptr].coef != INVALID_CORRCOEF )
					{
						break;
					}
					i --;
				}
				ArrhythmiaProcess( i+1 );

      // Moved from the next five line, you can see. LLH.2000.11.1
      if( gQrsAnaInfo.inLearning == TRUE && gArrAnalysisInfo.arrCode != ASY )
      {
        gArrAnalysisInfo.arrCode = ARRLRN;

        // ECG analyzing status: arrhythmia analysis learning.
				gArrStatus.ecgAnalysisStatus = ECG_ANA_STATUS_ARR_LRN;
      }
		}

        if (   (  gQrsAnaInfo.isEcgLost
            ||( gRGlobals.timePast > _4SECONDS + _200MS) )
         && ASY != gArrAnalysisInfo.arrCode )
			gArrStatus.ecgAnalysisStatus = ECG_ANA_STATUS_SIGNAL_TOO_SMALL;
	}

	gQrsAnaInfo.heartRate = InnerGetHeartRate();
  gQrsAnaInfo.hrOfCurrSec = sHrOfOneSecond;
}

/*///////////////////////////////////////////////////////////////////////////

	FUNCTION:		call functions to select channel for arrhythmia analysis

	RETURN:			channel selected.

*/////////////////////////////////////////////////////////////////////////////
UCHAR	ChannelAutoSelect( INT16 beginPos )
{
	INT i;

	for( i = 0; i < ECG_CHANNEL_MAX; i++ )
	{
		 __Ddf1(gEcgBuffer.channel[i], gChannelAutoSelInfo.lpDifEcg[i], beginPos,
            ARR_SAMPLE_RATE, ECG_BUF_LENGTH, ECG_BUF_LENGTH);
		 PulNum( gChannelAutoSelInfo.lpDifEcg[i], i );
	}
	ChSelectNum( ECG_CHANNEL_MAX );

	return gChannelAutoSelInfo.byCurChNum;
}


/**
 */
void ChSelectNum( INT16 iChNum )
{
	INT16  i;
	INT16  pulseNum = 0;

	// if there is only one channel, no other choice.
	if( iChNum == 1 )
		return;

	if( gChannelAutoSelInfo.byTimeDelay > 1 )
		gChannelAutoSelInfo.byTimeDelay--;
	else
		gChannelAutoSelInfo.byTimeDelay = 0;

	for( i = 0; i < iChNum; i++ )
	{
		if( i != gChannelAutoSelInfo.byCurChNum )
		{
			if( gChannelAutoSelInfo.byPulNum[gChannelAutoSelInfo.byCurChNum] != 0 )
			{
				if( gChannelAutoSelInfo.byPulNum[i] != 0 )
					pulseNum = 1;
				else
					pulseNum = 0;
			}

			if(   ( ( gChannelAutoSelInfo.byPulNum[i] < gChannelAutoSelInfo.byPulNum[gChannelAutoSelInfo.byCurChNum] ) && ( pulseNum == 1 ) )
				 || (   ( gChannelAutoSelInfo.byPulNum[i] <= gChannelAutoSelInfo.byPulNum[gChannelAutoSelInfo.byCurChNum] )
							&&( gChannelAutoSelInfo.iMaxDiff[i] > gChannelAutoSelInfo.iMaxDiff[gChannelAutoSelInfo.byCurChNum] )	 )
				 || ( gChannelAutoSelInfo.iMaxDiff[gChannelAutoSelInfo.byCurChNum] == 10 ) )
			{
				gChannelAutoSelInfo.byChgCount[i]++;
				if( ( gChannelAutoSelInfo.byChgCount[i] > 10 ) && ( gChannelAutoSelInfo.byTimeDelay == 0 ) )
				{
					gChannelAutoSelInfo.byCurChNum    = i;
					gChannelAutoSelInfo.byChgCount[i] = 0;
				}
			}
			else
				gChannelAutoSelInfo.byChgCount[i] = 0;
		}
	}
	return;
}


/**
 */
void PulNum( INT16 *lpDifEcg, INT16 iChNo)
{
	INT16    i;         // general loop index.
	INT16    iCutLine;  // cut line to get numbers of pulse.

	gChannelAutoSelInfo.iMaxDiff[iChNo] = *lpDifEcg;
	gChannelAutoSelInfo.byPulNum[iChNo] = 0;

	for( i = 1; i < ARR_SAMPLE_RATE; i++)
	{
		if( *( lpDifEcg + i ) > gChannelAutoSelInfo.iMaxDiff[iChNo] )
			gChannelAutoSelInfo.iMaxDiff[iChNo] = *( lpDifEcg + i );
	}

	iCutLine = gChannelAutoSelInfo.iMaxDiff[iChNo] / 3;
	if( iCutLine < 10 )
		iCutLine = 10;

	for( i = 0; i < ARR_SAMPLE_RATE; i++ )
	{
		if( ( *( lpDifEcg + i ) > iCutLine ) && ( gChannelAutoSelInfo.byPassThdFlag[iChNo]== 0 ) )
		{
			gChannelAutoSelInfo.byPulNum[iChNo] ++;
			gChannelAutoSelInfo.byPassThdFlag[iChNo] = 1;
		}
		else
		{
			if( *( lpDifEcg + i )  <= iCutLine )
				gChannelAutoSelInfo.byPassThdFlag[iChNo] = 0;
		}
	}
	if( gChannelAutoSelInfo.iMaxDiff[iChNo] < 10 )
		gChannelAutoSelInfo.iMaxDiff[iChNo] = 10;
	return;
}

/*///////////////////////////////////////////////////////////////////////////

		FUNCTION:		check whether relearn is needed:
								if Gain or Lead or channel changed, relearn is needed.

		RETURN:			TRUE(1): 	relearn is needed
								FALSE(0): relearn is not needed

*////////////////////////////////////////////////////////////////////////////
INT	IsRelearnNeeded( void )
{
	return FALSE;
}

/*///////////////////////////////////////////////////////////////////////////

		FUNCTION:		call functions to process arrhythmia analysis.
								when call this function, G_ECG.arrmoni or G_ECG.stch1moni
								must be ON.

		INPUT:		  qrsDetected: qrs number that will be analysis
								sample: 		 number of samples that will be analysis, it has
														 nothing to do with qrsDetected, that is, the qrs
														 detected may not in the sample segment.

		OUTPUT:     the arrhythmia's information was saved to gArrAnalysisInfo

*////////////////////////////////////////////////////////////////////////////
void  ArrhythmiaProcess( INT qrsProcessed )
{
	UCHAR  arrCode;
	INT		i, ptr, position, isArrSelectedValid;
	INT		tmp, firstSaved;

	ARRSELECTED arrSelected;

	//qrs classify to normal or PVC, even in PACE ON mode, this process
	//is needed.
	gRGlobals.qrsClassified = 0;
	for( i = qrsProcessed - 1; i >= 0; i -- )
	{
		ptr = mod( gRGlobals.qrsPtr - i, MAX_QRS_NUM);

		tmp = MorphClassify(
                ptr,
                gSysSetting.pace || (0 < gArrAnalysisInfo.ignoreResult)
             );

		if( tmp > 0 )
		{
			gRGlobals.qrsClassified += tmp;
			gRGlobals.classifiedQrsPtr = ptr;
		}
	}

	// if pace monitor is ON, or 0 < gArrAnalysisInfo.ignoreResult,
	// classify all QRS as normal beats
	if( gSysSetting.pace || (0 < gArrAnalysisInfo.ignoreResult) )
	{
		for( i = gRGlobals.qrsClassified - 1; i >= 0; i -- )
		{
			ptr = mod( gRGlobals.classifiedQrsPtr - i, MAX_QRS_NUM);
			gQrsComplex[ptr].type = QT_DOMINANT;
		}
	}

	//arrhythmia classify
  //this variable has been initialized out of this function

	arrSelected.type = gArrAnalysisInfo.lastArrCode;

	arrSelected.isSaved = 1;
	isArrSelectedValid = 0;
	firstSaved = 0;

	for( i = gRGlobals.qrsClassified - 1; i >= 0; i -- )
	{
		ptr = mod( gRGlobals.classifiedQrsPtr - i, MAX_QRS_NUM);

		//pvc number increase
		if( gQrsComplex[ptr].type == QT_ABNORMAL )
			gArrAnalysisInfo.pvcOfCurrentSecond ++;

		//important: type must be normal(0) or PVC(1).
		//classified information saved to gArrAnalysisInfo. gStates is a global variable
		arrCode = ArrhythmiaClassify( &gStates, ptr, &position );

		//if it's a valid type, normal or abnormal
		if( arrCode != NON && position >= 0 )
		{
			//if it's not a new type, continue
			if( gArrAnalysisInfo.lastArrCode == arrCode && sContinueAllowed[arrCode] )
			{
				if( sIsLastArrValid )
				{
					arrSelected.type = sLastArrSelected.type ;
					arrSelected.position = sLastArrSelected.position ;
					arrSelected.isSaved = 0;
					firstSaved = 1;
					sIsLastArrValid = 0;
					isArrSelectedValid = 1;
				}
				else
				if( !firstSaved )
				{
					arrSelected.type = arrCode ;
					arrSelected.position = gQrsComplex[position].peakPos;
					isArrSelectedValid = 1;
				}
				continue;
			}
			else
				gArrAnalysisInfo.lastArrCode = arrCode;

			//select the most serious arrhythmia for warning.
			if( sArrInfo[arrCode].warningLevel >= sArrInfo[arrSelected.type].warningLevel )
			{
				arrSelected.type = arrCode;
				arrSelected.position = gQrsComplex[position].peakPos;
				arrSelected.isSaved = 0;
				firstSaved = 1;
				isArrSelectedValid = 1;
			}
			else
			if( arrCode != NML )
			{
				sLastArrSelected.type = arrCode;
				sLastArrSelected.position = gQrsComplex[position].peakPos;
				sIsLastArrValid = 1;
			}
		}
	}

	//note: even in relearning process, VFIB and ASYSTOLE are detected.
	// if VFIB or ASYSTOLE is detected, does the following process need?
		arrCode = NON;
		//detecting asystole
		if( ASYSTOLEDetect() == 1 )
		{
			arrCode = ASY;
		}
		else
		if( (gArrAnalysisInfo.arrCode == ASY) && (arrSelected.type != ASY) )
		{
			arrSelected.type = NML;
			isArrSelectedValid = 1;
		}

#ifdef	ENABLE_EXTRA_VFIB_DETECTION
		//detecting ventricular fibrillation
		if( VFIBDetect( ) == 1 )
		{
			arrCode = FIB;//because VTA and VFIB are classified as one type.
		}
#endif

#ifdef	ENABLE_EXTRA_VFIB_DETECTION
		if( arrCode == ASY || arrCode == FIB )
#else
		if( arrCode == ASY )
#endif
		{
			arrSelected.type = arrCode;

			//position four seconds before current time as the time of occurring.
			arrSelected.position = mod( gEcgBuffer.pos - 4 * ARR_SAMPLE_RATE, ECG_BUF_LENGTH);
			isArrSelectedValid = 1;
			if( gArrAnalysisInfo.arrCode != arrCode )
			{
				arrSelected.isSaved = 0;
			}
		}
		if( isArrSelectedValid )
		{
			gArrAnalysisInfo.arrCode = arrSelected.type;
			gArrAnalysisInfo.position = arrSelected.position;
			if( arrSelected.isSaved == 0 )
				gArrAnalysisInfo.isSaved = 0;
		}

	//check whether current arrhythmia is valid or not.
	if( gArrAnalysisInfo.arrCode <= 19 && gArrAnalysisInfo.isSaved == 0 )
	{
		 i = mod( gArrAnalysisInfo.position - ARR_RECORD_SECONDS * ARR_SAMPLE_RATE, ECG_BUF_LENGTH);
		 i = mod( gEcgBuffer.pos - i , ECG_BUF_LENGTH );
		 if( i < ARR_RECORD_SECONDS * ARR_SAMPLE_RATE )
		 {
			 gArrAnalysisInfo.arrCode = NML;
		 }
	}
}

/*////////////////////////////////////////////////////////////////////////////

	FUNCTION:		check whether the ECG signal is weak or not.
							it checks the Moving-Window Intergration signal, if in a
							continuous time, and the signal is less than a predefined
							threshold, the signal is too weak.

	INPUT:		  sample: number of samples that will be analysis

	RETURN:     TRUE:	 signal is too weak
							FALSE: signal is not too weak

	GLOBAL VARIABLES:	 gMWIBuf: moving window intergration data buffer
										 gRDetEcgBufPtr: moving window intergration data buffer pointer
*/////////////////////////////////////////////////////////////////////////////
CHAR  IsECGLost( INT sample )
{
	INT i, ptr;

	ptr = mod(gRDetEcgBufPtr - sample, ECG_BUF_LENGTH);
	for( i = 0; i < sample; i ++)
	{
		if( MWI_THRESHOLD >= *(gMWIBuf + ptr) )
		{
			sBlankLength ++;  //value too low, sBlankLength increase
		}
		else
		{
			sBlankLength = 0;//clear flag, reset sBlankLength
		}

		ptr = mod(ptr + 1, ECG_BUF_LENGTH);
	}

	//detect ECGLOST signal
	if( ECGLOST_THRESHOLD < sBlankLength )
		return TRUE;
	else
		return FALSE;
}

/*///////////////////////////////////////////////////////////////////////////

*////////////////////////////////////////////////////////////////////////////
UCHAR  ArrhythmiaClassify( STATES* states, INT qrsPtr, INT* eventQrsIndex )
{
	INT arrCode, qrsIndex;
	INT i, tmp, qrsNumber, ptr0, ptr1;
	UCHAR	type;

	states->last = states->current;

	if( gQrsComplex[qrsPtr].type == QT_ABNORMAL )/* PVC*/
		type = 1;
	else/* gQrsComplex[qrsPtr].type == QT_DOMINANT, normal */
		type = 0;

	//assert that states->current and type are in predefined range
	if( states->current <= MAX_STATE && states->current >= 0 )
	{
		states->current = sStateTransfer[states->current][type];
	}
	else//error occurred, reset state.
	{
		states->current = MAX_STATE;
	}

	//save position and RR interval
	states->ptr = mod( states->ptr + 1, QRS_SAVED );
	states->rri[states->ptr] = gQrsComplex[qrsPtr].rri;
	states->matched[states->ptr] = gQrsComplex[qrsPtr].matched;
	states->qrsPtrs[states->ptr] = qrsPtr;

	//get arrhythmia type code and qrsIndex
	arrCode  = sStateToArrType[states->current][0];
	qrsIndex = sStateToArrType[states->current][1];

	//classify for some unclassified types
	switch( arrCode )
	{
		case VPB:

			//heart rate is less than 100. important: heart rate here
			//is not the same as heart rate in system display
			if( states->averageRRi > _600MS )
			{
				i = mod( states->ptr - qrsIndex , QRS_SAVED );

				//if PVC is premature more than 66% and has a rest interval
				//that greater than 1.25 average interval, the VPB is RonT.
				//subtract 5 for error correction
				if(  ( (states->rri[i] * 10) <= (states->averageRRi * 5 ) )
					 &&(states->rri[mod(i+1,QRS_SAVED)] >= states->averageRRi* 5 / 4 ) )
				{
					arrCode = ROT;
				}
			}
			break;

		case NML:
			if( states->current == 24 )
			{
				arrCode = TachBradyDetect( states );
			}
			break;

		default:
			break;
	}

	//if it has more than three continued normal QRS found, do MISSED beat analysis
	if( states->current >= 22 )
	{
		if( states->last == 8 )
			qrsNumber = 2;
		else
			qrsNumber = 1;

		ptr0 = mod( states->ptr - qrsNumber , QRS_SAVED );
		ptr1 = mod( ptr0 + 1, QRS_SAVED );
		for( i = 0; i < qrsNumber ; i ++ )
		{
			//missed beat: 1) heart rate < 100
      //                and current R-R interval > 1.75 * average R-R interval
			//						    and next R-R interval < 1.5 * average R-R interval
			//        or   2) heart rate >= 100 and current R-R interval > 1000mseconds

			if(  (states->rri[ptr0] < _4SECONDS )
				 &&(states->rri[ptr1] < states->averageRRi * 3 / 2 )
				 &&(    (states->averageRRi > _600MS && states->rri[ptr0] > states->averageRRi * 7 / 4 )
						 || (states->averageRRi < _600MS && states->rri[ptr0] > ( _1000MS - 4 ) )) )// subtract four for error correction
			{
				arrCode = MIS;
				ptr0 = mod( ptr0 - 1, QRS_SAVED ); //save the last QRS complex as the wave
				*eventQrsIndex = states->qrsPtrs[ptr0];

				//if pace monitor is ON, do pace analysis
				if( TRUE == gSysSetting.pace )
				{
					tmp = mod( gEcgBuffer.pos - gQrsComplex[*eventQrsIndex].peakPos, ECG_BUF_LENGTH);
					arrCode = PaceAnalysis( tmp, states->averageRRi );
				}
				break;
			}
			ptr0 = mod( ptr0 + 1, QRS_SAVED );
		}
	}

	qrsNumber = sStateToArrType[states->current][2];
	tmp = mod( states->ptr - qrsIndex, QRS_SAVED );

	//if current arrhythmia type is not missed beat.
	if( arrCode != MIS && arrCode != PNC && arrCode != PNP )
	{
		//if a new arrhythmia or normal state was found, save the QRS complex
		//position to *position, it will be use for saving ecg wave.
		if( qrsNumber > 0 )
		{
			if( arrCode == TAC || arrCode == BRD )
				*eventQrsIndex = states->qrsPtrs[mod(tmp - 5, QRS_SAVED)];
			else
				*eventQrsIndex = states->qrsPtrs[tmp];
		}
		else
			*eventQrsIndex = -1;
	}

	//calculate average RR interval
	for( i = 0; i < qrsNumber; i++)
	{
		if( states->matched[tmp] && states->matched[mod( tmp - 1, QRS_SAVED)] )
			states->averageRRi = UpdateAveRRi( states->rri[tmp], states->averageRRi );
			//states->averageRRi = ( states->rri[tmp] + states->averageRRi * 7 ) / 8;
		tmp = mod( tmp + 1, QRS_SAVED);
	}

	return arrCode;
}

/*///////////////////////////////////////////////////////////////////////////

	FUNCTION:	do superventricular tachycardia and bradycardia detection.
						tachycardia: continuous 5 RR intervals less than 500 milliseconds.
						bradycardia: continuous 5 RR intervals greater than 1.5 seconds.

	INPUT:	  global variable (gStates) is used.

	RETURN:	  normal: NML; tachycardia:TAC; bradycardia: BRD

*////////////////////////////////////////////////////////////////////////////
UCHAR TachBradyDetect( STATES* states )
{
	INT 		i, tmp, tmp1, ptr;
	UCHAR  arrCode;

	// added by two for error calibration
	static	INT	TACHY_LIMIT[3] = { ARR_SAMPLE_RATE/2+2, ARR_SAMPLE_RATE*3/8+2, ARR_SAMPLE_RATE*3/8+2 };//500ms and 375ms

	//get number of RRi that less than 500ms and greater 1500ms in
	//latest five RR intervals
	tmp = tmp1 = 0;
	for(i = 0 ; i < 5; i ++)
	{
		ptr = mod( states->ptr - i , QRS_SAVED );
		if( states->rri[ ptr ] <= TACHY_LIMIT[gSysSetting.patientType]  )
		{
			tmp ++;
		}
		else
		if( states->rri[ ptr ] >= (_1500MS - 2) )// subtract by two for error calibration
		{
			tmp1 ++;
		}
	}

	//if number of RRi is equal to or geater than 5, it's TAC or BRD.
	arrCode = NML;
	if( tmp >= 5 )
		arrCode = TAC;
	else
	if( tmp1 >= 5 )
		arrCode = BRD;

	return arrCode;
}

/*///////////////////////////////////////////////////////////////////////////

	FUNCTION:		update average R-R interval with current R-R interval.
							the function updates average R-R interval only when:

							1).(| currentRRi - averageRRi | < averageRRi / 5)
								 and (currentRRi > 500 ms) and (currentRRi < 1500ms)
							or
							2).averageRRi has not been updated for five seconds.

	INPUT:			currentRRi:	current R-R interval
							averageRRi:	average R-R interval

	RETURN:     updated average R-R interval if updated, else the original
							average R-R interval.

*////////////////////////////////////////////////////////////////////////////
INT	 UpdateAveRRi( INT currentRRi, INT averageRRi )
{
	if(  (  (abs( currentRRi - averageRRi ) < averageRRi / 5)
				&&(currentRRi > ARR_SAMPLE_RATE/2)
				&&(currentRRi < ARR_SAMPLE_RATE*3/2) )
			||( sNotUpdatedTime >= 3 ) )
	{
		averageRRi = (averageRRi * 7 + currentRRi) / 8;
		sNotUpdatedTime = 0;
	}
	else
	{
		sNotUpdatedTime ++;
	}
	return averageRRi;
}


/*///////////////////////////////////////////////////////////////////////////

	FUNCTION:		detect asystole

	INPUT:			sample:	sample number for detcting this time

	RETURN:     0: no asystole is detected
							1: asystole is detected

*////////////////////////////////////////////////////////////////////////////
UCHAR	ASYSTOLEDetect( void )
{
    return gQrsAnaInfo.isEcgLost || (gRGlobals.timePast > _4SECONDS + _200MS) ;
}


/*///////////////////////////////////////////////////////////////////////////

 FUNCTIONS:        DdfEcg1
 DESCRIPTIONS:     caculate 4 points deference value of  ECG
 ALGORITHM:        运用四点差分法,求ECG一阶差分值
										Y(n) = [x(n-2) - x(n-1) + 2*x(n-3) - 2*x(n)]/2
 INPUT:
		CHAR far      *Ecg_buf          //ECG buffer
		INT           Proc_begin        //beginning of processing
		INT           Proc_len          //length of processing

 RETURN:            -1              //failure
										 1              //success

*////////////////////////////////////////////////////////////////////////////
INT16 __Ddf1( UCHAR  *Ecg_buf, INT16* dif_buf,    UINT Proc_begin,
							UINT  Proc_len, UINT Ecgbuf_len, UINT Ddfbuf_len )
{
	INT16  i, n0;
	INT16  ecgPos;
	INT16  x0, x1, x2, x3;
	INT16  fib;

	if( (Proc_len > Ecgbuf_len) || (Proc_len > Ddfbuf_len) )
	{
		return(-1);
	}

	n0 = 0;

	//define the start position of ecg,ddf
	ecgPos = (Proc_begin - 4 + Ecgbuf_len)%Ecgbuf_len;

	//4 points deference formula
	for( i = n0; i < (int)Proc_len; i++ )
	{
		x0 = ecgPos;

		x1 = ( ecgPos + 1 ) ;
		if( x1 >= (int)Ecgbuf_len ) x1 = x1 - Ecgbuf_len;

		x2 = ( ecgPos + 3 );
		if( x2 >= (int)Ecgbuf_len ) x2 = x2 - Ecgbuf_len;

		x3 = ( ecgPos + 4 );
		if( x3 >= (int)Ecgbuf_len ) x3 = x3 - Ecgbuf_len;

		fib = ( *(Ecg_buf+x2) - *(Ecg_buf+x1)
					 + 2 * (*(Ecg_buf+x3) - *(Ecg_buf+x0) ) ) >> 1;

		if( Proc_len <= ARR_SAMPLE_RATE )
		{
			*( dif_buf + i ) = fib;
		}

		ecgPos = ( ecgPos + 1 );
		if(ecgPos>=(int)Ecgbuf_len) ecgPos = 0 ;
	}

	return 1;
}

/*///////////////////////////////////////////////////////////////////////////

	FUNCTION:		calculate number of PVC in a minute

	INPUT:			list:			the array for saving PVC of a second
							listSize: the length of the list, in fact, it will always be 60
							pvcNumber:PVC in the latest second

	RETURN:     number of PVC in the latest minute

*////////////////////////////////////////////////////////////////////////////
INT16  PVCStatistic( UCHAR* list, INT listSize, INT pvcNumber )
{
	INT16 i, total;

	total = pvcNumber;

	for( i = listSize - 1; i > 0 ; i -- )
	{
		*( list + i ) = *( list + i - 1);
		total += *( list + i );
	}

	*( list ) = pvcNumber;

	return total;
}

/*///////////////////////////////////////////////////////////////////////////

	FUNCTION:		calculate heart rate

	INPUT:      rrInterval: current R-R interval

	OUTPUT:			current heart rate

*////////////////////////////////////////////////////////////////////////////
#define  _1200MS          (ARR_SAMPLE_RATE+ARR_SAMPLE_RATE/5)
INT16	 CalculateHeartRate( INT16 rrInterval )
{
	INT  i, max, min, total, rri, validRRI;

	//update RR intervals
	for( i = RRINTERVAL_NUM	- 1; i > 0; i --)
	{
		sRrIntervals[i] = sRrIntervals[i-1];
	}
	sRrIntervals[0] = rrInterval;

  //if the latest three RR intervals are all greater than 1200MS,
  //calculate heart rate as the average value of latest four RR intervals.
  if(    (_1200MS <= sRrIntervals[0])
      && (_1200MS <= sRrIntervals[1])
      && (_1200MS <= sRrIntervals[2]) )
  {
    rri = 4;
  }
  else
    rri = RRINTERVAL_NUM;

  // calculate how many RRi is greater than 0
  validRRI = 0;
	for( i = 0; i < RRINTERVAL_NUM; i ++)
  {
    if ( 0 < sRrIntervals[i] )
      validRRI ++;
  }

  // return the last HeartRate if there is no more 5 valid RRIs
  if ( 5 > validRRI )
    return sHeartRate;

	// search maximal and minimal RR interval,
	// calculate summary of RR intervals
  total = 0;
  max   = 0;
  min   = 5120;

  if (RRINTERVAL_NUM == rri)
     rri = validRRI;

	for( i = 0; i < rri; i ++)
	{
		if( sRrIntervals[i] > max )
			max = sRrIntervals[i];

		if( sRrIntervals[i] < min )
			min = sRrIntervals[i];

		total += sRrIntervals[i];
	}

  //subtract min and max from total if rri is greater than 4
  if( rri > 4 )
  {
    total -= min + max;
    rri -= 2;
  }

	if( total != 0 )
	{
		sHeartRate = 600 * (INT32)rri * ARR_SAMPLE_RATE / total;
		sHeartRate = ( sHeartRate + 4 ) / 10;//error calibrate
	} else {
		sHeartRate = -100;
	}

	return sHeartRate;
}


/*///////////////////////////////////////////////////////////////////////////

	FUNCTION:		get heart rate after detecting QRS complex

	GLOBAL VARIABLES: gQrsAnaInfo.isEcgLost, gRGlobals.qrsDetected,
                    gRGlobals.initialized, gQrsComplex

	RETURN:			if process is in relearn of ECG signal is lost, HR = -100,
							else, return the true heart rate

*////////////////////////////////////////////////////////////////////////////
INT16  InnerGetHeartRate( void )
{
	INT i, ptr;
  INT sum_for_b2b_hr;
  BOOL tmpCondition = FALSE;

    tmpCondition = ( gRGlobals.timePast > _4SECONDS + _200MS );

	if(		 TRUE == gQrsAnaInfo.isEcgLost
	        || tmpCondition
			|| gRGlobals.initialized == 0
			|| gQrsAnaInfo.delay > 0 )
	{
		sInnerHeartRate = -100;
    sHrOfOneSecond  = -100;
	}
	else
	{
    sum_for_b2b_hr = 0;
		for( i = gRGlobals.qrsDetected - 1 ; i >= 0; i -- )
		{
			ptr = mod(gRGlobals.qrsPtr - i, MAX_QRS_NUM);

			if( sPreviousMatched && gQrsComplex[ptr].matched && gRGlobals.validRRi )
      {
				sInnerHeartRate = CalculateHeartRate( gQrsComplex[ptr].rri);
        sum_for_b2b_hr += gQrsComplex[ptr].rri;
      }
			else
				gRGlobals.validRRi = 1;

			sPreviousMatched = gQrsComplex[ptr].matched;
		}
    if (0 < sum_for_b2b_hr)
    {
       sHrOfOneSecond = 600 * (INT32)gRGlobals.qrsDetected * ARR_SAMPLE_RATE / sum_for_b2b_hr;
       sHrOfOneSecond = ( sHrOfOneSecond + 4 ) / 10;//error calibrate
    }
	}
	if( sOutHR == 1 || sInnerLastHeartRate == -100 )
	{
		sInnerLastHeartRate = sInnerHeartRate;
		sOutHR = 0;
	}
	else
	{
		sOutHR = 1;
	}
	return sInnerLastHeartRate;
}


#ifdef	ENABLE_EXTRA_VFIB_DETECTION
///////////////////////////////////////////////////////////////////////////
void CopyDiffData( INT sample )
{
	INT i, ptr, tmp;

	ptr = mod( gRDetEcgBufPtr - sample , ECG_BUF_LENGTH );

	//save differential data to gVFibInfo.noiseDetBuf for VFIB detection.
	for( i = 0; i < sample && i < ARR_SAMPLE_RATE; i ++)
	{
		tmp = *(gDiffBuf + ptr);
		*( gVFibInfo.data + i ) = tmp;

		if( tmp > 0 )
			*( gVFibInfo.noiseDetBuf + i ) = tmp;
		else
			*( gVFibInfo.noiseDetBuf + i ) = 0;

		ptr = mod( ptr + 1, ECG_BUF_LENGTH);
	}
}
#endif
/*//////////////////////////////////////////////////////////////////////////

*////////////////////////////////////////////////////////////////////////////
void ResetFlag( void )
{
	gQrsAnaInfo.inLearning = FALSE;
	gArrStatus.ecgAnalysisStatus = ECG_ANA_STATUS_NORMAL;
	gArrAnalysisInfo.arrCode = NML;
	gArrAnalysisInfo.lastArrCode = NML;
}

/*///////////////////////////////////////////////////////////////////////////

	FUNCTION:		detect noise in ECG signal

	INPUT:      sample:	data number to be propcessed

	OUTPUT:			0:	no noise detected, 1: noise detected.

*////////////////////////////////////////////////////////////////////////////
#define	 MINIMAL_CROSSPOINT  20
INT	IsNoise( INT sample )
{
	INT  isNoise,crossPoint, slope;

	isNoise = IsSaturation( sample );
    isNoise |= ( PacePulseOfTheSecond() > PACE_NUM_ALLOWED );
	
	if( !isNoise )
	{
		slope = gRGlobals.maxSlope / 4;
		if( slope < MIN_NOISESLOPE )
			slope = MIN_NOISESLOPE;

		crossPoint = CrossPointNum(gLpDiffBuf, mod( gRDetEcgBufPtr - sample, ECG_BUF_LENGTH),
                               sample/2, slope );
		if( crossPoint < MINIMAL_CROSSPOINT )
			crossPoint = CrossPointNum(gLpDiffBuf, mod( gRDetEcgBufPtr - sample/2, ECG_BUF_LENGTH),
                                 sample/2, slope );
		isNoise = ( crossPoint >= MINIMAL_CROSSPOINT );

	}
	/*
	if( !isNoise )
	{
		isNoise = IsBaselineWander( sample );
	}
	*/
	return isNoise;
}

/*///////////////////////////////////////////////////////////////////////////

	FUNCTION:		detect signal saturation in ECG signal

	INPUT:      sample:	data number to be propcessed

	OUTPUT:			0:	no saturation detected, 1: saturation detected.

*////////////////////////////////////////////////////////////////////////////
#define		SATURATION_HI			0xfd
#define		SATURATION_LO			0x2
#define	  FASTCHANGE_TIME 	5
#define	  FASTCHANGING 			5
INT	IsSaturation( INT sample )
{
	INT 		i, ptr, delay, result;
	INT 		numberFF, number00, fastChange;
	UCHAR   data;

	numberFF = number00 = fastChange = 0;
	delay = ARR_SAMPLE_RATE;

	ptr = mod( gRDetEcgBufPtr - sample, ECG_BUF_LENGTH);
	for( i = 0; i < sample; i++ )
	{
		data = *( gRDetEcgBuf + ptr );
		ptr += 1;
		if( ptr >= ECG_BUF_LENGTH )
			ptr = 0;

		if( data > SATURATION_HI )
		{
			numberFF ++;
			if( delay < FASTCHANGING )
			{
				fastChange ++;
			}
		}
		else
		if( data < SATURATION_LO )
		{
			number00 ++;
			delay = 0;
		}
		delay ++;
	}

	//if over 200 ms samples are greater than SATURATIONHI or less than
	//SATURATIONLO or times of fast changing from 0x1 to 0xfe is greater
	//than 5, the ECG signal is saturation
	if(		 number00 > _200MS
			|| numberFF > _200MS
			|| fastChange > FASTCHANGE_TIME  )
	{
		result = 1;
	}
	else
	{
		result = 0;
	}
	return result;
}

////////////////////////////////////////////////////////////////////////////
void InitStatics( void )
{
  INT i;

	sIsLastArrValid = 0;
  
  sHeartRate = -100;

  for (i = 0; i < RRINTERVAL_NUM; i ++)
    sRrIntervals[i] = 0;

  sHrOfOneSecond      = -100;
  sInnerHeartRate     = -100;
  sInnerLastHeartRate = -100;
  sPreviousMatched    = 1;
  sOutHR = 0;

  sLastArrSelected.type = NML;
  sLastArrSelected.position = 0;
  sLastArrSelected.isSaved  = 1;

  sBlankLength = 0;
  
  sNotUpdatedTime = 0;

  InitTompkinsStatic( );
}


INT PacePulseOfTheSecond( void )
{
	INT pace_count, i;
	
	pace_count = 0;
	
	//select valid pace spike position
	for( i = 0; i < MAX_PACE_NUM; i ++ )
	{
		if( gPaceEcg1Cnt[i] <= ARR_SAMPLE_RATE - 1 )
			pace_count ++;
	}
	return pace_count;
}

GeneralRe: C# ECG Analysis Algorithm Pin
Mycroft Holmes9-Dec-15 19:00
professionalMycroft Holmes9-Dec-15 19:00 
GeneralRe: C# ECG Analysis Algorithm Pin
Eddy Vluggen10-Dec-15 4:46
professionalEddy Vluggen10-Dec-15 4:46 
GeneralRe: C# ECG Analysis Algorithm Pin
Gerry Schmitz11-Dec-15 7:14
mveGerry Schmitz11-Dec-15 7:14 
GeneralHow I Can Make My Program With Multiple Language For Forms In C# ? Pin
eng_aza9-Dec-15 15:07
eng_aza9-Dec-15 15:07 
GeneralRe: How I Can Make My Program With Multiple Language For Forms In C# ? Pin
Mycroft Holmes9-Dec-15 19:03
professionalMycroft Holmes9-Dec-15 19:03 
GeneralRe: How I Can Make My Program With Multiple Language For Forms In C# ? Pin
Gerry Schmitz11-Dec-15 7:15
mveGerry Schmitz11-Dec-15 7:15 
QuestionSolid color image vs Panel with BackColor Pin
David Sattler9-Dec-15 5:34
David Sattler9-Dec-15 5:34 
AnswerRe: Solid color image vs Panel with BackColor Pin
OriginalGriff9-Dec-15 5:48
mveOriginalGriff9-Dec-15 5:48 
GeneralRe: Solid color image vs Panel with BackColor Pin
David Sattler9-Dec-15 5:52
David Sattler9-Dec-15 5:52 
Questionhow to fill the cells of a DataGridView directly Pin
Member 114494479-Dec-15 1:49
Member 114494479-Dec-15 1:49 
AnswerRe: how to fill the cells of a DataGridView directly Pin
Richard MacCutchan9-Dec-15 2:37
mveRichard MacCutchan9-Dec-15 2:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.