コード例 #1
0
ファイル: tclear.c プロジェクト: Magatte/ft_select
int				tclear(void)
{
	char		*res;

	if ((res = tgetstr("cd", NULL)) == NULL)
	{
		ft_putstr_fd("Attempt to clear the term failed", 2);
		return (-1);
	}
	tho();
	tputs(res, 2, ft_outc);
	tvi();
	return (0);
}
コード例 #2
0
//---------------------------------------------------------
DMat& NDG2D::ConformingHrefine2D(IMat& edgerefineflag, const DMat& Qin)
//---------------------------------------------------------
{
#if (0)
  OutputNodes(false); // volume nodes
//OutputNodes(true);  // face nodes
#endif


  // function newQ = ConformingHrefine2D(edgerefineflag, Q)
  // Purpose: apply edge splits as requested by edgerefineflag

  IVec v1("v1"), v2("v2"), v3("v3"), tvi;
  DVec x1("x1"), x2("x2"), x3("x3"), y1("y1"), y2("y2"), y3("y3");
  DVec a1("a1"), a2("a2"), a3("a3");

  // count vertices
  assert (VX.size() == Nv);

  // find vertex triplets for elements to be refined
  v1 = EToV(All,1);  v2 = EToV(All,2);  v3 = EToV(All,3);
  x1 = VX(v1);       x2 = VX(v2);       x3 = VX(v3);
  y1 = VY(v1);       y2 = VY(v2);       y3 = VY(v3);

  // find angles at each element vertex (in radians)
  VertexAngles(x1,x2,x3,y1,y2,y3, a1,a2,a3);

  // absolute value of angle size
  a1.set_abs(); a2.set_abs(); a3.set_abs();

  int k=0,k1=0,f1=0,k2=0,f2=0, e1=0,e2=0,e3=0, b1=0,b2=0,b3=0, ref=0;
  IVec m1,m2,m3; DVec mx1, my1, mx2, my2, mx3, my3;

  // create new vertices at edge centers of marked elements 
  // (use unique numbering derived from unique edge number))
  m1 = max(IVec(Nv*(v1-1)+v2+1), IVec(Nv*(v2-1)+v1+1)); mx1=0.5*(x1+x2); my1=0.5*(y1+y2);
  m2 = max(IVec(Nv*(v2-1)+v3+1), IVec(Nv*(v3-1)+v2+1)); mx2=0.5*(x2+x3); my2=0.5*(y2+y3);
  m3 = max(IVec(Nv*(v1-1)+v3+1), IVec(Nv*(v3-1)+v1+1)); mx3=0.5*(x3+x1); my3=0.5*(y3+y1);

  // ensure that both elements sharing an edge are split
  for (k1=1; k1<=K; ++k1) {
    for (f1=1; f1<=Nfaces; ++f1) {
      if (edgerefineflag(k1,f1)) {
        k2 = EToE(k1,f1); 
        f2 = EToF(k1,f1);
        edgerefineflag(k2,f2) = 1;
      }
    }
  }

  // store old data
  IMat oldEToV = EToV;  DVec oldVX = VX, oldVY = VY; 

  // count the number of elements in the refined mesh
  int newK = countrefinefaces(edgerefineflag);
  EToV.resize(newK, Nfaces, true, 0);
  IMat newBCType(newK,3, "newBCType");
  
  //   kold = [];
  IVec kold(newK, "kold");  Index1D KI,KIo;

  int sk=1, skstart=0, skend=0;

  for (k=1; k<=K; ++k)
  {
    skstart = sk;

    e1 = edgerefineflag(k,1); b1 = BCType(k,1);
    e2 = edgerefineflag(k,2); b2 = BCType(k,2);
    e3 = edgerefineflag(k,3); b3 = BCType(k,3);
    ref = e1 + 2*e2 + 4*e3;
    
    switch (ref) {

    case 0: 
      EToV(sk, All) = IVec(v1(k),v2(k),v3(k));    newBCType(sk,All) = IVec(b1, b2, b3); ++sk;
      break;

    case 1:
      EToV(sk, All) = IVec(v1(k),m1(k),v3(k));    newBCType(sk,All) = IVec(b1,  0, b3); ++sk;
      EToV(sk, All) = IVec(m1(k),v2(k),v3(k));    newBCType(sk,All) = IVec(b1, b2,  0); ++sk;
      break;

    case 2:
      EToV(sk, All) = IVec(v2(k),m2(k),v1(k));    newBCType(sk,All) = IVec(b2,  0, b1); ++sk;
      EToV(sk, All) = IVec(m2(k),v3(k),v1(k));    newBCType(sk,All) = IVec(b2, b3,  0); ++sk;
      break;

    case 4:
      EToV(sk, All) = IVec(v3(k),m3(k),v2(k));    newBCType(sk,All) = IVec(b3,  0, b2); ++sk;
      EToV(sk, All) = IVec(m3(k),v1(k),v2(k));    newBCType(sk,All) = IVec(b3, b1,  0); ++sk;
      break;

    case 3:
      EToV(sk, All) = IVec(m1(k),v2(k),m2(k));    newBCType(sk,All) = IVec(b1, b2,  0); ++sk;
      if (a1(k) > a3(k)) { // split largest angle
        EToV(sk, All) = IVec(v1(k),m1(k),m2(k));  newBCType(sk,All) = IVec(b1,  0,  0); ++sk;
        EToV(sk, All) = IVec(v1(k),m2(k),v3(k));  newBCType(sk,All) = IVec( 0, b2, b3); ++sk;
      } else {
        EToV(sk, All) = IVec(v3(k),m1(k),m2(k));  newBCType(sk,All) = IVec( 0,  0, b2); ++sk;
        EToV(sk, All) = IVec(v3(k),v1(k),m1(k));  newBCType(sk,All) = IVec(b3, b1,  0); ++sk;
      }
      break;

    case 5:
      EToV(sk, All) = IVec(v1(k),m1(k),m3(k));    newBCType(sk,All) = IVec(b1,  0, b3); ++sk;
      if (a2(k) > a3(k)) { 
        // split largest angle
        EToV(sk, All) = IVec(v2(k),m3(k),m1(k));  newBCType(sk,All) = IVec( 0,  0, b1); ++sk;
        EToV(sk, All) = IVec(v2(k),v3(k),m3(k));  newBCType(sk,All) = IVec(b2, b3,  0); ++sk;
      } else {
        EToV(sk, All) = IVec(v3(k),m3(k),m1(k));  newBCType(sk,All) = IVec(b3,  0,  0); ++sk;
        EToV(sk, All) = IVec(v3(k),m1(k),v2(k));  newBCType(sk,All) = IVec( 0, b1, b2); ++sk;
      }
      break;

    case 6:
      EToV(sk, All) = IVec(v3(k),m3(k),m2(k));    newBCType(sk,All) = IVec(b3,  0, b2); ++sk;
      if (a1(k) > a2(k)) { 
        // split largest angle
        EToV(sk, All) = IVec(v1(k),m2(k),m3(k));  newBCType(sk,All) = IVec( 0, 0, b3); ++sk;
        EToV(sk, All) = IVec(v1(k),v2(k),m2(k));  newBCType(sk,All) = IVec(b1, b2,  0); ++sk;
      } else {
        EToV(sk, All) = IVec(v2(k),m2(k),m3(k));  newBCType(sk,All) = IVec(b2,  0,  0); ++sk;
        EToV(sk, All) = IVec(v2(k),m3(k),v1(k));  newBCType(sk,All) = IVec( 0 , b3, b1); ++sk;
      }
      break;

    default:
      // split all 
      EToV(sk, All) = IVec(m1(k),m2(k),m3(k)); newBCType(sk, All) = IVec( 0, 0,  0); ++sk;
      EToV(sk, All) = IVec(v1(k),m1(k),m3(k)); newBCType(sk, All) = IVec(b1, 0, b3); ++sk;
      EToV(sk, All) = IVec(v2(k),m2(k),m1(k)); newBCType(sk, All) = IVec(b2, 0, b1); ++sk;
      EToV(sk, All) = IVec(v3(k),m3(k),m2(k)); newBCType(sk, All) = IVec(b3, 0, b2); ++sk;
      break;
    }
    
    skend = sk;

    // kold = [kold; k*ones(skend-skstart, 1)];

    // element k is to be refined into (1:4) child elements.
    // store parent element numbers in array "kold" to help 
    // with accessing parent vertex data during refinement.

    KI.reset(skstart, skend-1); // ids of child elements
    kold(KI) = k;               // mark as children of element k
  }

  // Finished with edgerefineflag.  Delete if OBJ_temp
  if (edgerefineflag.get_mode() == OBJ_temp) { 
    delete (&edgerefineflag); 
  }


  // renumber new nodes contiguously
  // ids = unique([v1;v2;v3;m1;m2;m3]);
  bool unique=true; IVec IDS, ids;
  IDS = concat( concat(v1,v2,v3), concat(m1,m2,m3) );
  ids = sort(IDS, unique);
  Nv = ids.size();

  int max_id = EToV.max_val();
  umMSG(1, "max id in EToV is %d\n", max_id);

  //         M     N   nnz vals triplet
  CSi newids(max_id,1, Nv,  1,    1  );
  //  newids = sparse(max(max(EToV)),1);

  int i=0, j=1;
  for (i=1; i<=Nv; ++i) {
  //     newids(ids)= (1:Nv);
    newids.set1(ids(i),j, i);   // load 1-based triplets
  }          // row   col x
  newids.compress();            // convert to csc form


  // Matlab -----------------------------------------------
  // v1 = newids(v1); v2 = newids(v2); v3 = newids(v3);
  // m1 = newids(m1); m2 = newids(m2); m3 = newids(m3);
  //-------------------------------------------------------

  int KVi=v1.size(), KMi=m1.size();
  // read from copies, overwrite originals 
  
  // 1. reload ids for new vertices
  tvi = v1;  for (i=1;i<=KVi;++i) {v1(i) = newids(tvi(i), 1);}
  tvi = v2;  for (i=1;i<=KVi;++i) {v2(i) = newids(tvi(i), 1);}
  tvi = v3;  for (i=1;i<=KVi;++i) {v3(i) = newids(tvi(i), 1);}

  // 2. load ids for new (midpoint) vertices
  tvi = m1;  for (i=1;i<=KMi;++i) {m1(i) = newids(tvi(i), 1);}
  tvi = m2;  for (i=1;i<=KMi;++i) {m2(i) = newids(tvi(i), 1);}
  tvi = m3;  for (i=1;i<=KMi;++i) {m3(i) = newids(tvi(i), 1);}

  VX.resize(Nv); VY.resize(Nv);
  VX(v1) =  x1; VX(v2) =  x2; VX(v3) =  x3;
  VY(v1) =  y1; VY(v2) =  y2; VY(v3) =  y3;
  VX(m1) = mx1; VX(m2) = mx2; VX(m3) = mx3;
  VY(m1) = my1; VY(m2) = my2; VY(m3) = my3;


  if (newK != (sk-1)) {
    umERROR("NDG2D::ConformingHrefine2D", "Inconsistent element count: expect %d, but sk = %d", newK, (sk-1));
  } else {
    K = newK; // sk-1;
  }

  // dumpIMat(EToV, "EToV (before)");

  // EToV = newids(EToV);
  for (j=1; j<=3; ++j) {
    for (k=1; k<=K; ++k) {
      EToV(k,j) = newids(EToV(k,j), 1);
    }
  }

#if (0)
  dumpIMat(EToV, "EToV (after)");
  // umERROR("Checking ids", "Nigel, check EToV");
#endif


  BCType = newBCType;

  Nv = VX.size();
  // xold = x; yold = y;

  StartUp2D();


#if (1)
  OutputNodes(false); // volume nodes
//OutputNodes(true);  // face nodes
//umERROR("Exiting early", "Check adapted {volume,face} nodes");
#endif


  // allocate return object
  int Nfields = Qin.num_cols();
  DMat* tmpQ = new DMat(Np*K, Nfields, "newQ", OBJ_temp);
  DMat& newQ = *tmpQ;  // use a reference for syntax

  // quick return, if no interpolation is required
  if (Qin.size()<1) {
    return newQ;
  }

  
  DVec rOUT(Np),sOUT(Np),xout,yout,xy1(2),xy2(2),xy3(2),tmp(2),rhs;
  int ko=0,kv1=0,kv2=0,kv3=0,n=0;  DMat A(2,2), interp;
  DMat oldQ = const_cast<DMat&>(Qin);

  for (k=1; k<=K; ++k)
  {
    ko = kold(k); xout = x(All,k); yout = y(All,k);
    kv1=oldEToV(ko,1); kv2=oldEToV(ko,2); kv3=oldEToV(ko,3);
    xy1.set(oldVX(kv1), oldVY(kv1));
    xy2.set(oldVX(kv2), oldVY(kv2));
    xy3.set(oldVX(kv3), oldVY(kv3));
    A.set_col(1, xy2-xy1); A.set_col(2, xy3-xy1);
    
    for (i=1; i<=Np; ++i) {
      tmp.set(xout(i), yout(i));
      rhs = 2.0*tmp - xy2 - xy3;
      tmp = A|rhs;
      rOUT(i) = tmp(1);
      sOUT(i) = tmp(2);
    }

    KI.reset (Np*(k -1)+1, Np*k );  // nodes in new element k
    KIo.reset(Np*(ko-1)+1, Np*ko);  // nodes in old element ko

    interp = Vandermonde2D(N, rOUT, sOUT)*invV;

    for (n=1; n<=Nfields; ++n) 
    {
    //newQ(:,k,n)= interp*  Q(:,ko,n);
      //DVec tm1 = interp*oldQ(KIo,n);
      //dumpDVec(tm1, "tm1");
      newQ(KI,n) = interp*oldQ(KIo,n);
    }
  }
    
  return newQ;
}
コード例 #3
0
ファイル: Metric.cpp プロジェクト: LawrenceWeng/openholdembot
bool Yee_Compare(CompareArgs &args) {
	if ((args.ImgA->Get_Width() != args.ImgB->Get_Width()) ||
		(args.ImgA->Get_Height() != args.ImgB->Get_Height())) {
    // args.ErrorStr = "Image dimensions do not match\n";
		args.PixelsFailed = 0xffffffff;
		return false;
	}
	int dim = args.ImgA->Get_Width() * args.ImgA->Get_Height();
	bool identical = true;
	for (int i = 0; i < dim; i++) {
		if (args.ImgA->Get(i) != args.ImgB->Get(i)) {
		  identical = false;
		  break;
		}
	}
	if (identical) {
    // args.ErrorStr = "Images are binary identical\n";
		args.PixelsFailed = 0;
		return true;
	}
	// assuming colorspaces are in Adobe RGB (1998) convert to XYZ
	float *aX = new float[dim];
	float *aY = new float[dim];
	float *aZ = new float[dim];
	float *bX = new float[dim];
	float *bY = new float[dim];
	float *bZ = new float[dim];
	float *aLum = new float[dim];
	float *bLum = new float[dim];
	
	float *aA = new float[dim];
	float *bA = new float[dim];
	float *aB = new float[dim];
	float *bB = new float[dim];

	if (args.Verbose) printf("Converting RGB to XYZ\n");
	
	unsigned int x, y, w, h;
	w = args.ImgA->Get_Width();
	h = args.ImgA->Get_Height();
	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			float r, g, b, l;
			int i = x + y * w;
			r = powf(args.ImgA->Get_Red(i) / 255.0f, args.Gamma);
			g = powf(args.ImgA->Get_Green(i) / 255.0f, args.Gamma);
			b = powf(args.ImgA->Get_Blue(i) / 255.0f, args.Gamma);						
			AdobeRGBToXYZ(r,g,b,aX[i],aY[i],aZ[i]);			
			XYZToLAB(aX[i], aY[i], aZ[i], l, aA[i], aB[i]);
			r = powf(args.ImgB->Get_Red(i) / 255.0f, args.Gamma);
			g = powf(args.ImgB->Get_Green(i) / 255.0f, args.Gamma);
			b = powf(args.ImgB->Get_Blue(i) / 255.0f, args.Gamma);						
			AdobeRGBToXYZ(r,g,b,bX[i],bY[i],bZ[i]);
			XYZToLAB(bX[i], bY[i], bZ[i], l, bA[i], bB[i]);
			aLum[i] = aY[i] * args.Luminance;
			bLum[i] = bY[i] * args.Luminance;
		}
	}
	
	if (args.Verbose) printf("Constructing Laplacian Pyramids\n");
	
	LPyramid *la = new LPyramid(aLum, w, h);
	LPyramid *lb = new LPyramid(bLum, w, h);
	
	float num_one_degree_pixels = (float) (2 * tan( args.FieldOfView * 0.5 * M_PI / 180) * 180 / M_PI);
	float pixels_per_degree = w / num_one_degree_pixels;
	
	if (args.Verbose) printf("Performing test\n");
	
	float num_pixels = 1;
	unsigned int adaptation_level = 0;
	for (int i = 0; i < MAX_PYR_LEVELS; i++) {
		adaptation_level = i;
		if (num_pixels > num_one_degree_pixels) break;
		num_pixels *= 2;
	}
	
	float cpd[MAX_PYR_LEVELS];
	cpd[0] = 0.5f * pixels_per_degree;
	for (int i = 1; i < MAX_PYR_LEVELS; i++) cpd[i] = 0.5f * cpd[i - 1];
	float csf_max = csf(3.248f, 100.0f);
	
	float F_freq[MAX_PYR_LEVELS - 2];
	for (int i = 0; i < MAX_PYR_LEVELS - 2; i++) F_freq[i] = csf_max / csf( cpd[i], 100.0f);
	
	unsigned int pixels_failed = 0;
	for (y = 0; y < h; y++) {
	  for (x = 0; x < w; x++) {
		int index = x + y * w;
		float contrast[MAX_PYR_LEVELS - 2];
		float sum_contrast = 0;
		for (int i = 0; i < MAX_PYR_LEVELS - 2; i++) {
			float n1 = fabsf(la->Get_Value(x,y,i) - la->Get_Value(x,y,i + 1));
			float n2 = fabsf(lb->Get_Value(x,y,i) - lb->Get_Value(x,y,i + 1));
			float numerator = (n1 > n2) ? n1 : n2;
			float d1 = fabsf(la->Get_Value(x,y,i+2));
			float d2 = fabsf(lb->Get_Value(x,y,i+2));
			float denominator = (d1 > d2) ? d1 : d2;
			if (denominator < 1e-5f) denominator = 1e-5f;
			contrast[i] = numerator / denominator;
			sum_contrast += contrast[i];
		}
		if (sum_contrast < 1e-5) sum_contrast = 1e-5f;
		float F_mask[MAX_PYR_LEVELS - 2];
		float adapt = la->Get_Value(x,y,adaptation_level) + lb->Get_Value(x,y,adaptation_level);
		adapt *= 0.5f;
		if (adapt < 1e-5) adapt = 1e-5f;
		for (int i = 0; i < MAX_PYR_LEVELS - 2; i++) {
			F_mask[i] = mask(contrast[i] * csf(cpd[i], adapt)); 
		}
		float factor = 0;
		for (int i = 0; i < MAX_PYR_LEVELS - 2; i++) {
			factor += contrast[i] * F_freq[i] * F_mask[i] / sum_contrast;
		}
		if (factor < 1) factor = 1;
		if (factor > 10) factor = 10;
		float delta = fabsf(la->Get_Value(x,y,0) - lb->Get_Value(x,y,0));
		bool pass = true;
		// pure luminance test
		if (delta > factor * tvi(adapt)) {
			pass = false;
		} else {
			// CIE delta E test with modifications
			float color_scale = 1.0f;
			// ramp down the color test in scotopic regions
			if (adapt < 10.0f) {
				color_scale = 1.0f - (10.0f - color_scale) / 10.0f;
				color_scale = color_scale * color_scale;
			}
			float da = aA[index] - bA[index];
			float db = aB[index] - bB[index];
			da = da * da;
			db = db * db;
			float delta_e = (da + db) * color_scale;
			if (delta_e > factor) {
				pass = false;
			}
		}
		if (!pass) {
			pixels_failed++;
//			if (args.ImgDiff) {
//				args.ImgDiff->Set(255, 0, 0, 255, index);
//			}
		} else {
//			if (args.ImgDiff) {
//				args.ImgDiff->Set(0, 0, 0, 255, index);
//			}
		}
	  }
	}
	
	if (aX) delete[] aX;
	if (aY) delete[] aY;
	if (aZ) delete[] aZ;
	if (bX) delete[] bX;
	if (bY) delete[] bY;
	if (bZ) delete[] bZ;
	if (aLum) delete[] aLum;
	if (bLum) delete[] bLum;
	if (la) delete la;
	if (lb) delete lb;
	if (aA) delete aA;
	if (bA) delete bA;
	if (aB) delete aB;
	if (bB) delete bB;
	
	args.PixelsFailed = pixels_failed;
	return true;

/*
	if (pixels_failed < args.ThresholdPixels) {
		args.ErrorStr = "Images are perceptually indistinguishable\n";
		return true;
	}
	
	char different[100];
	sprintf(different, "%d pixels are different\n", pixels_failed);

	args.ErrorStr = "Images are visibly different\n";
	args.ErrorStr += different;
	
	if (args.ImgDiff) {
		if (args.ImgDiff->WritePPM()) {
			args.ErrorStr += "Wrote difference image to ";
			args.ErrorStr+= args.ImgDiff->Get_Name();
			args.ErrorStr += "\n";
		} else {
			args.ErrorStr += "Could not write difference image to ";
			args.ErrorStr+= args.ImgDiff->Get_Name();
			args.ErrorStr += "\n";
		}
	}
	return false;
*/
}
コード例 #4
0
ファイル: metric.cpp プロジェクト: hylhero/perceptualdiff
bool yee_compare(CompareArgs &args)
{
    if ((args.image_a_->get_width()  != args.image_b_->get_width()) or
        (args.image_a_->get_height() != args.image_b_->get_height()))
    {
        args.error_string_ = "Image dimensions do not match\n";
        return false;
    }

    const auto w = args.image_a_->get_width();
    const auto h = args.image_a_->get_height();
    const auto dim = w * h;

    auto identical = true;
    for (auto i = 0u; i < dim; i++)
    {
        if (args.image_a_->get(i) != args.image_b_->get(i))
        {
            identical = false;
            break;
        }
    }
    if (identical)
    {
        args.error_string_ = "Images are binary identical\n";
        return true;
    }

    // Assuming colorspaces are in Adobe RGB (1998) convert to XYZ.
    std::vector<float> a_lum(dim);
    std::vector<float> b_lum(dim);

    std::vector<float> a_a(dim);
    std::vector<float> b_a(dim);
    std::vector<float> a_b(dim);
    std::vector<float> b_b(dim);

    if (args.verbose_)
    {
        std::cout << "Converting RGB to XYZ\n";
    }

    const auto gamma = args.gamma_;
    const auto luminance = args.luminance_;

    #pragma omp parallel for shared(args, a_lum, b_lum, a_a, a_b, b_a, b_b)
    for (auto y = 0; y < static_cast<ptrdiff_t>(h); y++)
    {
        for (auto x = 0u; x < w; x++)
        {
            const auto i = x + y * w;
            const auto a_color_r = powf(args.image_a_->get_red(i) / 255.0f,
                                        gamma);
            const auto a_color_g = powf(args.image_a_->get_green(i) / 255.0f,
                                        gamma);
            const auto a_color_b = powf(args.image_a_->get_blue(i) / 255.0f,
                                        gamma);
            float a_x;
            float a_y;
            float a_z;
            adobe_rgb_to_xyz(a_color_r, a_color_g, a_color_b, a_x, a_y, a_z);
            float l;
            xyz_to_lab(a_x, a_y, a_z, l, a_a[i], a_b[i]);
            const auto b_color_r = powf(args.image_b_->get_red(i) / 255.0f,
                                        gamma);
            const auto b_color_g = powf(args.image_b_->get_green(i) / 255.0f,
                                        gamma);
            const auto b_color_b = powf(args.image_b_->get_blue(i) / 255.0f,
                                        gamma);
            float b_x;
            float b_y;
            float b_z;
            adobe_rgb_to_xyz(b_color_r, b_color_g, b_color_b, b_x, b_y, b_z);
            xyz_to_lab(b_x, b_y, b_z, l, b_a[i], b_b[i]);
            a_lum[i] = a_y * luminance;
            b_lum[i] = b_y * luminance;
        }
    }

    if (args.verbose_)
    {
        std::cout << "Constructing Laplacian Pyramids\n";
    }

    const LPyramid la(a_lum, w, h);
    const LPyramid lb(b_lum, w, h);

    const auto num_one_degree_pixels =
        to_degrees(2 *
                   std::tan(args.field_of_view_ * to_radians(.5f)));
    const auto pixels_per_degree = w / num_one_degree_pixels;

    if (args.verbose_)
    {
        std::cout << "Performing test\n";
    }

    const auto adaptation_level = adaptation(num_one_degree_pixels);

    float cpd[MAX_PYR_LEVELS];
    cpd[0] = 0.5f * pixels_per_degree;
    for (auto i = 1u; i < MAX_PYR_LEVELS; i++)
    {
        cpd[i] = 0.5f * cpd[i - 1];
    }
    const auto csf_max = csf(3.248f, 100.0f);

    static_assert(MAX_PYR_LEVELS > 2,
                  "MAX_PYR_LEVELS must be greater than 2");

    float f_freq[MAX_PYR_LEVELS - 2];
    for (auto i = 0u; i < MAX_PYR_LEVELS - 2; i++)
    {
        f_freq[i] = csf_max / csf(cpd[i], 100.0f);
    }

    auto pixels_failed = 0u;
    auto error_sum = 0.;

    #pragma omp parallel for reduction(+ : pixels_failed, error_sum) \
        shared(args, a_a, a_b, b_a, b_b, cpd, f_freq)
    for (auto y = 0; y < static_cast<ptrdiff_t>(h); y++)
    {
        for (auto x = 0u; x < w; x++)
        {
            const auto index = y * w + x;
            const auto adapt = std::max((la.get_value(x, y, adaptation_level) +
                                         lb.get_value(x, y, adaptation_level)) * 0.5f,
                                        1e-5f);
            auto sum_contrast = 0.f;
            auto factor = 0.f;
            for (auto i = 0u; i < MAX_PYR_LEVELS - 2; i++)
            {
                const auto n1 =
                    fabsf(la.get_value(x, y, i) - la.get_value(x, y, i + 1));
                const auto n2 =
                    fabsf(lb.get_value(x, y, i) - lb.get_value(x, y, i + 1));
                const auto numerator = std::max(n1, n2);
                const auto d1 = fabsf(la.get_value(x, y, i + 2));
                const auto d2 = fabsf(lb.get_value(x, y, i + 2));
                const auto denominator = std::max(std::max(d1, d2), 1e-5f);
                const auto contrast = numerator / denominator;
                const auto f_mask = mask(contrast * csf(cpd[i], adapt));
                factor += contrast * f_freq[i] * f_mask;
                sum_contrast += contrast;
            }
            sum_contrast = std::max(sum_contrast, 1e-5f);
            factor /= sum_contrast;
            factor = std::min(std::max(factor, 1.f), 10.f);
            const auto delta =
                fabsf(la.get_value(x, y, 0) - lb.get_value(x, y, 0));
            error_sum += delta;
            auto pass = true;

            // pure luminance test
            if (delta > factor * tvi(adapt))
            {
                pass = false;
            }

            if (not args.luminance_only_)
            {
                // CIE delta E test with modifications
                auto color_scale = args.color_factor_;
                // ramp down the color test in scotopic regions
                if (adapt < 10.0f)
                {
                    // Don't do color test at all.
                    color_scale = 0.0;
                }
                const auto da = a_a[index] - b_a[index];
                const auto db = a_b[index] - b_b[index];
                const auto delta_e = (da * da + db * db) * color_scale;
                error_sum += delta_e;
                if (delta_e > factor)
                {
                    pass = false;
                }
            }

            if (not pass)
            {
                pixels_failed++;
                if (args.image_difference_)
                {
                    args.image_difference_->set(255, 0, 0, 255, index);
                }
            }
            else
            {
                if (args.image_difference_)
                {
                    args.image_difference_->set(0, 0, 0, 255, index);
                }
            }
        }
    }

    const auto error_sum_buff =
        std::to_string(error_sum) + " error sum\n";

    const auto different =
        std::to_string(pixels_failed) + " pixels are different\n";

    // Always output image difference if requested.
    if (args.image_difference_)
    {
        args.image_difference_->write_to_file(args.image_difference_->get_name());

        args.error_string_ += "Wrote difference image to ";
        args.error_string_ += args.image_difference_->get_name();
        args.error_string_ += "\n";
    }

    if (pixels_failed < args.threshold_pixels_)
    {
        args.error_string_ = "Images are perceptually indistinguishable\n";
        args.error_string_ += different;
        return true;
    }

    args.error_string_ = "Images are visibly different\n";
    args.error_string_ += different;
    if (args.sum_errors_)
    {
        args.error_string_ += error_sum_buff;
    }

    return false;
}