/*******************************************************************\ Fichier : EIMBase.cpp Date : 29/04/98 Version : 1.024 Auteur : P-J Touboul Description : Fonctions de détection et mesures de parois |*******************************************************************| Bugs: Il serait plus propre dans la fonction EIM, d'appliquer le masque sur le vecteur (procédure Mask mise en commentaire). Notes: |*******************************************************************| Historique : 29/04/98 1.024 : Modification du sens de la mesure en fonction de la position relative des points du segment utilisateur Mémorisation de la distance de la mesure 02/04/98 1.022 : Utilisation d'une valeur minimale permettant de considérer qu'il y a une variation de densité (STEP) Bug quand il y a avait UN SEUL lissage effectué 11/03/98 1.020 : Nouvel algorithme possible pour la mesure de diamètre (symétrie) 31/01/98 1.010 : Mesures 'sub-pixels' permettant de gagner jusqu'à un pixel en précision (1/2 pixel avant + 1/2 pixel après). Les points au milieu des pentes ont donc defs coordonnées décimales Correction d'un problème sur la détection de plateaux 22/01/98 1.001 : Correction sur le calcul des points médians Ajout de la position des points médians 17/03/97 1.000 : Première version \*******************************************************************/ /*----------------------------------------------------------\ Includes \----------------------------------------------------------*/ #include "EIMBase.h" #include "Ressource.h" #include "MeanEstimate.h" #include "CEIMResult.h" #include "EIMResult.h" #include "../Object/point.h" #include "../NR/ToolsMath.h" #include "../Container/ExtendedImage.h" #include #include "stdlib.h" #include "stdio.h" #include "string.h" //#define TEST_FOR_FANTOME /*----------------------------------------------------------\ Defines \----------------------------------------------------------*/ #define STEP 0 /*----------------------------------------------------------\ Variables locales \----------------------------------------------------------*/ //static char Version[] = "$VER: " __FILE__ " 1.024 (" __DATE__ ")"; /*----------------------------------------------------------\ | Delta | \----------------------------------------------------------*/ char CEIMBase::Delta (unsigned char b1, unsigned char b2) { int i; // En utilisant STEP, on considère par exemple que 15/16/17 représentent // la même valeur et que ce n'est donc pas une croissance i = b1 - b2; if (i > STEP) return ( 1); else if (i < -STEP) return (-1); else return ( 0); } // end of Delta /*----------------------------------------------------------\ | CEIM::CEIM | |-----------------------------------------------------------| | DESCRIPTION : | | Constructeur de la classe | |-----------------------------------------------------------| | PARAMETRES : | | scale : échelle à utiliser pour les mesures | \----------------------------------------------------------*/ CEIMBase::CEIMBase (void) { //debugOutput = NULL; m_pMeasures = NULL; m_bDelta1 = 30; // variation minimale pour la première pente m_bDelta2 = 10; // variation minimale entre le sommet des deux pentes // A voir Release m_bSeuil1 = 140; m_algoDiameter = algoProfile; // algorithme par défaut m_arVariance.clear(); m_arVarianceAA.clear(); m_arVarianceII.clear(); m_arVarianceINT.clear(); Release(); // après m_pMeasures = NULL; m_coeff_a = -1.0; m_coeff_b = -1.0; m_coeff_c = -1.0; m_bDisplay = false; m_parallelismeDiametre = false; // Expérimental m_image = NULL; m_versionTomtecAout08 = true; } // fin de CEIM CEIMBase::~CEIMBase(void) { Release(); } // fin de ~CEIM void CEIMBase::SetEimAssisted(bool valide) { m_Assist = valide; } bool CEIMBase::GetEimAssisted() { return m_Assist; } // Calcul automatique de l'EIM // Ancienne version : n'est plus utilisée int CEIMBase::CalculEimAutomatique(int mode, bool bActionDiametre, Point *ptClick0, Point *ptClick1, Point *ptLastClick, unsigned char *bClicks, ExtendedImage *img,bool *fBackToNone) { m_image = img; return CalculEimAutomatique( mode, bActionDiametre, ptClick0, ptClick1, ptLastClick, bClicks, fBackToNone ); } int CEIMBase::CalculEimAutomatique(int mode, bool bActionDiametre, Point *ptClick0, Point *ptClick1, Point *ptLastClick, unsigned char *bClicks, bool *fBackToNone) { int retour = 0; if (m_Assist) { if (mode == 0) { Measure(*ptClick0, *ptLastClick, bActionDiametre); } double dDirection = Direction(); if (dDirection == 0) { //CMessage::Warn(MyLoadString(IDS_EIM_FAILED)); *fBackToNone = false; return 0; } else { Point CorrectPoint = *ptLastClick; CorrectPoint.y = ptClick0->y + ((long) (dDirection*(CorrectPoint.x - ptClick0->x))); *ptClick1 = CorrectPoint; *bClicks = 1; retour = 1; // Pour lancer un Invalidate if (mode == 1) { Measure(*ptClick0, CorrectPoint, bActionDiametre); } *ptLastClick = CorrectPoint; } } return retour; } bool CEIMBase::PointInBuffer(const Point& pt) { assert( m_image ); int dx = m_image->GetWidth(); int dy = m_image->GetHeight(); return ( pt.x >= 0 && pt.y >= 0 && pt.x < dx && pt.y < dy ); } unsigned char CEIMBase::GetIntensity(const Point& pt) { assert( m_image ); return *m_image->GetPixel( pt.x, pt.y ); } /*----------------------------------------------------------\ | Release | |-----------------------------------------------------------| | DESCRIPTION : | | Libère les ressources allouées pour une mesure | \----------------------------------------------------------*/ void CEIMBase::Release (void) { m_dblEIMMin = m_dblEIMMean = m_dblEIMMax = m_dblINTMin = m_dblINTMean = m_dblINTMax = m_dblDiaAAMin = m_dblDiaAAMean = m_dblDiaAAMax = m_dblDiaIIMin = m_dblDiaIIMean = m_dblDiaIIMax = m_dblQI = m_dblQIMean = m_dblIA = m_dblEIMdMean = m_dblINTdMean = m_dblMEDdMean = m_dblIAd = m_dblVariance = m_dblDistance = 0.0; m_dwPoints = // nombre de points sur lesquels une mesure a été effectuée m_dwValidPoints = // nombre de points sur lesquels le profil a été reconnu m_uErrorID = 0; // ID de la chaîne décrivant l'erreur if (m_pMeasures) { delete [] m_pMeasures; m_pMeasures = NULL; } m_StartPoint.x = -1; m_StartPoint.y = -1; m_EndPoint.x = -1; m_EndPoint.x = -1; memset( m_bBuffer, 0, THICKNESS ); memset( m_cOffsets, 0, THICKNESS ); } // fin de Release /*----------------------------------------------------------\ | Measure | |-----------------------------------------------------------| | DESCRIPTION : | | Mesure d'une épaisseur de paroi ou d'un diamètre | |-----------------------------------------------------------| | PARAMETRES : | | gfx : image en niveau de gris d'où lire les | | données | | pGraphMean : graphe dans lequel ajouté les mesures si | | non nul | | pt1 : premier point du segment déterminant la | | ligne de la paroi | | pt2 : deuxième point du segment | | fDiameter : mesure d'un diamère ou d'une paroi | |-----------------------------------------------------------| | RETOURNE : | | VRAI si au moins une mesure a été effectuée | \----------------------------------------------------------*/ bool CEIMBase::Measure( ExtendedImage *h_image, const Point &point1, const Point &point2, bool fDiameter, CEIMResult *m_res) { m_image = h_image; return Measure( point1, point2, fDiameter, m_res ); } bool CEIMBase::Measure ( const Point &point1, const Point &point2, bool fDiameter, CEIMResult *m_res ) { Point point3, point4; // En fonction de la position du premier point, on détermine le côté d'analyse m_fDiameter = fDiameter; Release (); if (point1.x > point2.x) { m_bNearWall = true; m_vUser = CVector(point2, point1); if (!fDiameter) { m_StartPoint = point2; m_EndPoint = point1; } } else { m_bNearWall = false; m_vUser = CVector(point1, point2); // pour imposer le calcul de gauche à droite pour // le diamètre et la distensibilité même si le tracé // se fait de droite à gauche. if (!fDiameter) { m_StartPoint = point1; m_EndPoint = point2; } } m_minx_curve = std::numeric_limits< int >::max(); m_max_curve = 0; /* if ((m_Assist) && (!fDiameter)) { // CJ 2007 : Allongement auto de la distance à 10 mm dpoint pta, ptb, s1, s2, ptc; const long x1 = 0; pta.x = point1.x; pta.y = point1.y; ptb.x = point2.x; ptb.y = point2.y; if (point1.x < point2.x) { if (InterCercleDroite(pta, 10.0 / g_pCurrentScale->DistanceX(1L), pta, ptb, &s1, &s2)) { ptc = leplusproche(ptb, s1, s2); point3.x = (int) pta.x; point3.y = (int) pta.y; point4.x = (int) ptc.x; point4.y = (int) ptc.y; } #ifndef NEW_EIM m_vUser = CVector (point3, point4); #else if (point3.x <= point4.x) { m_vUser = CVector (point3, point4); m_StartPoint = point3; m_EndPoint = point4; } else { m_vUser = CVector (point4, point3); m_StartPoint = point4; m_EndPoint = point3; } if (point4.x > point3.x) { m_max_curve = point4.x; } else { m_max_curve = point3.x; } if (point3.x < point4.x) { m_minx_curve = point3.x; } else { m_minx_curve = point4.x; } #endif } } */ if (m_vUser.Nul ()) { m_uErrorID = IDS_EIM_INVALID; return (false); } else { // CWaitCursor wait; m_dwPoints = m_vUser.Length () + 1; m_pMeasures = new CEIMInfo [m_dwPoints]; memset (m_pMeasures, 0, sizeof (CEIMInfo) * m_dwPoints); // pour un algo plutôt basé sur la proximité de la droite par // rapport à la paroi potentielle la plus proche voir fichier EIM/AvecSeuil.cpp // comme le point de départ de vecteur à toujours une abscisse inférieure au point d'arrivée // la direction à donner à Orthogonal est 0. Voir algo de Orthogonal pour comprendre if (!m_bNearWall) { m_indexParoi = 0; m_Max_Alignement = 8; } else { m_indexParoi = 1; m_Max_Alignement = 20; } if (fDiameter) { Diameter(); } else { #ifdef NEW_EIM if (!m_Assist) #else // if ((m_Assist) && (!fDiameter)) { } // else #endif { m_vUser = CVector (point1, point2); } Paroi(); } } Update (m_res); return (Valid ()); } // fin de Measure /*----------------------------------------------------------\ | ParallelismeEIM | |-----------------------------------------------------------| | DESCRIPTION : | | Fonction de calcul du parallèlisme du trait utilisateur | | Par rapport à la paroi | Pour le calcul de l'EIM | \----------------------------------------------------------*/ void CEIMBase::ParallelismeEIM() { long dwPos, dwMin; long dwPos2; CEIMInfo *pInfo = &m_pMeasures[0]; dpoint tpoints[MAX_POINTS_EIM_AUTO]; int tContinueGauche[MAX_POINTS_EIM_AUTO]; int tContinueDroite[MAX_POINTS_EIM_AUTO]; int tContinue[MAX_POINTS_EIM_AUTO]; int tContinue2[MAX_POINTS_EIM_AUTO]; dpoint pta, ptb, ptc, ptd, pte, ptf, s1, s2; double dist_etalon, dist_etalon2, dist; int cptValid, cptNonValid; dpoint tabPointsValides[128]; dpoint pts; bool bDebut = false; for (int i = 0; i < MAX_POINTS_EIM_AUTO; i++) { tContinueGauche[i] = 0; tContinueDroite[i] = 0; tContinue[i] = 0; tContinue2[i] = 0; } // 2ème passe : On enlève les outliers : En allant de gauche à droite dwMin = long (-1); pInfo = &m_pMeasures[0]; cptValid = 0; cptNonValid = 0; // double dist2, dist3; for (dwPos = 0; dwPos < m_dwPoints; dwPos++) { if (pInfo->m_fValid == true) { pts.x = pInfo->m_Paroi[0].m_slope[2].m_ptDraw.x; pts.y = pInfo->m_Paroi[0].m_slope[2].m_ptDraw.y; if ((pts.x != 0) && (pts.y != 0)) { // Vérification de l'alignement if (cptValid >= m_Max_Alignement) { // Estimation droite à partir de tabPointsValides double ka, kb; RegressionLineaire(m_Max_Alignement, tabPointsValides, &ka, &kb); // Calcul de la distance orthogonale entre la droite et pts dpoint ptk, ptl, ptr; ptk.x = pts.x; ptk.y = ka * pts.x + kb; ptl.x = pts.x + 10; ptl.y = ka * ptl.x + kb; dist = distanceOrthPtDroite(pts, ptk, ptl, &ptr); if (dist < 3) // Si la distance est < à n pixels { cptNonValid = 0; // On remet le compteur à 0 // Ce point est OK, on le garde // Remise à jour du tableau des points for (int i = 0; i < m_Max_Alignement-1; i++) { tabPointsValides[i].x = tabPointsValides[i+1].x; tabPointsValides[i].y = tabPointsValides[i+1].y; } tabPointsValides[m_Max_Alignement-1].x = pts.x; tabPointsValides[m_Max_Alignement-1].y = pts.y; tContinueGauche[dwPos] = 1; } else { cptNonValid++; // Nb de points non valides à la suite } // Si un nombre de points non valides suffisant a été détecté à la suite if (cptNonValid >= m_Max_Alignement - 1) { // On remet à 0 le tableau des valeurs for (int i = 0; i < m_Max_Alignement-1; i++) { tabPointsValides[i].x = 0; tabPointsValides[i].y = 0; } cptValid = 0; // On redemande un remplissage cptNonValid = 0; } } else { tabPointsValides[0].x = pInfo->m_Paroi[m_indexParoi].m_slope[2].m_ptDraw.x; tabPointsValides[0].y = pInfo->m_Paroi[m_indexParoi].m_slope[2].m_ptDraw.y; cptValid++; } } } pInfo++; } // Puis de droite à gauche pInfo = &m_pMeasures[m_dwPoints-1]; cptValid = 0; cptNonValid = 0; bDebut = false; for (dwPos = m_dwPoints-1; dwPos > 0; dwPos--) { if (pInfo->m_fValid == true) { pts.x = pInfo->m_Paroi[0].m_slope[2].m_ptDraw.x; pts.y = pInfo->m_Paroi[0].m_slope[2].m_ptDraw.y; if ((pts.x != 0) && (pts.y != 0)) { // Vérification de l'alignement if (cptValid >= m_Max_Alignement) { // Estimation droite à partir de tabPointsValides double ka, kb; RegressionLineaire(m_Max_Alignement, tabPointsValides, &ka, &kb); // Calcul de la distance orthogonale entre la droite et pts dpoint ptk, ptl, ptr; ptk.x = pts.x; ptk.y = ka * pts.x + kb; ptl.x = pts.x + 10; ptl.y = ka * ptl.x + kb; dist = distanceOrthPtDroite(pts, ptk, ptl, &ptr); if (dist < 3) // Si la distance est < à x pixels { cptNonValid = 0; // On remet le compteur à 0 // Ce point est OK, on le garde // Remise à jour du tableau des points for (int i = 0; i < m_Max_Alignement-1; i++) { tabPointsValides[i].x = tabPointsValides[i+1].x; tabPointsValides[i].y = tabPointsValides[i+1].y; } tabPointsValides[m_Max_Alignement-1].x = pts.x; tabPointsValides[m_Max_Alignement-1].y = pts.y; tContinueDroite[dwPos] = 1; } else { cptNonValid++; // Nb de points non valides à la suite } // Si un nombre de points non valides suffisant a été détecté à la suite if (cptNonValid >= m_Max_Alignement - 1) { // On remet à 0 le tableau des valeurs for (int i = 0; i < m_Max_Alignement-1; i++) { tabPointsValides[i].x = 0; tabPointsValides[i].y = 0; } cptNonValid = 0; cptValid = 0; // On redemande un remplissage } } else { tabPointsValides[cptValid].x = pInfo->m_Paroi[0].m_slope[2].m_ptDraw.x; tabPointsValides[cptValid].y = pInfo->m_Paroi[0].m_slope[2].m_ptDraw.y; cptValid++; } } } pInfo--; } pInfo = &m_pMeasures[0]; for (dwPos = 0; dwPos < m_dwPoints; dwPos++) { if (pInfo->m_fValid == true) { if ((tContinueGauche[dwPos] == 1) || (tContinueDroite[dwPos] == 1)) { tContinue[dwPos] = 1; } } tContinue2[dwPos] = tContinue[dwPos]; pInfo++; } bool bNonContinue = false; int cptNonContinue = 0; for (dwPos = 0; dwPos < m_dwPoints; dwPos++) { if (tContinue2[dwPos] == 1) { cptNonContinue = 0; } else if (tContinue2[dwPos] == 0) { cptNonContinue++; bNonContinue = true; } // 1- On faits rejoindre les segments si l'écart est faible (sur front montant) if ((tContinue2[dwPos] == 1) && bNonContinue) { bNonContinue = false; if (cptNonContinue < 3*m_Max_Alignement) { // Dans ce cas on peut rejoindre les 2 segments for (dwPos2 = dwPos - 3*m_Max_Alignement; dwPos2 < dwPos; dwPos2++) { if (dwPos2 >= 0) { tContinue2[dwPos2] = 1; } } } } } int indexSegments = 0; bNonContinue = true; int maxSegment = 0; int longueurSegment = 0; int indexMaxSegment = 1; for (dwPos = 0; dwPos < m_dwPoints; dwPos++) { if (tContinue2[dwPos] == 0) { bNonContinue = true; } if ((tContinue2[dwPos] == 1) && bNonContinue) { indexSegments++; bNonContinue = false; longueurSegment = 0; } // 2 - On numérote chacun des segments if (tContinue2[dwPos] == 1) { tContinue2[dwPos] = indexSegments; longueurSegment++; if (longueurSegment > maxSegment) { maxSegment = longueurSegment; indexMaxSegment = indexSegments; } } } // 3 - Si plus de 1 segment on regarde celui qui est le plus grand if (indexSegments > 1) { for (dwPos = 0; dwPos < m_dwPoints; dwPos++) { if (tContinue2[dwPos] != indexMaxSegment) { // 4- On efface les traits continus des segments qui ne sont pas le plus long tContinue[dwPos] = 0; } } } dwMin = long (-1); pInfo = &m_pMeasures[0]; for (dwPos = 0; dwPos < m_dwPoints; dwPos++) { pInfo->m_fValid = (tContinue[dwPos] == 1); pInfo++; } dwMin = long (-1); pInfo = &m_pMeasures[0]; dist_etalon = 0; dist_etalon2 = 0; m_nbpoints_curve = 0; for (dwPos = 0; dwPos < m_dwPoints; dwPos++) { if (pInfo->m_fValid == true) { // Au premier point on mémorise la valeur de la distance qui va servir d'étalon pour les autres points pta.x = m_vUser[dwPos].x; pta.y = m_vUser[dwPos].y; ptb.x = pInfo->m_Paroi[0].m_slope[2].m_ptMiddle.x; ptb.y = pInfo->m_Paroi[0].m_slope[2].m_ptMiddle.y; pte.x = pInfo->m_Paroi[0].m_slope[2].m_ptDraw.x; pte.y = pInfo->m_Paroi[0].m_slope[2].m_ptDraw.y; ptf.x = pInfo->m_Paroi[0].m_slope[2].m_ptDraw.x; ptf.y = pInfo->m_Paroi[0].m_slope[2].m_ptDraw.y; if (m_nbpoints_curve == 0) { dist_etalon = distance(pta, ptb); dist_etalon2 = distance(pta, pte); tpoints[0].x = m_vUser[dwPos].x; tpoints[0].y = m_vUser[dwPos].y; m_nbpoints_curve++; } else { bool outOfImage; outOfImage = false; // Intersection entre le cercle centré sur ptb de rayon dist_etalon, et la droite pta, ptb if (InterCercleDroite(ptb, dist_etalon, pta, ptb, &s1, &s2)) { // Renvoie le point le plus proche de pta // Qui va être le nouveau point ptc = leplusproche(pta, s1, s2); dist = distance(ptc, pta); // Contre les bugs aux extrémités des images if (dist <= (2 * dist_etalon)) //if ((ptc.x <= m_rectCadre.GetRight()) && (ptc.x >= m_rectCadre.GetLeft())) { tpoints[m_nbpoints_curve].x = ptc.x; tpoints[m_nbpoints_curve].y = ptc.y; } else { outOfImage = true; } } else { tpoints[m_nbpoints_curve].x = m_vUser[dwPos].x; tpoints[m_nbpoints_curve].y = m_vUser[dwPos].y; } if (!outOfImage) { m_nbpoints_curve++; } } } pInfo++; } moindres_carres_parabole(m_nbpoints_curve, tpoints, &m_coeff_a, &m_coeff_b, &m_coeff_c); CVector vPerp; Point point1, point2, point3; // On recalcul l'EIM, en utilisant cette fois les perpendiculaires à la courbe obtenue dwMin = long (-1); int i; i = 0; pInfo = &m_pMeasures[0]; bool bDistDeb; bDistDeb = false; double ka, kb; bDebut = false; for (dwPos = 0; dwPos < m_dwPoints; dwPos++) { if (pInfo->m_fValid == true) { pta.x = tpoints[i].x; pta.y = tpoints[i].y; // La tangente (dérivée) de y = ax2 + b x + c est y = 2ax + b // Le coefficient directeur de la perpendiculaire à la tangente est -1/b // Pour la perpendiculaire qui passe par pta.x, pta.y, y = ka x + kb if (m_coeff_b != 0) { ka = (double) -1.0 / (2 * m_coeff_a); } else { ka = 0.0; } kb = pta.y - ka * pta.x; ptb.y = tpoints[i].y + 2 * dist_etalon; if (ka != 0) { ptb.x = (ptb.y - kb) / ka; } else { ptb.x = tpoints[i].x; } // Intersection entre le cercle centré sur pta de rayon dist_etalon, et la droite pta, ptb bool bInter = InterCercleDroite(pta, 2 * dist_etalon2, pta, ptb, &s1, &s2); if (bInter) { if (m_bNearWall) { if (s1.y <= s2.y) { ptc.x = s1.x; ptc.y = s1.y; ptd.x = s2.x; ptd.y = s2.y; } else { ptc.x = s2.x; ptc.y = s2.y; ptd.x = s1.x; ptd.y = s1.y; } } else { if (s1.y > s2.y) { ptc.x = s1.x; ptc.y = s1.y; ptd.x = s2.x; ptd.y = s2.y; } else { ptc.x = s2.x; ptc.y = s2.y; ptd.x = s1.x; ptd.y = s1.y; } } } else { ptc.x = ptb.x; ptc.y = ptb.y; ptd.x = ptb.x; ptd.y = ptb.y; } point1.x = (int) pta.x; point1.y = (int) pta.y; point2.x = (int) ptc.x; point2.y = (int) ptc.y; point3.x = (int) ptd.x; point3.y = (int) ptd.y; m_pt1perp[i].x = point1.x; m_pt1perp[i].y = point1.y; m_pt2perp[i].x = point2.x; m_pt2perp[i].y = point2.y; i++; } pInfo++; } /* Enlevé // Calculs aux extrémités // Au début dpoint pt0, pt1; pt0.x = debx; // y = ax2 + b x + c pt0.y = (m_coeff_a * pt0.x * pt0.x) + (m_coeff_b * pt0.x) + m_coeff_c; // La tangente (dérivée) de y = ax2 + b x + c est y = 2ax + b // Le coefficient directeur de la perpendiculaire à la tangente est -1/b double ka, kb; // Pour la perpendiculaire qui passe par pta.x, pta.y, y = ka x + kb if (m_coeff_b != 0) { ka = (double) -1.0 / (2 * m_coeff_a); } else { ka = 0.0; } kb = pt0.y - ka * pt0.x; double kb2 = m_coeff_b; pt1.y = pt0.y + 2 * dist_etalon; if (ka != 0) { pt1.x = (pt1.y - kb) / ka; } else { pt1.x = pt0.x; } // Intersection entre le cercle centré sur pt0 de rayon dist_etalon, et la droite xd,yd, ptcel if (InterCercleDroite(pt0, dist_Debut, pt0, pt1, &s1, &s2)) { dist2 = distance(s1, ptdeb); dist3 = distance(s2, ptdeb); if (dist2 < dist3) { m_ptDebut.x = (int) s1.x; m_ptDebut.y = (int) s1.y; } else { m_ptDebut.x = (int) s2.x; m_ptDebut.y = (int) s2.y; } } // A la fin pt0.x = finx; // y = ax2 + b x + c pt0.y = (m_coeff_a * pt0.x * pt0.x) + (m_coeff_b * pt0.x) + m_coeff_c; // La tangente (dérivée) de y = ax2 + b x + c est y = 2ax + b // Le coefficient directeur de la perpendiculaire à la tangente est -1/b // Pour la perpendiculaire qui passe par pta.x, pta.y, y = ka x + kb if (m_coeff_b != 0) { ka = (double) -1.0 / (2 * m_coeff_a); } else { ka = 0.0; } kb = pt0.y - ka * pt0.x; kb2 = m_coeff_b; pt1.y = pt0.y + 2 * dist_etalon; if (ka != 0) { pt1.x = (pt1.y - kb) / ka; } else { pt1.x = pt0.x; } // Intersection entre le cercle centré sur pt0 de rayon dist_etalon, et la droite xd,yd, ptcel if (InterCercleDroite(pt0, dist_Fin, pt0, pt1, &s1, &s2)) { dist2 = distance(s1, ptfin); dist3 = distance(s2, ptfin); if (dist2 < dist3) { m_ptFin.x = (int) s1.x; m_ptFin.y = (int) s1.y; } else { m_ptFin.x = (int) s2.x; m_ptFin.y = (int) s2.y; } } */ } /*----------------------------------------------------------\ | Paroi | |-----------------------------------------------------------| | DESCRIPTION : | | Calcul de l'épaisseur d'une paroi dans une zone donnée | \----------------------------------------------------------*/ void CEIMBase::Paroi() { long dwPos, dwMin, dwFirst; int debx, finx; CEIMInfo *pInfo = &m_pMeasures[0]; #ifdef NEW_EIM double dist_Debut, dist_Fin; dpoint pts; #endif bool bDebut = false; dpoint ptdeb, ptfin; ptdeb.x = ptfin.x = 0; ptdeb.y = ptfin.y = 0; m_nbpoints_curve = 0; m_coeff_a = -1.0; m_coeff_b = -1.0; m_coeff_c = -1.0; assert(!m_fDiameter && m_pMeasures && GfxImageValid()); dwMin = long (-1); for (dwPos = 0; dwPos < m_dwPoints; dwPos++) { // Ligne perpendiculaire à la paroi sur laquelle effectuer la mesure pInfo->m_Paroi[0].m_vMeasure = m_vUser.Orthogonal(m_vUser[dwPos], 0); if (AddVector(pInfo->m_Paroi[0])) { ComputeEIM(pInfo); // recherche des premières position valides, afin de réduire l'étendue de l'histogramme dwFirst = pInfo->m_Paroi[0].m_slope[0].m_dwPos[0]; if (dwFirst < dwMin) { dwMin = dwFirst; } if (bDebut == false) { bDebut = true; debx = m_vUser[dwPos].x; #ifndef NEW_EIM ptdeb.x = pInfo->m_Paroi[0].m_slope[0].m_ptDraw.x; ptdeb.y = pInfo->m_Paroi[0].m_slope[0].m_ptDraw.y; #endif } #ifndef NEW_EIM ptfin.x = pInfo->m_Paroi[0].m_slope[0].m_ptDraw.x; ptfin.y = pInfo->m_Paroi[0].m_slope[0].m_ptDraw.y; #endif finx = m_vUser[dwPos].x; } else { pInfo->m_fValid = false; } pInfo->m_diagnostic = m_diag; pInfo++; } #ifdef NEW_EIM // Modif CJ2007 if (m_Assist) { ParallelismeEIM(); CVector vPerp; // On recalcul l'EIM, en utilisant cette fois les perpendiculaires à la courbe obtenue dwMin = long (-1); int i; i = 0; pInfo = &m_pMeasures[0]; bool bDistDeb; bDistDeb = false; bDebut = false; for (dwPos = 0; dwPos < m_dwPoints; dwPos++) { if (pInfo->m_fValid == true) { if ((m_pt1perp[i].y >= 0) && (m_pt2perp[i].y >= 0) && (m_pt1perp[i].x >= 0) && (m_pt2perp[i].x >= 0) && (m_pt1perp[i].y < 576) && (m_pt2perp[i].y < 576) && (m_pt1perp[i].x < 768) && (m_pt2perp[i].x < 768)) { pInfo->m_Paroi[0].m_vMeasure = CVector(m_pt1perp[i], m_pt2perp[i]); if (AddVector(pInfo->m_Paroi[0])) { ComputeEIM(pInfo); // Recherche des premières position valides, afin de réduire l'étendue de l'histogramme dwFirst = pInfo->m_Paroi[0].m_slope[0].m_dwPos[0]; if (dwFirst < dwMin) { dwMin = dwFirst; } if (bDebut == false) { bDebut = true; ptdeb.x = pInfo->m_Paroi[0].m_slope[0].m_ptDraw.x; ptdeb.y = pInfo->m_Paroi[0].m_slope[0].m_ptDraw.y; } ptfin.x = pInfo->m_Paroi[0].m_slope[0].m_ptDraw.x; ptfin.y = pInfo->m_Paroi[0].m_slope[0].m_ptDraw.y; dpoint ptt, ptr; ptt.x = pInfo->m_Paroi[0].m_slope[0].m_ptDraw.x; ptt.y = (m_coeff_a * ptt.x * ptt.x) + (m_coeff_b * ptt.x) + m_coeff_c; ptr.x = pInfo->m_Paroi[0].m_slope[0].m_ptDraw.x; ptr.y = pInfo->m_Paroi[0].m_slope[0].m_ptDraw.y; if (!bDistDeb) { bDistDeb = true; dist_Debut = distance(ptt, ptr); } dist_Fin = distance(ptt, ptr); } else { pInfo->m_fValid = false; } pts.x = pInfo->m_Paroi[0].m_slope[2].m_ptDraw.x; } i++; } pInfo++; } } #endif if (ptdeb.x < ptfin.x) { m_ptDebut.x = (long) ptdeb.x; m_ptFin.x = (long) ptfin.x; } else { m_ptDebut.x = (long) ptfin.x; m_ptFin.x = (long) ptdeb.x; } // Ajustement des vecteurs de mesures et des positions pInfo = &m_pMeasures[0]; for (dwPos = 0; dwPos < m_dwPoints; dwPos++) { pInfo->m_Paroi[0].m_slope[0].m_dwPos[0] -= dwMin; pInfo->m_Paroi[0].m_slope[0].m_dwPos[1] -= dwMin; pInfo->m_Paroi[0].m_slope[0].m_dwMiddle -= dwMin; pInfo->m_Paroi[0].m_slope[1].m_dwPos[0] -= dwMin; pInfo->m_Paroi[0].m_slope[1].m_dwPos[1] -= dwMin; pInfo->m_Paroi[0].m_slope[1].m_dwMiddle -= dwMin; pInfo->m_Paroi[0].m_slope[2].m_dwPos[0] -= dwMin; pInfo->m_Paroi[0].m_slope[2].m_dwPos[1] -= dwMin; pInfo->m_Paroi[0].m_slope[2].m_dwMiddle -= dwMin; pInfo->m_Paroi[0].m_vMeasure = CVector(pInfo->m_Paroi[0].m_vMeasure[dwMin], pInfo->m_Paroi[0].m_vMeasure.EndPoint()); pInfo++; } } // end of Paroi /*-----------------------------------------------------------\ | ParallelismeDistensibilite | |-----------------------------------------------------------| | DESCRIPTION : | | Fonction de calcul du parallèlisme du trait utilisateur | | Par rapport à la paroi | | Pour le calcul du diamètre pour la distensibilité | \----------------------------------------------------------*/ void CEIMBase::ParallelismeDistensibilite() { int nbOk; long dwPos; dpoint pta, ptb, ptc, s1, s2; double distMoy, sommeDist; dpoint tpoints[MAX_POINTS_EIM_AUTO]; double ka, kb; Point vp1, vp2; bool bInter; CEIMInfo *pInfo; ka = 0; kb = 0; // 1)- On va estimer la distance du milieu de la veine sur les points valides nbOk = 0; distMoy = 0; sommeDist = 0; pInfo = &m_pMeasures [0]; for (dwPos = 0; dwPos < m_dwPoints; dwPos++) { pInfo->m_Paroi[0].m_vMeasure = m_vUser.Orthogonal(m_vUser[dwPos], 0); // pour l'histogramme, le deuxième vecteur est requis même si la mesure a échoué pInfo->m_Paroi[1].m_vMeasure = m_vUser.Orthogonal(m_vUser[dwPos], 1); if (AddVector(pInfo->m_Paroi [0]) && ((m_algoDiameter == algoProfile) ? AddVector(pInfo->m_Paroi [1]) : // recherche d'un profil sur la paroi supérieure FindOpposite(pInfo->m_Paroi[0], pInfo->m_Paroi[1]) // recherche sur la paroi supérieure du symétrique de la paroi inférieure ) ) { ComputeEIM(pInfo); distMoy = (pInfo->m_dblDia / 2.0); sommeDist += distMoy; nbOk++; } pInfo++; } if (nbOk > 0) { distMoy = sommeDist / nbOk; // 2)- On va déterminer les points au centre pInfo = &m_pMeasures [0]; m_nbpoints_curve = 0; nbOk = 0; for (dwPos = 0; dwPos < m_dwPoints; dwPos++) { pInfo->m_Paroi[0].m_vMeasure = m_vUser.Orthogonal(m_vUser[dwPos], 0); // On regarde que la paroi du bas pour le parallèlisme if (AddVector(pInfo->m_Paroi [0])) { ComputeEIM(pInfo); // Au premier point on mémorise la valeur de la distance qui va servir d'étalon pour les autres points pta.x = m_vUser[dwPos].x; pta.y = m_vUser[dwPos].y; ptb.x = pInfo->m_Paroi[0].m_slope[2].m_ptDraw.x; ptb.y = pInfo->m_Paroi[0].m_slope[2].m_ptDraw.y; // Intersection cercle centré sur ptb bInter = InterCercleDroite(ptb, distMoy, pta, ptb, &s1, &s2); // On prend celui qui est le plus près de pta ptc = leplusproche(pta, s1, s2); // On stocke le point trouvé pour faire l'approximation tpoints[nbOk].x = ptc.x; tpoints[nbOk].y = ptc.y; nbOk++; } pInfo++; } if (nbOk > 4) // Plusieurs points sinon érronés { // Alors on approxime les points par une droite RegressionLineaire(nbOk, tpoints, &ka, &kb); // On met à jour m_vUser en fonction de la droite approximée : il va être utilisé par la suite vp1.x = m_vUser[0].x; vp1.y = (int) ((double) ka * vp1.x + kb); vp2.x = m_vUser[dwPos].x; vp2.y = (int) ((double) ka * vp2.x + kb); // On détermine les nouvelles valeurs de m_vUser // Et c'est celui là qui va être utilisé dans la fonction de base m_vUser = CVector (vp1, vp2); m_StartPoint.x = vp1.x; m_StartPoint.y = vp1.y; m_EndPoint.x = vp2.x; m_EndPoint.y = vp2.y; } else { m_StartPoint.x = m_vUser[0].x; m_StartPoint.y = m_vUser[0].y; m_EndPoint.x = m_vUser[1].x; m_EndPoint.y = m_vUser[1].y; } // Les calculs de base vont se faire avec le nouvel m_vUser calculé } } /*----------------------------------------------------------\ | Diameter | |-----------------------------------------------------------| | DESCRIPTION : | | Mesure du diamètre d'un vaisseau | |-----------------------------------------------------------| | PARAMETRES : | | pScale : échelle à utiliser pour les mesures | | gfx : image en niveau de gris d'où lire les données | | pt1 : premier point du segment déterminant la ligne | | de la paroi. | | pt2 : deuxième point du segment | \----------------------------------------------------------*/ void CEIMBase::Diameter() { CEIMInfo *pInfo; long dwMin [2], dwPos, dwFirst; assert (m_fDiameter && m_pMeasures && GfxImageValid()); if (m_parallelismeDiametre) { ParallelismeDistensibilite(); } dwMin[0] = long(-1); dwMin[1] = long(-1); bool bDistDeb, bDebut; bDistDeb = false; bDebut = false; pInfo = &m_pMeasures[0]; for (dwPos = 0; dwPos < m_dwPoints; dwPos++) { pInfo->m_Paroi[0].m_vMeasure = m_vUser.Orthogonal(m_vUser[dwPos], 0); // Pour l'histogramme, le deuxième vecteur est requis même si la mesure a échoué pInfo->m_Paroi[1].m_vMeasure = m_vUser.Orthogonal(m_vUser[dwPos], 1); if ( AddVector(pInfo->m_Paroi [0]) && ( (m_algoDiameter == algoProfile) ? AddVector(pInfo->m_Paroi [1]) : // recherche d'un profil sur la paroi supérieure FindOpposite (pInfo->m_Paroi[0], pInfo->m_Paroi[1]) // recherche sur la paroi supérieure du symétrique de la paroi inférieure ) ) { ComputeEIM (pInfo); // recherche des premières position valides, afin de réduire l'étendue de l'histogramme for (int j = 0; j < 2; j++) { dwFirst = pInfo->m_Paroi[j].m_slope[0].m_dwPos[0]; if (dwFirst < dwMin [j]) dwMin[j] = dwFirst; } } else { // recalcul systématique, car cette fonction est appelé lors de la modification des seuils pInfo->m_fValid = false; } pInfo++; } // ajustement des vecteurs de mesures et des positions pInfo = &m_pMeasures [0]; for (dwPos = 0; dwPos < m_dwPoints; dwPos++) { for (int i = 0; i < 2; i++) { pInfo->m_Paroi[i].m_slope[0].m_dwPos[0] -= dwMin[i]; pInfo->m_Paroi[i].m_slope[0].m_dwPos[1] -= dwMin[i]; pInfo->m_Paroi[i].m_slope[0].m_dwMiddle -= dwMin[i]; pInfo->m_Paroi[i].m_slope[1].m_dwPos[0] -= dwMin[i]; pInfo->m_Paroi[i].m_slope[1].m_dwPos[1] -= dwMin[i]; pInfo->m_Paroi[i].m_slope[1].m_dwMiddle -= dwMin[i]; pInfo->m_Paroi[i].m_vMeasure = CVector (pInfo->m_Paroi[i].m_vMeasure[dwMin[i]], pInfo->m_Paroi[i].m_vMeasure.EndPoint()); } pInfo++; } } // end of Diameter /*----------------------------------------------------------\ | CEIM::AddVector | |-----------------------------------------------------------| | DESCRIPTION : | | Calcul de l'épaisseur d'une paroi dans une zone donnée | |-----------------------------------------------------------| | PARAMETRES : | | paroi : CEIMInfo à remplir avec les mesures | \----------------------------------------------------------*/ bool CEIMBase::AddVector(sParoi &paroi) { long dwPoint, dwLast = THICKNESS - 1; // le dernier point sur lequel effectuer une mesure (au pire) // on s'assure que le vecteur ne dépasse pas la zone d'affichage // paroi.m_vMeasure.Mask (*m_pGfx); dwLast = min( dwMax, m_vMeasure.Length()) assert (PointInBuffer (paroi.m_vMeasure [0])); // afin de traiter la variation d'intensité en même temps, on récupère le premier // point indépendamment des autres // ASSERT ((paroi.m_vMeasure [0].x != 301) && (paroi.m_vMeasure [0].x != 336)); m_bBuffer [0] = GetIntensity (paroi.m_vMeasure [0]); for (dwPoint = 1; dwPoint <= dwLast; dwPoint++) { const Point &pt = paroi.m_vMeasure [dwPoint]; if (PointInBuffer (pt)) { // remplissage de la mémoire tampon m_bBuffer [dwPoint] = GetIntensity (pt); // calcul de la variation de densité m_cOffsets [dwPoint] = Delta (m_bBuffer [dwPoint], m_bBuffer [dwPoint - 1]); } else // peu élégant, mais efficace { dwLast = dwPoint - 1; // interrompra également la prochaine boucle (de détection de profil) } } // On mesure l'E.I.M. sur la ligne extraite if (m_versionTomtecAout08) { // Pour pouvoir afficher les points milieux même s'ils sont pas valides int res = MeasureLineBuffer (paroi, m_bBuffer, m_cOffsets, dwPoint); { SetMiddlePoint (paroi.m_slope [0], paroi.m_vMeasure); SetMiddlePoint (paroi.m_slope [1], paroi.m_vMeasure); // On mesure l'IMT sur la ligne extraite SetMiddlePoint (paroi.m_slope [2], paroi.m_vMeasure, true); if (res) { return (true); } } } else { if (MeasureLineBuffer (paroi, m_bBuffer, m_cOffsets, dwPoint) > 0) { SetMiddlePoint (paroi.m_slope [0], paroi.m_vMeasure); SetMiddlePoint (paroi.m_slope [1], paroi.m_vMeasure); // On mesure l'IMT sur la ligne extraite SetMiddlePoint (paroi.m_slope [2], paroi.m_vMeasure, true); return (true); } } return (false); } // end of AddVector /*----------------------------------------------------------\ | FindOpposite | |-----------------------------------------------------------| | DESCRIPTION : | | Recherche le point adventice symétrique| |-----------------------------------------------------------| | PARAMETRES : | | paroi : CEIMInfo à remplir avec les mesures | \----------------------------------------------------------*/ bool CEIMBase::FindOpposite(sParoi &paroi1, sParoi &paroi0) { long dwSeek = paroi0.m_slope[0].m_dwIntensity, dwMax = GetIntensity(paroi1.m_vMeasure[paroi0.m_slope[1].m_dwPos[1]]), dwPoint; bool fFound, fInGfx; // afin de traiter la variation d'intensité en même temps, on récupère le premier // point indépendamment des autres dwPoint = 0; fFound = false; fInGfx = true; do { const Point &pt = paroi1.m_vMeasure [dwPoint]; fInGfx = PointInBuffer (pt); if (fInGfx) { unsigned char bLevel = GetIntensity (pt); m_bBuffer [dwPoint] = bLevel; if (bLevel < dwSeek) dwPoint++; else if (bLevel > dwMax) // la valeur trouvée dépasse la valeur du sommet de la pente de la paroi // du bas, on considère que c'est une erreur fInGfx = false; else { long dwPos = dwPoint; // il est très peu probable que l'on arrive juste sur le milieu de la pente. // on cherche donc les extrêmes. // recherche du bas de la pente while (dwPoint && (m_bBuffer [dwPoint - 1] <= m_bBuffer [dwPoint])) dwPoint--; paroi1.m_slope [1].m_dwPos [0] = dwPoint; // recherche du sommet de la pente dwPoint = dwPos + 1; paroi1.m_slope [1].m_dwPos [1] = 0; // normalement déjà fait while (!paroi1.m_slope [1].m_dwPos [1]) { const Point &pt = paroi1.m_vMeasure [dwPoint]; if (!PointInBuffer (pt)) paroi1.m_slope [1].m_dwPos [1] = dwPoint - 1; else { m_bBuffer [dwPoint] = GetIntensity (pt); if (m_bBuffer [dwPoint] > m_bBuffer [dwPoint - 1]) dwPoint++; else paroi1.m_slope [1].m_dwPos [1] = dwPoint - 1; } } // la 1° pente est ignorée par cette méthode, seule la 2° pente est analysée SetMiddlePoint (paroi1.m_slope [1], paroi1.m_vMeasure); fFound = true; } } } while (fInGfx && !fFound); return (fFound); } // end of FindOpposite /*----------------------------------------------------------\ | SetMiddlePoints | |-----------------------------------------------------------| | DESCRIPTION : | | Détermine la position des deux points encadrants le | | milieu d'une des deux pentes. | | Les deux points retournés peuvent être confondus. | |-----------------------------------------------------------| | PARAMETRES : | | slope : pente sur laquelle effectuer les mesures | | vMeasure : vecteur sur lequel cette pente a été détectée | \----------------------------------------------------------*/ void CEIMBase::SetMiddlePoint( sSlope &slope, CVector &vMeasure, bool reverseSlope) { int iMean; long dwPos; // on utilise le double du milieu, car 80+21/2=50, pas 50.5 puisque c'est un entier //assert (AfxIsValidAddress (&slope, sizeof (slope))); // [JAK - 06/09/02] l'assert surgit un peu trop souvent lors de mesure de la distensibilité // if ( reverseSlope ) // ASSERT (m_bBuffer[slope.m_dwPos[0]] > m_bBuffer [slope.m_dwPos[1]]);//ICI // else // ASSERT (m_bBuffer[slope.m_dwPos[0]] < m_bBuffer [slope.m_dwPos[1]]); dwPos = slope.m_dwPos[0]; iMean = m_bBuffer [dwPos] + m_bBuffer [slope.m_dwPos [1]]; if (reverseSlope) while (m_bBuffer [dwPos] * 2 > iMean) dwPos++; else while (m_bBuffer [dwPos] * 2 < iMean) dwPos++; // La mesure d'EIM est maintenant faite ici, car les intensités des points sont // nécessaires pour estimer la position exacte du milieu (en INTENSITE) if (m_bBuffer [dwPos] * 2 == iMean) { // en cas d'égalité, les deux points sont confondus slope.m_ptDraw = slope.m_ptMiddle = vMeasure [dwPos]; slope.m_dblMiddle = 0.0; } else { dwPos--; slope.m_ptMiddle = vMeasure [dwPos]; if ( reverseSlope ) { if (iMean - 2 * m_bBuffer [dwPos + 1] < 2 * m_bBuffer [dwPos] - iMean) slope.m_ptDraw = slope.m_ptMiddle; else slope.m_ptDraw = vMeasure [dwPos + 1]; // [JAK - 3/8/2002] ASSERT (m_bBuffer [dwPos + 1] < m_bBuffer [dwPos]); } else { if (2 * m_bBuffer [dwPos + 1] - iMean > iMean - 2 * m_bBuffer [dwPos]) slope.m_ptDraw = slope.m_ptMiddle; else slope.m_ptDraw = vMeasure [dwPos + 1]; //assert (m_bBuffer [dwPos + 1] > m_bBuffer [dwPos]); }// assert (abs (slope.m_ptMiddle.x - vMeasure [dwPos + 1].x) <= 1); assert (abs (slope.m_ptMiddle.y - vMeasure [dwPos + 1].y) <= 1); slope.m_dblMiddle = ( double (iMean - m_bBuffer [dwPos] * 2) * CVector (slope.m_ptMiddle, vMeasure [dwPos + 1]).Norm () / (m_bBuffer [dwPos + 1] - m_bBuffer [dwPos]) // bug potentiel : division par zero [JAK - 15/10/2002] / 2 ); } // permet un affichage beaucoup plus rapide de l'histogramme slope.m_dwMiddle = dwPos; slope.m_dwIntensity = iMean / 2; } // fin de SetMiddlePoints /*----------------------------------------------------------\ | CEIM::MeasureLineBuffer | |-----------------------------------------------------------| | DESCRIPTION : | | Mesure de l'E.I.M. à partir d'un buffer d'intensité et | | de variation | |-----------------------------------------------------------| | PARAMETRES : | | paroi : Info à définir sur la mesure d'EIM | | pbIntensity : pointeur sur les intensités | | pcOffsets : pointeur sur les variations d'intensité | | dwPoints : nombre de points à étudier | |-----------------------------------------------------------| | RETOUR : | | un booléen indiquant si le profil a été reconnu | \----------------------------------------------------------*/ int CEIMBase::MeasureLineBuffer ( sParoi &paroi, unsigned char* pbBuffer, char *pcOffsets, long dwPoints ) { m_diag = 0; // Permet de voir où l'algorithme s'est arrété (afin de l'améliorer) // Valeur négative if (dwPoints < 3) { return (0); } else { long dwStart, dwPos, dwFirst, dwLast, dwLastChanged; bool fChanged; ///////////////////////////////// // lissage de la courbe de densité ///////////////////////////////// // il ne faut pas corriger les extérieurs sinon, à cause du report, // tous les points seront corrigés // sur 300 points le lissage dure moins d'une milliseconde dwFirst = 2; dwLastChanged = dwLast = dwPoints - 2; do { char c, c1, c2, c3; fChanged = false; // on ne prend pas en compte les variations nulles, autrement dit // un 1,1,0,-1,1 ->1,1,0,1(ou 0),1. // ainsi les variations de 1 pixel sont ignorées // on conserve donc en mémoire le dernier point significatif (au début 0...) c1 = pcOffsets [dwFirst - 1]; for (dwPos = dwFirst; dwPos < dwLast; dwPos++) { c = pcOffsets [dwPos - 1]; if (c) // non nul c1 = c; // nouvelle variation c2 = pcOffsets [dwPos ]; c3 = pcOffsets [dwPos + 1]; // on met à jour le pt central à jour si les deux extrêmes varient dans le même sens // et que le point central varie dans le sens inverse. if ((c1 == c3) && (c2 != c1)) { pbBuffer [dwPos ] = (unsigned char )((pbBuffer [dwPos - 1] + pbBuffer [dwPos + 1]) / 2); // on est obligé de recalculer les variations pcOffsets [dwPos ] = Delta (m_bBuffer [dwPos ], m_bBuffer [dwPos - 1]); pcOffsets [dwPos + 1] = Delta (m_bBuffer [dwPos + 1], m_bBuffer [dwPos ]); // ce test n'est pas la pour le FUN !! si l'écart entre deux intensité est 1 // le milieu sera le point de plus faible intensité, ce qui ne changera rien if ((pcOffsets [dwPos] != c2) || (pcOffsets [dwPos + 1] != c3)) { if (!fChanged) { fChanged = true; dwFirst = dwPos; } // dès que fChanged est VRAI il faut initialiser dwLastChanged (petit oubli !!) dwLastChanged = dwPos; // si on change dwLast, on quitte la boucle!! } } } dwLast = dwLastChanged; } while (fChanged); //////////////////////////////////// // recherche du profil de densité // //////////////////////////////////// // pour un algo avec gestion de seuils voir fichier EIM/AvecSeuil.cpp // a) recherche d'une croissance (3 pixels de suite au moins : 2 variations de même sens) // à partir du pixel donné, pour un maximum de points donné // le premier est le plus complexe à trouver. #ifdef TEST_FOR_FANTOME dwStart = 2; #endif if ( LookForFirstPoint (dwStart, dwPoints, dwPos, m_bBuffer) ) { m_diag = -1; paroi.m_slope [0].m_dwPos [0] = dwPos; // sera modifié si le delta est insuffisant // b) recherche d'une décroissance (3 pixels de suite au moins : 2 variations de même sens) bool variation = LookForVariation (dwStart, pcOffsets, dwPoints, enumDown, dwPos); #ifdef TEST_FOR_FANTOME if ( !variation ) { if ( LookForFirstPoint (dwStart, dwPoints, dwPos, m_bBuffer) ) { variation = LookForVariation (dwStart, pcOffsets, dwPoints, enumDown, dwPos); } } #endif if ( variation ) { m_diag = -2; paroi.m_slope [0].m_dwPos [1] = dwPos; paroi.m_slope [2].m_dwPos [0] = dwPos; // Pour INT // "génération" de plateaux. Suppression des petites irrégularités sur des plateaux, // afin de détecter aisément les zones de saturation // ce n'est nécessaire qu'à ce niveau for (dwFirst = dwPos; dwFirst < dwPoints - 3; dwFirst++) { unsigned char b = pbBuffer [dwFirst]; // il faut également mettre à jour pcOffsets, sinon on engendre une incohérence if ( (b == pbBuffer [dwFirst + 2]) && (abs (b - pbBuffer [dwFirst + 1]) < 5)) { pbBuffer [dwFirst + 1] = b; pcOffsets [dwFirst + 1] = 0; } else if ( (b == pbBuffer [dwFirst + 3]) && (abs (b - pbBuffer [dwFirst + 1]) < 5) && (abs (b - pbBuffer [dwFirst + 2]) < 5)) { pbBuffer [dwFirst + 1] = b; pbBuffer [dwFirst + 2] = b; pcOffsets [dwFirst + 1] = 0; pcOffsets [dwFirst + 2] = 0; } } // c) recherche d'une croissance (3 pixels de suite au moins : 2 variations de même sens) if (LookForVariation (dwStart, pcOffsets, dwPoints, enumUp, dwPos)) { m_diag = -3; if (pbBuffer [paroi.m_slope [0].m_dwPos [1]] <= m_bDelta2 + pbBuffer [dwStart - 1]) { m_diag = -4; // Test Ajout test 1 180808 if ((m_versionTomtecAout08 && ((2 * pbBuffer [dwStart - 1])> m_bSeuil1)) || (!m_versionTomtecAout08)) { // if ((2 * pbBuffer [dwStart - 1])> (2 * pbBuffer [paroi.m_slope [0].m_dwPos [1]])) { paroi.m_slope[1].m_dwPos[0] = dwPos; paroi.m_slope[1].m_dwPos[1] = dwStart - 1; paroi.m_slope[2].m_dwPos[1] = dwPos; } return (1); } } if (m_versionTomtecAout08) { dwStart += 3; // On fait 2 fois de suite la recherche d'une croissance 180808 // La première croissance peut être une erreur // d) recherche d'une seconde croissance (3 pixels de suite au moins : 2 variations de même sens) if (LookForVariation (dwStart, pcOffsets, dwPoints, enumUp, dwPos)) { m_diag = -5; if (pbBuffer [paroi.m_slope [0].m_dwPos [1]] <= m_bDelta2 + pbBuffer [dwStart - 1]) { m_diag = -6; // Test Ajout test 1 180808 if ((2 * pbBuffer [dwStart - 1])> m_bSeuil1) { // if ((2 * pbBuffer [dwStart - 1])> (2 * pbBuffer [paroi.m_slope [0].m_dwPos [1]])) { paroi.m_slope[1].m_dwPos[0] = dwPos; paroi.m_slope[1].m_dwPos[1] = dwStart - 1; paroi.m_slope[2].m_dwPos[1] = dwPos; } return (1); } } } } /* // d) recherche d'une décroissance (3 pixels de suite au moins : 2 variations de même sens) // il faut que le maximal de la deuxième pente soit plus haut que le maximal de la première // (c'est un critère très efficace pour éliminer de mauvaise mesures, en général lorsque la // "1°" paroi qui a été détectée n'est pas la bonne) if (LookForVariation (dwStart, pcOffsets, dwPoints, enumDown, dwPos)) { // on cherche le début du plateau si on est dessus, le dernier point doit être la fin // d'une croissance STRICTE dwFirst = dwPos; // on veut connaître la taille du plateau while (pbBuffer [dwPos] == pbBuffer [dwPos - 1]) dwPos--; if ( (pbBuffer [paroi.m_dwPos [1]] + m_bDelta2 < pbBuffer [dwPos]) || ((dwFirst - dwPos >= 2) && (pbBuffer [dwPos] >= 240)) ) { paroi.m_dwPos[3] = dwPos; return (1); } } */ } } } } return (0); } // end of Measure /*----------------------------------------------------------\ | LookForFirstPoint | |-----------------------------------------------------------| | DESCRIPTION : | | Recherche du premier point caractéristique de la paroi | | Il mérite à lui seul une fonction... | |-----------------------------------------------------------| | PARAMETRES : | | dwStart : premier point à examine | | dwMax : dernier point examinable | | dwPos : où stocker la position trouvée | | pbBuffer : ligne d'intensité | |-----------------------------------------------------------| | RETOURNE : | | un booléen indiquant si la variation recherchée a été | | trouvée | \----------------------------------------------------------*/ bool CEIMBase::LookForFirstPoint ( long &dwStart, long dwMax, long &dwPos, unsigned char* pbBuffer ) { // règles de détection de la première pente: // 1. trois points doivent se se suivre avec une intensité croissante // 2. le dernier des points, doit avoir une intensité égal à m_bDelta1 + la moyenne des précédents // 3. au moins trois points consécutifs doivent ensuite avoir une intensité inférieur au premier sommet // // l'intensité du premier point sert de repère unsigned char bFirst = pbBuffer [1]; // intensité du premier point unsigned char* pb; #ifndef TEST_FOR_FANTOME dwStart = 2; // on commence à 2 pour avoir accès à -1 et -2 #endif pb = &pbBuffer [dwStart]; while ( (dwStart < dwMax) && ( ( ( pb [ 0] - bFirst ) < m_bDelta1) // tant que le point courant n'est pas assez lumineux, on continue || ( pb [-2] >= pb [-1] ) ) ) { pb++, dwStart++; } if (dwStart >= dwMax) { return (false); } else { dwPos = dwStart - 1; // on incrémente dwStart jusqu'à une décroissance STRICTE double // une simple décroissance est une irrégularité à ignorer while ((dwStart < dwMax) && ((pb [1] >= pb [0]) || (pb [2] >= pb [0]))) { pb++, dwStart++; } dwStart++; // il faut que l'on pointe sur le début de la décroissance if (dwStart >= dwMax) { return (false); } else { pb = &pbBuffer [dwPos]; // on cherche le premier point à m_bDelta1 du sommet // on est certain de trouver un point (au pire dwStart) // bFirst = BYTE (pbBuffer [dwStart - 1] - m_bDelta1); // dwStart pointe juste après le sommet // while (bFirst < pb [0]) // comparaison STRICTE // pb--, dwPos--; // on continue tant qu'il y a une décroissance STRICTE (on autorise UNE irrégularité même après lissage) // sinon si que du noir, on arrive au début du segment!! // le test sur m_bDelta1 empêche fréquemment la détection réelle du début de la pente while (dwPos && ((pb[0] > pb[-1]) || ((dwPos > 1) && (pb[0] > pb[-2])))) { pb--, dwPos--; } return (true); } } } // fin de LookForFirstPoint /*----------------------------------------------------------\ | CEIM::LookForVariation | |-----------------------------------------------------------| | DESCRIPTION : | | Recherche dans le buffer une variation de densité dans le| | sens donné, et déplace le pointeur tant que cette | | variation est maintenue | |-----------------------------------------------------------| | PARAMETRES : | | dwStart : premier point à examine | | pcOffsets : tableau de variation de densités | | dwMax : dernier point examinable | | dir : sens de variation recherché | | dwPos : où stocker la position trouvée | |-----------------------------------------------------------| | RETOURNE : | | un booléen indiquant si la variation recherchée a été | | trouvée | \----------------------------------------------------------*/ bool CEIMBase::LookForVariation (long &dwStart, char *pcOffsets, long dwMax, enumDirection dir, long &dwPos) { char c1, c2; assert (dwStart); // le tableau de variation débute à 1 while (dwStart < dwMax) { c1 = pcOffsets [dwStart]; c2 = pcOffsets [dwStart + 1]; if (c1 && (c1 == c2)) // il faut une variation (c1 != 0) <=> (c2 != 0) { if (dir == enumUp) { if (c1 < 0) { return (false); // Sens inverse de celui recherché } else { dwPos = dwStart - 1; dwStart++; do { dwStart++; // } while ((dwStart < dwMax) && (pcOffsets [dwStart] >= 0)); } while ((dwStart < dwMax) && (pcOffsets [dwStart] > 0)); return (true); } } else { if (c1 > 0) { return (false); // croissance, au lieu de décroissance } else { dwPos = dwStart - 1; dwStart++; do { dwStart++; } while ((dwStart < dwMax) && (pcOffsets [dwStart] <= 0)); return (true); } } } dwStart++; } return (false); } // fin de LookForVariation /*----------------------------------------------------------\ | Update | |-----------------------------------------------------------| | DESCRIPTION : | | Mesures des parois reconnues et statistiques | |-----------------------------------------------------------| | RETOURNE : | | VRAI si au moins un profil a été reconnu | \----------------------------------------------------------*/ bool CEIMBase::Update (CEIMResult *m_res) { // ATTENTION, il se peut qu'aucun profil n'ait été détecté // [JAK - 24/6/2002] no histogram window //initialisation GraphMeanInit(); m_arVariance.clear(); m_arVarianceAA.clear(); m_arVarianceII.clear(); m_arVarianceINT.clear(); m_dwValidPoints = 0; m_dblDistance = 0.0; m_dblEIMMin = m_dblEIMMean = m_dblEIMMax = m_dblINTMin = m_dblINTMean = m_dblINTMax = m_dblEIMdMean = m_dblINTdMean = m_dblMEDdMean = m_dblIAd = m_dblDiaAAMin = m_dblDiaAAMean = m_dblDiaAAMax = m_dblDiaIIMin = m_dblDiaIIMean = m_dblDiaIIMax = 0.0; if (m_pMeasures == NULL) return (false); else { CEIMInfo *pInfo = &m_pMeasures [0]; long dw; double dblMeanEIM, // valeur moyenne de l'EIM lors de la 1° phase dblMeanINT, dblMeanEIMd, dblMeanINTd, dblMeanMEDd, dblGapEIM, dblMeanQI, dblMeanDia, // valeur moyenne du diamètre lors de la 1° phase dblGapDia, dblMeanDist, // distance moyenne du 1° milieu au trait utilisateur dblStdDist, dblGapDist, dblII; // diamètre intima/intima // dblGapQI; // la première étape consiste: // - à calculer la valeur de l'EIM à partir des points détectés // - à calculer la moyenne des mesures afin d'éliminer ensuite // celles qui s'écartent trop de cette valeur moyenne. dblGapEIM = 0; dblGapDist = 0; dblGapDia = 0; dblII = 0; dblMeanEIM = 0; dblMeanINT = 0; dblMeanEIMd = 0; dblMeanINTd = 0; dblMeanMEDd = 0; dblMeanDia = 0; dblMeanDist = 0; dblStdDist = 0.0; dblMeanQI = 0.0; for (dw = 0; dw < m_dwPoints; dw++) { if (pInfo->m_fValid) // le profil a été repéré { dblMeanEIM += pInfo->m_dblEIM; dblMeanINT += pInfo->m_dblINT;//PJT dblMeanEIMd += pInfo->m_dblEIMd; dblMeanINTd += pInfo->m_dblINTd; dblMeanMEDd += pInfo->m_dblMEDd; dblMeanDia += pInfo->m_dblDia; dblMeanDist += pInfo->m_dblDist; dblStdDist += pInfo->m_dblDist * pInfo->m_dblDist; dblMeanQI += pInfo->m_dblQI; m_dwValidPoints++; // requis pour obtenir la moyenne } pInfo++; } if (m_dwValidPoints == 0) { m_uErrorID = IDS_EIM_FAILED;//BUG ICI //[JAK - 09/09/02] return (false); // aucune mesure valide } else { try// [JAK - 5/7/2002] { // calcul des moyennes dblMeanEIM /= m_dwValidPoints; dblMeanDia /= m_dwValidPoints; dblMeanDist /= m_dwValidPoints; dblStdDist /= m_dwValidPoints; dblMeanINT /= m_dwValidPoints;//PJT dblMeanEIMd /= m_dwValidPoints; dblMeanINTd /= m_dwValidPoints; dblMeanMEDd /= m_dwValidPoints; dblMeanQI /= m_dwValidPoints; // l'écart autorisé est égal à 25% de la moyenne 23/02/99 Touboul // l'écart autorisé est égal à 50% de la moyenne 06/05/99 Touboul // l'écart autorisé est égal à 35% de la moyenne 19/07/99 Touboul // l'écart autorisé est égal à 10% de la moyenne 19/07/05 Touboul if (m_versionTomtecAout08) { dblGapEIM = dblMeanEIM *.25;//.35 20/10/05 PJT // 0.2 180808 dblGapDia = dblMeanDia *.02;//.04 20/10/05 PJT dblGapDist = dblMeanDist *.15;//.25 20/10/05 PJT // 0.2 180808 } else { dblGapEIM = dblMeanEIM *.20; dblGapDia = dblMeanDia *.02; dblGapDist = dblMeanDist *.20; } // dblGapDist = sqrt(dblStdDist - dblMeanDist * dblMeanDist);// 20/12/06 FP pInfo = &m_pMeasures[0]; // exclusion de tous les points hors normes for (dw = 0; dw < m_dwPoints; dw++) { if ( pInfo->m_fValid // on exclut tous les point qui s'écartent trop de la moyenne && ( (fabs (pInfo->m_dblEIM - dblMeanEIM ) > dblGapEIM) // EIM incohérent || (fabs (pInfo->m_dblDist - dblMeanDist) > dblGapDist) // ou distance à l'axe incohérente || (m_fDiameter && (fabs (pInfo->m_dblDia - dblMeanDia) > dblGapDia)) // ou diamètre incohérent || (fabs (pInfo->m_dblQI)> 0.3) // IQ < 50% Touboul ) ) { pInfo->m_fThrownOut = true; // par défaut à FALSE grâce au memset (on laisse valide pour affichage palette) pInfo->m_fValid = false; m_dwValidPoints--; } pInfo++; } // il se peut que TOUS les points soient à + de 20% de la moyenne // (par exemple deux lignes très éloignées) if (m_dwValidPoints == 0) { m_uErrorID = IDS_EIM_FAILED; return (false); // aucune mesure valide } else { if (m_fDiameter) // Calcul de la moyenne des diamètres trouvés { std::vector arrayAA, arrayII;//il s'agit en faite d'une duplication de m_arVarianceAA et m_arVarianceII double dblMeanAA = 0.0; double dblMeanII = 0.0; double dblVarianceAA = 0.0; double dblVarianceII = 0.0; // recherche du premier profil valide pInfo = &m_pMeasures [0]; for (dw = 0; !pInfo->m_fValid; dw++) { pInfo++; } // on est certain qu'un pInfo valide va être trouvé //----- resultat d'un calcul de diametre [JAK - 13/1/2003] m_dblDiaAAMin = m_dblDiaAAMean = m_dblDiaAAMax = pInfo->m_dblDia; m_dblDiaIIMin = m_dblDiaIIMean = m_dblDiaIIMax = pInfo->m_dblDia - 2 * pInfo->m_dblEIM; arrayII.push_back(g_pCurrentScale->Distance(m_dblDiaIIMean)); arrayAA.push_back(g_pCurrentScale->Distance(m_dblDiaAAMean)); GraphMeanAddMeasure(true, pInfo->m_dblDia); m_arVarianceAA.push_back (g_pCurrentScale->Distance (pInfo->m_dblDia)); m_arVarianceII.push_back (g_pCurrentScale->Distance (pInfo->m_dblDia- 2 * pInfo->m_dblEIM)); dblMeanAA = g_pCurrentScale->Distance (pInfo->m_dblDia); dblMeanII = g_pCurrentScale->Distance (pInfo->m_dblDia - 2 * pInfo->m_dblEIM); dw++,pInfo++; while (dw < m_dwPoints) { if (pInfo->m_fValid) { m_dblDiaAAMean += pInfo->m_dblDia; if (pInfo->m_dblDia < m_dblDiaAAMin) m_dblDiaAAMin = pInfo->m_dblDia; if (pInfo->m_dblDia > m_dblDiaAAMax) m_dblDiaAAMax = pInfo->m_dblDia; dblII = pInfo->m_dblDia - 2 * pInfo->m_dblEIM; m_dblDiaIIMean += dblII; if (dblII < m_dblDiaIIMin) m_dblDiaIIMin = dblII; if (dblII > m_dblDiaIIMax) m_dblDiaIIMax = dblII; // [JAK - 24/6/2002] no histogram window GraphMeanAddMeasure (true, pInfo->m_dblDia); arrayAA.push_back(g_pCurrentScale->Distance(pInfo->m_dblDia)); arrayII.push_back(g_pCurrentScale->Distance(dblII)); m_arVarianceAA.push_back (g_pCurrentScale->Distance (pInfo->m_dblDia)); m_arVarianceII.push_back (g_pCurrentScale->Distance (dblII)); dblMeanAA += g_pCurrentScale->Distance (pInfo->m_dblDia); dblMeanII += g_pCurrentScale->Distance (dblII); } dw++; pInfo++; } /* if (m_vUser.Length () < QI_MIN) { CString msg; msg.Format(MyLoadString(IDS_EIM_QI_0), QI_MIN); CMessage::Info (msg); m_dblQI = 0.; } else*/ m_dblQI = double (m_dwValidPoints) / m_dwPoints; // calcul de la variance dblMeanAA /= (double)m_arVarianceAA.size (); dblMeanII /= (double)m_arVarianceII.size (); dblVarianceAA = 0.0; dblVarianceII = 0.0; for (unsigned int i = 0; i < m_arVarianceAA.size (); i++) { dblVarianceAA += (m_arVarianceAA [i] - dblMeanAA) * (m_arVarianceAA [i] - dblMeanAA); dblVarianceII += (m_arVarianceII [i] - dblMeanII) * (m_arVarianceII [i] - dblMeanII); } dblVarianceAA /= (double)m_arVarianceAA.size (); dblVarianceII /= (double)m_arVarianceII.size (); m_dblDistance = m_vUser.Norm (g_pCurrentScale); m_dblDiaAAMean /= (double)m_dwValidPoints; m_dblDiaIIMean /= (double)m_dwValidPoints; m_dblDiaAAMin = g_pCurrentScale->Distance (m_dblDiaAAMin); m_dblDiaAAMean = g_pCurrentScale->Distance (m_dblDiaAAMean); m_dblDiaAAMax = g_pCurrentScale->Distance (m_dblDiaAAMax); m_dblDiaIIMin = g_pCurrentScale->Distance (m_dblDiaIIMin); m_dblDiaIIMean = g_pCurrentScale->Distance (m_dblDiaIIMean); m_dblDiaIIMax = g_pCurrentScale->Distance (m_dblDiaIIMax); assert(arrayAA.size() == (size_t)m_dwValidPoints); assert(arrayII.size() == (size_t)m_dwValidPoints); //#define OUTPUT #ifdef OUTPUT { // CMeanEstimate::startFileOutput("C:\\diameter.xls"); // CMeanEstimate::titleFileOutput(&arrayAA); // double k=0.02; // FILE* file = fopen_s("C:\\coef.txt", "r"); // if(file!=NULL){ // if(fscanf(file, "%f", k)!=1) k=0.02; // fclose(file); // } // //for(double k=0.01; k<=0.05; k+=0.01) // CMeanEstimate::GetMeanEstimateFileOutput(&arrayAA, k);// Output [JAK - 13/1/2003] // CMeanEstimate::endFileOutput(); CMeanEstimate::getInstance().startFileOutput("diameter.xls"); CMeanEstimate::getInstance().titleFileOutput(&arrayAA); //char title[512];//, text[512]; //theApp.GetMainWnd()->GetWindowText(title, 512); //CMeanEstimate::titleFileOutput(title,&arrayAA, &arrayII); // double k=0.02; // //for(double k=0.01; k<=0.05; k+=0.01) // CMeanEstimate::PrintMeanEstimate(&arrayAA, &arrayII, k);// Output [JAK - 13/1/2003] CMeanEstimate::getInstance().endFileOutput(); //m_dblDiaAAMean = CMeanEstimate::GetMeanEstimate(&arrayAA); //m_dblDiaIIMean = CMeanEstimate::GetMeanEstimate(&arrayAA); m_strInfo.Format ("Mean:%f\tMeanEstimated%f\nQI:%f\n", m_dblDiaAAMean, CMeanEstimate::GetMeanEstimate(&arrayAA), m_dblQI ); } #else // double diam = CMeanEstimate::GetMeanEstimate(&arrayAA); // m_dblDiaAAMean = (CMeanEstimate::IsANumber(diam)? diam : -1); m_dblDiaAAMean = CMeanEstimate::GetMeanEstimate(&arrayAA); m_dblDiaIIMean = CMeanEstimate::GetMeanEstimate(&arrayII); m_dblVarianceAA = dblVarianceAA; m_dblVarianceII = dblVarianceII; PrintDiameter(); #endif if (CDebugOutput::getInstance().currentIndex >= 0) { assert((int)(1000*CMeanEstimate::GetMeanEstimate(&arrayAA)) == (int)(1000*m_dblDiaAAMean)); CDebugOutput::getInstance().m_diameter[CDebugOutput::getInstance().currentIndex] = CMeanEstimate::GetMean(&arrayAA); CDebugOutput::getInstance().m_estimate2[CDebugOutput::getInstance().currentIndex] = CMeanEstimate::GetMeanEstimate(&arrayAA, 0.02); } if (!CMeanEstimate::IsANumber(m_dblDiaAAMean)) { //if is is infinite because of the estimate return false; } } else // de l'EIM { double dblMean = 0.0, dblMeanINT = 0.0; // double dblMeanEIMd = 0.0, dblMeanINTd = 0.0, dblMeanMEDd = 0.0; double dblVariance = 0.0, dblVarianceINT = 0.0; imt::Point invalidPoint, tmpPt; invalidPoint.x = -1; invalidPoint.y = -1; if ( m_res ) { m_res->result->numberOfPoints = m_dwPoints; m_res->allocate_vectors( m_dwPoints ); } // recherche du premier profil valide pInfo = &m_pMeasures [0]; for (dw = 0; !pInfo->m_fValid; dw++) { if ( m_res ) { m_res->result->vect_adventitia[dw] = invalidPoint; m_res->result->vect_media[dw] = invalidPoint; m_res->result->vect_intima[dw] = invalidPoint; } pInfo++; } // on est certain qu'un pInfo valide va être trouvé m_dblEIMMin = m_dblEIMMean = m_dblEIMMax = pInfo->m_dblEIM; m_dblINTMin = m_dblINTMean = m_dblINTMax = pInfo->m_dblINT; m_dblEIMdMean = pInfo->m_dblEIMd; m_dblINTdMean = pInfo->m_dblINTd; m_dblMEDdMean = pInfo->m_dblMEDd; // [JAK - 24/6/2002] no histogram window GraphMeanAddMeasure (true, pInfo->m_dblEIM); m_arVariance.push_back (g_pCurrentScale->Distance (pInfo->m_dblEIM)); m_arVarianceINT.push_back (g_pCurrentScale->Distance (pInfo->m_dblINT)); dblMean = g_pCurrentScale->Distance (pInfo->m_dblEIM); dblMeanINT = g_pCurrentScale->Distance (pInfo->m_dblINT); if ( m_res ) { tmpPt.x = pInfo->m_Paroi[0].m_slope[1].m_ptDraw.x; tmpPt.y = pInfo->m_Paroi[0].m_slope[1].m_ptDraw.y; m_res->result->vect_adventitia[dw] = tmpPt; tmpPt.x = pInfo->m_Paroi[0].m_slope[2].m_ptDraw.x; tmpPt.y = pInfo->m_Paroi[0].m_slope[2].m_ptDraw.y; m_res->result->vect_media[dw] = tmpPt; tmpPt.x = pInfo->m_Paroi[0].m_slope[0].m_ptDraw.x; tmpPt.y = pInfo->m_Paroi[0].m_slope[0].m_ptDraw.y; m_res->result->vect_intima[dw] = tmpPt; } dw++,pInfo++; while (dw < m_dwPoints) { if (pInfo->m_fValid) { m_dblEIMMean += pInfo->m_dblEIM; if (pInfo->m_dblEIM < m_dblEIMMin) m_dblEIMMin = pInfo->m_dblEIM; if (pInfo->m_dblEIM > m_dblEIMMax) m_dblEIMMax = pInfo->m_dblEIM; m_dblINTMean += pInfo->m_dblINT; if (pInfo->m_dblINT < m_dblINTMin) m_dblINTMin = pInfo->m_dblINT; if (pInfo->m_dblINT > m_dblINTMax) m_dblINTMax = pInfo->m_dblINT; m_dblEIMdMean += pInfo->m_dblEIMd; m_dblINTdMean += pInfo->m_dblINTd; m_dblMEDdMean += pInfo->m_dblMEDd; // [JAK - 24/6/2002] no histogram window GraphMeanAddMeasure (true, pInfo->m_dblEIM); m_arVariance.push_back (g_pCurrentScale->Distance (pInfo->m_dblEIM)); m_arVarianceINT.push_back (g_pCurrentScale->Distance (pInfo->m_dblINT)); dblMean += g_pCurrentScale->Distance (pInfo->m_dblEIM); dblMeanINT += g_pCurrentScale->Distance (pInfo->m_dblINT); if ( m_res ) { tmpPt.x = pInfo->m_Paroi[0].m_slope[1].m_ptDraw.x; tmpPt.y = pInfo->m_Paroi[0].m_slope[1].m_ptDraw.y; m_res->result->vect_adventitia[dw] = tmpPt; tmpPt.x = pInfo->m_Paroi[0].m_slope[2].m_ptDraw.x; tmpPt.y = pInfo->m_Paroi[0].m_slope[2].m_ptDraw.y; m_res->result->vect_media[dw] = tmpPt; tmpPt.x = pInfo->m_Paroi[0].m_slope[0].m_ptDraw.x; tmpPt.y = pInfo->m_Paroi[0].m_slope[0].m_ptDraw.y; m_res->result->vect_intima[dw] = tmpPt; } } else { if ( m_res ) { m_res->result->vect_adventitia[dw] = invalidPoint; m_res->result->vect_media[dw] = invalidPoint; m_res->result->vect_intima[dw] = invalidPoint; } } dw++; pInfo++; } if (m_vUser.Length () < QI_MIN) { PrintErrorQIMin(); m_dblQI = 0.; } else { m_dblQI = double (m_dwValidPoints) / m_dwPoints; } // calcul de la variance dblMean /= (double)m_arVariance.size (); dblVariance = 0.0; dblMeanINT /= (double)m_arVarianceINT.size (); dblVarianceINT = 0.0; unsigned int i; for (i = 0; i < m_arVariance.size (); i++) dblVariance += (m_arVariance [i] - dblMean) * (m_arVariance [i] - dblMean); dblVariance /= (double)m_arVariance.size (); for (i = 0; i < m_arVarianceINT.size (); i++) dblVarianceINT += (m_arVarianceINT [i] - dblMeanINT) * (m_arVarianceINT [i] - dblMeanINT); dblVarianceINT /= (double)m_arVarianceINT.size (); m_dblDistance = m_vUser.Norm (g_pCurrentScale); // calcul des valeurs moyennes et mise à l'échelle des normes m_dblEIMMean /= (double)m_dwValidPoints; m_dblEIMMin = g_pCurrentScale->Distance (m_dblEIMMin); m_dblEIMMean = g_pCurrentScale->Distance (m_dblEIMMean); m_dblEIMMax = g_pCurrentScale->Distance (m_dblEIMMax); m_dblINTMean /= (double)m_dwValidPoints; m_dblINTMin = g_pCurrentScale->Distance (m_dblINTMin); m_dblINTMean = g_pCurrentScale->Distance (m_dblINTMean); m_dblINTMax = g_pCurrentScale->Distance (m_dblINTMax); m_dblIA = m_dblINTMean / m_dblEIMMean; m_dblEIMdMean /= (double)m_dwValidPoints; m_dblINTdMean /= (double)m_dwValidPoints; m_dblMEDdMean /= (double)m_dwValidPoints; m_dblIAd = m_dblINTdMean / m_dblEIMdMean; //#ifdef VERSION_SHOW_VARIANCE // [JAK - 17/6/2002] m_dblVariance = dblVariance; //#endif // [JAK - 4/7/2002] IDS_EIM_RESULT4 = \tE .I.M.\nI.Q.: \t%.2f\nMaximale : \t%.3f mm\nMoyenne : \t%.3f mm\nEcart Type : \t%.3f mm\nMesures Valides :\td% PrintResult(); if ( m_res ) { m_res->result->imt_max = m_dblEIMMax; m_res->result->imt_mean = m_dblEIMMean; m_res->result->imt_standardDeviation = sqrt (dblVariance); m_res->result->intima_mean = m_dblINTMean; m_res->result->media_mean = m_dblEIMMean - m_dblINTMean; m_res->result->qualityIndex = m_dblQI; } } return (true); } }catch(...){ // [JAK - 5/7/2002] assert(0); // not very clean to catch any exception, but it is at least efficient //TODO change the "catch(...)" for something more precise like "catch(Float Divide by Zero)" m_uErrorID = IDS_EIM_FAILED; return (false); // aucune mesure valide } } } } // end of Update /*----------------------------------------------------------\ | ComputeEIM | |-----------------------------------------------------------| | DESCRIPTION : | | calcule l'épaisseur EIM et la distance au vecteur tracé | | par l'utilisateur à l'aide points détectés | |-----------------------------------------------------------| | PARAMETRES : | | pInfo : une info sur un segment de paroi | \----------------------------------------------------------*/ void CEIMBase::ComputeEIM (CEIMInfo *pInfo) { pInfo->m_fValid = true; // en approximation, m_ptMiddle [0] suffit largement pour la distance, qui n'intervient // dans la mesure que pour écarter les points trop éloignés de la moyenne pInfo->m_dblDist = CVector (pInfo->m_Paroi [0].m_slope [0].m_ptMiddle, pInfo->m_Paroi [0].m_vMeasure [0]).Norm (); pInfo->m_dblEIM = CVector (pInfo->m_Paroi [0].m_slope [0].m_ptMiddle, pInfo->m_Paroi [0].m_slope [1].m_ptMiddle).Norm () - pInfo->m_Paroi [0].m_slope [0].m_dblMiddle + pInfo->m_Paroi [0].m_slope [1].m_dblMiddle; pInfo->m_dblINT = CVector (pInfo->m_Paroi [0].m_slope [0].m_ptMiddle, pInfo->m_Paroi [0].m_slope [2].m_ptMiddle).Norm () - pInfo->m_Paroi [0].m_slope [0].m_dblMiddle + pInfo->m_Paroi [0].m_slope [2].m_dblMiddle; // dans le cas de la paroi, on additionne les deux écarts, car la paroi du bas est mesuré // dans le sens opposé à la paroi du haut if (m_fDiameter) { pInfo->m_dblDia = CVector (pInfo->m_Paroi [0].m_slope [1].m_ptMiddle, pInfo->m_Paroi [1].m_slope [1].m_ptMiddle).Norm () + pInfo->m_Paroi [0].m_slope [1].m_dblMiddle + pInfo->m_Paroi [1].m_slope [1].m_dblMiddle; } else // Calcul des moyennes des intensites entre les differents points { pInfo->m_dblEIMd = pInfo->m_dblINTd = pInfo->m_dblMEDd = 0.0; long dw, nb=0, nbTot=0; for ( dw=pInfo->m_Paroi[0].m_slope[0].m_dwMiddle; dwm_Paroi[0].m_slope[2].m_dwMiddle; dw++ ) { nb++; nbTot++; pInfo->m_dblEIMd += m_bBuffer[dw]; pInfo->m_dblINTd += m_bBuffer[dw]; } if (nb != 0 ) // Contre la division par 0 { pInfo->m_dblINTd /= (double) nb; } else { pInfo->m_dblINTd = 0; } nb = 0; for ( dw=pInfo->m_Paroi[0].m_slope[2].m_dwMiddle; dwm_Paroi[0].m_slope[1].m_dwMiddle; dw++ ) { nb++; nbTot++; pInfo->m_dblEIMd += m_bBuffer[dw]; pInfo->m_dblMEDd += m_bBuffer[dw]; } if (nb != 0) // Contre la division par 0 { pInfo->m_dblMEDd /= (double) nb; } else { pInfo->m_dblMEDd = 0; } if (nbTot != 0) // Contre la division par 0 { pInfo->m_dblEIMd /= (double) nbTot; } else { pInfo->m_dblEIMd = 0; } } } // fin de ComputeEIM /*----------------------------------------------------------\ | Direction | |-----------------------------------------------------------| | DESCRIPTION : | | Retourne la direction de la droite approximée constituée | | d'après l'ensemble des points mesurés! | \----------------------------------------------------------*/ double CEIMBase::Direction() { double r = 0; double x = 0, y = 0; Point LastPoint( 0, 0 ); double Valide = 0; double sx, sx2, sy, sy2; int nbPoints; double xmin, xmax, ymin, ymax; double moyx, moyy, stdx, stdy; if (m_pMeasures) { sx = sx2 = sy = sy2 = 0.0; nbPoints = 0; // Calcul de la somme des x et des y et de la somme des x2 et des y2 CEIMInfo *pInfo = &m_pMeasures[0]; for (long dw = 0; dw < m_dwPoints; dw++) { if (pInfo->m_fValid) { if (dw) { x = (pInfo->m_Paroi[0].m_slope[0].m_ptDraw.x - LastPoint.x); y = (pInfo->m_Paroi[0].m_slope[0].m_ptDraw.y - LastPoint.y); if (x) { sx += x; sx2 += (x * x); sy += y; sy2 += (y * y); nbPoints++; } } LastPoint = pInfo->m_Paroi[0].m_slope[0].m_ptDraw; } pInfo++; } // Calcul des moyennes et des écarts types if (nbPoints != 0) { moyx = sx / (double) nbPoints; moyy = sy / (double) nbPoints; stdx = sqrt( (sx2 / (double) nbPoints) - sx * sx); stdy = sqrt( (sy2 / (double) nbPoints) - sy * sy); xmin = moyx - 2 * stdx; xmax = moyx + 2 * stdx; ymin = moyy - 2 * stdy; ymax = moyy + 2 * stdy; } pInfo = &m_pMeasures[0]; for (long dw = 0; dw < m_dwPoints; dw++) { if (pInfo->m_fValid) { if (dw) { x = (pInfo->m_Paroi[0].m_slope[0].m_ptDraw.x - LastPoint.x); y = (pInfo->m_Paroi[0].m_slope[0].m_ptDraw.y - LastPoint.y); if (x) { // if ((x >= xmin) && (x <= xmax) && (y >= ymin) && (y <= ymax)) { r += y / x; Valide++; } } } LastPoint = pInfo->m_Paroi[0].m_slope [0].m_ptDraw; } pInfo++; } } if (Valide < 20) return 0; // Nombre de points valides insuffisants Point PtDirect(100, (int) (100.0 * r / Valide)); PtDirect.Offset(m_vUser.StartPoint()); if ( (m_vUser.Angle(PtDirect) > 20) && (m_vUser.Angle(PtDirect) < 160) ) return 0; return (r / Valide); } unsigned int CEIMBase::GetErrorID(void) { return m_uErrorID; }