From 6333b8aebd6bb46a6c1214c3051949ec4e0e097f Mon Sep 17 00:00:00 2001 From: sronilsson Date: Mon, 11 Nov 2024 11:00:01 -0500 Subject: [PATCH] cuda jit --- .../img/cumsum_animal_geometries_grid.webp | Bin 0 -> 6812 bytes docs/_static/img/path_aspect_ratio.webp | Bin 0 -> 17662 bytes docs/tables/sliding_linearity_index_cuda.csv | 13 +++ simba/data_processors/cuda/timeseries.py | 73 ++++++++++++++++- simba/mixins/geometry_mixin.py | 65 +++++++-------- simba/mixins/timeseries_features_mixin.py | 76 +++++++++++++++++- simba/utils/errors.py | 6 ++ 7 files changed, 190 insertions(+), 43 deletions(-) create mode 100644 docs/_static/img/cumsum_animal_geometries_grid.webp create mode 100644 docs/_static/img/path_aspect_ratio.webp create mode 100644 docs/tables/sliding_linearity_index_cuda.csv diff --git a/docs/_static/img/cumsum_animal_geometries_grid.webp b/docs/_static/img/cumsum_animal_geometries_grid.webp new file mode 100644 index 0000000000000000000000000000000000000000..0d0f242cd3d51b202c1d874c5b64e5b54f82b5df GIT binary patch literal 6812 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 AYybcN literal 0 HcmV?d00001 diff --git a/docs/tables/sliding_linearity_index_cuda.csv b/docs/tables/sliding_linearity_index_cuda.csv new file mode 100644 index 000000000..70b7d99d9 --- /dev/null +++ b/docs/tables/sliding_linearity_index_cuda.csv @@ -0,0 +1,13 @@ +FRAMES (MILLIONS),GPU TIME (S),GPU TIME (STEV) +2,0.03031,0.002 +4,0.05208,0.004 +8,0.08882,0.0056 +16,0.17612,0.0108 +32,0.37699,0.01587 +64,0.70194,0.01848 +128,1.36778,0.06145 +256,3.09965,0.21796 +512,17.9707,10.5061 +NVIDIA GeForce RTX 4070,, +time window = 2.5s @ 30 FPS,, +3 ITERATIONS,, diff --git a/simba/data_processors/cuda/timeseries.py b/simba/data_processors/cuda/timeseries.py index 5d03ce6cc..434a7f74f 100644 --- a/simba/data_processors/cuda/timeseries.py +++ b/simba/data_processors/cuda/timeseries.py @@ -3,10 +3,10 @@ import numpy as np from numba import cuda -from simba.data_processors.cuda.utils import _euclid_dist -from simba.data_processors.cuda.utils import _cuda_mean, _cuda_std +from simba.data_processors.cuda.utils import _euclid_dist, _cuda_mean, _cuda_std, _cuda_available from simba.utils.checks import check_float, check_valid_array from simba.utils.enums import Formats +from simba.utils.errors import SimBAGPUError THREADS_PER_BLOCK = 1024 @@ -158,6 +158,9 @@ def sliding_spatial_density_cuda(x: np.ndarray, :align: center :header-rows: 1 + .. seealso:: + :func:`simba.mixins.timeseries_features_mixin.TimeseriesFeatureMixin.spatial_density`, :func:`simba.mixins.timeseries_features_mixin.TimeseriesFeatureMixin.sliding_spatial_density` + :param np.ndarray x: A 2D array of shape (N, 2), where N is the number of points and each point has two spatial coordinates (x, y). The array represents the trajectory path of points in a 2D space (e.g., x and y positions in space). :param float radius: The radius (in millimeters) within which to count neighboring points around each trajectory point. Defines the area of interest around each point. :param float pixels_per_mm: The scaling factor that converts the physical radius (in millimeters) to pixel units for spatial density calculations. @@ -189,3 +192,69 @@ def sliding_spatial_density_cuda(x: np.ndarray, results = cuda.device_array(shape=x.shape[0], dtype=np.float16) _sliding_spatial_density_kernel[bpg, THREADS_PER_BLOCK](x_dev, time_window_frames_dev, radius_dev, results) return results.copy_to_host() + + +@cuda.jit() +def _sliding_linearity_index_kernel(x, time_frms, results): + r = cuda.grid(1) + if r >= x.shape[0] or r < 0: + return + l = int(r - time_frms[0]) + if l < 0 or l >= r: + return + sample_x = x[l:r] + straight_line_distance = _euclid_dist(sample_x[0], sample_x[-1]) + path_dist = 0 + for i in range(1, sample_x.shape[0]): + path_dist += _euclid_dist(sample_x[i - 1], sample_x[i]) + if path_dist == 0: + results[r] = 0.0 + else: + results[r] = straight_line_distance / path_dist + + +def sliding_linearity_index_cuda(x: np.ndarray, + window_size: float, + sample_rate: float) -> np.ndarray: + """ + + Calculates the straightness (linearity) index of a path using CUDA acceleration. + + The output is a value between 0 and 1, where 1 indicates a perfectly straight path. + + .. csv-table:: + :header: EXPECTED RUNTIMES + :file: ../../../docs/tables/sliding_linearity_index_cuda.csv + :widths: 10, 45, 45 + :align: center + :header-rows: 1 + + .. seealso:: + :func:`simba.mixins.timeseries_features_mixin.TimeseriesFeatureMixin.sliding_linearity_index`, :func:`simba.mixins.timeseries_features_mixin.TimeseriesFeatureMixin.linearity_index` + + + :param np.ndarray x: An (N, M) array representing the path, where N is the number of points and M is the number of spatial dimensions (e.g., 2 for 2D or 3 for 3D). Each row represents the coordinates of a point along the path. + :param float x: The size of the sliding window in seconds. This defines the time window over which the linearity index is calculated. The window size should be specified in seconds. + :param float sample_rate: The sample rate in Hz (samples per second), which is used to convert the window size from seconds to frames. + :return: A 1D array of length N, where each element represents the linearity index of the path within a sliding window. The value is a ratio between the straight-line distance and the actual path length for each window. Values range from 0 to 1, with 1 indicating a perfectly straight path. + :rtype: np.ndarray + + :example: + >>> x = np.random.randint(0, 500, (100, 2)).astype(np.float32) + >>> q = sliding_linearity_index_cuda(x=x, window_size=2, sample_rate=30) + """ + + check_valid_array(data=x, source=f'{sliding_linearity_index_cuda.__name__} x', accepted_ndims=(2,), + accepted_axis_1_shape=[2, ], accepted_dtypes=Formats.NUMERIC_DTYPES.value) + check_float(name=f'{sliding_linearity_index_cuda.__name__} window_size', value=window_size) + check_float(name=f'{sliding_linearity_index_cuda.__name__} sample_rate', value=sample_rate) + x = np.ascontiguousarray(x) + time_window_frames = np.array([max(1.0, np.ceil(window_size * sample_rate))]) + if not _cuda_available()[0]: + SimBAGPUError(msg='No GPU found', source=sliding_linearity_index_cuda.__name__) + x_dev = cuda.to_device(x) + time_window_frames_dev = cuda.to_device(time_window_frames) + bpg = (x.shape[0] + (THREADS_PER_BLOCK - 1)) // THREADS_PER_BLOCK + results = cuda.device_array(shape=x.shape[0], dtype=np.float16) + _sliding_linearity_index_kernel[bpg, THREADS_PER_BLOCK](x_dev, time_window_frames_dev, results) + return results.copy_to_host() diff --git a/simba/mixins/geometry_mixin.py b/simba/mixins/geometry_mixin.py index 3ce6d768d..28b87650e 100644 --- a/simba/mixins/geometry_mixin.py +++ b/simba/mixins/geometry_mixin.py @@ -3071,7 +3071,7 @@ def cumsum_coord_geometries(self, Compute the cumulative time a body-part has spent inside a grid of geometries using multiprocessing. :param np.ndarray data: Input data array where rows represent frames and columns represent body-part x and y coordinates. - :param Dict[Tuple[int, int], Polygon] geometries: Dictionary of polygons representing spatial regions. Created by ``GeometryMixin.bucket_img_into_squares``. + :param Dict[Tuple[int, int], Polygon] geometries: Dictionary of polygons representing spatial regions. E.g., created by :func:`simba.mixins.geometry_mixin.GeometryMixin.bucket_img_into_grid_square` or :func:`simba.mixins.geometry_mixin.GeometryMixin.bucket_img_into_grid_hexagon`. :param Optional[int] fps: Frames per second (fps) for time normalization. If None, cumulative sum of frame count is returned. :example: @@ -3154,7 +3154,7 @@ def cumsum_bool_geometries( E.g., compute the cumulative time of classified events within spatial locations at all time-points of the video. :param np.ndarray data: Array containing spatial data with shape (n, 2). E.g., 2D-array with body-part coordinates. - :param Dict[Tuple[int, int], Polygon] geometries: Dictionary of polygons representing spatial regions. Created by ``GeometryMixin.bucket_img_into_squares``. + :param Dict[Tuple[int, int], Polygon] geometries: Dictionary of polygons representing spatial regions. E.g., created by :func:`simba.mixins.geometry_mixin.GeometryMixin.bucket_img_into_grid_square` or :func:`simba.mixins.geometry_mixin.GeometryMixin.bucket_img_into_grid_hexagon`. :param np.ndarray bool_data: Boolean array with shape (data.shape[0],) or (data.shape[0], 1) indicating the presence or absence in each frame. :param Optional[float] fps: Frames per second. If provided, the result is normalized by the frame rate. :param Optional[float] core_cnt: Number of CPU cores to use for parallel processing. Default is -1, which means using all available cores. @@ -3246,54 +3246,45 @@ def _cumsum_animal_geometries_grid_helper( results[k[0], k[1]] = 1 return results - def cumsum_animal_geometries_grid( - self, - data: List[Polygon], - grid: Dict[Tuple[int, int], Polygon], - fps: Optional[int] = None, - core_cnt: Optional[int] = -1, - verbose: Optional[bool] = True, - ): + def cumsum_animal_geometries_grid(self, + data: List[Polygon], + grid: Dict[Tuple[int, int], Polygon], + fps: Optional[int] = None, + core_cnt: Optional[int] = -1, + verbose: Optional[bool] = True) -> np.ndarray: + """ + .. image:: _static/img/cumsum_animal_geometries_grid.webp + :width: 400 + :align: center + + :param List[Polygon] data: List of polygons where every index represent a frame and every value the animal convex hull + :param Dict[Tuple[int, int], Polygon] grid: Dictionary of polygons representing spatial regions. E.g., created by :func:`simba.mixins.geometry_mixin.GeometryMixin.bucket_img_into_grid_square` or :func:`simba.mixins.geometry_mixin.GeometryMixin.bucket_img_into_grid_hexagon`. + :param Optional[float] fps: Frames per second. If provided, the result is normalized by the frame rate. + :param Optional[float] core_cnt: Number of CPU cores to use for parallel processing. Default is -1, which means using all available cores. + :param Optional[bool] verbose: If True, then prints progress. + :returns np.ndarray: Array of size (frames x horizontal bins x verical bins) with values representing time in seconds (if fps passed) or frames (if fps not passed) + """ + timer = SimbaTimer(start=True) - check_valid_lst( - data=data, - source=GeometryMixin.cumsum_animal_geometries_grid.__name__, - valid_dtypes=(Polygon,), - ) - check_instance( - source=GeometryMixin.cumsum_animal_geometries_grid.__name__, - instance=grid, - accepted_types=(dict,), - ) - if fps is not None: - check_int(name="fps", value=fps, min_value=1) + check_valid_lst(data=data,source=GeometryMixin.cumsum_animal_geometries_grid.__name__,valid_dtypes=(Polygon,)) + check_instance( source=GeometryMixin.cumsum_animal_geometries_grid.__name__, instance=grid, accepted_types=(dict,)) + if fps is not None: check_int(name="fps", value=fps, min_value=1) check_int(name="core_cnt", value=core_cnt, min_value=-1) - if core_cnt == -1: - core_cnt = find_core_cnt()[0] + if core_cnt == -1: core_cnt = find_core_cnt()[0] w, h = 0, 0 for k in grid.keys(): w, h = max(w, k[0]), max(h, k[1]) frm_id = np.arange(0, len(data)).reshape(-1, 1) data = np.hstack((frm_id, np.array(data).reshape(-1, 1))) img_arr = np.zeros((data.shape[0], h + 1, w + 1)) - with multiprocessing.Pool( - core_cnt, maxtasksperchild=Defaults.LARGE_MAX_TASK_PER_CHILD.value - ) as pool: - constants = functools.partial( - self._cumsum_animal_geometries_grid_helper, - grid=grid, - size=(h, w), - verbose=verbose, - ) + with multiprocessing.Pool(core_cnt, maxtasksperchild=Defaults.LARGE_MAX_TASK_PER_CHILD.value) as pool: + constants = functools.partial(self._cumsum_animal_geometries_grid_helper, grid=grid, size=(h, w), verbose=verbose) for cnt, result in enumerate(pool.imap(constants, data, chunksize=1)): img_arr[cnt] = result timer.stop_timer() - stdout_success( - msg="Cumulative animal geometries in grid complete", - elapsed_time=timer.elapsed_time_str, - ) + stdout_success(msg="Cumulative animal geometries in grid complete", elapsed_time=timer.elapsed_time_str,) if fps is None: return np.cumsum(img_arr, axis=0) else: diff --git a/simba/mixins/timeseries_features_mixin.py b/simba/mixins/timeseries_features_mixin.py index 9717bd3dd..140c41f97 100644 --- a/simba/mixins/timeseries_features_mixin.py +++ b/simba/mixins/timeseries_features_mixin.py @@ -1942,7 +1942,7 @@ def linearity_index(x: np.ndarray) -> float: :align: center .. seealso:: - :func:`simba.mixins.timeseries_features_mixin.TimeseriesFeatureMixin.sliding_linearity_index` + :func:`simba.mixins.timeseries_features_mixin.TimeseriesFeatureMixin.sliding_linearity_index`, :func:`simba.data_processors.cuda.timeseries.sliding_linearity_index_cuda` .. math:: \text{linearity\_index} = \frac{\text{straight\_line\_distance}}{\text{path\_length}} @@ -1982,7 +1982,7 @@ def sliding_linearity_index(x: np.ndarray, The Linearity Index measures how straight a path is by comparing the straight-line distance between the start and end points of each window to the total distance traveled along the path. .. seealso:: - :func:`simba.mixins.timeseries_features_mixin.TimeseriesFeatureMixin.linearity_index` + :func:`simba.mixins.timeseries_features_mixin.TimeseriesFeatureMixin.linearity_index`, :func:`simba.data_processors.cuda.timeseries.sliding_linearity_index_cuda` :param np.ndarray x: An (N, M) array representing the path, where N is the number of points and M is the number of spatial dimensions (e.g., 2 for 2D or 3 for 3D). Each row represents the coordinates of a point along the path. :param float x: The size of the sliding window in seconds. This defines the time window over which the linearity index is calculated. The window size should be specified in seconds. @@ -2217,7 +2217,7 @@ def spatial_density(x: np.ndarray, :align: center .. seealso:: - :func:`simba.mixins.timeseries_features_mixin.TimeseriesFeatureMixin.sliding_spatial_density` + :func:`simba.mixins.timeseries_features_mixin.TimeseriesFeatureMixin.sliding_spatial_density`, :func:`simba.data_processors.cuda.timeseries.sliding_spatial_density_cuda` :param np.ndarray x: A 2D array of shape (N, 2), where N is the number of points and each point has two spatial coordinates. :param float radius: The radius within which to count neighboring points around each point. Defines the area of interest around each trajectory point. @@ -2261,7 +2261,7 @@ def sliding_spatial_density(x: np.ndarray, of the trajectory. .. seealso:: - :func:`simba.mixins.timeseries_features_mixin.TimeseriesFeatureMixin.spatial_density` + :func:`simba.mixins.timeseries_features_mixin.TimeseriesFeatureMixin.spatial_density`, :func:`simba.data_processors.cuda.timeseries.sliding_spatial_density_cuda` :param np.ndarray x: A 2D array of shape (N, 2), where N is the number of points and each point has two spatial coordinates (x, y). The array represents the trajectory path of points in a 2D space (e.g., x and y positions in space). :param float radius: The radius (in millimeters) within which to count neighboring points around each trajectory point. Defines the area of interest around each point. @@ -2290,3 +2290,71 @@ def sliding_spatial_density(x: np.ndarray, results[r - 1] = total_neighbors / n_points return results + + @staticmethod + def path_aspect_ratio(x: np.ndarray, px_per_mm: float) -> float: + """ + Calculates the aspect ratio of the bounding box that encloses a given path. + + .. image:: _static/img/path_aspect_ratio.webp + :width: 400 + :align: center + + :param np.ndarray x: A 2D array of shape (N, 2) representing the path, where N is the number of points and each point has two spatial coordinates (e.g., x and y for 2D space). The path should be in the form of an array of consecutive (x, y) points. + :param float px_per_mm: Convertion factor representing the number of pixels per millimeter + :return: The aspect ratio of the bounding box enclosing the path. If the width or height of the bounding box is zero (e.g., if all points are aligned vertically or horizontally), returns -1. + :rtype: float + + :example: + >>> x = np.random.randint(0, 500, (10, 2)) + >>> TimeseriesFeatureMixin.path_aspect_ratio(x=x) + """ + + check_valid_array(data=x, source=TimeseriesFeatureMixin.path_aspect_ratio.__name__, accepted_ndims=(2,), accepted_axis_1_shape=[2, ], accepted_dtypes=Formats.NUMERIC_DTYPES.value) + check_float(name=TimeseriesFeatureMixin.path_aspect_ratio.__name__, value=px_per_mm) + xmin, ymin = np.min(x[:, 0]), np.min(x[:, 1]) + xmax, ymax = np.max(x[:, 0]), np.max(x[:, 1]) + w, h = (xmax - xmin), (ymax - ymin) + if w == 0 or h == 0: + return -1 + else: + return (w / h) * px_per_mm + + @staticmethod + def sliding_path_aspect_ratio(x: np.ndarray, + window_size: float, + sample_rate: float, + px_per_mm: float) -> np.ndarray: + + """ + Computes the aspect ratio of the bounding box for a sliding window along a path. + + This function calculates the aspect ratio (width/height) of the smallest bounding box that encloses a sequence of points within a sliding window over a 2D path. The path is defined by consecutive (x, y) coordinates. The sliding window moves forward by one point at each step, and the aspect ratio is computed for each position of the window. + + :param np.ndarray x: A 2D array of shape (N, 2) representing the path, where N is the number of points, and each point has two spatial coordinates (x and y). + :param float window_size: The size of the sliding window in seconds. + :param float px_per_mm: Convertion factor representing the number of pixels per millimeter + :param float sample_rate: The sample rate of the path data in points per second. + + :return: An array of aspect ratios for each position of the sliding window. If the window contains a path segment that is aligned vertically or horizontally (leading to a zero width or height), the function returns -1.0 for that position. NaN values are used for the initial positions where the window cannot be fully applied. + :rtype: np.ndarray + + :example: + >>> x = np.random.randint(0, 500, (10, 2)) + >>> TimeseriesFeatureMixin.(x=x, window_size=1, sample_rate=2) + """ + + window_frm = np.ceil(window_size * sample_rate).astype(np.int32) + results = np.full(x.shape[0], dtype=np.float32, fill_value=np.nan) + for r in range(window_frm, x.shape[0] + 1): + l = r - window_frm + sample_x = x[l:r, :] + xmin, ymin = np.min(sample_x[:, 0]), np.min(sample_x[:, 1]) + xmax, ymax = np.max(sample_x[:, 0]), np.max(sample_x[:, 1]) + w, h = (xmax - xmin), (ymax - ymin) + if w == 0 or h == 0: + results[r - 1] = -1.0 + else: + results[r - 1] = (w / h) * px_per_mm + + return results \ No newline at end of file diff --git a/simba/utils/errors.py b/simba/utils/errors.py index fc84e5e8e..69e865767 100644 --- a/simba/utils/errors.py +++ b/simba/utils/errors.py @@ -415,6 +415,12 @@ def __init__(self, msg: str, source: str = "", show_window: bool = False): super().__init__(msg=msg, source=source, show_window=show_window) +class SimBAGPUError(SimbaError): + def __init__(self, msg: str, source: str = "", show_window: bool = False): + msg = f"SIMBA GPU ERROR: {msg}" + super().__init__(msg=msg, source=source, show_window=show_window) + + # test = NoSpecifiedOutputError(msg='test', source='test.method') # test = FFMPEGNotFoundError(msg='323')