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: find_minpoints.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_find_minpoints_line_69, __pyx_kp_u_Compute_the_minimum_distance_poi) < 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              : find_minpoints.pyx
 035: # License           : GNU v3.0
 036: # Author            : Andrei Leonard Nicusan <a.l.nicusan@bham.ac.uk>
 037: # Date              : 20.10.2020
 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: import numpy as np      # import numpy for Python functions
  __pyx_t_1 = __Pyx_Import(__pyx_n_s_numpy, 0, 0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 49, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_1);
  if (PyDict_SetItem(__pyx_d, __pyx_n_s_np, __pyx_t_1) < 0) __PYX_ERR(0, 49, __pyx_L1_error)
  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
 050: cimport numpy as np     # import numpy for C functions (numpy's C API)
 051: 
 052: 
+053: np.import_array()
  __pyx_t_2 = __pyx_f_5numpy_import_array(); if (unlikely(__pyx_t_2 == ((int)-1))) __PYX_ERR(0, 53, __pyx_L1_error)
 054: 
 055: 
 056: cdef extern from "find_minpoints_ext.c":
 057:     # C is included here so that it doesn't need to be compiled externally
 058:     pass
 059: 
 060: 
 061: cdef extern from "find_minpoints_ext.h":
 062:     double* find_minpoints_ext(
 063:         const double *, const Py_ssize_t, const Py_ssize_t, const Py_ssize_t,
 064:         const double, const double *, const int, Py_ssize_t *, Py_ssize_t *
 065:     ) nogil
 066: 
 067: 
 068: 
+069: cpdef find_minpoints(
static PyObject *__pyx_pw_4pept_9utilities_9cutpoints_14find_minpoints_1find_minpoints(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
static PyObject *__pyx_f_4pept_9utilities_9cutpoints_14find_minpoints_find_minpoints(__Pyx_memviewslice __pyx_v_sample_lines, Py_ssize_t __pyx_v_num_lines, double __pyx_v_max_distance, __Pyx_memviewslice __pyx_v_cutoffs, CYTHON_UNUSED int __pyx_skip_dispatch, struct __pyx_opt_args_4pept_9utilities_9cutpoints_14find_minpoints_find_minpoints *__pyx_optional_args) {
  int __pyx_v_append_indices = ((int)0);
  Py_ssize_t __pyx_v_nrows;
  Py_ssize_t __pyx_v_ncols;
  Py_ssize_t __pyx_v_mpts_nrows;
  Py_ssize_t __pyx_v_mpts_ncols;
  double *__pyx_v_minpoints;
  npy_intp __pyx_v_size[2];
  PyArrayObject *__pyx_v_mpts_arr = 0;
  __Pyx_LocalBuf_ND __pyx_pybuffernd_mpts_arr;
  __Pyx_Buffer __pyx_pybuffer_mpts_arr;
  PyObject *__pyx_r = NULL;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("find_minpoints", 0);
  if (__pyx_optional_args) {
    if (__pyx_optional_args->__pyx_n > 0) {
      __pyx_v_append_indices = __pyx_optional_args->append_indices;
    }
  }
  __pyx_pybuffer_mpts_arr.pybuffer.buf = NULL;
  __pyx_pybuffer_mpts_arr.refcount = 0;
  __pyx_pybuffernd_mpts_arr.data = NULL;
  __pyx_pybuffernd_mpts_arr.rcbuffer = &__pyx_pybuffer_mpts_arr;
/* … */
  /* function exit code */
  __pyx_L1_error:;
  __Pyx_XDECREF(__pyx_t_4);
  __Pyx_XDECREF(__pyx_t_6);
  __Pyx_XDECREF(__pyx_t_7);
  { PyObject *__pyx_type, *__pyx_value, *__pyx_tb;
    __Pyx_PyThreadState_declare
    __Pyx_PyThreadState_assign
    __Pyx_ErrFetch(&__pyx_type, &__pyx_value, &__pyx_tb);
    __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_mpts_arr.rcbuffer->pybuffer);
  __Pyx_ErrRestore(__pyx_type, __pyx_value, __pyx_tb);}
  __Pyx_AddTraceback("pept.utilities.cutpoints.find_minpoints.find_minpoints", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __pyx_r = 0;
  goto __pyx_L2;
  __pyx_L0:;
  __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_mpts_arr.rcbuffer->pybuffer);
  __pyx_L2:;
  __Pyx_XDECREF((PyObject *)__pyx_v_mpts_arr);
  __Pyx_XGIVEREF(__pyx_r);
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}

/* Python wrapper */
static PyObject *__pyx_pw_4pept_9utilities_9cutpoints_14find_minpoints_1find_minpoints(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
static char __pyx_doc_4pept_9utilities_9cutpoints_14find_minpoints_find_minpoints[] = "find_minpoints(double[:, :] sample_lines, Py_ssize_t num_lines, double max_distance, double[:] cutoffs, bool append_indices=0)\nCompute the minimum distance points (MDPs) from all combinations of\n    `num_lines` lines given in an array of lines `sample_lines`.\n\n    ::\n\n        Function signature:\n            find_minpoints(\n                double[:, :] sample_lines,  # LoRs in sample\n                Py_ssize_t num_lines,       # Number of LoRs in combinations\n                double max_distance,        # Max distance from MDP to LoRs\n                double[:] cutoffs,          # Spatial cutoff for minpoints\n                bool append_indices = 0     # Append LoR indices used\n            )\n\n    Given a sample of lines, this functions computes the minimum distance\n    points (MDPs) for every possible combination of `num_lines` lines. The\n    returned numpy array contains all MDPs that satisfy the following:\n\n    1. Are within the `cutoffs`.\n    2. Are closer to all the constituent LoRs than `max_distance`.\n\n    Parameters\n    ----------\n    sample_lines: (M, N) numpy.ndarray\n        A 2D array of lines, where each line is defined by two points such that\n        every row is formatted as `[t, x1, y1, z1, x2, y2, z2, etc.]`. It\n        *must* have at least 2 lines and the combination size `num_lines`\n        *must* be smaller or equal to the number of lines. Put differently:\n        2 <= num_lines <= len(sample_lines).\n\n    num_lines: int\n        The number of lines in each combination of LoRs used to compute the\n        MDP. This function considers every combination of `numlines` from the\n        input `sample_lines`. It must be smaller or equal to the number of input\n        lines `sample_lines`.\n\n    max_distance: float\n        The maximum allowed distance between an MDP and its constituent lines.\n        If any distance from the MDP to one of its lines is larger than\n        `max_distance`, the MDP is thrown away.\n\n    cutof""fs: (6,) numpy.ndarray\n        An array of spatial cutoff coordinates with *exactly 6 elements* as\n        [x_min, x_max, y_min, y_max, z_min, z_max]. If any MDP lies outside\n        this region, it is thrown away.\n\n    append_indices: bool\n        A boolean specifying whether to include the indices of the lines used\n        to compute each MDP. If `False`, the output array will only contain the\n        [time, x, y, z] of the MDPs. If `True`, the output array will have\n        extra columns [time, x, y, z, line_idx(1), ..., line_idx(n)] where\n        n = `num_lines`.\n\n    Returns\n    -------\n    minpoints: (M, N) numpy.ndarray\n        A 2D array of `float`s containing the time and coordinates of the MDPs\n        [time, x, y, z]. The time is computed as the average of the constituent\n        lines. If `append_indices` is `True`, then `num_lines` indices of the\n        constituent lines are appended as extra columns:\n        [time, x, y, z, line_idx1, line_idx2, ..].\n\n    Notes\n    -----\n    There must be at least two lines in `sample_lines` and `num_lines` must be\n    greater or equal to the number of lines (i.e. `len(sample_lines)`).\n    Put another way: 2 <= num_lines <= len(sample_lines).\n\n    This is a low-level Cython function that does not do any checks on the\n    input data - it is meant to be used in other modules / libraries. For a\n    normal user, the `pept.tracking.peptml` function `find_minpoints` and\n    class `Minpoints` are recommended as higher-level APIs. They do check the\n    input data and are easier to use (for example, they automatically compute\n    the cutoffs).\n\n    Examples\n    --------\n\n    >>> import numpy as np\n    >>> from pept.utilities import find_minpoints\n    >>>\n    >>> lines = np.random.random((500, 7)) * 500\n    >>> num_lines = 3\n    >>> max_distance = 0.1\n    >>> cutoffs = np.array([0, 500, 0, 500, 0, 500], dtype = float)\n    >>>\n    >>> minpoints = find_minpoints(lines, num_lines, max_d""istance, cutoffs)\n\n    ";
static PyObject *__pyx_pw_4pept_9utilities_9cutpoints_14find_minpoints_1find_minpoints(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
  __Pyx_memviewslice __pyx_v_sample_lines = { 0, 0, { 0 }, { 0 }, { 0 } };
  Py_ssize_t __pyx_v_num_lines;
  double __pyx_v_max_distance;
  __Pyx_memviewslice __pyx_v_cutoffs = { 0, 0, { 0 }, { 0 }, { 0 } };
  int __pyx_v_append_indices;
  PyObject *__pyx_r = 0;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("find_minpoints (wrapper)", 0);
  {
    static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_sample_lines,&__pyx_n_s_num_lines,&__pyx_n_s_max_distance,&__pyx_n_s_cutoffs,&__pyx_n_s_append_indices,0};
    PyObject* values[5] = {0,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  5: values[4] = PyTuple_GET_ITEM(__pyx_args, 4);
        CYTHON_FALLTHROUGH;
        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_sample_lines)) != 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_num_lines)) != 0)) kw_args--;
        else {
          __Pyx_RaiseArgtupleInvalid("find_minpoints", 0, 4, 5, 1); __PYX_ERR(0, 69, __pyx_L3_error)
        }
        CYTHON_FALLTHROUGH;
        case  2:
        if (likely((values[2] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_max_distance)) != 0)) kw_args--;
        else {
          __Pyx_RaiseArgtupleInvalid("find_minpoints", 0, 4, 5, 2); __PYX_ERR(0, 69, __pyx_L3_error)
        }
        CYTHON_FALLTHROUGH;
        case  3:
        if (likely((values[3] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_cutoffs)) != 0)) kw_args--;
        else {
          __Pyx_RaiseArgtupleInvalid("find_minpoints", 0, 4, 5, 3); __PYX_ERR(0, 69, __pyx_L3_error)
        }
        CYTHON_FALLTHROUGH;
        case  4:
        if (kw_args > 0) {
          PyObject* value = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_append_indices);
          if (value) { values[4] = value; kw_args--; }
        }
      }
      if (unlikely(kw_args > 0)) {
        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "find_minpoints") < 0)) __PYX_ERR(0, 69, __pyx_L3_error)
      }
    } else {
      switch (PyTuple_GET_SIZE(__pyx_args)) {
        case  5: values[4] = PyTuple_GET_ITEM(__pyx_args, 4);
        CYTHON_FALLTHROUGH;
        case  4: values[3] = PyTuple_GET_ITEM(__pyx_args, 3);
        values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
        values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
        values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
        break;
        default: goto __pyx_L5_argtuple_error;
      }
    }
    __pyx_v_sample_lines = __Pyx_PyObject_to_MemoryviewSlice_dsds_double(values[0], PyBUF_WRITABLE); if (unlikely(!__pyx_v_sample_lines.memview)) __PYX_ERR(0, 70, __pyx_L3_error)
    __pyx_v_num_lines = __Pyx_PyIndex_AsSsize_t(values[1]); if (unlikely((__pyx_v_num_lines == (Py_ssize_t)-1) && PyErr_Occurred())) __PYX_ERR(0, 71, __pyx_L3_error)
    __pyx_v_max_distance = __pyx_PyFloat_AsDouble(values[2]); if (unlikely((__pyx_v_max_distance == (double)-1) && PyErr_Occurred())) __PYX_ERR(0, 72, __pyx_L3_error)
    __pyx_v_cutoffs = __Pyx_PyObject_to_MemoryviewSlice_ds_double(values[3], PyBUF_WRITABLE); if (unlikely(!__pyx_v_cutoffs.memview)) __PYX_ERR(0, 73, __pyx_L3_error)
    if (values[4]) {
      __pyx_v_append_indices = __Pyx_PyObject_IsTrue(values[4]); if (unlikely((__pyx_v_append_indices == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 74, __pyx_L3_error)
    } else {
      __pyx_v_append_indices = ((int)0);
    }
  }
  goto __pyx_L4_argument_unpacking_done;
  __pyx_L5_argtuple_error:;
  __Pyx_RaiseArgtupleInvalid("find_minpoints", 0, 4, 5, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 69, __pyx_L3_error)
  __pyx_L3_error:;
  __Pyx_AddTraceback("pept.utilities.cutpoints.find_minpoints.find_minpoints", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __Pyx_RefNannyFinishContext();
  return NULL;
  __pyx_L4_argument_unpacking_done:;
  __pyx_r = __pyx_pf_4pept_9utilities_9cutpoints_14find_minpoints_find_minpoints(__pyx_self, __pyx_v_sample_lines, __pyx_v_num_lines, __pyx_v_max_distance, __pyx_v_cutoffs, __pyx_v_append_indices);
  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_9cutpoints_14find_minpoints_find_minpoints(CYTHON_UNUSED PyObject *__pyx_self, __Pyx_memviewslice __pyx_v_sample_lines, Py_ssize_t __pyx_v_num_lines, double __pyx_v_max_distance, __Pyx_memviewslice __pyx_v_cutoffs, int __pyx_v_append_indices) {
  PyObject *__pyx_r = NULL;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("find_minpoints", 0);
  __Pyx_XDECREF(__pyx_r);
  __pyx_t_2.__pyx_n = 1;
  __pyx_t_2.append_indices = __pyx_v_append_indices;
  __pyx_t_1 = __pyx_f_4pept_9utilities_9cutpoints_14find_minpoints_find_minpoints(__pyx_v_sample_lines, __pyx_v_num_lines, __pyx_v_max_distance, __pyx_v_cutoffs, 0, &__pyx_t_2); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 69, __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.cutpoints.find_minpoints.find_minpoints", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __pyx_r = NULL;
  __pyx_L0:;
  __PYX_XDEC_MEMVIEW(&__pyx_v_sample_lines, 1);
  __PYX_XDEC_MEMVIEW(&__pyx_v_cutoffs, 1);
  __Pyx_XGIVEREF(__pyx_r);
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}
+070:     double[:, :] sample_lines,  # LoRs in sample
struct __pyx_opt_args_4pept_9utilities_9cutpoints_14find_minpoints_find_minpoints {
  int __pyx_n;
  int append_indices;
};
 071:     Py_ssize_t num_lines,       # Number of LoRs in groups for computing MDP
 072:     double max_distance,        # Max allowed distance between two LoRs
 073:     double[:] cutoffs,          # Spatial cutoff for cutpoints
 074:     bint append_indices = 0     # Append LoR indices used for each cutpoint
 075: ):
 076:     '''Compute the minimum distance points (MDPs) from all combinations of
 077:     `num_lines` lines given in an array of lines `sample_lines`.
 078: 
 079:     ::
 080: 
 081:         Function signature:
 082:             find_minpoints(
 083:                 double[:, :] sample_lines,  # LoRs in sample
 084:                 Py_ssize_t num_lines,       # Number of LoRs in combinations
 085:                 double max_distance,        # Max distance from MDP to LoRs
 086:                 double[:] cutoffs,          # Spatial cutoff for minpoints
 087:                 bool append_indices = 0     # Append LoR indices used
 088:             )
 089: 
 090:     Given a sample of lines, this functions computes the minimum distance
 091:     points (MDPs) for every possible combination of `num_lines` lines. The
 092:     returned numpy array contains all MDPs that satisfy the following:
 093: 
 094:     1. Are within the `cutoffs`.
 095:     2. Are closer to all the constituent LoRs than `max_distance`.
 096: 
 097:     Parameters
 098:     ----------
 099:     sample_lines: (M, N) numpy.ndarray
 100:         A 2D array of lines, where each line is defined by two points such that
 101:         every row is formatted as `[t, x1, y1, z1, x2, y2, z2, etc.]`. It
 102:         *must* have at least 2 lines and the combination size `num_lines`
 103:         *must* be smaller or equal to the number of lines. Put differently:
 104:         2 <= num_lines <= len(sample_lines).
 105: 
 106:     num_lines: int
 107:         The number of lines in each combination of LoRs used to compute the
 108:         MDP. This function considers every combination of `numlines` from the
 109:         input `sample_lines`. It must be smaller or equal to the number of input
 110:         lines `sample_lines`.
 111: 
 112:     max_distance: float
 113:         The maximum allowed distance between an MDP and its constituent lines.
 114:         If any distance from the MDP to one of its lines is larger than
 115:         `max_distance`, the MDP is thrown away.
 116: 
 117:     cutoffs: (6,) numpy.ndarray
 118:         An array of spatial cutoff coordinates with *exactly 6 elements* as
 119:         [x_min, x_max, y_min, y_max, z_min, z_max]. If any MDP lies outside
 120:         this region, it is thrown away.
 121: 
 122:     append_indices: bool
 123:         A boolean specifying whether to include the indices of the lines used
 124:         to compute each MDP. If `False`, the output array will only contain the
 125:         [time, x, y, z] of the MDPs. If `True`, the output array will have
 126:         extra columns [time, x, y, z, line_idx(1), ..., line_idx(n)] where
 127:         n = `num_lines`.
 128: 
 129:     Returns
 130:     -------
 131:     minpoints: (M, N) numpy.ndarray
 132:         A 2D array of `float`s containing the time and coordinates of the MDPs
 133:         [time, x, y, z]. The time is computed as the average of the constituent
 134:         lines. If `append_indices` is `True`, then `num_lines` indices of the
 135:         constituent lines are appended as extra columns:
 136:         [time, x, y, z, line_idx1, line_idx2, ..].
 137: 
 138:     Notes
 139:     -----
 140:     There must be at least two lines in `sample_lines` and `num_lines` must be
 141:     greater or equal to the number of lines (i.e. `len(sample_lines)`).
 142:     Put another way: 2 <= num_lines <= len(sample_lines).
 143: 
 144:     This is a low-level Cython function that does not do any checks on the
 145:     input data - it is meant to be used in other modules / libraries. For a
 146:     normal user, the `pept.tracking.peptml` function `find_minpoints` and
 147:     class `Minpoints` are recommended as higher-level APIs. They do check the
 148:     input data and are easier to use (for example, they automatically compute
 149:     the cutoffs).
 150: 
 151:     Examples
 152:     --------
 153: 
 154:     >>> import numpy as np
 155:     >>> from pept.utilities import find_minpoints
 156:     >>>
 157:     >>> lines = np.random.random((500, 7)) * 500
 158:     >>> num_lines = 3
 159:     >>> max_distance = 0.1
 160:     >>> cutoffs = np.array([0, 500, 0, 500, 0, 500], dtype = float)
 161:     >>>
 162:     >>> minpoints = find_minpoints(lines, num_lines, max_distance, cutoffs)
 163: 
 164:     '''
 165: 
 166:     # Lines for a single sample => (m, n >= 7) array
 167:     # sample_lines row: [time X1 Y1 Z1 X2 Y2 Z2 etc.]
+168:     cdef Py_ssize_t nrows = sample_lines.shape[0]
  __pyx_v_nrows = (__pyx_v_sample_lines.shape[0]);
+169:     cdef Py_ssize_t ncols = sample_lines.shape[1]
  __pyx_v_ncols = (__pyx_v_sample_lines.shape[1]);
 170: 
+171:     cdef Py_ssize_t mpts_nrows = 0
  __pyx_v_mpts_nrows = 0;
+172:     cdef Py_ssize_t mpts_ncols = 0
  __pyx_v_mpts_ncols = 0;
 173: 
 174:     cdef double *minpoints
 175:     cdef np.npy_intp[2] size
 176: 
+177:     with nogil:
  {
      #ifdef WITH_THREAD
      PyThreadState *_save;
      Py_UNBLOCK_THREADS
      __Pyx_FastGIL_Remember();
      #endif
      /*try:*/ {
/* … */
      /*finally:*/ {
        /*normal exit:*/{
          #ifdef WITH_THREAD
          __Pyx_FastGIL_Forget();
          Py_BLOCK_THREADS
          #endif
          goto __pyx_L5;
        }
        __pyx_L5:;
      }
  }
+178:         minpoints = find_minpoints_ext(
        __pyx_v_minpoints = find_minpoints_ext((&(*((double *) ( /* dim=1 */ (( /* dim=0 */ (__pyx_v_sample_lines.data + __pyx_t_1 * __pyx_v_sample_lines.strides[0]) ) + __pyx_t_2 * __pyx_v_sample_lines.strides[1]) )))), __pyx_v_nrows, __pyx_v_ncols, __pyx_v_num_lines, __pyx_v_max_distance, (&(*((double *) ( /* dim=0 */ (__pyx_v_cutoffs.data + __pyx_t_3 * __pyx_v_cutoffs.strides[0]) )))), __pyx_v_append_indices, (&__pyx_v_mpts_nrows), (&__pyx_v_mpts_ncols));
      }
+179:             &sample_lines[0, 0],
        __pyx_t_1 = 0;
        __pyx_t_2 = 0;
 180:             nrows,
 181:             ncols,
 182:             num_lines,
 183:             max_distance,
+184:             &cutoffs[0],
        __pyx_t_3 = 0;
 185:             append_indices,
 186:             &mpts_nrows,
 187:             &mpts_ncols
 188:         )
 189: 
+190:     size[0] = mpts_nrows
  (__pyx_v_size[0]) = __pyx_v_mpts_nrows;
+191:     size[1] = mpts_ncols
  (__pyx_v_size[1]) = __pyx_v_mpts_ncols;
 192: 
 193:     # Use the `minpoints` pointer as the internal data of a numpy array
 194:     cdef extern from "numpy/arrayobject.h":
 195:         void PyArray_ENABLEFLAGS(np.ndarray arr, int flags)
 196: 
+197:     cdef np.ndarray[double, ndim=2] mpts_arr = np.PyArray_SimpleNewFromData(
  __pyx_t_4 = PyArray_SimpleNewFromData(2, __pyx_v_size, NPY_FLOAT64, __pyx_v_minpoints); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 197, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_4);
  if (!(likely(((__pyx_t_4) == Py_None) || likely(__Pyx_TypeTest(__pyx_t_4, __pyx_ptype_5numpy_ndarray))))) __PYX_ERR(0, 197, __pyx_L1_error)
  __pyx_t_5 = ((PyArrayObject *)__pyx_t_4);
  {
    __Pyx_BufFmt_StackElem __pyx_stack[1];
    if (unlikely(__Pyx_GetBufferAndValidate(&__pyx_pybuffernd_mpts_arr.rcbuffer->pybuffer, (PyObject*)__pyx_t_5, &__Pyx_TypeInfo_double, PyBUF_FORMAT| PyBUF_STRIDES, 2, 0, __pyx_stack) == -1)) {
      __pyx_v_mpts_arr = ((PyArrayObject *)Py_None); __Pyx_INCREF(Py_None); __pyx_pybuffernd_mpts_arr.rcbuffer->pybuffer.buf = NULL;
      __PYX_ERR(0, 197, __pyx_L1_error)
    } else {__pyx_pybuffernd_mpts_arr.diminfo[0].strides = __pyx_pybuffernd_mpts_arr.rcbuffer->pybuffer.strides[0]; __pyx_pybuffernd_mpts_arr.diminfo[0].shape = __pyx_pybuffernd_mpts_arr.rcbuffer->pybuffer.shape[0]; __pyx_pybuffernd_mpts_arr.diminfo[1].strides = __pyx_pybuffernd_mpts_arr.rcbuffer->pybuffer.strides[1]; __pyx_pybuffernd_mpts_arr.diminfo[1].shape = __pyx_pybuffernd_mpts_arr.rcbuffer->pybuffer.shape[1];
    }
  }
  __pyx_t_5 = 0;
  __pyx_v_mpts_arr = ((PyArrayObject *)__pyx_t_4);
  __pyx_t_4 = 0;
 198:         2, size, np.NPY_FLOAT64, minpoints
 199:     )
+200:     PyArray_ENABLEFLAGS(mpts_arr, np.NPY_OWNDATA)
  PyArray_ENABLEFLAGS(((PyArrayObject *)__pyx_v_mpts_arr), NPY_OWNDATA);
 201: 
 202:     # Sort rows based on time (column 0)
+203:     mpts_arr = mpts_arr[mpts_arr[:, 0].argsort()]
  __pyx_slice_ = PySlice_New(Py_None, Py_None, Py_None); if (unlikely(!__pyx_slice_)) __PYX_ERR(0, 203, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_slice_);
  __Pyx_GIVEREF(__pyx_slice_);
/* … */
  __pyx_t_6 = __Pyx_PyObject_GetItem(((PyObject *)__pyx_v_mpts_arr), __pyx_tuple__2); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 203, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_6);
  __pyx_t_7 = __Pyx_PyObject_GetAttrStr(__pyx_t_6, __pyx_n_s_argsort); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 203, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_7);
  __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
  __pyx_t_6 = NULL;
  if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_7))) {
    __pyx_t_6 = PyMethod_GET_SELF(__pyx_t_7);
    if (likely(__pyx_t_6)) {
      PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_7);
      __Pyx_INCREF(__pyx_t_6);
      __Pyx_INCREF(function);
      __Pyx_DECREF_SET(__pyx_t_7, function);
    }
  }
  __pyx_t_4 = (__pyx_t_6) ? __Pyx_PyObject_CallOneArg(__pyx_t_7, __pyx_t_6) : __Pyx_PyObject_CallNoArg(__pyx_t_7);
  __Pyx_XDECREF(__pyx_t_6); __pyx_t_6 = 0;
  if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 203, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_4);
  __Pyx_DECREF(__pyx_t_7); __pyx_t_7 = 0;
  __pyx_t_7 = __Pyx_PyObject_GetItem(((PyObject *)__pyx_v_mpts_arr), __pyx_t_4); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 203, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_7);
  __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
  if (!(likely(((__pyx_t_7) == Py_None) || likely(__Pyx_TypeTest(__pyx_t_7, __pyx_ptype_5numpy_ndarray))))) __PYX_ERR(0, 203, __pyx_L1_error)
  __pyx_t_5 = ((PyArrayObject *)__pyx_t_7);
  {
    __Pyx_BufFmt_StackElem __pyx_stack[1];
    __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_mpts_arr.rcbuffer->pybuffer);
    __pyx_t_8 = __Pyx_GetBufferAndValidate(&__pyx_pybuffernd_mpts_arr.rcbuffer->pybuffer, (PyObject*)__pyx_t_5, &__Pyx_TypeInfo_double, PyBUF_FORMAT| PyBUF_STRIDES, 2, 0, __pyx_stack);
    if (unlikely(__pyx_t_8 < 0)) {
      PyErr_Fetch(&__pyx_t_9, &__pyx_t_10, &__pyx_t_11);
      if (unlikely(__Pyx_GetBufferAndValidate(&__pyx_pybuffernd_mpts_arr.rcbuffer->pybuffer, (PyObject*)__pyx_v_mpts_arr, &__Pyx_TypeInfo_double, PyBUF_FORMAT| PyBUF_STRIDES, 2, 0, __pyx_stack) == -1)) {
        Py_XDECREF(__pyx_t_9); Py_XDECREF(__pyx_t_10); Py_XDECREF(__pyx_t_11);
        __Pyx_RaiseBufferFallbackError();
      } else {
        PyErr_Restore(__pyx_t_9, __pyx_t_10, __pyx_t_11);
      }
      __pyx_t_9 = __pyx_t_10 = __pyx_t_11 = 0;
    }
    __pyx_pybuffernd_mpts_arr.diminfo[0].strides = __pyx_pybuffernd_mpts_arr.rcbuffer->pybuffer.strides[0]; __pyx_pybuffernd_mpts_arr.diminfo[0].shape = __pyx_pybuffernd_mpts_arr.rcbuffer->pybuffer.shape[0]; __pyx_pybuffernd_mpts_arr.diminfo[1].strides = __pyx_pybuffernd_mpts_arr.rcbuffer->pybuffer.strides[1]; __pyx_pybuffernd_mpts_arr.diminfo[1].shape = __pyx_pybuffernd_mpts_arr.rcbuffer->pybuffer.shape[1];
    if (unlikely(__pyx_t_8 < 0)) __PYX_ERR(0, 203, __pyx_L1_error)
  }
  __pyx_t_5 = 0;
  __Pyx_DECREF_SET(__pyx_v_mpts_arr, ((PyArrayObject *)__pyx_t_7));
  __pyx_t_7 = 0;
  __pyx_tuple__2 = PyTuple_Pack(2, __pyx_slice_, __pyx_int_0); if (unlikely(!__pyx_tuple__2)) __PYX_ERR(0, 203, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_tuple__2);
  __Pyx_GIVEREF(__pyx_tuple__2);
 204: 
+205:     return mpts_arr
  __Pyx_XDECREF(__pyx_r);
  __Pyx_INCREF(((PyObject *)__pyx_v_mpts_arr));
  __pyx_r = ((PyObject *)__pyx_v_mpts_arr);
  goto __pyx_L0;