Exemplo n.º 1
0
int fit_sip_wcs(const double* starxyz,
                const double* fieldxy,
                const double* weights,
                int M,
                const tan_t* tanin1,
                int sip_order,
                int inv_order,
                sip_t* sipout) {
	int sip_coeffs;
	double xyzcrval[3];
	double cdinv[2][2];
	double sx, sy, sU, sV, su, sv;
	int N;
	int i, j, p, q, order;
	double totalweight;
	int rtn;
	gsl_matrix *mA;
	gsl_vector *b1, *b2, *x1, *x2;
	gsl_vector *r1=NULL, *r2=NULL;
    tan_t tanin2;
    int ngood;
    const tan_t* tanin = &tanin2;
	// We need at least the linear terms to compute CD.
	if (sip_order < 1)
		sip_order = 1;

    // convenience: allow the user to call like:
    //    fit_sip_wcs(... &(sipout.wcstan), ..., sipout);
    memcpy(&tanin2, tanin1, sizeof(tan_t));

    memset(sipout, 0, sizeof(sip_t));
    memcpy(&(sipout->wcstan), tanin, sizeof(tan_t));
    sipout->a_order  = sipout->b_order  = sip_order;
    sipout->ap_order = sipout->bp_order = inv_order;

	// The SIP coefficients form an (order x order) upper triangular
	// matrix missing the 0,0 element.
	sip_coeffs = (sip_order + 1) * (sip_order + 2) / 2;
	N = sip_coeffs;

    if (M < N) {
        ERROR("Too few correspondences for the SIP order specified (%i < %i)\n", M, N);
        return -1;
    }

	mA = gsl_matrix_alloc(M, N);
	b1 = gsl_vector_alloc(M);
	b2 = gsl_vector_alloc(M);
	assert(mA);
	assert(b1);
	assert(b2);

	/*
     *  We use a clever trick to estimate CD, A, and B terms in two
     *  seperated least squares fits, then finding A and B by multiplying
     *  the found parameters by CD inverse.
     * 
     *  Rearranging the SIP equations (see sip.h) we get the following
     *  matrix operation to compute x and y in world intermediate
     *  coordinates, which is convienently written in a way which allows
     *  least squares estimation of CD and terms related to A and B.
     * 
     *  First use the x's to find the first set of parametetrs
     * 
     *     +--------------------- Intermediate world coordinates in DEGREES
     *     |          +--------- Pixel coordinates u and v in PIXELS
     *     |          |     +--- Polynomial u,v terms in powers of PIXELS
     *     v          v     v
     *   ( x1 )   ( 1 u1 v1 p1 )   (sx              )
     *   ( x2 ) = ( 1 u2 v2 p2 ) * (cd11            ) :
     *   ( x3 )   ( 1 u3 v3 p3 )   (cd12            ) :
     *   ( ...)   (   ...    )     (cd11*A + cd12*B ) :
     * cd11 is a scalar, degrees per pixel
     * cd12 is a scalar, degrees per pixel
     * cd11*A and cs12*B are mixture of SIP terms (A,B) and CD matrix
     *   (cd11,cd12)
     * 
     *  Then find cd21 and cd22 with the y's
     * 
     *   ( y1 )   ( 1 u1 v1 p1 )   (sy              )
     *   ( y2 ) = ( 1 u2 v2 p2 ) * (cd21            ) :
     *   ( y3 )   ( 1 u3 v3 p3 )   (cd22            ) :
     *   ( ...)   (   ...    )     (cd21*A + cd22*B ) : (Y4)
     *  y2: scalar, degrees per pixel
     *  y3: scalar, degrees per pixel
     *  Y4: mixture of SIP terms (A,B) and CD matrix (cd21,cd22)
     * 
     *  These are both standard least squares problems which we solve with
     *  QR decomposition, ie
     *      min_{cd,A,B} || x - [1,u,v,p]*[s;cd;cdA+cdB]||^2 with
     *  x reference, cd,A,B unrolled parameters.
     * 
     *  We get back (for x) a vector of optimal
     *    [sx;cd11;cd12; cd11*A + cd12*B]
     *  Now we can pull out sx, cd11 and cd12 from the beginning of this vector,
     *  and call the rest of the vector [cd11*A] + [cd12*B];
     *  similarly for the y fit, we get back a vector of optimal
     *    [sy;cd21;cd22; cd21*A + cd22*B]
     *  once we have all those we can figure out A and B as follows
     *                   -1
     *    A' = [cd11 cd12]    *  [cd11*A' + cd12*B']
     *    B'   [cd21 cd22]       [cd21*A' + cd22*B']
     * 
     *  which recovers the A and B's.
     *
     */

	/*
     *  Dustin's interpretation of the above:
     *  We want to solve:
     * 
     *     min || b[M-by-1] - A[M-by-N] x[N-by-1] ||_2
     * 
     *  M = the number of correspondences.
     *  N = the number of SIP terms.
     *
     * And we want an overdetermined system, so M >= N.
     * 
     *           [ 1  u_1   v_1  u_1^2  u_1 v_1  v_1^2  ... ]
     *    mA  =  [ 1  u_2   v_2  u_2^2  u_2 v_2  v_2^2  ... ]
     *           [           ......                         ]
	 *
	 * Where (u_i, v_i) are *undistorted* pixel positions minus CRPIX.
	 *
     *  The answers we want are:
     *
     *         [ sx                  ]
     *    x1 = [ cd11                ]
     *         [ cd12                ]
	 *         [      (A)        (B) ]
     *         [ cd11*(A) + cd12*(B) ]
	 *         [      (A)        (B) ]
     *
     *         [ sy                  ]
     *    x2 = [ cd21                ]
     *         [ cd22                ]
	 *         [      (A)        (B) ]
     *         [ cd21*(A) + cd22*(B) ]
	 *         [      (A)        (B) ]
	 *
	 * And the target vectors are the intermediate world coords of the
	 * reference stars, in degrees.
     *
     *         [ ix_1 ]
     *    b1 = [ ix_2 ]
     *         [ ...  ]
     *
     *         [ iy_1 ]
     *    b2 = [ iy_2 ]
     *         [ ...  ]
     *
     *
     *  (where A and B are tall vectors of SIP coefficients of order 2
     *  and above)
     *
     */

	// Fill in matrix mA:
	radecdeg2xyzarr(tanin->crval[0], tanin->crval[1], xyzcrval);
	totalweight = 0.0;
    ngood = 0;
	for (i=0; i<M; i++) {
        double x=0, y=0;
        double weight = 1.0;
        double u;
        double v;
        Unused anbool ok;

        u = fieldxy[2*i + 0] - tanin->crpix[0];
        v = fieldxy[2*i + 1] - tanin->crpix[1];

        // B contains Intermediate World Coordinates (in degrees)
		// tangent-plane projection
        ok = star_coords(starxyz + 3*i, xyzcrval, TRUE, &x, &y);
        if (!ok) {
            logverb("Skipping star that cannot be projected to tangent plane\n");
            continue;
        }

        gsl_vector_set(b1, ngood, weight * rad2deg(x));
        gsl_vector_set(b2, ngood, weight * rad2deg(y));

        if (weights) {
            weight = weights[i];
            assert(weight >= 0.0);
            assert(weight <= 1.0);
            totalweight += weight;
            if (weight == 0.0)
                continue;
        }

        /* The coefficients are stored in this order:
         *   p q
         *  (0,0) = 1     <- order 0
         *  (1,0) = u     <- order 1
         *  (0,1) = v
         *  (2,0) = u^2   <- order 2
         *  (1,1) = uv
         *  (0,2) = v^2
         *  ...
         */

        j = 0;
        for (order=0; order<=sip_order; order++) {
            for (q=0; q<=order; q++) {
                p = order - q;
                assert(j >= 0);
                assert(j < N);
                assert(p >= 0);
                assert(q >= 0);
                assert(p + q <= sip_order);
                gsl_matrix_set(mA, ngood, j,
                               weight * pow(u, (double)p) * pow(v, (double)q));
                j++;
            }
        }
        assert(j == N);

        // The shift - aka (0,0) - SIP coefficient must be 1.
        assert(gsl_matrix_get(mA, i, 0) == 1.0 * weight);
        assert(fabs(gsl_matrix_get(mA, i, 1) - u * weight) < 1e-12);
        assert(fabs(gsl_matrix_get(mA, i, 2) - v * weight) < 1e-12);

        ngood++;
    }

    if (ngood == 0) {
        ERROR("No stars projected within the image\n");
        return -1;
    }

	if (weights)
		logverb("Total weight: %g\n", totalweight);

    if (ngood < M) {
        _gsl_vector_view sub_b1 = gsl_vector_subvector(b1, 0, ngood);
        _gsl_vector_view sub_b2 = gsl_vector_subvector(b2, 0, ngood);
        _gsl_matrix_view sub_mA = gsl_matrix_submatrix(mA, 0, 0, ngood, N);

        rtn = gslutils_solve_leastsquares_v(&(sub_mA.matrix), 2,
                                            &(sub_b1.vector), &x1, NULL,
                                            &(sub_b2.vector), &x2, NULL);

    } else {
        // Solve the equation.
        rtn = gslutils_solve_leastsquares_v(mA, 2, b1, &x1, NULL, b2, &x2, NULL);
    }
	if (rtn) {
        ERROR("Failed to solve SIP matrix equation!");
        return -1;
    }

	// Row 0 of X are the shift (p=0, q=0) terms.
	// Row 1 of X are the terms that multiply "u".
	// Row 2 of X are the terms that multiply "v".

	// Grab CD.
	sipout->wcstan.cd[0][0] = gsl_vector_get(x1, 1);
	sipout->wcstan.cd[0][1] = gsl_vector_get(x1, 2);
	sipout->wcstan.cd[1][0] = gsl_vector_get(x2, 1);
	sipout->wcstan.cd[1][1] = gsl_vector_get(x2, 2);

	// Compute inv(CD)
	i = invert_2by2_arr((const double*)(sipout->wcstan.cd),
                        (double*)cdinv);
	assert(i == 0);

	// Grab the shift.
	sx = gsl_vector_get(x1, 0);
	sy = gsl_vector_get(x2, 0);

	// Extract the SIP coefficients.
	//  (this includes the 0 and 1 order terms, which we later overwrite)
	j = 0;
	for (order=0; order<=sip_order; order++) {
		for (q=0; q<=order; q++) {
			p = order - q;
			assert(j >= 0);
			assert(j < N);
			assert(p >= 0);
			assert(q >= 0);
			assert(p + q <= sip_order);

			sipout->a[p][q] =
				cdinv[0][0] * gsl_vector_get(x1, j) +
				cdinv[0][1] * gsl_vector_get(x2, j);

			sipout->b[p][q] =
				cdinv[1][0] * gsl_vector_get(x1, j) +
				cdinv[1][1] * gsl_vector_get(x2, j);
			j++;
		}
	}
	assert(j == N);

	// We have already dealt with the shift and linear terms, so zero them out
	// in the SIP coefficient matrix.
	sipout->a[0][0] = 0.0;
	sipout->a[0][1] = 0.0;
	sipout->a[1][0] = 0.0;
	sipout->b[0][0] = 0.0;
	sipout->b[0][1] = 0.0;
	sipout->b[1][0] = 0.0;

	sip_compute_inverse_polynomials(sipout, 0, 0, 0, 0, 0, 0);

    sU =
        cdinv[0][0] * sx +
        cdinv[0][1] * sy;
    sV =
        cdinv[1][0] * sx +
        cdinv[1][1] * sy;
    logverb("Applying shift of sx,sy = %g,%g deg (%g,%g pix) to CRVAL and CD.\n",
            sx, sy, sU, sV);

    sip_calc_inv_distortion(sipout, sU, sV, &su, &sv);

    debug("sx = %g, sy = %g\n", sx, sy);
    debug("sU = %g, sV = %g\n", sU, sV);
    debug("su = %g, sv = %g\n", su, sv);

    wcs_shift(&(sipout->wcstan), -su, -sv);

	if (r1)
		gsl_vector_free(r1);
	if (r2)
		gsl_vector_free(r2);

	gsl_matrix_free(mA);
	gsl_vector_free(b1);
	gsl_vector_free(b2);
	gsl_vector_free(x1);
	gsl_vector_free(x2);

    return 0;
}
Exemplo n.º 2
0
void test_tweak_1(CuTest* tc) {
    int GX = 5;
    int GY = 5;
    double origxy[GX*GY*2];
    double xy[GX*GY*2];
    double radec[GX*GY*2];
    double gridx[GX];
    double gridy[GY];
    sip_t thesip;
    sip_t* sip = &thesip;
    tan_t* tan = &(sip->wcstan);
    int i;
    sip_t* outsip;

    printf("\ntest_tweak_1\n\n");

    log_init(LOG_VERB);

    memset(sip, 0, sizeof(sip_t));

    tan->imagew = 2000;
    tan->imageh = 2000;
    tan->crval[0] = 150;
    tan->crval[1] = -30;
    tan->crpix[0] = 1000.5;
    tan->crpix[1] = 1000.5;
    tan->cd[0][0] = 1./1000.;
    tan->cd[0][1] = 0;
    tan->cd[1][1] = 1./1000.;
    tan->cd[1][0] = 0;

    sip->a_order = sip->b_order = 2;
    sip->a[2][0] = 10.*1e-6;
    sip->ap_order = sip->bp_order = 4;

    sip_compute_inverse_polynomials(sip, 0, 0, 0, 0, 0, 0);

    /*
     printf("After compute_inverse_polynomials:\n");
     sip_print_to(sip, stdout);
     fflush(NULL);
     */

    set_grid(GX, GY, tan, sip, origxy, radec, xy, gridx, gridy);

    /*{
     int i,j;
     printf("RA,Dec\n");
     for (i=0; i<GY; i++) {
     for (j=0; j<GX; j++) {
     fflush(NULL);
     printf("gy %i gyx %i: %g %g\n", i, j, radec[2*(i*GX+j)],
     radec[2*(i*GX+j) + 1]);
     fflush(NULL);
     }
     }
     }*/

    outsip = run_test(tc, sip, GX*GY, xy, radec);

    CuAssertDblEquals(tc, tan->crval[0], outsip->wcstan.crval[0], 1e-6);
    CuAssertDblEquals(tc, tan->crval[1], outsip->wcstan.crval[1], 1e-6);

    CuAssertDblEquals(tc, tan->cd[0][0], outsip->wcstan.cd[0][0], 1e-10);
    CuAssertDblEquals(tc, tan->cd[0][1], outsip->wcstan.cd[0][1], 1e-14);
    CuAssertDblEquals(tc, tan->cd[1][0], outsip->wcstan.cd[1][0], 1e-14);
    CuAssertDblEquals(tc, tan->cd[1][1], outsip->wcstan.cd[1][1], 1e-10);

    double *d1, *d2;
    d1 = (double*)outsip->a;
    d2 = (double*)&(sip->a);
    for (i=0; i<(SIP_MAXORDER * SIP_MAXORDER); i++)
        CuAssertDblEquals(tc, d2[i], d1[i], 1e-13);
    d1 = (double*)outsip->b;
    d2 = (double*)&(sip->b);
    for (i=0; i<(SIP_MAXORDER * SIP_MAXORDER); i++) {
        printf("test_tweak_1: Expecting %.18g, got %.18g\n", d2[i], d1[i]);
        fflush(NULL);
        CuAssertDblEquals(tc, d2[i], d1[i], 3e-18);
    }
    d1 = (double*)outsip->ap;
    d2 = (double*)&(sip->ap);
    for (i=0; i<(SIP_MAXORDER * SIP_MAXORDER); i++)
        CuAssertDblEquals(tc, d2[i], d1[i], 1e-10);
    d1 = (double*)outsip->bp;
    d2 = (double*)&(sip->bp);
    for (i=0; i<(SIP_MAXORDER * SIP_MAXORDER); i++)
        CuAssertDblEquals(tc, d2[i], d1[i], 1e-10);
    CuAssertIntEquals(tc, sip->a_order, outsip->a_order);
    CuAssertIntEquals(tc, sip->b_order, outsip->b_order);
    CuAssertIntEquals(tc, sip->ap_order, outsip->ap_order);
    CuAssertIntEquals(tc, sip->bp_order, outsip->bp_order);
}
Exemplo n.º 3
0
static void tst_tweak_n(CuTest* tc, int run, int GX, int GY) {
    double origxy[GX*GY*2];
    double xy[GX*GY*2];
    double noisyxy[GX*GY*2];
    double radec[GX*GY*2];
    double gridx[GX];
    double gridy[GY];
    sip_t thesip;
    sip_t* sip = &thesip;
    tan_t* tan = &(sip->wcstan);
    int i,j;
    sip_t* outsip;

    printf("\ntest_tweak_%i\n\n", run);

    log_init(LOG_VERB);

    memset(sip, 0, sizeof(sip_t));

    tan->imagew = 2000;
    tan->imageh = 2000;
    tan->crval[0] = 150;
    tan->crval[1] = -30;
    tan->crpix[0] = 1000.5;
    tan->crpix[1] = 1000.5;
    tan->cd[0][0] = 1./1000.;
    tan->cd[0][1] = 0;
    tan->cd[1][1] = 1./1000.;
    tan->cd[1][0] = 0;

    sip->a_order = sip->b_order = 2;
    sip->a[2][0] = 10.*1e-6;
    sip->b[0][2] = -10.*1e-6;
    sip->ap_order = sip->bp_order = 4;

    sip_compute_inverse_polynomials(sip, 0, 0, 0, 0, 0, 0);

    set_grid(GX, GY, tan, sip, origxy, radec, xy, gridx, gridy);

    // add noise to observed xy positions.
    srand(42);
    for (i=0; i<(GX*GY*2); i++) {
        noisyxy[i] = xy[i] + gaussian_sample(0.0, 1.0);
    }

    fprintf(stderr, "from numpy import array\n");
    fprintf(stderr, "x0,y0 = %g,%g\n", tan->crpix[0], tan->crpix[1]);
    fprintf(stderr, "gridx_%i=array([", run);
    for (i=0; i<GX; i++)
        fprintf(stderr, "%g, ", gridx[i]);
    fprintf(stderr, "])\n");
    fprintf(stderr, "gridy_%i=array([", run);
    for (i=0; i<GY; i++)
        fprintf(stderr, "%g, ", gridy[i]);
    fprintf(stderr, "])\n");
    fprintf(stderr, "origxy_%i = array([", run);
    for (i=0; i<(GX*GY); i++)
        fprintf(stderr, "[%g,%g],", origxy[2*i+0], origxy[2*i+1]);
    fprintf(stderr, "])\n");
    fprintf(stderr, "xy_%i = array([", run);
    for (i=0; i<(GX*GY); i++)
        fprintf(stderr, "[%g,%g],", xy[2*i+0], xy[2*i+1]);
    fprintf(stderr, "])\n");

    fprintf(stderr, "noisyxy_%i = array([", run);
    for (i=0; i<(GX*GY); i++)
        fprintf(stderr, "[%g,%g],", noisyxy[2*i+0], noisyxy[2*i+1]);
    fprintf(stderr, "])\n");

    fprintf(stderr, "truesip_a_%i = array([", run);
    for (i=0; i<=sip->a_order; i++)
        for (j=0; j<=sip->a_order; j++)
            if (sip->a[i][j] != 0)
                fprintf(stderr, "[%i,%i,%g],", i, j, sip->a[i][j]);
    fprintf(stderr, "])\n");

    fprintf(stderr, "truesip_b_%i = array([", run);
    for (i=0; i<=sip->a_order; i++)
        for (j=0; j<=sip->a_order; j++)
            if (sip->b[i][j] != 0)
                fprintf(stderr, "[%i,%i,%g],", i, j, sip->b[i][j]);
    fprintf(stderr, "])\n");

    fprintf(stderr, "sip_a_%i = {}\n", run);
    fprintf(stderr, "sip_b_%i = {}\n", run);

    int o;
    for (o=2; o<6; o++) {
        sip->a_order = o;
        outsip = run_test(tc, sip, GX*GY, noisyxy, radec);

        fprintf(stderr, "sip_a_%i[%i] = array([", run, o);
        for (i=0; i<=outsip->a_order; i++)
            for (j=0; j<=outsip->a_order; j++)
                if (outsip->a[i][j] != 0)
                    fprintf(stderr, "[%i,%i,%g],", i, j, outsip->a[i][j]);
        fprintf(stderr, "])\n");

        fprintf(stderr, "sip_b_%i[%i] = array([", run, o);
        for (i=0; i<=outsip->a_order; i++)
            for (j=0; j<=outsip->a_order; j++)
                if (outsip->b[i][j] != 0)
                    fprintf(stderr, "[%i,%i,%g],", i, j, outsip->b[i][j]);
        fprintf(stderr, "])\n");
    }

    sip->a_order = 2;
    outsip = run_test(tc, sip, GX*GY, noisyxy, radec);

    CuAssertDblEquals(tc, tan->crval[0], outsip->wcstan.crval[0], 1e-3);
    CuAssertDblEquals(tc, tan->crval[1], outsip->wcstan.crval[1], 1e-3);

    CuAssertDblEquals(tc, tan->cd[0][0], outsip->wcstan.cd[0][0], 1e-6);
    CuAssertDblEquals(tc, tan->cd[0][1], outsip->wcstan.cd[0][1], 1e-6);
    CuAssertDblEquals(tc, tan->cd[1][0], outsip->wcstan.cd[1][0], 1e-6);
    CuAssertDblEquals(tc, tan->cd[1][1], outsip->wcstan.cd[1][1], 1e-6);

    if (run == 2) {
        double *d1, *d2;
        d1 = (double*)outsip->a;
        d2 = (double*)&(sip->a);
        for (i=0; i<(SIP_MAXORDER * SIP_MAXORDER); i++)
            // rather large error, no?
            CuAssertDblEquals(tc, d2[i], d1[i], 6e-7);
        d1 = (double*)outsip->b;
        d2 = (double*)&(sip->b);
        for (i=0; i<(SIP_MAXORDER * SIP_MAXORDER); i++) {
            printf("test_tweak_2, run 2: Expecting %.18g, got %.18g\n", d2[i], d1[i]);
            fflush(NULL);
            CuAssertDblEquals(tc, d2[i], d1[i], 3e-7);
        }
        d1 = (double*)outsip->ap;
        d2 = (double*)&(sip->ap);
        for (i=0; i<(SIP_MAXORDER * SIP_MAXORDER); i++)
            CuAssertDblEquals(tc, d2[i], d1[i], 1e-6);
        d1 = (double*)outsip->bp;
        d2 = (double*)&(sip->bp);
        for (i=0; i<(SIP_MAXORDER * SIP_MAXORDER); i++)
            CuAssertDblEquals(tc, d2[i], d1[i], 1e-6);
        CuAssertIntEquals(tc, sip->a_order, outsip->a_order);
        CuAssertIntEquals(tc, sip->b_order, outsip->b_order);
        CuAssertIntEquals(tc, sip->ap_order, outsip->ap_order);
        CuAssertIntEquals(tc, sip->bp_order, outsip->bp_order);
    } else if (run == 3) {
        double *d1, *d2;
        d1 = (double*)outsip->a;
        d2 = (double*)&(sip->a);
        for (i=0; i<(SIP_MAXORDER * SIP_MAXORDER); i++) {
            // rather large error, no?
            printf("test_tweak_2, run 3: Expecting %.18g, got %.18g\n", d2[i], d1[i]);
            fflush(NULL);
            CuAssertDblEquals(tc, d2[i], d1[i], 7e-7);
        }
        d1 = (double*)outsip->b;
        d2 = (double*)&(sip->b);
        for (i=0; i<(SIP_MAXORDER * SIP_MAXORDER); i++) {
            printf("test_tweak_2, run 3b: Expecting %.18g, got %.18g\n", d2[i], d1[i]);
            fflush(NULL);
            CuAssertDblEquals(tc, d2[i], d1[i], 2e-6);
        }
        d1 = (double*)outsip->ap;
        d2 = (double*)&(sip->ap);
        for (i=0; i<(SIP_MAXORDER * SIP_MAXORDER); i++)
            CuAssertDblEquals(tc, d2[i], d1[i], 1e-6);
        d1 = (double*)outsip->bp;
        d2 = (double*)&(sip->bp);
        for (i=0; i<(SIP_MAXORDER * SIP_MAXORDER); i++) {
            printf("test_tweak_2, run 3c: Expecting %.18g, got %.18g\n", d2[i], d1[i]);
            fflush(NULL);
            CuAssertDblEquals(tc, d2[i], d1[i], 2e-6);
        }
        CuAssertIntEquals(tc, sip->a_order, outsip->a_order);
        CuAssertIntEquals(tc, sip->b_order, outsip->b_order);
        CuAssertIntEquals(tc, sip->ap_order, outsip->ap_order);
        CuAssertIntEquals(tc, sip->bp_order, outsip->bp_order);
    }
}
Exemplo n.º 4
0
sip_t* wcs_pv2sip_header(qfits_header* hdr,
                         double* xy, int Nxy,
               
                         double stepsize,
                         double xlo, double xhi,
                         double ylo, double yhi,

                         int imageW, int imageH,
                         int order,
                         anbool forcetan,
                         int doshift) {
	double* radec = NULL;
	int rtn = -1;
	tan_t tanwcs;
	double x,y, px,py;
	double* rddist = NULL;
	int i, j;
    int nx, ny;
    double xstep, ystep;
    sip_t* sip = NULL;

    /**
     From http://iraf.noao.edu/projects/mosaic/tpv.html

     p = PV1_

     xi' = p0 +
           p1 * xi + p2 * eta + p3 * r +
           p4 * xi^2 + p5 * xi * eta + p6 * eta^2 +
           p7 * xi^3 + p8 * xi^2 * eta + p9 * xi * eta^2 +
              p10 * eta^3 + p11 * r^3 + 
           p12 * xi^4 + p13 * xi^3 * eta + p14 * xi^2 * eta^2 +
              p15 * xi * eta^3 + p16 * eta^4 +
	       p17 * xi^5 + p18 * xi^4 * eta + p19 * xi^3 * eta^2 +
	          p20 * xi^2 * eta^3 + p21 * xi * eta^4 + p22 * eta^5 + p23 * r^5 +
           p24 * xi^6 + p25 * xi^5 * eta + p26 * xi^4 * eta^2 +
              p27 * xi^3 * eta^3 + p28 * xi^2 * eta^4 + p29 * xi * eta^5 +
              p30 * eta^6
           p31 * xi^7 + p32 * xi^6 * eta + p33 * xi^5 * eta^2 +
              p34 * xi^4 * eta^3 + p35 * xi^3 * eta^4 + p36 * xi^2 * eta^5 +
              p37 * xi * eta^6 + p38 * eta^7 + p39 * r^7

     p = PV2_
     eta' = p0 +
            p1 * eta + p2 * xi + p3 * r +
            p4 * eta^2 + p5 * eta * xi + p6 * xi^2 +
            p7 * eta^3 + p8 * eta^2 * xi + p9 * eta * xi^2 + p10 * xi^3 +
                 p11 * r^3 +
            p12 * eta^4 + p13 * eta^3 * xi + p14 * eta^2 * xi^2 +
                 p15 * eta * xi^3 + p16 * xi^4 +
            p17 * eta^5 + p18 * eta^4 * xi + p19 * eta^3 * xi^2 +
	             p20 * eta^2 * xi^3 + p21 * eta * xi^4 + p22 * xi^5 +
                 p23 * r^5 +
            p24 * eta^6 + p25 * eta^5 * xi + p26 * eta^4 * xi^2 +
                 p27 * eta^3 * xi^3 + p28 * eta^2 * xi^4 + p29 * eta * xi^5 +
                 p30 * xi^6
            p31 * eta^7 + p32 * eta^6 * xi + p33 * eta^5 * xi^2 +
                 p34 * eta^4 * xi^3 + p35 * eta^3 * xi^4 + p36 * eta^2 * xi^5 +
                 p37 * eta * xi^6 + p38 * xi^7 + p39 * r^7

     Note the "cross-over" -- the xi' powers are in terms of xi,eta
     while the eta' powers are in terms of eta,xi.
     */

	//           1  x  y  r x2 xy y2 x3 x2y xy2 y3 r3 x4 x3y x2y2 xy3 y4
	//          x5 x4y x3y2 x2y3 xy4 y5 r5 x6 x5y x4y2, x3y3 x2y4 xy5 y6
	//          x7 x6y x5y2 x4y3 x3y4 x2y5 xy6 y7 r7
	int xp[] = {
     0,
     1, 0, 0,
     2, 1, 0,
     3, 2, 1, 0, 0,
     4, 3, 2, 1, 0,
     5, 4, 3, 2, 1, 0, 0,
     6, 5, 4, 3, 2, 1, 0,
     7, 6, 5, 4, 3, 2, 1, 0, 0};
	int yp[] = {
     0,
     0, 1, 0,
     0, 1, 2,
     0, 1, 2, 3, 0,
     0, 1, 2, 3, 4,
     0, 1, 2, 3, 4, 5, 0,
     0, 1, 2, 3, 4, 5, 6,
     0, 1, 2, 3, 4, 5, 6, 7, 0};
	int rp[] = {
     0,
     0, 0, 1,
     0, 0, 0,
     0, 0, 0, 0, 3,
     0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 5,
     0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 7};
	double xpows[8];
	double ypows[8];
	double rpows[8];
	double pv1[40];
	double pv2[40];
	double r;

    char* ct;
    ct = fits_get_dupstring(hdr, "CTYPE1");
    if ((ct && streq(ct, "RA---TPV")) || forcetan) {
        // http://iraf.noao.edu/projects/ccdmosaic/tpv.html
        logmsg("Replacing CTYPE1 = %s header with RA---TAN\n", ct);
        fits_update_value(hdr, "CTYPE1", "RA---TAN");
    }
    ct = fits_get_dupstring(hdr, "CTYPE2");
    if ((ct && streq(ct, "DEC--TPV")) || forcetan) {
        logmsg("Replacing CTYPE2 = %s header with DEC--TAN\n", ct);
        fits_update_value(hdr, "CTYPE2", "DEC--TAN");
    }

	tan_read_header(hdr, &tanwcs);

    if (log_get_level() >= LOG_VERB) {
        printf("Read TAN header:\n");
        tan_print(&tanwcs);
    }

    if (imageW && (imageW != tanwcs.imagew)) {
        logmsg("Overriding image width %f with user-specified %i\n",
               tanwcs.imagew, imageW);
        tanwcs.imagew = imageW;
    }
    if (imageH && (imageH != tanwcs.imageh)) {
        logmsg("Overriding image height %f with user-specified %i\n",
               tanwcs.imageh, imageH);
        tanwcs.imageh = imageH;
    }

	for (i=0; i<sizeof(pv1)/sizeof(double); i++) {
		char key[10];
        double defaultval;

        if (i == 1) {
            defaultval = 1.0;
        } else {
            defaultval = 0.0;
        }
		sprintf(key, "PV1_%i", i);
		pv1[i] = qfits_header_getdouble(hdr, key, defaultval);
		sprintf(key, "PV2_%i", i);
		pv2[i] = qfits_header_getdouble(hdr, key, defaultval);
	}


    // choose grid for evaluating TAN-PV WCS
    if (xlo == 0 && xhi == 0) {
        xlo = 1.;
        xhi = tanwcs.imagew;
    }
    if (ylo == 0 && yhi == 0) {
        ylo = 1.;
        yhi = tanwcs.imageh;
    }

	assert(xhi >= xlo);
	assert(yhi >= ylo);

	if (stepsize == 0)
        stepsize = 100.;
    nx = MAX(2, round((xhi - xlo)/stepsize));
    ny = MAX(2, round((yhi - ylo)/stepsize));
    xstep = (xhi - xlo) / (double)(nx - 1);
    ystep = (yhi - ylo) / (double)(ny - 1);

	logverb("Stepping from x = %g to %g, steps of %g\n", xlo, xhi, xstep);
	logverb("Stepping from y = %g to %g, steps of %g\n", ylo, yhi, ystep);

    Nxy = nx * ny;

    if (xy == NULL) {
        int k = 0;
        xy = malloc(Nxy * 2 * sizeof(double));
        for (i=0; i<ny; i++) {
            y = ylo + i*ystep;
            for (j=0; j<nx; j++) {
                x = xlo + j*xstep;
                //if (i == 0)
                //printf("x=%f\n", x);
                xy[k] = x;
                k++;
                xy[k] = y;
                k++;
            }
            //printf("y=%f\n", y);
        }
        assert(k == (Nxy*2));
    }

    // distorted RA,Dec
	rddist = malloc(2 * Nxy * sizeof(double));

	for (j=0; j<Nxy; j++) {
        double ix = xy[2*j+0];
		double iy = xy[2*j+1];

		tan_pixelxy2iwc(&tanwcs, ix, iy, &x, &y);
        // "x,y" here are most commonly known as "xi, eta".
		r = sqrt(x*x + y*y);
        // compute powers of x,y
		xpows[0] = ypows[0] = rpows[0] = 1.0;
		for (i=1; i<sizeof(xpows)/sizeof(double); i++) {
			xpows[i] = xpows[i-1]*x;
			ypows[i] = ypows[i-1]*y;
			rpows[i] = rpows[i-1]*r;
		}
		px = py = 0;
		for (i=0; i<sizeof(xp)/sizeof(int); i++) {
			px += pv1[i] * xpows[xp[i]] * ypows[yp[i]] * rpows[rp[i]];
            // here's the "cross-over" mentioned above
			py += pv2[i] * ypows[xp[i]] * xpows[yp[i]] * rpows[rp[i]];
		}

        // Note that the PV terms *include* a linear term, so no need
        // to re-add x,y to px,py.
        tan_iwc2radec(&tanwcs, px, py,
                      rddist + 2*j, rddist + 2*j + 1);
	}

    sip = malloc(sizeof(sip_t));
    assert(sip);
    {
        double* starxyz;
        starxyz = malloc(3 * Nxy * sizeof(double));
        for (i=0; i<Nxy; i++)
            radecdegarr2xyzarr(rddist + i*2, starxyz + i*3);
        memset(sip, 0, sizeof(sip_t));
        rtn = fit_sip_coefficients(starxyz, xy, NULL, Nxy,
                                   &tanwcs, order, order, sip);
        assert(rtn == 0);

        if (log_get_level() >= LOG_VERB) {
            printf("Fit SIP:\n");
            sip_print(sip);
        }

        // FIXME? -- use xlo,xhi,ylo,yhi here??  Not clear.
        sip_compute_inverse_polynomials(sip, 0, 0, 0, 0, 0, 0);

        if (log_get_level() >= LOG_VERB) {
            printf("Fit SIP inverse polynomials:\n");
            sip_print(sip);
        }
        free(starxyz);
    }

	free(rddist);
	free(radec);
    return sip;
}