8
#include "numpy/libnumarray.h"
27
SlowCoord(long x, long maxx, PixMode m)
32
if (x >= maxx) x = maxx-1;
36
if (x >= maxx) x = maxx - (x - maxx) - 1;
40
if (x >= maxx) x -= maxx;
42
case PIX_CONSTANT: /* handled in SlowPix, suppress warnings */
49
SlowPix(long r, long c, PixData *p)
52
if (p->mode == PIX_CONSTANT) {
53
if ((r < 0) || (r >= p->rows) || (c < 0) || (c >= p->cols))
60
fr = SlowCoord(r, p->rows, p->mode);
61
fc = SlowCoord(c, p->cols, p->mode);
63
return p->data[fr*p->cols + fc];
67
_reject_complex(PyObject *a)
70
if ((a == Py_None) || (a == NULL))
72
t = NA_NumarrayType(a);
77
if (t == tComplex32 || t == tComplex64) {
78
PyErr_Format(PyExc_TypeError,
79
"function doesn't support complex arrays.");
86
Correlate1d(long ksizex, Float64 *kernel,
87
long dsizex, Float64 *data,
91
long halfk = ksizex/2;
93
for(xc=0; xc<halfk; xc++)
94
correlated[xc] = data[xc];
96
for(xc=halfk; xc<dsizex-halfk; xc++) {
99
for (xk=0; xk<ksizex; xk++)
100
temp += kernel[xk]*data[xc-halfk+xk];
101
correlated[xc] = temp;
104
for(xc=dsizex-halfk; xc<dsizex; xc++)
105
correlated[xc] = data[xc];
109
Py_Correlate1d(PyObject *obj, PyObject *args)
111
PyObject *okernel, *odata, *ocorrelated=NULL;
112
PyArrayObject *kernel, *data, *correlated;
114
if (!PyArg_ParseTuple(args, "OO|O:Correlate1d",
115
&okernel, &odata, &ocorrelated))
118
/* Align, Byteswap, Contiguous, Typeconvert */
119
kernel = NA_InputArray(okernel, tFloat64, C_ARRAY);
120
data = NA_InputArray(odata, tFloat64, C_ARRAY);
121
correlated = NA_OptionalOutputArray(ocorrelated, tFloat64, C_ARRAY,
124
if (!kernel || !data || !correlated)
127
if (_reject_complex(okernel) || _reject_complex(odata) ||
128
_reject_complex(ocorrelated))
131
if ((kernel->nd != 1) || (data->nd != 1)) {
132
PyErr_Format(PyExc_ValueError,
133
"Correlate1d: numarray must have exactly 1 dimension.");
137
if (!NA_ShapeEqual(data, correlated)) {
138
PyErr_Format(PyExc_ValueError,
139
"Correlate1d: data and output must have identical length.");
143
Correlate1d(kernel->dimensions[0], NA_OFFSETDATA(kernel),
144
data->dimensions[0], NA_OFFSETDATA(data),
145
NA_OFFSETDATA(correlated));
150
/* Align, Byteswap, Contiguous, Typeconvert */
151
return NA_ReturnOutput(ocorrelated, correlated);
156
Py_XDECREF(correlated);
160
/* SlowCorrelate computes 2D correlation near the boundaries of an array.
161
The output array shares the same dimensions as the input array, the latter
162
fully described by PixData.
164
The region defined by rmin,rmax,cmin,cmax is assumed to contain only valid
165
coordinates. However, access to the input array is performed using SlowPix
166
because pixels reachable via "kernel offsets" may be at invalid coordinates.
169
SlowCorrelate2d(long rmin, long rmax, long cmin, long cmax,
170
long krows, long kcols, Float64 *kernel,
171
PixData *pix, Float64 *output)
174
long halfkrows = krows/2;
175
long halfkcols = kcols/2;
177
for(r=rmin; r<rmax; r++) {
178
for(c=cmin; c<cmax; c++) {
180
for(kr=0; kr<krows; kr++) {
181
long pr = r + kr - halfkrows;
182
for(kc=0; kc<kcols; kc++) {
183
long pc = c + kc - halfkcols;
184
temp += SlowPix(pr, pc, pix) *
188
output[r*pix->cols+c] = temp;
194
Correlate2d(long krows, long kcols, Float64 *kernel,
195
long drows, long dcols, Float64 *data, Float64 *correlated,
196
PixMode mode, Float64 cval)
199
long halfkrows = krows/2;
200
long halfkcols = kcols/2;
209
/* Compute the boundaries using SlowPix */
211
SlowCorrelate2d(0, halfkrows, 0, dcols,
212
krows, kcols, kernel, &pix, correlated); /* top */
213
SlowCorrelate2d(drows-halfkrows, drows, 0, dcols,
214
krows, kcols, kernel, &pix, correlated); /* bottom */
215
SlowCorrelate2d(halfkrows, drows-halfkrows, 0, halfkcols,
216
krows, kcols, kernel, &pix, correlated); /* left */
217
SlowCorrelate2d(halfkrows, drows-halfkrows, dcols-halfkcols, dcols,
218
krows, kcols, kernel, &pix, correlated); /* right */
220
/* Correlate the center data using unchecked array access */
221
for(di=halfkrows; di<drows-halfkrows; di++) {
222
for(dj=halfkcols; dj<dcols-halfkcols; dj++) {
224
for(ki=0; ki<krows; ki++) {
225
long pi = di + ki - halfkrows;
226
for(kj=0; kj<kcols; kj++) {
227
long pj = dj + kj - halfkcols;
228
temp += data[pi*dcols+pj] *
232
correlated[di*dcols+dj] = temp;
238
Py_Correlate2d(PyObject *obj, PyObject *args, PyObject *kw)
240
PyObject *okernel, *odata, *ocorrelated=NULL;
241
PyArrayObject *kernel, *data, *correlated;
243
int mode = PIX_NEAREST;
244
char *keywds[] = { "kernel", "data", "output", "mode", "cval", NULL };
246
if (!PyArg_ParseTupleAndKeywords(
247
args, kw, "OO|Oid:Correlate2d", keywds,
248
&okernel, &odata, &ocorrelated, &mode, &cval))
251
if ((mode < PIX_NEAREST) || (mode > PIX_CONSTANT))
252
return PyErr_Format(PyExc_ValueError,
253
"Correlate2d: mode value not in range(%d,%d)",
254
PIX_NEAREST, PIX_CONSTANT);
256
/* Align, Byteswap, Contiguous, Typeconvert */
257
kernel = NA_InputArray(okernel, tFloat64, C_ARRAY);
258
data = NA_InputArray(odata, tFloat64, C_ARRAY);
259
correlated = NA_OptionalOutputArray(ocorrelated, tFloat64, C_ARRAY,
262
if (!kernel || !data || !correlated)
265
if ((kernel->nd != 2) || (data->nd != 2) || (correlated->nd != 2)) {
266
PyErr_Format(PyExc_ValueError, "Correlate2d: inputs must have 2 dimensions.");
270
if (!NA_ShapeEqual(data, correlated)) {
271
PyErr_Format(PyExc_ValueError,
272
"Correlate2d: data and output numarray need identical shapes.");
276
if (_reject_complex(okernel) || _reject_complex(odata) ||
277
_reject_complex(ocorrelated))
280
Correlate2d(kernel->dimensions[0], kernel->dimensions[1],
281
NA_OFFSETDATA(kernel),
282
data->dimensions[0], data->dimensions[1],
284
NA_OFFSETDATA(correlated),
290
/* Align, Byteswap, Contiguous, Typeconvert */
291
return NA_ReturnOutput(ocorrelated, correlated);
296
Py_XDECREF(correlated);
300
void Shift2d( long rows, long cols, Float64 *data, long dx, long dy, Float64 *output, int mode, Float64 cval)
310
for(r=0; r<rows; r++)
311
for(c=0; c<cols; c++)
312
output[ r*cols + c] = SlowPix(r+dy, c+dx, &pix);
316
Py_Shift2d(PyObject *obj, PyObject *args, PyObject *kw)
318
PyObject *odata, *ooutput=NULL;
319
PyArrayObject *data, *output;
322
int mode = PIX_NEAREST;
323
char *keywds[] = { "data", "dx", "dy",
324
"output", "mode", "cval", NULL };
326
if (!PyArg_ParseTupleAndKeywords(args, kw, "Oii|Oid:Shift2d", keywds,
327
&odata, &dx, &dy, &ooutput, &mode, &cval))
330
if ((mode < PIX_NEAREST) || (mode > PIX_CONSTANT))
331
return PyErr_Format(PyExc_ValueError,
332
"Shift2d: mode value not in range(%d,%d)",
333
PIX_NEAREST, PIX_CONSTANT);
335
/* Align, Byteswap, Contiguous, Typeconvert */
336
data = NA_InputArray(odata, tFloat64, C_ARRAY);
337
output = NA_OptionalOutputArray(ooutput, tFloat64, C_ARRAY,
340
if (!data || !output)
343
if (_reject_complex(odata) || _reject_complex(ooutput))
346
if ((data->nd != 2)) {
347
PyErr_Format(PyExc_ValueError,
348
"Shift2d: numarray must have 2 dimensions.");
352
if (!NA_ShapeEqual(data, output)) {
353
PyErr_Format(PyExc_ValueError,
354
"Shift2d: data and output numarray need identical shapes.");
358
/* Invert sign of deltas to match sense of 2x2 correlation. */
359
Shift2d( data->dimensions[0], data->dimensions[1], NA_OFFSETDATA(data),
360
-dx, -dy, NA_OFFSETDATA(output), mode, cval);
364
/* Align, Byteswap, Contiguous, Typeconvert */
365
return NA_ReturnOutput(ooutput, output);
372
typedef struct s_BoxData BoxData;
374
typedef Float64 (*SumColFunc)(long,long,BoxData*);
375
typedef Float64 (*SumBoxFunc)(long,long,BoxData*);
385
SlowSumCol(long r, long c, BoxData *D)
388
long i, krows = D->krows;
389
for(i=0; i<krows; i++) {
390
sum += SlowPix(r+i, c, &D->pix);
396
SlowSumBox(long r, long c, BoxData *D)
400
for(i=0; i<D->krows; i++)
401
for(j=0; j<D->kcols; j++)
402
sum += SlowPix(r+i, c+j, &D->pix);
407
FastSumCol(long r, long c, BoxData *D)
410
long krows = D->krows;
411
long cols = D->pix.cols;
412
Float64 *data = D->pix.data;
415
for(; krows--; data += cols) {
422
FastSumBox(long r, long c, BoxData *D)
426
long cols = D->pix.cols;
427
Float64 *data = D->pix.data;
429
for(i=0; i<D->krows; i++, data += cols-D->kcols)
430
for(j=0; j<D->kcols; j++, data++)
435
static long bound(long x, long max)
438
else if (x > max) return max;
443
BoxFunc(long rmin, long rmax, long cmin, long cmax, Float64 *output, BoxData *D)
446
long krows2 = D->krows/2;
447
long kcols2 = D->kcols/2;
448
long kcolseven = !(D->kcols & 1);
449
long rows = D->pix.rows;
450
long cols = D->pix.cols;
452
rmin = bound(rmin, rows);
453
rmax = bound(rmax, rows);
454
cmin = bound(cmin, cols);
455
cmax = bound(cmax, cols);
457
for(r=rmin; r<rmax; r++) {
458
Float64 sum = D->sumbox(r - krows2, cmin - kcols2, D);
459
for(c=cmin; c<cmax; c++) {
460
output[r*cols + c] = sum;
461
sum -= D->sumcol(r - krows2, c - kcols2, D);
462
sum += D->sumcol(r - krows2, c + kcols2 - kcolseven + 1, D);
467
/* BoxFuncI computes a boxcar incrementally, using a formula independent of
468
the size of the boxcar. Each incremental step is based on dropping a
469
whole column of the "back" of the boxcar, and adding in a new column in
470
the "front". The sums of these columns are further optimized by realizing
471
they can be computed from their counterparts one element above by adding in
472
bottom corners and subtracting out top corners.
474
incremental pixel layout: B C where S is the unknown, and A, B, C are known neighbors
475
A S each of these refers to the output array
477
S = A + a1 - a0 where a0 and a1 are column vectors with *bottom* elements { a, d }
478
C - B = b1 - b0 where b0 and b1 are column vectors with *top* elements { b, g }
479
column vectors and corner elements refer to the input array
481
offset matrix layout: b g where b is actually in b0
484
a d d is actually in a1
486
a0 = b0 - b + a column vector a0 is b0 dropping top element b and adding bottom a
487
a1 = b1 - g + d column vector a1 is b1 dropping top element g and adding bottom d
489
S = A + (b1 - g + f) - (b0 - b + a) by substitution
490
S = A + (b1 - b0) - g + d + b - a rearranging additions
491
S = A + C - B - g + d + b - a by substitution
494
BoxFuncI(long rmin, long rmax, long cmin, long cmax, Float64 *output, BoxData *D)
497
long krows2 = D->krows/2;
498
long kcols2 = D->kcols/2;
499
long krowseven = !(D->krows & 1);
500
long kcolseven = !(D->kcols & 1);
501
long rows = D->pix.rows;
502
long cols = D->pix.cols;
503
Float64 *input = D->pix.data;
505
rmin = bound(rmin, rows);
506
rmax = bound(rmax, rows);
507
cmin = bound(cmin, cols);
508
cmax = bound(cmax, cols);
510
for(r=rmin; r<rmax; r++) {
511
long top = r - krows2 - 1;
512
long bottom = r + krows2 - krowseven;
514
for(c=cmin; c<cmax; c++) {
515
long left = c - kcols2 - 1;
516
long right = c + kcols2 - kcolseven;
518
Float64 A = output [ r * cols + (c-1) ];
519
Float64 B = output [ (r-1) * cols + (c-1) ];
520
Float64 C = output [ (r-1) * cols + c ];
521
Float64 a = input [ bottom * cols + left ];
522
Float64 b = input [ top * cols + left ];
523
Float64 g = input [ top * cols + right ];
524
Float64 d = input [ bottom * cols + right ];
526
output[r*cols + c] = A + C - B - g + d + b - a;
532
Boxcar2d(long krows, long kcols, long rows, long cols, Float64 *data,
533
Float64 *output, PixMode mode, Float64 constval)
535
long krows2 = krows/2;
536
long krowseven = !(krows&1);
537
long kcols2 = kcols/2;
538
long kcolseven = !(kcols&1);
544
D.pix.constval = constval;
550
D.sumcol = SlowSumCol;
551
D.sumbox = SlowSumBox;
553
/* The next 4 calls compute boxcars on the boundary pixels with
554
different modes detemining what data values are fetched when "out
555
of bounds". Presumably, this is a small minority of the data and
556
therefore the implementation inefficiency is not *too* damaging.
557
It should at least beat making 2 outsized copies of the data array. */
559
/* top whole plus one */
560
BoxFunc(0, krows2+2, 0, cols, output, &D);
563
BoxFunc(rows-krows2+krowseven, rows, 0, cols, output, &D);
565
/* left whole plus one */
566
BoxFunc(0, rows, 0, kcols2+2, output, &D);
569
BoxFunc(0, rows, cols-kcols2+kcolseven, cols, output, &D);
571
/* Do the boxcar on the "center" data, the data where the boxcar is
572
always "in bounds". Presumably, this is the bulk of the data so
575
D.sumcol = FastSumCol;
576
D.sumbox = FastSumBox;
578
BoxFuncI( krows2+2, rows-krows2+krowseven,
579
kcols2+2, cols-kcols2+kcolseven,
582
karea = kcols * krows;
583
for(r=0; r<rows; r++)
584
for(c=0; c<cols; c++)
585
output[r*cols + c] /= karea;
589
Py_Boxcar2d(PyObject *obj, PyObject *args, PyObject *kw)
591
PyObject *odata, *ooutput=NULL;
592
PyArrayObject *data, *output;
593
int krows, kcols, mode=PIX_NEAREST;
595
char *keywds[] = { "data", "krows", "kcols",
596
"output", "mode", "cval", NULL };
598
if (!PyArg_ParseTupleAndKeywords(args, kw, "Oii|Oid:Boxcar2d", keywds,
599
&odata, &krows, &kcols, &ooutput, &mode, &cval))
602
/* Align, Byteswap, Contiguous, Typeconvert */
603
data = NA_InputArray(odata, tFloat64, C_ARRAY);
604
output = NA_OptionalOutputArray(ooutput, tFloat64, C_ARRAY, data);
605
if (!data || !output)
608
if (_reject_complex(odata) || _reject_complex(ooutput))
611
if ((krows < 0) || (kcols < 0)) {
612
PyErr_Format(PyExc_ValueError, "krows and kcols must be > 0.");
616
if ((mode < PIX_NEAREST) || (mode > PIX_CONSTANT)) {
617
PyErr_Format(PyExc_ValueError,
618
"Boxcar2d: mode value not in range(%d,%d)",
619
PIX_NEAREST, PIX_CONSTANT);
623
if ((data->nd != 2)|| (output->nd != 2)) {
624
PyErr_Format(PyExc_ValueError,
625
"Boxcar2d: numarray must have 2 dimensions.");
629
if (!NA_ShapeEqual(data, output)) {
630
PyErr_Format(PyExc_ValueError,
631
"Boxcar2d: data and output numarray need identical shapes.");
635
if ((kcols <=0) || (krows <= 0)) {
636
PyErr_Format(PyExc_ValueError,
637
"Boxcar2d: invalid data shape.");
640
if ((kcols > data->dimensions[1]) || (krows > data->dimensions[0])) {
641
PyErr_Format(PyExc_ValueError, "Boxcar2d: boxcar shape incompatible with"
646
Boxcar2d(krows, kcols, data->dimensions[0], data->dimensions[1],
647
NA_OFFSETDATA(data), NA_OFFSETDATA(output), mode, cval);
651
/* Align, Byteswap, Contiguous, Typeconvert */
652
return NA_ReturnOutput(ooutput, output);
659
static PyMethodDef _correlateMethods[] = {
660
{"Correlate1d", Py_Correlate1d, METH_VARARGS},
661
{"Correlate2d", (PyCFunction) Py_Correlate2d, METH_VARARGS | METH_KEYWORDS},
662
{"Shift2d", (PyCFunction) Py_Shift2d, METH_VARARGS | METH_KEYWORDS,
663
"Shift2d shifts and image by an integer number of pixels, and uses IRAF compatible modes for the boundary pixels."},
664
{"Boxcar2d", (PyCFunction) Py_Boxcar2d, METH_VARARGS | METH_KEYWORDS,
665
"Boxcar2d computes a sliding 2D boxcar average on a 2D array"},
666
{NULL, NULL} /* Sentinel */
669
PyMODINIT_FUNC init_correlate(void)
672
m = Py_InitModule("_correlate", _correlateMethods);
673
d = PyModule_GetDict(m);
674
import_libnumarray();
680
* c-file-style: "python"