Generated by Cython 0.29.23

Yellow lines hint at Python interaction.
Click on a line that starts with a "+" to see the C code that Cython generated for it.

Raw output: traverse2d.c

+001: # -*- coding: utf-8 -*-
  __pyx_t_1 = __Pyx_PyDict_NewPresized(1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 1, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_1);
  if (PyDict_SetItem(__pyx_t_1, __pyx_kp_u_traverse2d_line_99, __pyx_kp_u_Fast_pixel_traversal_for_2D_lin) < 0) __PYX_ERR(0, 1, __pyx_L1_error)
  if (PyDict_SetItem(__pyx_d, __pyx_n_s_test, __pyx_t_1) < 0) __PYX_ERR(0, 1, __pyx_L1_error)
  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
 002: 
 003: 
 004: #    pept is a Python library that unifies Positron Emission Particle
 005: #    Tracking (PEPT) research, including tracking, simulation, data analysis
 006: #    and visualisation tools.
 007: #
 008: #    If you used this codebase or any software making use of it in a scientific
 009: #    publication, you must cite the following paper:
 010: #        Nicuşan AL, Windows-Yule CR. Positron emission particle tracking
 011: #        using machine learning. Review of Scientific Instruments.
 012: #        2020 Jan 1;91(1):013329.
 013: #        https://doi.org/10.1063/1.5129251
 014: #
 015: #    Copyright (C) 2019-2021 the pept developers
 016: #
 017: #    This program is free software: you can redistribute it and/or modify
 018: #    it under the terms of the GNU General Public License as published by
 019: #    the Free Software Foundation, either version 3 of the License, or
 020: #    (at your option) any later version.
 021: #
 022: #    This program is distributed in the hope that it will be useful,
 023: #    but WITHOUT ANY WARRANTY; without even the implied warranty of
 024: #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 025: #    GNU General Public License for more details.
 026: #
 027: #    You should have received a copy of the GNU General Public License
 028: #    along with this program.  If not, see <https://www.gnu.org/licenses/>.
 029: #    pept is a Python library that unifies Positron Emission Particle
 030: #    Tracking (PEPT) research, including tracking, simulation, data analysis
 031: #    and visualisation tools
 032: 
 033: 
 034: # File              : traverse2d.pyx
 035: # License           : GNU v3.0
 036: # Author            : Andrei Leonard Nicusan <a.l.nicusan@bham.ac.uk>
 037: # Date              : 03.02.2019
 038: 
 039: 
 040: # cython: language_level=3
 041: # cython: boundscheck=False
 042: # cython: wraparound=False
 043: # cython: initializedcheck=False
 044: # cython: nonecheck=False
 045: # cython: embedsignature=True
 046: # cython: cdivision=True
 047: 
 048: 
 049: from libc.float cimport DBL_MAX
 050: 
 051: 
+052: cdef inline double fabs(double x) nogil:
static CYTHON_INLINE double __pyx_f_4pept_9utilities_8traverse_10traverse2d_fabs(double __pyx_v_x) {
  double __pyx_r;
/* … */
  /* function exit code */
  __pyx_L0:;
  return __pyx_r;
}
+053:     return (x if x >= 0 else -x)
  if (((__pyx_v_x >= 0.0) != 0)) {
    __pyx_t_1 = __pyx_v_x;
  } else {
    __pyx_t_1 = (-__pyx_v_x);
  }
  __pyx_r = __pyx_t_1;
  goto __pyx_L0;
 054: 
 055: 
+056: cdef inline void swap(double *x, double *y) nogil:
static CYTHON_INLINE void __pyx_f_4pept_9utilities_8traverse_10traverse2d_swap(double *__pyx_v_x, double *__pyx_v_y) {
  double __pyx_v_aux;
/* … */
  /* function exit code */
}
 057:     cdef double aux
 058: 
+059:     aux = x[0]
  __pyx_v_aux = (__pyx_v_x[0]);
+060:     x[0] = y[0]
  (__pyx_v_x[0]) = (__pyx_v_y[0]);
+061:     y[0] = aux
  (__pyx_v_y[0]) = __pyx_v_aux;
 062: 
 063: 
+064: cdef double intersect(
static double __pyx_f_4pept_9utilities_8traverse_10traverse2d_intersect(double *__pyx_v_u, double *__pyx_v_v, double __pyx_v_xmin, double __pyx_v_xmax, double __pyx_v_ymin, double __pyx_v_ymax) {
  double __pyx_v_tmin;
  double __pyx_v_tmax;
  double __pyx_v_tymin;
  double __pyx_v_tymax;
  double __pyx_r;
/* … */
  /* function exit code */
  __pyx_L0:;
  return __pyx_r;
}
 065:     double[2] u,
 066:     double[2] v,
 067:     double xmin,
 068:     double xmax,
 069:     double ymin,
 070:     double ymax,
 071: ) nogil:
 072:     '''Given a 2D line defined as L(t) = U + t V and an axis-aligned bounding
 073:     box (AABB), find the `t` for which the line intersects the box, if it
 074:     exists.
 075: 
 076:     It is assumed that the line starts *outside* the AABB, so that t == 0.0 is
 077:     only reserved for when the line does not intersect the box.
 078:     '''
 079: 
 080:     cdef double tmin, tmax, tymin, tymax
 081: 
+082:     tmin = (xmin - u[0]) / v[0]     # Relies on IEEE FP behaviour for div by 0
  __pyx_v_tmin = ((__pyx_v_xmin - (__pyx_v_u[0])) / (__pyx_v_v[0]));
+083:     tmax = (xmax - u[0]) / v[0]
  __pyx_v_tmax = ((__pyx_v_xmax - (__pyx_v_u[0])) / (__pyx_v_v[0]));
 084: 
+085:     tymin = (ymin - u[1]) / v[1]
  __pyx_v_tymin = ((__pyx_v_ymin - (__pyx_v_u[1])) / (__pyx_v_v[1]));
+086:     tymax = (ymax - u[1]) / v[1]
  __pyx_v_tymax = ((__pyx_v_ymax - (__pyx_v_u[1])) / (__pyx_v_v[1]));
 087: 
+088:     if tmin > tmax: swap(&tmin, &tmax)
  __pyx_t_1 = ((__pyx_v_tmin > __pyx_v_tmax) != 0);
  if (__pyx_t_1) {
    __pyx_f_4pept_9utilities_8traverse_10traverse2d_swap((&__pyx_v_tmin), (&__pyx_v_tmax));
  }
+089:     if tymin > tymax: swap(&tymin, &tymax)
  __pyx_t_1 = ((__pyx_v_tymin > __pyx_v_tymax) != 0);
  if (__pyx_t_1) {
    __pyx_f_4pept_9utilities_8traverse_10traverse2d_swap((&__pyx_v_tymin), (&__pyx_v_tymax));
  }
 090: 
+091:     if tmin > tymax or tymin > tmax: return 0.0
  __pyx_t_2 = ((__pyx_v_tmin > __pyx_v_tymax) != 0);
  if (!__pyx_t_2) {
  } else {
    __pyx_t_1 = __pyx_t_2;
    goto __pyx_L6_bool_binop_done;
  }
  __pyx_t_2 = ((__pyx_v_tymin > __pyx_v_tmax) != 0);
  __pyx_t_1 = __pyx_t_2;
  __pyx_L6_bool_binop_done:;
  if (__pyx_t_1) {
    __pyx_r = 0.0;
    goto __pyx_L0;
  }
 092: 
+093:     if tymin > tmin: tmin = tymin
  __pyx_t_1 = ((__pyx_v_tymin > __pyx_v_tmin) != 0);
  if (__pyx_t_1) {
    __pyx_v_tmin = __pyx_v_tymin;
  }
+094:     if tymax < tmax: tmax = tymax
  __pyx_t_1 = ((__pyx_v_tymax < __pyx_v_tmax) != 0);
  if (__pyx_t_1) {
    __pyx_v_tmax = __pyx_v_tymax;
  }
 095: 
+096:     return tmin
  __pyx_r = __pyx_v_tmin;
  goto __pyx_L0;
 097: 
 098: 
+099: cpdef void traverse2d(
static PyObject *__pyx_pw_4pept_9utilities_8traverse_10traverse2d_1traverse2d(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
static void __pyx_f_4pept_9utilities_8traverse_10traverse2d_traverse2d(__Pyx_memviewslice __pyx_v_pixels, __Pyx_memviewslice __pyx_v_lines, __Pyx_memviewslice __pyx_v_grid_x, __Pyx_memviewslice __pyx_v_grid_y, CYTHON_UNUSED int __pyx_skip_dispatch) {
  Py_ssize_t __pyx_v_n_lines;
  Py_ssize_t __pyx_v_nx;
  Py_ssize_t __pyx_v_ny;
  double __pyx_v_gsize_x;
  double __pyx_v_gsize_y;
  double __pyx_v_xmin;
  double __pyx_v_xmax;
  double __pyx_v_ymin;
  double __pyx_v_ymax;
  Py_ssize_t __pyx_v_ix;
  Py_ssize_t __pyx_v_iy;
  double __pyx_v_p1[2];
  double __pyx_v_p2[2];
  double __pyx_v_u[2];
  double __pyx_v_v[2];
  Py_ssize_t __pyx_v_step_x;
  Py_ssize_t __pyx_v_step_y;
  double __pyx_v_tnext_x;
  double __pyx_v_tnext_y;
  double __pyx_v_deltat_x;
  double __pyx_v_deltat_y;
  Py_ssize_t __pyx_v_i;
  Py_ssize_t __pyx_v_j;
  double __pyx_v_t;
/* … */
  /* function exit code */
}

/* Python wrapper */
static PyObject *__pyx_pw_4pept_9utilities_8traverse_10traverse2d_1traverse2d(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
static char __pyx_doc_4pept_9utilities_8traverse_10traverse2d_traverse2d[] = "traverse2d(double[:, :] pixels, double[:, :] lines, double[:] grid_x, double[:] grid_y) -> void\n Fast pixel traversal for 2D lines (or LoRs).\n\n    ::\n\n        Function Signature:\n            traverse2d(\n                double[:, :] pixels,        # Initialised to zero!\n                double[:, :] lines,         # Has exactly 7 columns!\n                double[:] grid_x,           # Has pixels.shape[0] + 1 elements!\n                double[:] grid_y,           # Has pixels.shape[1] + 1 elements!\n            )\n\n    This function computes the number of lines that passes through each pixel,\n    saving the result in `pixels`. It does so in an efficient manner, in which\n    for every line, only the pixels that it passes through are traversed.\n\n    As it is highly optimised, this function does not perform any checks on the\n    validity of the input data. Please check the parameters before calling\n    `traverse2d`, as it WILL segfault on wrong input data. Details are given\n    below, along with an example call.\n\n    Parameters\n    ----------\n    pixels : numpy.ndarray(dtype = numpy.float64, ndim = 2)\n        The `pixels` parameter is a numpy.ndarray of shape (X, Y) that has been\n        initialised to zeros before the function call. The values will be\n        modified in-place in the function to reflect the number of lines that\n        pass through each pixel.\n\n    lines : numpy.ndarray(dtype = numpy.float64, ndim = 2)\n        The `lines` parameter is a numpy.ndarray of shape(N, 5), where each row\n        is formatted as [time, x1, y1, x2, y2]. Only indices 1:5 will be used\n        as the two points P1 = [x1, y1] and P2 = [x2, y2] defining the line (or\n        LoR).\n\n    grid_x : numpy.ndarray(dtype = numpy.float64, ndim = 1)\n        The grid_x parameter is a one-dimensional grid that delimits the pixels\n        in the x-dimension. It must be *sorted* in ascending order with\n        *equally-spaced* numbers and length X + 1 (pixels.shap""e[0] + 1).\n\n    grid_y : numpy.ndarray(dtype = numpy.float64, ndim = 1)\n        The grid_y parameter is a one-dimensional grid that delimits the pixels\n        in the y-dimension. It must be *sorted* in ascending order with\n        *equally-spaced* numbers and length Y + 1 (pixels.shape[1] + 1).\n\n    Examples\n    --------\n    The input parameters can be easily generated using numpy before calling the\n    function. For example, if a plane of 300 x 400 is split into\n    30 x 40 pixels, a possible code would be:\n\n    >>> import numpy as np\n    >>> from pept.utilities.traverse import traverse2d\n    >>>\n    >>> plane = [300, 400]\n    >>> number_of_pixels = [30, 40]\n    >>> pixels = np.zeros(number_of_pixels)\n\n    The grid has one extra element than the number of pixels. For example, 5\n    pixels between 0 and 5 would be delimited by the grid [0, 1, 2, 3, 4, 5]\n    which has 6 elements (see off-by-one errors - story of my life).\n\n    >>> grid_x = np.linspace(0, plane[0], number_of_pixels[0] + 1)\n    >>> grid_y = np.linspace(0, plane[1], number_of_pixels[1] + 1)\n    >>>\n    >>> random_lines = np.random.random((100, 5)) * 100\n\n    Calling `traverse2d` will modify `pixels` in-place.\n\n    >>> traverse2d(pixels, random_lines, grid_x, grid_y)\n\n    Notes\n    -----\n    This function is an adaptation of a widely-used algorithm [1]_, optimised\n    for PEPT LoRs traversal.\n\n    .. [1] Amanatides J, Woo A. A fast voxel traversal algorithm for ray tracing.\n       InEurographics 1987 Aug 24 (Vol. 87, No. 3, pp. 3-10).\n\n    ";
static PyObject *__pyx_pw_4pept_9utilities_8traverse_10traverse2d_1traverse2d(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
  __Pyx_memviewslice __pyx_v_pixels = { 0, 0, { 0 }, { 0 }, { 0 } };
  __Pyx_memviewslice __pyx_v_lines = { 0, 0, { 0 }, { 0 }, { 0 } };
  __Pyx_memviewslice __pyx_v_grid_x = { 0, 0, { 0 }, { 0 }, { 0 } };
  __Pyx_memviewslice __pyx_v_grid_y = { 0, 0, { 0 }, { 0 }, { 0 } };
  PyObject *__pyx_r = 0;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("traverse2d (wrapper)", 0);
  {
    static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_pixels,&__pyx_n_s_lines,&__pyx_n_s_grid_x,&__pyx_n_s_grid_y,0};
    PyObject* values[4] = {0,0,0,0};
    if (unlikely(__pyx_kwds)) {
      Py_ssize_t kw_args;
      const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args);
      switch (pos_args) {
        case  4: values[3] = PyTuple_GET_ITEM(__pyx_args, 3);
        CYTHON_FALLTHROUGH;
        case  3: values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
        CYTHON_FALLTHROUGH;
        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
        CYTHON_FALLTHROUGH;
        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
        CYTHON_FALLTHROUGH;
        case  0: break;
        default: goto __pyx_L5_argtuple_error;
      }
      kw_args = PyDict_Size(__pyx_kwds);
      switch (pos_args) {
        case  0:
        if (likely((values[0] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_pixels)) != 0)) kw_args--;
        else goto __pyx_L5_argtuple_error;
        CYTHON_FALLTHROUGH;
        case  1:
        if (likely((values[1] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_lines)) != 0)) kw_args--;
        else {
          __Pyx_RaiseArgtupleInvalid("traverse2d", 1, 4, 4, 1); __PYX_ERR(0, 99, __pyx_L3_error)
        }
        CYTHON_FALLTHROUGH;
        case  2:
        if (likely((values[2] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_grid_x)) != 0)) kw_args--;
        else {
          __Pyx_RaiseArgtupleInvalid("traverse2d", 1, 4, 4, 2); __PYX_ERR(0, 99, __pyx_L3_error)
        }
        CYTHON_FALLTHROUGH;
        case  3:
        if (likely((values[3] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_grid_y)) != 0)) kw_args--;
        else {
          __Pyx_RaiseArgtupleInvalid("traverse2d", 1, 4, 4, 3); __PYX_ERR(0, 99, __pyx_L3_error)
        }
      }
      if (unlikely(kw_args > 0)) {
        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "traverse2d") < 0)) __PYX_ERR(0, 99, __pyx_L3_error)
      }
    } else if (PyTuple_GET_SIZE(__pyx_args) != 4) {
      goto __pyx_L5_argtuple_error;
    } else {
      values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
      values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
      values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
      values[3] = PyTuple_GET_ITEM(__pyx_args, 3);
    }
    __pyx_v_pixels = __Pyx_PyObject_to_MemoryviewSlice_dsds_double(values[0], PyBUF_WRITABLE); if (unlikely(!__pyx_v_pixels.memview)) __PYX_ERR(0, 100, __pyx_L3_error)
    __pyx_v_lines = __Pyx_PyObject_to_MemoryviewSlice_dsds_double(values[1], PyBUF_WRITABLE); if (unlikely(!__pyx_v_lines.memview)) __PYX_ERR(0, 101, __pyx_L3_error)
    __pyx_v_grid_x = __Pyx_PyObject_to_MemoryviewSlice_ds_double(values[2], PyBUF_WRITABLE); if (unlikely(!__pyx_v_grid_x.memview)) __PYX_ERR(0, 102, __pyx_L3_error)
    __pyx_v_grid_y = __Pyx_PyObject_to_MemoryviewSlice_ds_double(values[3], PyBUF_WRITABLE); if (unlikely(!__pyx_v_grid_y.memview)) __PYX_ERR(0, 103, __pyx_L3_error)
  }
  goto __pyx_L4_argument_unpacking_done;
  __pyx_L5_argtuple_error:;
  __Pyx_RaiseArgtupleInvalid("traverse2d", 1, 4, 4, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 99, __pyx_L3_error)
  __pyx_L3_error:;
  __Pyx_AddTraceback("pept.utilities.traverse.traverse2d.traverse2d", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __Pyx_RefNannyFinishContext();
  return NULL;
  __pyx_L4_argument_unpacking_done:;
  __pyx_r = __pyx_pf_4pept_9utilities_8traverse_10traverse2d_traverse2d(__pyx_self, __pyx_v_pixels, __pyx_v_lines, __pyx_v_grid_x, __pyx_v_grid_y);
  int __pyx_lineno = 0;
  const char *__pyx_filename = NULL;
  int __pyx_clineno = 0;

  /* function exit code */
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}

static PyObject *__pyx_pf_4pept_9utilities_8traverse_10traverse2d_traverse2d(CYTHON_UNUSED PyObject *__pyx_self, __Pyx_memviewslice __pyx_v_pixels, __Pyx_memviewslice __pyx_v_lines, __Pyx_memviewslice __pyx_v_grid_x, __Pyx_memviewslice __pyx_v_grid_y) {
  PyObject *__pyx_r = NULL;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("traverse2d", 0);
  __Pyx_XDECREF(__pyx_r);
  __pyx_t_1 = __Pyx_void_to_None(__pyx_f_4pept_9utilities_8traverse_10traverse2d_traverse2d(__pyx_v_pixels, __pyx_v_lines, __pyx_v_grid_x, __pyx_v_grid_y, 0)); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 99, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_1);
  __pyx_r = __pyx_t_1;
  __pyx_t_1 = 0;
  goto __pyx_L0;

  /* function exit code */
  __pyx_L1_error:;
  __Pyx_XDECREF(__pyx_t_1);
  __Pyx_AddTraceback("pept.utilities.traverse.traverse2d.traverse2d", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __pyx_r = NULL;
  __pyx_L0:;
  __PYX_XDEC_MEMVIEW(&__pyx_v_pixels, 1);
  __PYX_XDEC_MEMVIEW(&__pyx_v_lines, 1);
  __PYX_XDEC_MEMVIEW(&__pyx_v_grid_x, 1);
  __PYX_XDEC_MEMVIEW(&__pyx_v_grid_y, 1);
  __Pyx_XGIVEREF(__pyx_r);
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}
 100:     double[:, :] pixels,        # Initialised to zero!
 101:     double[:, :] lines,         # Has exactly 5 columns!
 102:     double[:] grid_x,           # Has pixels.shape[0] + 1 elements!
 103:     double[:] grid_y,           # Has pixels.shape[1] + 1 elements!
 104: ) nogil:
 105:     ''' Fast pixel traversal for 2D lines (or LoRs).
 106: 
 107:     ::
 108: 
 109:         Function Signature:
 110:             traverse2d(
 111:                 double[:, :] pixels,        # Initialised to zero!
 112:                 double[:, :] lines,         # Has exactly 7 columns!
 113:                 double[:] grid_x,           # Has pixels.shape[0] + 1 elements!
 114:                 double[:] grid_y,           # Has pixels.shape[1] + 1 elements!
 115:             )
 116: 
 117:     This function computes the number of lines that passes through each pixel,
 118:     saving the result in `pixels`. It does so in an efficient manner, in which
 119:     for every line, only the pixels that it passes through are traversed.
 120: 
 121:     As it is highly optimised, this function does not perform any checks on the
 122:     validity of the input data. Please check the parameters before calling
 123:     `traverse2d`, as it WILL segfault on wrong input data. Details are given
 124:     below, along with an example call.
 125: 
 126:     Parameters
 127:     ----------
 128:     pixels : numpy.ndarray(dtype = numpy.float64, ndim = 2)
 129:         The `pixels` parameter is a numpy.ndarray of shape (X, Y) that has been
 130:         initialised to zeros before the function call. The values will be
 131:         modified in-place in the function to reflect the number of lines that
 132:         pass through each pixel.
 133: 
 134:     lines : numpy.ndarray(dtype = numpy.float64, ndim = 2)
 135:         The `lines` parameter is a numpy.ndarray of shape(N, 5), where each row
 136:         is formatted as [time, x1, y1, x2, y2]. Only indices 1:5 will be used
 137:         as the two points P1 = [x1, y1] and P2 = [x2, y2] defining the line (or
 138:         LoR).
 139: 
 140:     grid_x : numpy.ndarray(dtype = numpy.float64, ndim = 1)
 141:         The grid_x parameter is a one-dimensional grid that delimits the pixels
 142:         in the x-dimension. It must be *sorted* in ascending order with
 143:         *equally-spaced* numbers and length X + 1 (pixels.shape[0] + 1).
 144: 
 145:     grid_y : numpy.ndarray(dtype = numpy.float64, ndim = 1)
 146:         The grid_y parameter is a one-dimensional grid that delimits the pixels
 147:         in the y-dimension. It must be *sorted* in ascending order with
 148:         *equally-spaced* numbers and length Y + 1 (pixels.shape[1] + 1).
 149: 
 150:     Examples
 151:     --------
 152:     The input parameters can be easily generated using numpy before calling the
 153:     function. For example, if a plane of 300 x 400 is split into
 154:     30 x 40 pixels, a possible code would be:
 155: 
 156:     >>> import numpy as np
 157:     >>> from pept.utilities.traverse import traverse2d
 158:     >>>
 159:     >>> plane = [300, 400]
 160:     >>> number_of_pixels = [30, 40]
 161:     >>> pixels = np.zeros(number_of_pixels)
 162: 
 163:     The grid has one extra element than the number of pixels. For example, 5
 164:     pixels between 0 and 5 would be delimited by the grid [0, 1, 2, 3, 4, 5]
 165:     which has 6 elements (see off-by-one errors - story of my life).
 166: 
 167:     >>> grid_x = np.linspace(0, plane[0], number_of_pixels[0] + 1)
 168:     >>> grid_y = np.linspace(0, plane[1], number_of_pixels[1] + 1)
 169:     >>>
 170:     >>> random_lines = np.random.random((100, 5)) * 100
 171: 
 172:     Calling `traverse2d` will modify `pixels` in-place.
 173: 
 174:     >>> traverse2d(pixels, random_lines, grid_x, grid_y)
 175: 
 176:     Notes
 177:     -----
 178:     This function is an adaptation of a widely-used algorithm [1]_, optimised
 179:     for PEPT LoRs traversal.
 180: 
 181:     .. [1] Amanatides J, Woo A. A fast voxel traversal algorithm for ray tracing.
 182:        InEurographics 1987 Aug 24 (Vol. 87, No. 3, pp. 3-10).
 183: 
 184:     '''
 185: 
+186:     n_lines = lines.shape[0]
  __pyx_v_n_lines = (__pyx_v_lines.shape[0]);
+187:     cdef Py_ssize_t nx = pixels.shape[0]
  __pyx_v_nx = (__pyx_v_pixels.shape[0]);
+188:     cdef Py_ssize_t ny = pixels.shape[1]
  __pyx_v_ny = (__pyx_v_pixels.shape[1]);
 189: 
 190:     # Grid size
+191:     cdef double gsize_x = grid_x[1] - grid_x[0]
  __pyx_t_1 = 1;
  __pyx_t_2 = 0;
  __pyx_v_gsize_x = ((*((double *) ( /* dim=0 */ (__pyx_v_grid_x.data + __pyx_t_1 * __pyx_v_grid_x.strides[0]) ))) - (*((double *) ( /* dim=0 */ (__pyx_v_grid_x.data + __pyx_t_2 * __pyx_v_grid_x.strides[0]) ))));
+192:     cdef double gsize_y = grid_y[1] - grid_y[0]
  __pyx_t_2 = 1;
  __pyx_t_1 = 0;
  __pyx_v_gsize_y = ((*((double *) ( /* dim=0 */ (__pyx_v_grid_y.data + __pyx_t_2 * __pyx_v_grid_y.strides[0]) ))) - (*((double *) ( /* dim=0 */ (__pyx_v_grid_y.data + __pyx_t_1 * __pyx_v_grid_y.strides[0]) ))));
 193: 
 194:     # Delimiting grid
+195:     cdef double xmin = grid_x[0]
  __pyx_t_1 = 0;
  __pyx_v_xmin = (*((double *) ( /* dim=0 */ (__pyx_v_grid_x.data + __pyx_t_1 * __pyx_v_grid_x.strides[0]) )));
+196:     cdef double xmax = grid_x[nx]
  __pyx_t_1 = __pyx_v_nx;
  __pyx_v_xmax = (*((double *) ( /* dim=0 */ (__pyx_v_grid_x.data + __pyx_t_1 * __pyx_v_grid_x.strides[0]) )));
 197: 
+198:     cdef double ymin = grid_y[0]
  __pyx_t_1 = 0;
  __pyx_v_ymin = (*((double *) ( /* dim=0 */ (__pyx_v_grid_y.data + __pyx_t_1 * __pyx_v_grid_y.strides[0]) )));
+199:     cdef double ymax = grid_y[ny]
  __pyx_t_1 = __pyx_v_ny;
  __pyx_v_ymax = (*((double *) ( /* dim=0 */ (__pyx_v_grid_y.data + __pyx_t_1 * __pyx_v_grid_y.strides[0]) )));
 200: 
 201:     # The current pixel indices [ix, iy] that the line passes
 202:     # through.
 203:     cdef Py_ssize_t ix, iy
 204: 
 205:     # Define a line as L(t) = U + t V
 206:     # If an LoR is defined as two points P1 and P2, then
 207:     # U = P1 and V = P2 - P1
 208:     cdef double[2] p1, p2, u, v
 209: 
 210:     # The step [step_x, step_y, step_z] defines the sense of the LoR.
 211:     # If V[0] is positive, then step_x = 1
 212:     # If V[0] is negative, then step_x = -1
 213:     cdef Py_ssize_t step_x, step_y
 214: 
 215:     # The value of t at which the line passes through to the next
 216:     # pixel, for each dimension.
 217:     cdef double tnext_x, tnext_y
 218: 
 219:     # deltat indicates how far along the ray we must move (in units of
 220:     # t) for each component to be equal to the size of the pixel in
 221:     # that dimension.
 222:     cdef double deltat_x, deltat_y
 223: 
 224:     cdef Py_ssize_t i, j
 225: 
+226:     for i in range(n_lines):
  __pyx_t_3 = __pyx_v_n_lines;
  __pyx_t_4 = __pyx_t_3;
  for (__pyx_t_5 = 0; __pyx_t_5 < __pyx_t_4; __pyx_t_5+=1) {
    __pyx_v_i = __pyx_t_5;
 227: 
+228:         for j in range(2):
    for (__pyx_t_6 = 0; __pyx_t_6 < 2; __pyx_t_6+=1) {
      __pyx_v_j = __pyx_t_6;
+229:             p1[j] = lines[i, 1 + j]
      __pyx_t_1 = __pyx_v_i;
      __pyx_t_2 = (1 + __pyx_v_j);
      (__pyx_v_p1[__pyx_v_j]) = (*((double *) ( /* dim=1 */ (( /* dim=0 */ (__pyx_v_lines.data + __pyx_t_1 * __pyx_v_lines.strides[0]) ) + __pyx_t_2 * __pyx_v_lines.strides[1]) )));
+230:             p2[j] = lines[i, 3 + j]
      __pyx_t_2 = __pyx_v_i;
      __pyx_t_1 = (3 + __pyx_v_j);
      (__pyx_v_p2[__pyx_v_j]) = (*((double *) ( /* dim=1 */ (( /* dim=0 */ (__pyx_v_lines.data + __pyx_t_2 * __pyx_v_lines.strides[0]) ) + __pyx_t_1 * __pyx_v_lines.strides[1]) )));
 231: 
+232:             u[j] = p1[j]
      (__pyx_v_u[__pyx_v_j]) = (__pyx_v_p1[__pyx_v_j]);
+233:             v[j] = p2[j] - p1[j]
      (__pyx_v_v[__pyx_v_j]) = ((__pyx_v_p2[__pyx_v_j]) - (__pyx_v_p1[__pyx_v_j]));
    }
 234: 
 235:         ##############################################################
 236:         # Initialisation stage
 237: 
+238:         step_x = 1 if v[0] >= 0 else -1
    if ((((__pyx_v_v[0]) >= 0.0) != 0)) {
      __pyx_t_6 = 1;
    } else {
      __pyx_t_6 = -1L;
    }
    __pyx_v_step_x = __pyx_t_6;
+239:         step_y = 1 if v[1] >= 0 else -1
    if ((((__pyx_v_v[1]) >= 0.0) != 0)) {
      __pyx_t_6 = 1;
    } else {
      __pyx_t_6 = -1L;
    }
    __pyx_v_step_y = __pyx_t_6;
 240: 
 241:         # If the first point is outside the box, find the first pixel it hits
+242:         if (u[0] < xmin or u[0] > xmax or
    __pyx_t_8 = (((__pyx_v_u[0]) < __pyx_v_xmin) != 0);
    if (!__pyx_t_8) {
    } else {
      __pyx_t_7 = __pyx_t_8;
      goto __pyx_L8_bool_binop_done;
    }
    __pyx_t_8 = (((__pyx_v_u[0]) > __pyx_v_xmax) != 0);
    if (!__pyx_t_8) {
    } else {
      __pyx_t_7 = __pyx_t_8;
      goto __pyx_L8_bool_binop_done;
    }
/* … */
    if (__pyx_t_7) {
/* … */
    }
+243:                 u[1] < ymin or u[1] > ymax):
    __pyx_t_8 = (((__pyx_v_u[1]) < __pyx_v_ymin) != 0);
    if (!__pyx_t_8) {
    } else {
      __pyx_t_7 = __pyx_t_8;
      goto __pyx_L8_bool_binop_done;
    }
    __pyx_t_8 = (((__pyx_v_u[1]) > __pyx_v_ymax) != 0);
    __pyx_t_7 = __pyx_t_8;
    __pyx_L8_bool_binop_done:;
 244: 
+245:             t = intersect(u, v, xmin, xmax, ymin, ymax)
      __pyx_v_t = __pyx_f_4pept_9utilities_8traverse_10traverse2d_intersect(__pyx_v_u, __pyx_v_v, __pyx_v_xmin, __pyx_v_xmax, __pyx_v_ymin, __pyx_v_ymax);
 246: 
 247:             # No intersection
+248:             if t == 0.0:
      __pyx_t_7 = ((__pyx_v_t == 0.0) != 0);
      if (__pyx_t_7) {
/* … */
      }
+249:                 continue
        goto __pyx_L3_continue;
 250: 
 251:             # Overwrite U to correspond to the first intersection point
+252:             for j in range(2):
      for (__pyx_t_6 = 0; __pyx_t_6 < 2; __pyx_t_6+=1) {
        __pyx_v_j = __pyx_t_6;
+253:                 u[j] = u[j] + t * v[j]
        (__pyx_v_u[__pyx_v_j]) = ((__pyx_v_u[__pyx_v_j]) + (__pyx_v_t * (__pyx_v_v[__pyx_v_j])));
      }
 254: 
 255:         # Corner case: every pixel is defined as lower boundary (inclusive) and
 256:         # upper boundary (exclusive). Therefore, at the upper end of the pixel
 257:         # grid an undefined case occurs. If a point lies right at the upper
 258:         # boundary of the pixel space, "move it" a bit lower on the line
+259:         if u[0] == xmax or u[1] == ymax:
    __pyx_t_8 = (((__pyx_v_u[0]) == __pyx_v_xmax) != 0);
    if (!__pyx_t_8) {
    } else {
      __pyx_t_7 = __pyx_t_8;
      goto __pyx_L16_bool_binop_done;
    }
    __pyx_t_8 = (((__pyx_v_u[1]) == __pyx_v_ymax) != 0);
    __pyx_t_7 = __pyx_t_8;
    __pyx_L16_bool_binop_done:;
    if (__pyx_t_7) {
/* … */
    }
+260:             for j in range(2):
      for (__pyx_t_6 = 0; __pyx_t_6 < 2; __pyx_t_6+=1) {
        __pyx_v_j = __pyx_t_6;
+261:                 u[j] = u[j] + 1e-5 * v[j]
        (__pyx_v_u[__pyx_v_j]) = ((__pyx_v_u[__pyx_v_j]) + (1e-5 * (__pyx_v_v[__pyx_v_j])));
      }
 262: 
 263:         # If, for dimension x, there are 5 pixels between coordinates 0
 264:         # and 5, then the delimiting grid is [0, 1, 2, 3, 4, 5].
 265:         # If the line starts at 1.5, then it is part of the pixel at
 266:         # index 1
 267: 
+268:         ix = <int>(u[0] / gsize_x)
    __pyx_v_ix = ((int)((__pyx_v_u[0]) / __pyx_v_gsize_x));
+269:         iy = <int>(u[1] / gsize_y)
    __pyx_v_iy = ((int)((__pyx_v_u[1]) / __pyx_v_gsize_y));
 270: 
 271:         # Check the indices are inside the pixel grid
+272:         if (ix < 0 or ix >= nx or iy < 0 or iy >= ny):
    __pyx_t_8 = ((__pyx_v_ix < 0) != 0);
    if (!__pyx_t_8) {
    } else {
      __pyx_t_7 = __pyx_t_8;
      goto __pyx_L21_bool_binop_done;
    }
    __pyx_t_8 = ((__pyx_v_ix >= __pyx_v_nx) != 0);
    if (!__pyx_t_8) {
    } else {
      __pyx_t_7 = __pyx_t_8;
      goto __pyx_L21_bool_binop_done;
    }
    __pyx_t_8 = ((__pyx_v_iy < 0) != 0);
    if (!__pyx_t_8) {
    } else {
      __pyx_t_7 = __pyx_t_8;
      goto __pyx_L21_bool_binop_done;
    }
    __pyx_t_8 = ((__pyx_v_iy >= __pyx_v_ny) != 0);
    __pyx_t_7 = __pyx_t_8;
    __pyx_L21_bool_binop_done:;
    if (__pyx_t_7) {
/* … */
    }
+273:             continue
      goto __pyx_L3_continue;
 274: 
 275:         # If the line is going "up", the next pixel is the next one
 276:         # If the line is going "down", the next pixel is the current one
+277:         if v[0] > 0:
    __pyx_t_7 = (((__pyx_v_v[0]) > 0.0) != 0);
    if (__pyx_t_7) {
/* … */
      goto __pyx_L25;
    }
+278:             tnext_x = (grid_x[ix + 1] - u[0]) / v[0]
      __pyx_t_1 = (__pyx_v_ix + 1);
      __pyx_v_tnext_x = (((*((double *) ( /* dim=0 */ (__pyx_v_grid_x.data + __pyx_t_1 * __pyx_v_grid_x.strides[0]) ))) - (__pyx_v_u[0])) / (__pyx_v_v[0]));
+279:         elif v[0] < 0:
    __pyx_t_7 = (((__pyx_v_v[0]) < 0.0) != 0);
    if (__pyx_t_7) {
/* … */
      goto __pyx_L25;
    }
+280:             tnext_x = (grid_x[ix] - u[0]) / v[0]
      __pyx_t_1 = __pyx_v_ix;
      __pyx_v_tnext_x = (((*((double *) ( /* dim=0 */ (__pyx_v_grid_x.data + __pyx_t_1 * __pyx_v_grid_x.strides[0]) ))) - (__pyx_v_u[0])) / (__pyx_v_v[0]));
 281:         else:
+282:             tnext_x = DBL_MAX
    /*else*/ {
      __pyx_v_tnext_x = DBL_MAX;
    }
    __pyx_L25:;
 283: 
+284:         if v[1] > 0:
    __pyx_t_7 = (((__pyx_v_v[1]) > 0.0) != 0);
    if (__pyx_t_7) {
/* … */
      goto __pyx_L26;
    }
+285:             tnext_y = (grid_y[iy + 1] - u[1]) / v[1]
      __pyx_t_1 = (__pyx_v_iy + 1);
      __pyx_v_tnext_y = (((*((double *) ( /* dim=0 */ (__pyx_v_grid_y.data + __pyx_t_1 * __pyx_v_grid_y.strides[0]) ))) - (__pyx_v_u[1])) / (__pyx_v_v[1]));
+286:         elif v[1] < 0:
    __pyx_t_7 = (((__pyx_v_v[1]) < 0.0) != 0);
    if (__pyx_t_7) {
/* … */
      goto __pyx_L26;
    }
+287:             tnext_y = (grid_y[iy] - u[1]) / v[1]
      __pyx_t_1 = __pyx_v_iy;
      __pyx_v_tnext_y = (((*((double *) ( /* dim=0 */ (__pyx_v_grid_y.data + __pyx_t_1 * __pyx_v_grid_y.strides[0]) ))) - (__pyx_v_u[1])) / (__pyx_v_v[1]));
 288:         else:
+289:             tnext_y = DBL_MAX
    /*else*/ {
      __pyx_v_tnext_y = DBL_MAX;
    }
    __pyx_L26:;
 290: 
+291:         deltat_x = fabs((grid_x[1] - grid_x[0]) / v[0]) if v[0] else 0
    if (((__pyx_v_v[0]) != 0)) {
      __pyx_t_1 = 1;
      __pyx_t_2 = 0;
      __pyx_t_9 = __pyx_f_4pept_9utilities_8traverse_10traverse2d_fabs((((*((double *) ( /* dim=0 */ (__pyx_v_grid_x.data + __pyx_t_1 * __pyx_v_grid_x.strides[0]) ))) - (*((double *) ( /* dim=0 */ (__pyx_v_grid_x.data + __pyx_t_2 * __pyx_v_grid_x.strides[0]) )))) / (__pyx_v_v[0])));
    } else {
      __pyx_t_9 = 0.0;
    }
    __pyx_v_deltat_x = __pyx_t_9;
+292:         deltat_y = fabs((grid_y[1] - grid_y[0]) / v[1]) if v[1] else 0
    if (((__pyx_v_v[1]) != 0)) {
      __pyx_t_2 = 1;
      __pyx_t_1 = 0;
      __pyx_t_9 = __pyx_f_4pept_9utilities_8traverse_10traverse2d_fabs((((*((double *) ( /* dim=0 */ (__pyx_v_grid_y.data + __pyx_t_2 * __pyx_v_grid_y.strides[0]) ))) - (*((double *) ( /* dim=0 */ (__pyx_v_grid_y.data + __pyx_t_1 * __pyx_v_grid_y.strides[0]) )))) / (__pyx_v_v[1])));
    } else {
      __pyx_t_9 = 0.0;
    }
    __pyx_v_deltat_y = __pyx_t_9;
 293: 
 294:         ###############################################################
 295:         # Incremental traversal stage
 296: 
 297:         # Loop until we reach the last pixel in space
+298:         while (ix < nx and iy < ny) and (ix >= 0 and iy >= 0):
    while (1) {
      __pyx_t_8 = ((__pyx_v_ix < __pyx_v_nx) != 0);
      if (__pyx_t_8) {
      } else {
        __pyx_t_7 = __pyx_t_8;
        goto __pyx_L29_bool_binop_done;
      }
      __pyx_t_8 = ((__pyx_v_iy < __pyx_v_ny) != 0);
      if (__pyx_t_8) {
      } else {
        __pyx_t_7 = __pyx_t_8;
        goto __pyx_L29_bool_binop_done;
      }
      __pyx_t_8 = ((__pyx_v_ix >= 0) != 0);
      if (__pyx_t_8) {
      } else {
        __pyx_t_7 = __pyx_t_8;
        goto __pyx_L29_bool_binop_done;
      }
      __pyx_t_8 = ((__pyx_v_iy >= 0) != 0);
      __pyx_t_7 = __pyx_t_8;
      __pyx_L29_bool_binop_done:;
      if (!__pyx_t_7) break;
 299: 
+300:             pixels[ix, iy] += 1
      __pyx_t_1 = __pyx_v_ix;
      __pyx_t_2 = __pyx_v_iy;
      *((double *) ( /* dim=1 */ (( /* dim=0 */ (__pyx_v_pixels.data + __pyx_t_1 * __pyx_v_pixels.strides[0]) ) + __pyx_t_2 * __pyx_v_pixels.strides[1]) )) += 1.0;
 301: 
 302:             # Select the minimum t that makes the line pass
 303:             # through to the next pixel
+304:             if tnext_x < tnext_y:
      __pyx_t_7 = ((__pyx_v_tnext_x < __pyx_v_tnext_y) != 0);
      if (__pyx_t_7) {
/* … */
        goto __pyx_L33;
      }
 305:                 # If the next pixel falls beyond the end of the line (that is
 306:                 # at t = 1), then stop the traversal stage
+307:                 if tnext_x > 1.:
        __pyx_t_7 = ((__pyx_v_tnext_x > 1.) != 0);
        if (__pyx_t_7) {
/* … */
        }
+308:                     break
          goto __pyx_L28_break;
 309: 
+310:                 ix = ix + step_x
        __pyx_v_ix = (__pyx_v_ix + __pyx_v_step_x);
+311:                 tnext_x = tnext_x + deltat_x
        __pyx_v_tnext_x = (__pyx_v_tnext_x + __pyx_v_deltat_x);
 312:             else:
+313:                 if tnext_y > 1.:
      /*else*/ {
        __pyx_t_7 = ((__pyx_v_tnext_y > 1.) != 0);
        if (__pyx_t_7) {
/* … */
        }
+314:                     break
          goto __pyx_L28_break;
 315: 
+316:                 iy = iy + step_y
        __pyx_v_iy = (__pyx_v_iy + __pyx_v_step_y);
+317:                 tnext_y = tnext_y + deltat_y
        __pyx_v_tnext_y = (__pyx_v_tnext_y + __pyx_v_deltat_y);
      }
      __pyx_L33:;
    }
    __pyx_L28_break:;
    __pyx_L3_continue:;
  }
 318: