Skip to content

Commit 04f32aa

Browse files
authored
Fix incorrect implementation of bilinear interpolation (#240)
* Fix incorrect implementation of bilinear interpolation. Both the pixel index calculation and the weights calculation were wrong * Added a unit test to ensure bilinear mapmaking stays correct
1 parent bb39681 commit 04f32aa

2 files changed

Lines changed: 64 additions & 13 deletions

File tree

src/Projection.cxx

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ inline bool isNone(const bp::object &pyo)
5151
return (pyo.ptr() == Py_None);
5252
}
5353

54+
int ifloor(double x) { return int(x)-int(x<0); }
55+
int iround(double x) { return ifloor(x+0.5); }
5456

5557
// ProjEng template system
5658
//
@@ -812,6 +814,7 @@ class Pixelizor2_Flat<NonTiled, Interpol> {
812814
double iy0=0., double ix0=0.) {
813815
naxis[0] = ny;
814816
naxis[1] = nx;
817+
// Note, this y,x order is the opposite of what wcslib uses
815818
cdelt[0] = dy;
816819
cdelt[1] = dx;
817820
crpix[0] = iy0;
@@ -930,12 +933,16 @@ inline int Pixelizor2_Flat<NonTiled, NearestNeighbor>::GetPixels(int i_det, int
930933
template<>
931934
inline int Pixelizor2_Flat<NonTiled, Bilinear>::GetPixels(int i_det, int i_time, const double *coords, int pixinds[interp_count][index_count], FSIGNAL pixweights[interp_count]) {
932935
// For bilinear mapmaking we need to visit the four bounding pixels
933-
double x = coords[0] / cdelt[1] + crpix[1] - 1 + 0.5;
934-
double y = coords[1] / cdelt[0] + crpix[0] - 1 + 0.5;
935-
int x1 = int(x)-int(x<0);
936-
int y1 = int(y)-int(y<0);
937-
double wx[2] = {x-x1, 1-(x-x1)};
938-
double wy[2] = {y-y1, 1-(y-y1)};
936+
// 0-based pixel coordinate
937+
double x = coords[0] / cdelt[1] + crpix[1] - 1;
938+
double y = coords[1] / cdelt[0] + crpix[0] - 1;
939+
// index of pixel to the left of this point. The pixel to the right of it
940+
// will be that number +1
941+
int x1 = ifloor(x);
942+
int y1 = ifloor(y);
943+
// Weight of before and after pixels. Sum to 1.
944+
double wx[2] = {1-(x-x1), x-x1};
945+
double wy[2] = {1-(y-y1), y-y1};
939946
// Loop through the our cases
940947
int iout = 0;
941948
for(int iy = y1; iy < y1+2; iy++) {
@@ -1141,12 +1148,12 @@ inline int Pixelizor2_Flat<Tiled, NearestNeighbor>::GetPixels(int i_det, int i_t
11411148
template<>
11421149
inline int Pixelizor2_Flat<Tiled, Bilinear>::GetPixels(int i_det, int i_time, const double *coords, int pixinds[interp_count][index_count], FSIGNAL pixweights[interp_count]) {
11431150
// For bilinear mapmaking we need to visit the four bounding pixels
1144-
double x = coords[0] / parent_pix.cdelt[1] + parent_pix.crpix[1] - 1 + 0.5;
1145-
double y = coords[1] / parent_pix.cdelt[0] + parent_pix.crpix[0] - 1 + 0.5;
1146-
int x1 = int(x);
1147-
int y1 = int(y);
1148-
double wx[2] = {x-x1, 1-(x-x1)};
1149-
double wy[2] = {y-y1, 1-(y-y1)};
1151+
double x = coords[0] / parent_pix.cdelt[1] + parent_pix.crpix[1] - 1;
1152+
double y = coords[1] / parent_pix.cdelt[0] + parent_pix.crpix[0] - 1;
1153+
int x1 = ifloor(x);
1154+
int y1 = ifloor(y);
1155+
double wx[2] = {1-(x-x1), x-x1};
1156+
double wy[2] = {1-(y-y1), y-y1};
11501157
// Loop through the our cases
11511158
int iout = 0;
11521159
for(int iy = y1; iy < y1+2; iy++) {

test/test_proj_eng.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
# Don't require pixell for testing
99
try:
10-
from pixell import enmap
10+
from pixell import enmap, wcsutils
1111
pixell_found = True
1212
except ModuleNotFoundError:
1313
pixell_found = False
@@ -150,6 +150,50 @@ def test_20_threads(self):
150150
if interpol == 'nearest':
151151
self.assertEqual(counts1.sum(), 0)
152152

153+
@requires_pixell
154+
def test_30_bilin(self):
155+
"""1-sample single boresight-pointed detector with no coordinate transformation"""
156+
# Trivial geometry where pixel coordinate and sky coordinate are the same thing
157+
shape = (2,2)
158+
wcs = wcsutils.explicit(crval=[0,0], crpix=[1,1], cdelt=[1,1], ctype=["RA---CAR","DEC--CAR"])
159+
# Cases we will consider
160+
cases = [
161+
# A single sample hitting (0.1, 0.7)
162+
np.array([[0.1],[0.7]]),
163+
# A single sample with integer coordinates
164+
np.array([[1.0],[0.0]]),
165+
]
166+
for x, y in cases:
167+
whole = np.all(x==np.round(x)) and np.all(y==np.round(y))
168+
csl = proj.CelestialSightLine.for_lonlat(x*DEG, y*DEG)
169+
fp = proj.FocalPlane.from_xieta([0.0],[0.0])
170+
asm = proj.Assembly.attach(csl, fp)
171+
dtype = np.float32
172+
tod = np.ones((1,1), dtype)
173+
# Expected result for NN and bilin
174+
wy = np.sum(np.round([1-y,y]),1)
175+
wx = np.sum(np.round([1-x,x]),1)
176+
targ_nn = wy[:,None]*wx[None,:]
177+
wy = np.sum([1-y,y],1)
178+
wx = np.sum([1-x,x],1)
179+
targ_li = wy[:,None]*wx[None,:]
180+
# Nearest neighbor untiled
181+
p = proj.Projectionist.for_geom(shape, wcs, interpol="nearest")
182+
m_nn = p.to_map(tod, asm, comps="T")[0]
183+
assert np.allclose(m_nn, targ_nn)
184+
# Bilinear untiled
185+
p = proj.Projectionist.for_geom(shape, wcs, interpol="bilinear")
186+
m_li = p.to_map(tod, asm, comps="T")[0]
187+
assert np.allclose(m_li, targ_li)
188+
if whole: assert np.allclose(m_nn, m_li)
189+
# Nearest neighbor tiled
190+
p = proj.Projectionist.for_tiled(shape, wcs, (10, 10), interpol="nearest")
191+
m_nn = p.to_map(tod, asm, comps="T")[0][0]
192+
assert np.allclose(m_nn, targ_nn)
193+
p = proj.Projectionist.for_tiled(shape, wcs, (10, 10), interpol="bilinear")
194+
m_li = p.to_map(tod, asm, comps="T")[0][0]
195+
assert np.allclose(m_li, targ_li)
196+
if whole: assert np.allclose(m_nn, m_li)
153197

154198
if __name__ == '__main__':
155199
unittest.main()

0 commit comments

Comments
 (0)