/* -*- coding: utf-8;mode:c++;c-file-style:"stroustrup" -*- */ /* Licensed under the terms of the BSD 3-Clause (see plotpy/__init__.py for details) */ #include #undef NO_IMPORT_ARRAY #define PY_ARRAY_UNIQUE_SYMBOL PyScalerArray #include #ifdef _MSC_VER #include #define FE_TOWARDZERO _RC_CHOP #define fegetround() (_controlfp(0, 0) & _MCW_RC) int fesetround(int r) { if ((r & _MCW_RC) == r) { /* check the supplied value is one of those allowed */ int result = _controlfp(r, _MCW_RC) & _MCW_RC; return !(r == result); } return !0; } #else #include #endif #include #if defined(_MSC_VER) || defined(__MINGW32__) #define isnan(x) _isnan(x) #endif #include #include #include #include #include "points.hpp" #include "arrays.hpp" #include "scaler.hpp" using std::max; using std::min; using std::swap; using std::vector; enum { INTERP_NEAREST = 0, INTERP_LINEAR = 1, INTERP_AA = 2 }; typedef union { npy_uint32 v; npy_uint8 c[4]; } rgba_t; typedef XYTransform> XYScale; template struct params { typedef Transform transform_type; params(PyArrayObject *_src, PyArrayObject *_dst, PyObject *_dst_data, PyObject *_lut, PyObject *_interp, Transform &_trans) : p_dst(_dst), p_dst_data(_dst_data), p_src(_src), p_lut(_lut), p_interpolation(_interp), trans(_trans) { } // Source, dest, coordinate transformation PyArrayObject *p_dst; PyObject *p_dst_data; PyArrayObject *p_src; PyObject *p_lut; // Pixel value transformation tuple PyObject *p_interpolation; Transform &trans; int dx1, dx2, dy1, dy2; }; template struct NearestInterpolation { T operator()(const Array2D &src, const TR &tr, const typename TR::point &p) { int nx = p.ix(); int ny = p.iy(); return src.value(nx, ny); } }; template struct LinearInterpolation { T operator()(const Array2D &src, const TR &tr, const typename TR::point &p) { int nx = p.ix(); int ny = p.iy(); double v = src.value(nx, ny); double a = 0; // The following couple of lines were commented out to avoid disabling // the linear interpolation on image edges. Demonstrating the effect of // this change is quite easy: just try to show a very small image // (e.g. 10x10) with pyplot.imshow for example. // if (nx==0||nx==src.nj-1) return (T)v; // if (ny==0||ny==src.ni-1) return (T)v; if (nx < src.nj - 1) { a = p.x() - nx; v = (1 - a) * v + a * src.value(nx + 1, ny); } if (ny >= src.ni - 1) return (T)v; double v2 = src.value(nx, ny + 1); double b = p.y() - ny; if (nx < src.nj - 1) { v2 = (1 - a) * v2 + a * src.value(nx + 1, ny + 1); } return (T)(v * (1 - b) + b * v2); } }; template struct LinearInterpolation { npy_uint32 operator()(const Array2D &src, const TR &tr, const typename TR::point &p) { int k; int nx = p.ix(); int ny = p.iy(); rgba_t p1, p2, p3, p4, r; p1.v = src.value(nx, ny); float v[4], v2[4]; double a = 0; if (nx < src.nj - 1) { p2.v = src.value(nx + 1, ny); a = p.x() - nx; for (k = 0; k < 4; ++k) { v[k] = (1 - a) * p1.c[k] + a * p2.c[k]; } } else { for (k = 0; k < 4; ++k) { v[k] = p1.c[k]; } } if (ny >= src.ni - 1) { for (k = 0; k < 4; ++k) { r.c[k] = (npy_uint8)(v[k]); } return r.v; } p3.v = src.value(nx, ny + 1); double b = p.y() - ny; if (nx < src.nj - 1) { p4.v = src.value(nx + 1, ny + 1); for (k = 0; k < 4; ++k) { v2[k] = (1 - a) * p3.c[k] + a * p4.c[k]; } } else { for (k = 0; k < 4; ++k) { v2[k] = p3.c[k]; } } for (k = 0; k < 4; ++k) { float px = v[k] * (1 - b) + b * v2[k]; if (px < 0.0) px = 0.0; if (px > 255.0) px = 255.0; r.c[k] = (npy_uint8)px; } return r.v; } }; #if 0 template struct QuadInterpolation { T operator()(const Array2D& src, const TR& tr, const typename TR::point& p) { int nx = p.ix(); int ny = p.iy(); double v = src.value(nx, ny); double a=0; if (nx==0||nx==src.nj-1) return src.value(nx,ny); if (ny==0||ny==src.ni-1) return src.value(nx,ny); v0 = interp(src.value(nx-1,ny-1)); if (nx=src.ni-1) return v; double v2 = src.value(nx,ny+1); double b = p.y()-ny; if (nx struct LinearInterpolation { T operator()(const Array2D &src, const XYScale &tr, const typename XYScale::point &p) { int nx = p.ix(); int ny = p.iy(); double v = src.value(nx, ny); double a = 0; if (nx == 0 || nx == src.nj - 1) return (T)v; if (ny == 0 || ny == src.ni - 1) return (T)v; if (nx < src.nj - 1) { double x0 = tr.ax.value(nx); double x1 = tr.ax.value(nx + 1); a = (p.x() - x0) / (x1 - x0); v = (1 - a) * v + a * src.value(nx + 1, ny); } if (ny >= src.ni - 1) return (T)v; double v2 = src.value(nx, ny + 1); double y0 = tr.ay.value(ny); double y1 = tr.ay.value(ny + 1); double b = (p.y() - y0) / (y1 - y0); if (nx < src.nj - 1) { v2 = (1 - a) * v2 + a * src.value(nx + 1, ny + 1); } return (T)(v * (1 - b) + b * v2); } }; template <> struct LinearInterpolation { npy_uint32 operator()(const Array2D &src, const XYScale &tr, const XYScale::point &p) { return 0; } }; template struct SubSampleInterpolation { SubSampleInterpolation(const Array2D &_mask) : mask(_mask) { ki = 1. / (mask.ni - 1); kj = 1. / (mask.nj - 1); } T operator()(const Array2D &src, const TR &tr, const typename TR::point &p0) { int i, j; typename TR::point p, p1; typename num_trait::large_type value = 0; typename num_trait::large_type count = 0, msk, val; p1.copy(p0); tr.incy(p1, -0.5); tr.incx(p1, -0.5); for (i = 0; i < mask.ni; ++i) { p.copy(p1); for (j = 0; j < mask.nj; ++j) { if (p.inside()) { msk = mask.value(j, i); val = src.value(p.ix(), p.iy()); value += msk * val; count += msk; // printf("i,j=%d,%d : %f,%f / %d,%d = %lfx%lf\n", i, j, p.x(), p.y(), p.ix(), p.iy(), (double)val, (double)msk ); } tr.incx(p, kj); } tr.incy(p1, ki); } // printf("%d/%d\n", (int)value, (int)count); if (count) return value / count; else return value; } typename TR::real ki, kj; const Array2D &mask; }; template void _scale_rgb(DEST &dest, Array2D &src, const Scale &scale, const Trans &tr, int dx1, int dy1, int dx2, int dy2, Interpolation &interpolate) { int i, j; ST val; int round = fegetround(); PixelIterator it(dest); typename Trans::point p, p0; fesetround(FE_TOWARDZERO); /* printf("SRC: ni=%d nj=%d\n", src.ni, src.nj); printf("TR: dx=%lf dy=%lf\n", tr.dx, tr.dy); printf("DST: ni=%d nj=%d si=%d sj=%d\n", dest.ni, dest.nj, dest.si, dest.sj); */ tr.set(p0, dx1, dy1); for (i = dy1; i < dy2; ++i) { it.moveto(dx1, i); p = p0; for (j = dx1; j < dx2; ++j) { if (!p.inside()) { scale.set_bg(it()); } else { val = interpolate(src, tr, p); if (isnan(val)) { scale.set_bg(it()); } else { it() = scale.eval(val); } } tr.incx(p); it.move(1, 0); } tr.incy(p0); } fesetround(round); } static bool check_dispatch_type(const char *name, PyArrayObject *p_src) { if (PyArray_TYPE(p_src) != NPY_DOUBLE && PyArray_TYPE(p_src) != NPY_FLOAT && PyArray_TYPE(p_src) != NPY_UINT64 && PyArray_TYPE(p_src) != NPY_INT64 && PyArray_TYPE(p_src) != NPY_UINT32 && PyArray_TYPE(p_src) != NPY_INT32 && PyArray_TYPE(p_src) != NPY_UINT16 && PyArray_TYPE(p_src) != NPY_INT16 && PyArray_TYPE(p_src) != NPY_UINT8 && PyArray_TYPE(p_src) != NPY_INT8 && PyArray_TYPE(p_src) != NPY_BOOL) { PyErr_Format(PyExc_TypeError, "%s data type must be one of the following:" " double, float, uint64, int64, uint32, int32, uint16, int16, uint8, int8, bool", name); return false; } return true; } static bool check_array_2d(const char *name, PyArrayObject *arr, int dtype) { if (!PyArray_Check(arr)) { PyErr_Format(PyExc_TypeError, "%s must be a ndarray", name); return false; } if (PyArray_NDIM(arr) != 2) { PyErr_Format(PyExc_TypeError, "%s must be 2-D array", name); return false; } if (dtype >= 0 && PyArray_TYPE(arr) != dtype) { PyErr_Format(PyExc_TypeError, "%s data type must be %d", name, dtype); return false; } return true; } bool check_arrays(PyArrayObject *p_src, PyArrayObject *p_dest) { if (!PyArray_Check(p_src) || !PyArray_Check(p_dest)) { PyErr_SetString(PyExc_TypeError, "src and dst must be ndarrays"); return false; } if (PyArray_TYPE(p_dest) != NPY_UINT32 && PyArray_TYPE(p_dest) != NPY_FLOAT32 && PyArray_TYPE(p_dest) != NPY_FLOAT64) { PyErr_SetString(PyExc_TypeError, "dst data type must be uint32 or float"); return false; } if (PyArray_NDIM(p_src) != 2 || PyArray_NDIM(p_dest) != 2) { PyErr_SetString(PyExc_TypeError, "dst and src must be 2-D arrays"); return false; } return check_dispatch_type("src", p_src); } bool check_lut(PyArrayObject *p_lut) { if (!PyArray_Check(p_lut)) { PyErr_SetString(PyExc_TypeError, "lut must be an ndarray"); return false; } if (PyArray_NDIM(p_lut) != 1) { PyErr_SetString(PyExc_TypeError, "lut must be a 1D array"); return false; } if (PyArray_TYPE(p_lut) != NPY_UINT32) { PyErr_SetString(PyExc_TypeError, "lut data type must be uint32"); return false; } return true; } static bool check_transform(PyArrayObject *p_tr) { if (!PyArray_Check(p_tr)) { PyErr_SetString(PyExc_TypeError, "transform must be an ndarray"); return false; } if (PyArray_TYPE(p_tr) != NPY_DOUBLE) { PyErr_SetString(PyExc_TypeError, "transform data type must be float"); return false; } int ni = PyArray_DIM(p_tr, 0); int nj = PyArray_DIM(p_tr, 1); if (ni != 3 || nj != 3) { PyErr_SetString(PyExc_TypeError, "transform must be 3x3"); return false; } return true; } static void check_image_bounds(int ni, int nj, int &dx, int &dy) { if (dx < 0) dx = 0; if (dy < 0) dy = 0; if (dx > nj) dx = nj; if (dy > ni) dy = ni; } template static bool scale_src_dst_interp(Params &p, PixelScale &pixel_scale, Interp &interp) { typedef typename PixelScale::source_type ST; typedef typename PixelScale::dest_type DT; Array2D src(p.p_src); Array2D
dst(p.p_dst); _scale_rgb(dst, src, pixel_scale, p.trans, p.dx1, p.dy1, p.dx2, p.dy2, interp); return true; } template static bool scale_src_dst(Params &p, PixelScale &pixel_scale) { typedef typename PixelScale::source_type ST; typedef typename Params::transform_type TR; typedef NearestInterpolation Nearest; typedef SubSampleInterpolation SubAA; typedef LinearInterpolation Linear; int interpolation; PyArrayObject *p_mask = 0; if (!PyArg_ParseTuple(p.p_interpolation, "i|O", &interpolation, &p_mask)) { PyErr_SetString(PyExc_ValueError, "Can't interpret interpolation"); return false; } switch (interpolation) { case INTERP_NEAREST: { Nearest interp; return scale_src_dst_interp(p, pixel_scale, interp); } case INTERP_AA: { if (!check_array_2d("AA Mask", p_mask, PyArray_TYPE(p.p_src))) return false; Array2D mask(p_mask); SubAA interp(mask); return scale_src_dst_interp(p, pixel_scale, interp); } case INTERP_LINEAR: { Linear interp; return scale_src_dst_interp(p, pixel_scale, interp); } default: PyErr_SetString(PyExc_ValueError, "Unknown interpolation type"); return false; }; } /* we know the transformation and source type, now we dispatch on the destination type, which determines the LUT transformation */ template static bool scale_src_bw(Params &p) { typedef LutScale color_scale; typedef LinearScale bw32_scale; typedef LinearScale bw64_scale; double a, b; PyObject *p_bg; PyArrayObject *p_cmap = 0; bool apply_bg = true; if (!PyArg_ParseTuple(p.p_lut, "ddO|O", &a, &b, &p_bg, &p_cmap)) { PyErr_SetString(PyExc_ValueError, "Can't interpret pixel transformation tuple"); return false; } if (p_bg == Py_None) apply_bg = false; switch (PyArray_TYPE(p.p_dst)) { case NPY_UINT32: { /* Destination is RGB */ unsigned long bg = 0; if (apply_bg) { #if PY_MAJOR_VERSION >= 3 bg = PyLong_AsUnsignedLongMask(p_bg); #else bg = PyInt_AsUnsignedLongMask(p_bg); #endif if (PyErr_Occurred()) return false; } if (!check_lut(p_cmap)) { return false; } Array1D cmap(p_cmap); color_scale scale(a, b, cmap, bg, apply_bg); return scale_src_dst(p, scale); } case NPY_FLOAT32: { double bg = 0.0; if (apply_bg) { bg = PyFloat_AsDouble(p_bg); if (PyErr_Occurred()) return false; } bw32_scale scale(a, b, bg, apply_bg); return scale_src_dst(p, scale); } case NPY_FLOAT64: { double bg = 0.0; if (apply_bg) { bg = PyFloat_AsDouble(p_bg); if (PyErr_Occurred()) return false; } bw64_scale scale(a, b, bg, apply_bg); return scale_src_dst(p, scale); } default: PyErr_SetString(PyExc_TypeError, "Destination array must be uint32 (rgb) or float (BW)"); return false; } } template static bool scale_src_rgb(Params &p) { // Special case p_lut = None NoScale scale(0, false); return scale_src_dst>(p, scale); } template static PyObject *dispatch_source(Params &p) { bool ok; int dni = PyArray_DIM(p.p_dst, 0); int dnj = PyArray_DIM(p.p_dst, 1); if (!PyArg_ParseTuple(p.p_dst_data, "iiii", &p.dx1, &p.dy1, &p.dx2, &p.dy2)) { PyErr_SetString(PyExc_ValueError, "Invalid destination rectangle (expected tuple of 4 integers)"); return NULL; } if (p.dx2 < p.dx1) swap(p.dx1, p.dx2); if (p.dy2 < p.dy1) swap(p.dy1, p.dy2); check_image_bounds(dni, dnj, p.dx1, p.dy1); check_image_bounds(dni, dnj, p.dx2, p.dy2); switch (PyArray_TYPE(p.p_src)) { case NPY_FLOAT32: ok = scale_src_bw(p); break; case NPY_FLOAT64: ok = scale_src_bw(p); break; case NPY_UINT64: ok = scale_src_bw(p); break; case NPY_INT64: ok = scale_src_bw(p); break; case NPY_INT32: ok = scale_src_bw(p); break; case NPY_UINT32: // RGBA ok = scale_src_rgb(p); break; case NPY_UINT16: ok = scale_src_bw(p); break; case NPY_INT16: ok = scale_src_bw(p); break; case NPY_UINT8: ok = scale_src_bw(p); break; case NPY_INT8: ok = scale_src_bw(p); break; case NPY_BOOL: ok = scale_src_bw(p); break; default: PyErr_SetString(PyExc_TypeError, "Unknown source data type"); return NULL; } if (!ok) return NULL; return Py_BuildValue("iiii", p.dx1, p.dy1, p.dx2, p.dy2); } /* Input data : SRC, SRC_DATA, DST, DST_DATA, LUT_DATA SRC : PyArrayObject (i8,u8,i16,u16,float32,float64) DST : PyArrayObject : u32 -> rgb, float32 : bw SRC_DATA : varies : Scale : source rect (x1,y1,x2,y2) Transform : transformation matrix XY : source rect, X array, Y array DST_DATA : dest rect (dx1,dy1,dx2,dy2) LUT_DATA : (a,b,bg) if DST is bw or (a,b,bg,cmap) if DST is rgb */ static PyObject *py_scale_xy(PyObject *self, PyObject *args) { typedef params Params; PyArrayObject *p_src = 0, *p_dst = 0, *p_ax = 0, *p_ay = 0; PyObject *p_lut_data, *p_src_data, *p_dst_data, *p_interp_data; double x1, y1, x2, y2; if (!PyArg_ParseTuple(args, "OOOOOO:_scale_xy", &p_src, &p_src_data, &p_dst, &p_dst_data, &p_lut_data, &p_interp_data)) { return NULL; } if (!check_arrays(p_src, p_dst)) { return NULL; } if (!PyArg_ParseTuple(p_src_data, "OO(dddd):_scale_xy", &p_ax, &p_ay, &x1, &y1, &x2, &y2)) { return NULL; } int ni = PyArray_DIM(p_src, 0); int nj = PyArray_DIM(p_src, 1); int dni = PyArray_DIM(p_dst, 0); int dnj = PyArray_DIM(p_dst, 1); double dx = (x2 - x1) / dnj; double dy = (y2 - y1) / dni; Array1D ax(p_ax), ay(p_ay); XYScale trans(nj, ni, ax, ay, x1, y1, dx, dy); Params scale_params(p_src, p_dst, p_dst_data, p_lut_data, p_interp_data, trans); // examine source type return dispatch_source(scale_params); } static PyObject *py_scale_tr(PyObject *self, PyObject *args) { typedef params Params; PyArrayObject *p_src = 0, *p_dst = 0, *p_tr; PyObject *p_lut_data, *p_dst_data, *p_interp_data; if (!PyArg_ParseTuple(args, "OOOOOO:_scale_tr", &p_src, &p_tr, &p_dst, &p_dst_data, &p_lut_data, &p_interp_data)) { return NULL; } if (!check_arrays(p_src, p_dst)) { return NULL; } if (!check_transform(p_tr)) { return NULL; } int ni = PyArray_DIM(p_src, 0); int nj = PyArray_DIM(p_src, 1); Array2D tr(p_tr); LinearTransform trans(nj, ni, tr.value(2, 0), tr.value(2, 1), // x0, y0 tr.value(0, 0), tr.value(1, 0), // xx, xy tr.value(0, 1), tr.value(1, 1) // yx, yy ); Params scale_params(p_src, p_dst, p_dst_data, p_lut_data, p_interp_data, trans); // examine source type return dispatch_source(scale_params); } static PyObject *py_scale_rect(PyObject *self, PyObject *args) { typedef params Params; PyArrayObject *p_src = 0, *p_dst = 0; PyObject *p_lut_data, *p_dst_data, *p_interp_data, *p_src_data; double x1, x2, y1, y2; if (!PyArg_ParseTuple(args, "OOOOOO:_scale_rect", &p_src, &p_src_data, &p_dst, &p_dst_data, &p_lut_data, &p_interp_data)) { return NULL; } if (!check_arrays(p_src, p_dst)) { return NULL; } if (!PyArg_ParseTuple(p_src_data, "dddd:_scale_rect", &x1, &y1, &x2, &y2)) { return NULL; } int ni = PyArray_DIM(p_src, 0); int nj = PyArray_DIM(p_src, 1); int dni = PyArray_DIM(p_dst, 0); int dnj = PyArray_DIM(p_dst, 1); double dx = (x2 - x1) / dnj; double dy = (y2 - y1) / dni; ScaleTransform trans(nj, ni, x1, y1, dx, dy); Params scale_params(p_src, p_dst, p_dst_data, p_lut_data, p_interp_data, trans); // examine source type return dispatch_source(scale_params); } class Histogram { public: Histogram(PyArrayObject *_data, PyArrayObject *_bins, PyArrayObject *_res, int ignore_bounds) : p_data(_data), p_bins(_bins), p_res(_res), mode(ignore_bounds) { } template void run() { Array1D res(p_res); Array1D data(p_data); Array1D bins(p_bins); typename Array1D::iterator it, end, bin, bin0, bin1; end = data.end(); bin0 = bins.begin(); bin1 = bins.end(); for (it = data.begin(); it < end; ++it) { bin = std::lower_bound(bin0, bin1, *it); res.value(bin - bin0)++; } } PyArrayObject *p_data, *p_bins, *p_res; int mode; }; static PyObject *py_histogram(PyObject *self, PyObject *args) { PyArrayObject *p_data = 0, *p_bins = 0, *p_res = 0; int ignore_bounds = 0; if (!PyArg_ParseTuple(args, "OOO|i:_histogram", &p_data, &p_bins, &p_res, &ignore_bounds)) { return NULL; } if (!PyArray_Check(p_data) || !PyArray_Check(p_bins) || !PyArray_Check(p_res)) { PyErr_SetString(PyExc_TypeError, "data, bins, dest must be ndarray"); return NULL; } if (!check_dispatch_type("data", p_data)) { return NULL; } Histogram hist(p_data, p_bins, p_res, ignore_bounds); dispatch_array(PyArray_TYPE(p_data), hist); Py_INCREF(Py_None); return Py_None; } PyObject *py_vert_line(PyObject *self, PyObject *args); PyObject *py_scale_quads(PyObject *self, PyObject *args); static PyMethodDef _meths[] = { {"_scale_xy", py_scale_xy, METH_VARARGS, "transform source to destination with arbitrary X and Y scale"}, {"_scale_tr", py_scale_tr, METH_VARARGS, "Linear transformation of source to destination"}, {"_scale_rect", py_scale_rect, METH_VARARGS, "Linear rescale of source to destination parallel to axes"}, {"_scale_quads", py_scale_quads, METH_VARARGS, "Linear rescale of a structured grid to destination parallel to axes"}, {"_histogram", py_histogram, METH_VARARGS, "Compute histogram of 1d data"}, {"_line_test", py_vert_line, METH_VARARGS, "Rasterize lines"}, {NULL, NULL, 0, NULL} /* Sentinel */ }; #if PY_MAJOR_VERSION >= 3 static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "_scaler", /* m_name */ "Scaler module", /* m_doc */ -1, /* m_size */ _meths, /* m_methods */ NULL, /* m_reload */ NULL, /* m_traverse */ NULL, /* m_clear */ NULL, /* m_free */ }; #endif PyMODINIT_FUNC #if PY_MAJOR_VERSION >= 3 PyInit__scaler(void) #else init_scaler() #endif { PyObject *m; #if PY_MAJOR_VERSION >= 3 m = PyModule_Create(&moduledef); #else m = Py_InitModule("_scaler", _meths); #endif import_array(); PyModule_AddIntConstant(m, "INTERP_NEAREST", INTERP_NEAREST); PyModule_AddIntConstant(m, "INTERP_LINEAR", INTERP_LINEAR); PyModule_AddIntConstant(m, "INTERP_AA", INTERP_AA); #if PY_MAJOR_VERSION >= 3 return m; #endif }