From f8e41e26ec0eed1d5c357dfa398e5c32bcb22777 Mon Sep 17 00:00:00 2001 From: sronilsson Date: Sat, 31 Aug 2024 18:42:16 +0000 Subject: [PATCH 1/5] cleaned --- .../data_processors/cuda/circular_statistics.py | 2 ++ .../cuda/create_average_frame_cuda.py | 14 +++----------- simba/data_processors/cuda/geometry.py | 8 +++++--- simba/data_processors/cuda/image.py | 16 ++++++++++------ .../cuda/sliding_circular_range.py | 1 - simba/data_processors/cuda/statistics.py | 6 +++++- 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/simba/data_processors/cuda/circular_statistics.py b/simba/data_processors/cuda/circular_statistics.py index 82751e1d4..be4c00d3f 100644 --- a/simba/data_processors/cuda/circular_statistics.py +++ b/simba/data_processors/cuda/circular_statistics.py @@ -3,6 +3,7 @@ import math from typing import Optional, Tuple + try: from typing import Literal except: @@ -10,6 +11,7 @@ import numpy as np from numba import cuda, int32 + try: import cupy as cp except: diff --git a/simba/data_processors/cuda/create_average_frame_cuda.py b/simba/data_processors/cuda/create_average_frame_cuda.py index d2c9217ca..6820841bf 100644 --- a/simba/data_processors/cuda/create_average_frame_cuda.py +++ b/simba/data_processors/cuda/create_average_frame_cuda.py @@ -3,14 +3,16 @@ import os from typing import Optional, Union + try: from typing import Literal except: from typing_extensions import Literal +from copy import deepcopy + import cupy as cp import cv2 -from copy import deepcopy import numpy as np from numba import cuda @@ -28,16 +30,6 @@ get_video_meta_data, read_img_batch_from_video_gpu) - - - - - - - - - - def average_3d_stack_cupy(image_stack: np.ndarray) -> np.ndarray: num_frames, height, width, _ = image_stack.shape image_stack = cp.array(image_stack).astype(cp.float32) diff --git a/simba/data_processors/cuda/geometry.py b/simba/data_processors/cuda/geometry.py index a7d9ea84c..13b112517 100644 --- a/simba/data_processors/cuda/geometry.py +++ b/simba/data_processors/cuda/geometry.py @@ -1,13 +1,15 @@ __author__ = "Simon Nilsson" __email__ = "sronilsson@gmail.com" +import math from typing import Optional -from simba.utils.checks import check_float, check_valid_array -from simba.utils.enums import Formats import numpy as np from numba import cuda, njit -import math + +from simba.utils.checks import check_float, check_valid_array +from simba.utils.enums import Formats + try: import cupy as cp except: diff --git a/simba/data_processors/cuda/image.py b/simba/data_processors/cuda/image.py index 139bf1151..85be6bd18 100644 --- a/simba/data_processors/cuda/image.py +++ b/simba/data_processors/cuda/image.py @@ -4,6 +4,7 @@ import os from typing import Optional, Union + try: from typing import Literal except: @@ -12,23 +13,26 @@ import cupy as cp except: import numpy as cp + +from copy import deepcopy + import cv2 import numpy as np from numba import cuda -from copy import deepcopy from simba.utils.checks import (check_file_exist_and_readable, check_if_dir_exists, check_if_string_value_is_valid_video_timestamp, - check_int, check_nvidea_gpu_available, - check_if_valid_img, + check_if_valid_img, check_instance, check_int, + check_nvidea_gpu_available, check_that_hhmmss_start_is_before_end, - check_instance, check_valid_array) + check_valid_array) from simba.utils.data import find_frame_numbers_from_time_stamp from simba.utils.errors import FFMPEGCodecGPUError, InvalidInputError from simba.utils.printing import stdout_success -from simba.utils.read_write import (check_if_hhmmss_timestamp_is_valid_part_of_video, get_fn_ext, get_video_meta_data, read_img_batch_from_video_gpu) - +from simba.utils.read_write import ( + check_if_hhmmss_timestamp_is_valid_part_of_video, get_fn_ext, + get_video_meta_data, read_img_batch_from_video_gpu) PHOTOMETRIC = 'photometric' DIGITAL = 'digital' diff --git a/simba/data_processors/cuda/sliding_circular_range.py b/simba/data_processors/cuda/sliding_circular_range.py index fb47e1f81..ecdd4aba7 100644 --- a/simba/data_processors/cuda/sliding_circular_range.py +++ b/simba/data_processors/cuda/sliding_circular_range.py @@ -12,7 +12,6 @@ import numpy as np - def sliding_circular_range(x: np.ndarray, time_window: float, sample_rate: float, diff --git a/simba/data_processors/cuda/statistics.py b/simba/data_processors/cuda/statistics.py index 83c3802f0..a0e89205d 100644 --- a/simba/data_processors/cuda/statistics.py +++ b/simba/data_processors/cuda/statistics.py @@ -2,15 +2,19 @@ __email__ = "sronilsson@gmail.com" -from typing import Optional import math +from typing import Optional + import numpy as np from numba import cuda + from simba.utils.read_write import read_df + try: import cupy as cp except: import numpy as cp + from simba.utils.checks import check_int, check_valid_array from simba.utils.enums import Formats From 92e94ba834b21bb90ed43bf0f596621061325f04 Mon Sep 17 00:00:00 2001 From: simon Date: Sun, 1 Sep 2024 15:39:29 -0400 Subject: [PATCH 2/5] cuda docs --- docs/conf.py | 7 ++ docs/tables/.~lock.direction_two_bps.csv# | 1 + docs/tables/.~lock.is_inside_rectangle.csv# | 1 + docs/tables/count_values_in_ranges.csv | 9 ++ docs/tables/cuda_shap.csv | 10 +++ docs/tables/direction_two_bps.csv | 10 +++ docs/tables/get_3pt_angle.csv | 8 ++ docs/tables/get_euclidean_distance_cuda.csv | 13 +++ docs/tables/img_stack_to_grayscale_cuda.csv | 23 +++++ docs/tables/img_stack_to_grayscale_cupy.csv | 12 +++ docs/tables/is_inside_circle.csv | 11 +++ docs/tables/is_inside_polygon.csv | 11 +++ docs/tables/is_inside_rectangle.csv | 23 +++-- docs/tables/sliding_circular_hotspots.csv | 14 ++++ docs/tables/sliding_circular_mean.csv | 12 +++ docs/tables/sliding_circular_range.csv | 14 ++++ docs/tables/sliding_circular_std.csv | 14 ++++ docs/tables/sliding_mean.csv | 12 +++ docs/tables/sliding_min.csv | 12 +++ docs/tables/sliding_rayleigh_z.csv | 14 ++++ .../sliding_resultant_vector_length.csv | 14 ++++ .../cuda/circular_statistics.py | 76 ++++++++++++++++- simba/data_processors/cuda/geometry.py | 42 ++++++++-- simba/data_processors/cuda/image.py | 61 +++++++++++--- simba/data_processors/cuda/statistics.py | 83 +++++++++++++------ simba/mixins/circular_statistics.py | 59 ++++++++++++- simba/mixins/feature_extraction_mixin.py | 37 ++++++--- simba/mixins/statistics_mixin.py | 7 +- 28 files changed, 536 insertions(+), 74 deletions(-) create mode 100644 docs/tables/.~lock.direction_two_bps.csv# create mode 100644 docs/tables/.~lock.is_inside_rectangle.csv# create mode 100644 docs/tables/count_values_in_ranges.csv create mode 100644 docs/tables/cuda_shap.csv create mode 100644 docs/tables/direction_two_bps.csv create mode 100644 docs/tables/get_3pt_angle.csv create mode 100644 docs/tables/get_euclidean_distance_cuda.csv create mode 100644 docs/tables/img_stack_to_grayscale_cuda.csv create mode 100644 docs/tables/img_stack_to_grayscale_cupy.csv create mode 100644 docs/tables/is_inside_circle.csv create mode 100644 docs/tables/is_inside_polygon.csv create mode 100644 docs/tables/sliding_circular_hotspots.csv create mode 100644 docs/tables/sliding_circular_mean.csv create mode 100644 docs/tables/sliding_circular_range.csv create mode 100644 docs/tables/sliding_circular_std.csv create mode 100644 docs/tables/sliding_mean.csv create mode 100644 docs/tables/sliding_min.csv create mode 100644 docs/tables/sliding_rayleigh_z.csv create mode 100644 docs/tables/sliding_resultant_vector_length.csv diff --git a/docs/conf.py b/docs/conf.py index c9b37b353..14f063021 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -22,14 +22,21 @@ extensions = ['sphinx.ext.napoleon', 'sphinx.ext.imgmath', 'sphinx.ext.mathjax', + 'sphinx-mathjax-offline', 'sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.viewcode', + 'sphinxemoji.sphinxemoji', #'sphinx_autodoc_typehints', 'sphinx_togglebutton', 'nbsphinx', 'sphinx.ext.intersphinx', 'sphinxcontrib.video'] + +#mathjax_path = "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-AMS-MML_HTMLorMML" + + + intersphinx_mapping = { 'python': ('https://docs.python.org/3', None), } diff --git a/docs/tables/.~lock.direction_two_bps.csv# b/docs/tables/.~lock.direction_two_bps.csv# new file mode 100644 index 000000000..945f92486 --- /dev/null +++ b/docs/tables/.~lock.direction_two_bps.csv# @@ -0,0 +1 @@ +,simon,simon-Inspiron-15-3520,31.08.2024 19:05,file:///home/simon/.config/libreoffice/4; \ No newline at end of file diff --git a/docs/tables/.~lock.is_inside_rectangle.csv# b/docs/tables/.~lock.is_inside_rectangle.csv# new file mode 100644 index 000000000..55d629f6b --- /dev/null +++ b/docs/tables/.~lock.is_inside_rectangle.csv# @@ -0,0 +1 @@ +,simon,simon-Inspiron-15-3520,31.08.2024 19:02,file:///home/simon/.config/libreoffice/4; \ No newline at end of file diff --git a/docs/tables/count_values_in_ranges.csv b/docs/tables/count_values_in_ranges.csv new file mode 100644 index 000000000..44f154c6b --- /dev/null +++ b/docs/tables/count_values_in_ranges.csv @@ -0,0 +1,9 @@ +FRAMES (MILLION),TIME (S) +4,0.038 +8,0.201 +16,0.344 +32,0.306 +64,0.776 +128,1.611 +NVIDIA GeForce RTX 4070, +"(n, 11)", diff --git a/docs/tables/cuda_shap.csv b/docs/tables/cuda_shap.csv new file mode 100644 index 000000000..e83cc00ad --- /dev/null +++ b/docs/tables/cuda_shap.csv @@ -0,0 +1,10 @@ +FRAMES (THOUSANDS),TIME (S) +1,1.295 +10,4.401 +20,8.627 +40,15.280 +80,30.759 +160,65.575 +240,87.468 +NVIDIA GeForce RTX 4070, +"RF: 100 estimators, 501 features", diff --git a/docs/tables/direction_two_bps.csv b/docs/tables/direction_two_bps.csv new file mode 100644 index 000000000..e5bc20eed --- /dev/null +++ b/docs/tables/direction_two_bps.csv @@ -0,0 +1,10 @@ +FRAMES (MILLIONS),CUDA JIT GPU (S) +2,0.0285 +4,0.0404 +8,0.069 +16,0.1352 +32,0.2711 +64,0.5586 +128,0.8525 +256,1.6652 +512,4.1223 diff --git a/docs/tables/get_3pt_angle.csv b/docs/tables/get_3pt_angle.csv new file mode 100644 index 000000000..3784a7fe0 --- /dev/null +++ b/docs/tables/get_3pt_angle.csv @@ -0,0 +1,8 @@ +FRAMES,TIME (S) +**4 million**,0.020 +**8 million**,0.040 +**16 million**,0.159 +**32 million**,0.290 +**64 million** ,0.335 +**128 million**,0.792 +256 million**,1.371 diff --git a/docs/tables/get_euclidean_distance_cuda.csv b/docs/tables/get_euclidean_distance_cuda.csv new file mode 100644 index 000000000..86e54b531 --- /dev/null +++ b/docs/tables/get_euclidean_distance_cuda.csv @@ -0,0 +1,13 @@ +OBSERVATION,TIME (S) +**110k**,0.007 +**181k**,0.021 +**327k**,0.032 +**620k**,0.02 +**1.2m**,0.082 +**2.4m**,0.046 +**4.7m**,0.106 +**9.3m**,0.209 +**18.6m**,0.238 +**37.2m**,0.926 +**74.5m**,1.136 +**149m**,2.046 diff --git a/docs/tables/img_stack_to_grayscale_cuda.csv b/docs/tables/img_stack_to_grayscale_cuda.csv new file mode 100644 index 000000000..5362130ad --- /dev/null +++ b/docs/tables/img_stack_to_grayscale_cuda.csv @@ -0,0 +1,23 @@ +FRAMES (k),GPU (s),STDEV (s) +1,0.16022,0.125786 +2,0.14428,0.003726 +3,0.19769,0.005645 +4,0.2571,0.006002 +5,0.33574,0.017868 +6,0.39921,0.026858 +7,0.45734,0.0333 +8,0.53216,0.015075 +9,0.50257,0.023186 +10,0.54235,0.090055 +11,0.69896,0.00999 +12,0.57932,0.0010399 +13,0.63791,0.017 +14,0.99677,0.1073426 +15,1.56103,0.5308574 +16,1.08143,0.082 +17,1.39189,0.3308389 +18,1.23648,0.1386822 +19,1.39554,0.161 +20,1.962,0.576 +NVIDIA GeForce RTX 4070,, +REPEATS= 3,, diff --git a/docs/tables/img_stack_to_grayscale_cupy.csv b/docs/tables/img_stack_to_grayscale_cupy.csv new file mode 100644 index 000000000..a1d53182c --- /dev/null +++ b/docs/tables/img_stack_to_grayscale_cupy.csv @@ -0,0 +1,12 @@ +FRAMES (THOUSANDS),GPU (s) +0.5,0.3419 +1,0.52333 +1.5,0.81614 +2,1.0632 +2.5,1.32399 +3,1.61488 +3.5,1.87857 +4,2.30137 +4.5,2.54736 +5,2.90824 +5.5,3.09271 diff --git a/docs/tables/is_inside_circle.csv b/docs/tables/is_inside_circle.csv new file mode 100644 index 000000000..682e7eb05 --- /dev/null +++ b/docs/tables/is_inside_circle.csv @@ -0,0 +1,11 @@ +FRAMES (MILLIONS),CUDA JIT GPU (S) +2,0.006 +4,0.007 +8,0.016 +16,0.028 +32,0.054 +64,0.114 +128,0.319 +256,0.44 +512,1.085 +1000,2.966 diff --git a/docs/tables/is_inside_polygon.csv b/docs/tables/is_inside_polygon.csv new file mode 100644 index 000000000..319a77863 --- /dev/null +++ b/docs/tables/is_inside_polygon.csv @@ -0,0 +1,11 @@ +FRAMES (MILLIONS),CUDA JIT GPU (S),NUMBA CPU TIME (S) +2,0.002,0.038 +4,0.004,0.082 +8,0.006,0.170 +16,0.009,0.295 +32,0.021,0.823 +64,0.041,1.395 +128,0.101,2.688 +256,0.369,4.640 +512,0.614,10.940 +1000,1.293,19.947 diff --git a/docs/tables/is_inside_rectangle.csv b/docs/tables/is_inside_rectangle.csv index 6ad75afa5..13d9f59af 100644 --- a/docs/tables/is_inside_rectangle.csv +++ b/docs/tables/is_inside_rectangle.csv @@ -1,12 +1,11 @@ -FRAMES (MILLION),CUDA JIT GPU (S),NUMBA CPU TIME (S) -**2 million**,0.005,0.022 -**4 million**,0.009,0.031 -**8 million**,0.016,0.097 -**16 million**,0.028,0.199 -**32 million**,0.054,0.399 -**64 million**,0.111,0.769 -**128 million**,0.33,1.3 -**256 million**,0.666,2.531 -**512 million**,1.161,7.273 -**1 billion**,3.828,13.342 -NVIDIA GeForce RTX 4070,, +FRAMES (MILLIONS),CUDA JIT GPU (S),NUMBA CPU TIME (S) +2,0.005,0.022 +4,0.009,0.031 +8,0.016,0.097 +16,0.028,0.199 +32,0.054,0.399 +64,0.111,0.769 +128,0.33,1.300 +256,0.666,2.531 +512,1.161,7.273 +1000,3.828,13.342 diff --git a/docs/tables/sliding_circular_hotspots.csv b/docs/tables/sliding_circular_hotspots.csv new file mode 100644 index 000000000..b82357f8d --- /dev/null +++ b/docs/tables/sliding_circular_hotspots.csv @@ -0,0 +1,14 @@ +FRAMES (MILLIONS),GPU (s),GPU (STDEV) +2,0.0219,0 +4,0.0387,0.002 +8,0.0809,0.015 +16,0.1304,0.008 +32,0.3135,0.043 +64,0.593,0.054 +128,1.2028,0.156 +256,2.3236,0.101 +512,4.9477,0.538 +1024,10.266,1.351 +NVIDIA GeForce RTX 4070,, +BATCH_SIZE: 3e+7,, +REPEATS 5,, diff --git a/docs/tables/sliding_circular_mean.csv b/docs/tables/sliding_circular_mean.csv new file mode 100644 index 000000000..84b7dc118 --- /dev/null +++ b/docs/tables/sliding_circular_mean.csv @@ -0,0 +1,12 @@ +FRAMES (MILLIONS),GPU (s),GPU (STDEV) +1,0.0755,0.07201 +2,0.03908,0.00131 +4,0.0726,0.00205 +8,0.13196,0.00872 +16,0.26056,0.01587 +32,0.60962,0.07974 +64,1.01012,0.02032 +128,2.05793,0.15376 +256,4.08802,0.14152 +512,14.67234,3.66222 +1000,27.43026,3.35774 diff --git a/docs/tables/sliding_circular_range.csv b/docs/tables/sliding_circular_range.csv new file mode 100644 index 000000000..2aa5086bd --- /dev/null +++ b/docs/tables/sliding_circular_range.csv @@ -0,0 +1,14 @@ +FRAMES (MILLIONS),GPU (s),GPU (STDEV) +2,0.055,0.199 +4,0.115,0.005 +8,0.239,0.003 +16,0.398,0.023 +32,0.768,0.012 +64,1.596,0.04 +128,3.118,0.131 +256,6.84703,0.73121 +512,12.36,0.122 +1024,25.17,0.075 +NVIDIA GeForce RTX 4070,, +BATCH_SIZE: 3e+7,, +REPEATS 5,, diff --git a/docs/tables/sliding_circular_std.csv b/docs/tables/sliding_circular_std.csv new file mode 100644 index 000000000..bd24553cc --- /dev/null +++ b/docs/tables/sliding_circular_std.csv @@ -0,0 +1,14 @@ +FRAMES (MILLIONS),GPU (s),GPU (STDEV) +2,0.027,0.0009 +4,0.048,0.0028 +8,0.117,0.0199 +16,0.163,0.0052 +32,0.362,0.0571 +64,0.653,0.0134 +128,1.439,0.1888 +256,2.836,0.1929 +512,6.107,0.232 +1024,22.893,5.933 +NVIDIA GeForce RTX 4070,, +BATCH_SIZE: 3e+7,, +REPEATS 5,, diff --git a/docs/tables/sliding_mean.csv b/docs/tables/sliding_mean.csv new file mode 100644 index 000000000..6e0ad4a21 --- /dev/null +++ b/docs/tables/sliding_mean.csv @@ -0,0 +1,12 @@ +FRAMES (MILLIONS),TIME (S) +2,0.005 +4,0.025 +8,0.015 +16,0.028 +32,0.059 +64,0.182 +128,0.237 +256,0.507 +512,1.022 +NVIDIA GeForce RTX 4070, +time window = 1s / 10 FPS, diff --git a/docs/tables/sliding_min.csv b/docs/tables/sliding_min.csv new file mode 100644 index 000000000..8e8fb8936 --- /dev/null +++ b/docs/tables/sliding_min.csv @@ -0,0 +1,12 @@ +FRAMES (MILLIONS),TIME (S) +2,0.003 +4,0.016 +8,0.012 +16,0.049 +32,0.053 +64,0.099 +128,0.211 +256,0.495 +512,1.031 +NVIDIA GeForce RTX 4070, +time window = 1s / 10 FPS, diff --git a/docs/tables/sliding_rayleigh_z.csv b/docs/tables/sliding_rayleigh_z.csv new file mode 100644 index 000000000..b9b7af685 --- /dev/null +++ b/docs/tables/sliding_rayleigh_z.csv @@ -0,0 +1,14 @@ +FRAMES (MILLIONS),GPU (s),GPU (STDEV) +2,0.02531,0.00328 +4,0.04003,0.00533 +8,0.07184,0.01016 +16,0.12379,0.01608 +32,0.2542,0.03084 +64,0.52848,0.05978 +128,1.05474,0.16997 +256,1.93246,0.07944 +512,4.45524,0.07773 +1000,8.46498,0.33432 +NVIDIA GeForce RTX 4070,, +BATCH_SIZE: 3e+7,, +REPEATS 5,, diff --git a/docs/tables/sliding_resultant_vector_length.csv b/docs/tables/sliding_resultant_vector_length.csv new file mode 100644 index 000000000..09438352a --- /dev/null +++ b/docs/tables/sliding_resultant_vector_length.csv @@ -0,0 +1,14 @@ +FRAMES (MILLIONS),GPU (s),GPU (STDEV) +2,0.04253,0.002 +4,0.06979,0 +8,0.14922,0.015 +16,0.34029,0.062 +32,0.48812,0.012 +64,1.0269,0.059 +128,2.16228,0.156 +256,4.15671,0.027 +512,11.4188,2.501 +1000,28.76021,2.59123 +NVIDIA GeForce RTX 4070,, +BATCH_SIZE: 3e+7,, +REPEATS 5,, diff --git a/simba/data_processors/cuda/circular_statistics.py b/simba/data_processors/cuda/circular_statistics.py index be4c00d3f..bb3a1286b 100644 --- a/simba/data_processors/cuda/circular_statistics.py +++ b/simba/data_processors/cuda/circular_statistics.py @@ -36,14 +36,20 @@ def direction_from_two_bps(x: np.ndarray, y: np.ndarray) -> np.ndarray: Compute the directionality in degrees from two body-parts. E.g., ``nape`` and ``nose``, or ``swim_bladder`` and ``tail`` with GPU acceleration. - .. image:: _static/img/direction_from_two_bps_cuda.png - :width: 1200 + .. csv-table:: + :header: EXPECTED RUNTIMES + :file: ../../../docs/tables/direction_from_two_bps.csv + :widths: 10, 90 :align: center + :header-rows: 1 + .. seealso:: + For CPU function see :func:`~simba.mixins.circular_statistics.CircularStatisticsMixin.direction_two_bps`. :parameter np.ndarray x: Size len(frames) x 2 representing x and y coordinates for first body-part. :parameter np.ndarray y: Size len(frames) x 2 representing x and y coordinates for second body-part. - :return np.ndarray: Frame-wise directionality in degrees. + :return: Frame-wise directionality in degrees. + :rtype: np.ndarray. """ x = np.ascontiguousarray(x).astype(np.int32) @@ -69,6 +75,17 @@ def sliding_circular_hotspots(x: np.ndarray, points within specified angular bins over a sliding window. The calculations are performed in batches to accommodate large datasets efficiently. + .. csv-table:: + :header: EXPECTED RUNTIMES + :file: ../../../docs/tables/sliding_circular_hotspots.csv + :widths: 10, 45, 45 + :align: center + :header-rows: 1 + + .. seealso:: + For CPU function see :func:`~simba.mixins.circular_statistics.CircularStatisticsMixin.sliding_circular_hotspots`. + + :param np.ndarray x: The input time series data in degrees. Should be a 1D numpy array. :param float time_window: The size of the sliding window in seconds. :param float sample_rate: The sample rate of the time series data (i.e., hz, fps). @@ -127,6 +144,16 @@ def sliding_circular_mean(x: np.ndarray, - :math:`N` is the number of samples in the window + .. csv-table:: + :header: EXPECTED RUNTIMES + :file: ../../../docs/tables/sliding_circular_mean.csv + :widths: 10, 45, 45 + :align: center + :header-rows: 1 + + .. seealso:: + For CPU function see :func:`~simba.mixins.circular_statistics.CircularStatisticsMixin.sliding_circular_mean`. + :param np.ndarray x: Input array containing angle values in degrees. The array should be 1-dimensional. :param float time_window: Time duration for the sliding window, in seconds. This determines the number of samples in each window based on the `sample_rate`. :param int sample_rate: The number of samples per second (i.e., FPS). This is used to calculate the window size in terms of array indices. @@ -177,6 +204,16 @@ def sliding_circular_range(x: np.ndarray, - :math:`\\Delta \\theta` is the difference between angles within the window, - :math:`360` accounts for the circular nature of the data (i.e., wrap-around at 360 degrees). + .. csv-table:: + :header: EXPECTED RUNTIMES + :file: ../../../docs/tables/sliding_circular_range.csv + :widths: 10, 45, 45 + :align: center + :header-rows: 1 + + .. seealso:: + For CPU function see :func:`~simba.mixins.circular_statistics.CircularStatisticsMixin.sliding_circular_range`. + :param np.ndarray x: The input time series data in degrees. Should be a 1D numpy array. :param float time_window: The size of the sliding window in seconds. :param float sample_rate: The sample rate of the time series data (i.e., hz, fps). @@ -228,6 +265,16 @@ def sliding_circular_std(x: np.ndarray, where :math:`x_{\text{batch}}` is the data within the current sliding window, and :math:`\text{mean}` and :math:`\log` are computed in the circular (complex plane) domain. + .. csv-table:: + :header: EXPECTED RUNTIMES + :file: ../../../docs/tables/sliding_circular_std.csv + :widths: 10, 45, 45 + :align: center + :header-rows: 1 + + .. seealso:: + For CPU function see :func:`~simba.mixins.circular_statistics.CircularStatisticsMixin.sliding_circular_std`. + :param np.ndarray x: The input time series data in degrees. Should be a 1D numpy array. :param float time_window: The size of the sliding window in seconds. :param float sample_rate: The sample rate of the time series data (i.e., hz, fps). @@ -286,6 +333,18 @@ def sliding_rayleigh_z(x: np.ndarray, - :math:`\theta_i` are the angles in the window. - :math:`n` is the number of angles in the window. + + .. csv-table:: + :header: EXPECTED RUNTIMES + :file: ../../../docs/tables/sliding_rayleigh_z.csv + :widths: 10, 45, 45 + :align: center + :header-rows: 1 + + .. seealso:: + For CPU function see :func:`~simba.mixins.circular_statistics.CircularStatisticsMixin.sliding_rayleigh_z`. + + :param np.ndarray x: Input array of angles in degrees. Should be a 1D numpy array. :param float time_window: The size of the sliding window in time units (e.g., seconds). :param float sample_rate: The sampling rate of the input time series in samples per time unit (e.g., Hz, fps). @@ -347,13 +406,22 @@ def sliding_resultant_vector_length(x: np.ndarray, with CuPy for efficiency, especially on large datasets. + .. csv-table:: + :header: EXPECTED RUNTIMES + :file: ../../../docs/tables/sliding_resultant_vector_length.csv + :widths: 10, 10, 80 + :align: center + :header-rows: 1 + + .. seealso:: + For CPU function see :func:`~simba.mixins.circular_statistics.CircularStatisticsMixin.sliding_resultant_vector_length`. + :param np.ndarray x: Input array containing angle values in degrees. The array should be 1-dimensional. :param float time_window: Time duration for the sliding window, in seconds. This determines the number of samples in each window based on the `sample_rate`. :param int sample_rate: The number of samples per second (i.e., FPS). This is used to calculate the window size in terms of array indices. :param Optional[int] batch_size: The maximum number of elements to process in each batch. This is used to handle large arrays by processing them in chunks to avoid memory overflow. Defaults to 3e+7 (30 million elements). :return np.ndarray: A 1D numpy array of the same length as `x`, containing the resultant vector length for each sliding window. Values before the window is fully populated will be set to -1. - :example: >>> x = np.random.randint(0, 361, (5000, )).astype(np.int32) >>> results = sliding_resultant_vector_length(x, 1, 10) diff --git a/simba/data_processors/cuda/geometry.py b/simba/data_processors/cuda/geometry.py index 13b112517..0043932b6 100644 --- a/simba/data_processors/cuda/geometry.py +++ b/simba/data_processors/cuda/geometry.py @@ -30,6 +30,7 @@ def _cuda_is_inside_rectangle(x, y, r): def is_inside_rectangle(x: np.ndarray, y: np.ndarray) -> np.ndarray: """ Determines whether points in array `x` are inside the rectangle defined by the top left and bottom right vertices in array `y`. + |:heart_eyes:| .. csv-table:: :header: EXPECTED RUNTIMES @@ -39,13 +40,14 @@ def is_inside_rectangle(x: np.ndarray, y: np.ndarray) -> np.ndarray: :class: simba-table :header-rows: 1 + .. seealso:: + For numba CPU function see :func:`~simba.mixins.feature_extraction_mixin.FeatureExtractionMixin.framewise_inside_rectangle_roi` + :param np.ndarray x: 2d numeric np.ndarray size (N, 2). :param np.ndarray y: 2d numeric np.ndarray size (2, 2) (top left[x, y], bottom right[x, y]) :return np.ndarray: 2d numeric boolean (N, 1) with 1s representing the point being inside the rectangle and 0 if the point is outside the rectangle. - """ - x = np.ascontiguousarray(x).astype(np.int32) y = np.ascontiguousarray(y).astype(np.int32) x_dev = cuda.to_device(x) @@ -67,11 +69,21 @@ def _cuda_is_inside_circle(x, y, r, results): results[i] = 1 def is_inside_circle(x: np.ndarray, y: np.ndarray, r: float) -> np.ndarray: """ - Determines whether points in array `x` are inside the rectangle defined by the top left and bottom right vertices in array `y`. + Determines whether points in array `x` are inside the circle with center ``y`` and radius ``r`` + + .. csv-table:: + :header: EXPECTED RUNTIMES + :file: ../../../docs/tables/is_inside_circle.csv + :widths: 10, 90 + :align: center + :class: simba-table + :header-rows: 1 :param np.ndarray x: 2d numeric np.ndarray size (N, 2). - :param np.ndarray y: 2d numeric np.ndarray size (2, 2) (top left[x, y], bottom right[x, y]) - :return np.ndarray: 2d numeric boolean (N, 1) with 1s representing the point being inside the rectangle and 0 if the point is outside the rectangle. + :param np.ndarray y: 2d numeric np.ndarray size (1, 2) representing the center of the circle. + :param float r: The radius of the circle. + :return: 2d numeric boolean (N, 1) with 1s representing the point being inside the circle and 0 if the point is outside the rectangle. + :rtype: 1d np.ndarray vector. """ x = np.ascontiguousarray(x).astype(np.int32) @@ -121,13 +133,21 @@ def is_inside_polygon(x: np.ndarray, y: np.ndarray) -> np.ndarray: the polygon defined by the vertices in `y`. The result is an array where each element indicates whether the corresponding point is inside the polygon. - .. image:: _static/img/is_inside_polygon_cuda.webp - :width: 500 + .. csv-table:: + :header: EXPECTED RUNTIMES + :file: ../../../docs/tables/is_inside_polygon.csv + :widths: 10, 45, 45 :align: center + :class: simba-table + :header-rows: 1 + + .. seealso:: + For numba CPU function see :func:`~simba.mixins.feature_extraction_mixin.FeatureExtractionMixin.framewise_inside_polygon_roi` :param np.ndarray x: An array of shape (N, 2) where each row represents a point in 2D space. The points are checked against the polygon. :param np.ndarray y: An array of shape (M, 2) where each row represents a vertex of the polygon in 2D space. - :return np.ndarray: An array of shape (N,) where each element is 1 if the corresponding point in `x` is inside the polygon defined by `y`, and 0 otherwise. + :return: An array of shape (N,) where each element is 1 if the corresponding point in `x` is inside the polygon defined by `y`, and 0 otherwise. + :rtype: np.ndarray :example: >>> x = np.random.randint(0, 200, (i, 2)).astype(np.int8) @@ -229,6 +249,9 @@ def get_convex_hull(pts: np.ndarray) -> np.ndarray: .. note:: `Modified from Jacob Hultman `_ + .. seealso:: + :func:`~simba.feature_extractors.perimeter_jit.jitted_hull`. + :func:`~simba.mixins.feature_extraction_mixin.FeatureExtractionMixin.convex_hull_calculator_mp`. :param pts: A 3D numpy array of shape (M, N, 2) where: - M is the number of frames. - N is the number of points (body-parts) in each frame. - The last dimension (2) represents the x and y coordinates of each point. :return: An upated 3D numpy array of shape (M, N, 2) consisting of the points in the hull. @@ -267,6 +290,9 @@ def poly_area(data: np.ndarray, The computation is done in batches to handle large datasets efficiently. + .. seealso:: + :func:`~simba.feature_extractors.perimeter_jit.jitted_hull`. + :param data: A 3D numpy array of shape (N, M, 2), where N is the number of polygons, M is the number of points per polygon, and 2 represents the x and y coordinates. :param pixels_per_mm: Optional scaling factor to convert the area from pixels squared to square millimeters. Default is 1.0. :param batch_size: Optional batch size for processing the data in chunks to fit in memory. Default is 0.5e+7. diff --git a/simba/data_processors/cuda/image.py b/simba/data_processors/cuda/image.py index 85be6bd18..4bc1ae50e 100644 --- a/simba/data_processors/cuda/image.py +++ b/simba/data_processors/cuda/image.py @@ -54,6 +54,9 @@ def create_average_frm_cupy(video_path: Union[str, os.PathLike], to a specified file. If `save_path` is provided, the average frame is saved as an image file; otherwise, the average frame is returned as a NumPy array. + .. seealso:: + For CPU function see :func:`~simba.video_processors.video_processing.create_average_frm`. + For CUDA function see :func:`~simba.data_processors.cuda.image.create_average_frm_cuda` :param Union[str, os.PathLike] video_path: The path to the video file from which to extract frames. :param Optional[int] start_frm: The starting frame number (inclusive). Either `start_frm`/`end_frm` or `start_time`/`end_time` must be provided, but not both. @@ -76,17 +79,17 @@ def average_3d_stack(image_stack: np.ndarray) -> np.ndarray: return img.get() if not check_nvidea_gpu_available(): - raise FFMPEGCodecGPUError(msg="No GPU found (as evaluated by nvidea-smi returning None)", source=create_average_frm.__name__) + raise FFMPEGCodecGPUError(msg="No GPU found (as evaluated by nvidea-smi returning None)", source=create_average_frm_cupy.__name__) if ((start_frm is not None) or (end_frm is not None)) and ((start_time is not None) or (end_time is not None)): - raise InvalidInputError(msg=f'Pass start_frm and end_frm OR start_time and end_time', source=create_average_frm.__name__) + raise InvalidInputError(msg=f'Pass start_frm and end_frm OR start_time and end_time', source=create_average_frm_cupy.__name__) elif type(start_frm) != type(end_frm): - raise InvalidInputError(msg=f'Pass start frame and end frame', source=create_average_frm.__name__) + raise InvalidInputError(msg=f'Pass start frame and end frame', source=create_average_frm_cupy.__name__) elif type(start_time) != type(end_time): - raise InvalidInputError(msg=f'Pass start time and end time', source=create_average_frm.__name__) + raise InvalidInputError(msg=f'Pass start time and end time', source=create_average_frm_cupy.__name__) if save_path is not None: - check_if_dir_exists(in_dir=os.path.dirname(save_path), source=create_average_frm.__name_) + check_if_dir_exists(in_dir=os.path.dirname(save_path), source=create_average_frm_cupy.__name__) check_file_exist_and_readable(file_path=video_path) video_meta_data = get_video_meta_data(video_path=video_path) video_name = get_fn_ext(filepath=video_path)[1] @@ -96,12 +99,12 @@ def average_3d_stack(image_stack: np.ndarray) -> np.ndarray: check_int(name='start_frm', value=start_frm, min_value=0, max_value=video_meta_data['frame_count']) check_int(name='end_frm', value=end_frm, min_value=0, max_value=video_meta_data['frame_count']) if start_frm > end_frm: - raise InvalidInputError(msg=f'Start frame ({start_frm}) has to be before end frame ({end_frm}).', source=create_average_frm.__name__) + raise InvalidInputError(msg=f'Start frame ({start_frm}) has to be before end frame ({end_frm}).', source=create_average_frm_cupy.__name__) frame_ids = list(range(start_frm, end_frm)) elif (start_time is not None) and (end_time is not None): - check_if_string_value_is_valid_video_timestamp(value=start_time, name=create_average_frm.__name__) - check_if_string_value_is_valid_video_timestamp(value=end_time, name=create_average_frm.__name__) - check_that_hhmmss_start_is_before_end(start_time=start_time, end_time=end_time, name=create_average_frm.__name__) + check_if_string_value_is_valid_video_timestamp(value=start_time, name=create_average_frm_cupy.__name__) + check_if_string_value_is_valid_video_timestamp(value=end_time, name=create_average_frm_cupy.__name__) + check_that_hhmmss_start_is_before_end(start_time=start_time, end_time=end_time, name=create_average_frm_cupy.__name__) check_if_hhmmss_timestamp_is_valid_part_of_video(timestamp=start_time, video_path=video_path) frame_ids = find_frame_numbers_from_time_stamp(start_time=start_time, end_time=end_time, fps=video_meta_data['fps']) else: @@ -118,7 +121,7 @@ def average_3d_stack(image_stack: np.ndarray) -> np.ndarray: if save_path is not None: cv2.imwrite(save_path, avg_img) if verbose: - stdout_success(msg=f'Saved average frame at {save_path}', source=create_average_frm.__name__) + stdout_success(msg=f'Saved average frame at {save_path}', source=create_average_frm_cupy.__name__) else: return avg_img @@ -177,6 +180,9 @@ def create_average_frm_cuda(video_path: Union[str, os.PathLike], to a specified file. If `save_path` is provided, the average frame is saved as an image file; otherwise, the average frame is returned as a NumPy array. + .. selalso:: + For CuPy function see :func:`~simba.data_processors.cuda.image.create_average_frm_cupy`. + For CPU function see :func:`~simba.video_processors.video_processing.create_average_frm`. :param Union[str, os.PathLike] video_path: The path to the video file from which to extract frames. :param Optional[int] start_frm: The starting frame number (inclusive). Either `start_frm`/`end_frm` or `start_time`/`end_time` must be provided, but not both. @@ -189,7 +195,7 @@ def create_average_frm_cuda(video_path: Union[str, os.PathLike], :return: Returns `None` if the result is saved to `save_path`. Otherwise, returns the average frame as a NumPy array. :example: - >>> create_average_frm(video_path=r"C:\troubleshooting\RAT_NOR\project_folder\videos\2022-06-20_NOB_DOT_4_downsampled.mp4", verbose=True, start_frm=0, end_frm=9000) + >>> create_average_frm_cuda(video_path=r"C:\troubleshooting\RAT_NOR\project_folder\videos\2022-06-20_NOB_DOT_4_downsampled.mp4", verbose=True, start_frm=0, end_frm=9000) """ if not check_nvidea_gpu_available(): @@ -284,6 +290,8 @@ def img_stack_brightness(x: np.ndarray, .. math:: \text{brightness} = 0.299 \cdot R + 0.587 \cdot G + 0.114 \cdot B + .. selalso:: + For CPU function see :func:`~simba.mixins.image_mixin.ImageMixin.brightness_intensity`. :param np.ndarray x: A 4D array of images with dimensions (N, H, W, C), where N is the number of images, H and W are the height and width, and C is the number of channels (RGB). :param Optional[Literal['photometric', 'digital']] method: The method to use for calculating brightness. It can be 'photometric' for the standard luminance calculation or 'digital' for an alternative set of coefficients. Default is 'digital'. @@ -379,6 +387,11 @@ def stack_sliding_mse(x: np.ndarray, where the reference image is determined by a sliding window approach with a specified stride. The function is optimized for large image stacks by processing them in batches. + .. seelalso:: + For CPU function see :func:`~simba.mixins.image_mixin.ImageMixin.img_stack_mse` and + :func:`~simba.mixins.image_mixin.ImageMixin.img_sliding_mse`. + + :param np.ndarray x: Input array of images, where the first dimension corresponds to the stack of images. The array should be either 3D (height, width, channels) or 4D (batch, height, width, channels). :param Optional[int] stride: The stride or step size for the sliding window that determines the reference image. Defaults to 1, meaning the previous image in the stack is used as the reference. :param Optional[int] batch_size: The number of images to process in a single batch. Larger batch sizes may improve performance but require more GPU memory. Defaults to 1000. @@ -428,6 +441,19 @@ def img_stack_to_grayscale_cupy(imgs: np.ndarray, """ Converts a stack of color images to grayscale using GPU acceleration with CuPy. + .. seelalso:: + For CPU function single images :func:`~simba.mixins.image_mixin.ImageMixin.img_to_greyscale` and + :func:`~simba.mixins.image_mixin.ImageMixin.img_stack_to_greyscale` for stack. For CUDA JIT, see + :func:`~simba.data_processors.cuda.image.img_stack_to_grayscale_cuda`. + + .. csv-table:: + :header: EXPECTED RUNTIMES + :file: ../../../docs/tables/img_stack_to_grayscale_cupy.csv + :widths: 10, 45, 45 + :align: center + :class: simba-table + :header-rows: 1 + :param np.ndarray imgs: A 4D NumPy array representing a stack of images with shape (num_images, height, width, channels). The images are expected to have 3 channels (RGB). :param Optional[int] batch_size: The number of images to process in each batch. Defaults to 250. Adjust this parameter to fit your GPU's memory capacity. :return np.ndarray: m A 3D NumPy array of shape (num_images, height, width) containing the grayscale images. If the input array is not 4D, the function returns the input as is. @@ -476,6 +502,19 @@ def img_stack_to_grayscale_cuda(x: np.ndarray) -> np.ndarray: """ Convert image stack to grayscale using CUDA. + .. seelalso:: + For CPU function single images :func:`~simba.mixins.image_mixin.ImageMixin.img_to_greyscale` and + :func:`~simba.mixins.image_mixin.ImageMixin.img_stack_to_greyscale` for stack. For CuPy, see + :func:`~simba.data_processors.cuda.image.img_stack_to_grayscale_cupy`. + + .. csv-table:: + :header: EXPECTED RUNTIMES + :file: ../../../docs/tables/img_stack_to_grayscale_cuda.csv + :widths: 10, 45, 45 + :align: center + :class: simba-table + :header-rows: 1 + :param np.ndarray x: 4d array of color images in numpy format. :return np.ndarray: 3D array of greyscaled images. diff --git a/simba/data_processors/cuda/statistics.py b/simba/data_processors/cuda/statistics.py index a0e89205d..d1e586740 100644 --- a/simba/data_processors/cuda/statistics.py +++ b/simba/data_processors/cuda/statistics.py @@ -41,9 +41,16 @@ def get_3pt_angle(x: np.ndarray, y: np.ndarray, z: np.ndarray) -> np.ndarray: GPU. The points x, y, and z represent the coordinates of three points in space, and the angle is calculated at point `y` between the line segments `xy` and `yz`. - .. image:: _static/img/get_3pt_angle_cuda.png - :width: 500 + .. csv-table:: + :header: EXPECTED RUNTIMES + :file: ../../../docs/tables/sliding_resultant_vector_length.csv + :widths: 10, 90 :align: center + :header-rows: 1 + + .. seealso:: + For CPU function see :func:`~simba.mixins.FeatureExtractionMixin.angle3pt` and + For CPU function see :func:`~simba.mixins.FeatureExtractionMixin.angle3pt_serialized`. :param x: A numpy array of shape (n, 2) representing the first point (e.g., nose) coordinates. :param y: A numpy array of shape (n, 2) representing the second point (e.g., center) coordinates, where the angle is computed. @@ -94,9 +101,16 @@ def count_values_in_ranges(x: np.ndarray, r: np.ndarray) -> np.ndarray: """ Counts the number of values in each feature within specified ranges for each row in a 2D array using CUDA. - .. image:: _static/img/get_euclidean_distance_cuda.png - :width: 500 + + .. csv-table:: + :header: EXPECTED RUNTIMES + :file: ../../../docs/tables/count_values_in_ranges.csv + :widths: 10, 90 :align: center + :header-rows: 1 + + .. seealso:: + For CPU function see :func:`~simba.mixins.FeatureExtractionMixin.count_values_in_range`. :param np.ndarray x: 2d array with feature values. :param np.ndarray r: 2d array with lower and upper boundaries. @@ -131,9 +145,17 @@ def get_euclidean_distance_cuda(x: np.ndarray, y: np.ndarray) -> np.ndarray: """ Computes the Euclidean distance between two sets of points using CUDA for GPU acceleration. - .. image:: _static/img/get_euclidean_distance_cuda.png - :width: 500 + .. csv-table:: + :header: EXPECTED RUNTIMES + :file: ../../../docs/tables/get_euclidean_distance_cuda.csv + :widths: 10, 90 :align: center + :header-rows: 1 + + .. seealso:: + For CPU function see :func:`~simba.mixins.FeatureExtractionMixin.framewise_euclidean_distance`. + For CuPY function see :func:`~simba.data_processors.cuda.statistics.get_euclidean_distance_cupy`. + :param np.ndarray x: A 2D array of shape (n, m) representing n points in m-dimensional space. Each row corresponds to a point. :param np.ndarray y: A 2D array of shape (n, m) representing n points in m-dimensional space. Each row corresponds to a point. @@ -169,6 +191,11 @@ def get_euclidean_distance_cupy(x: np.ndarray, using CuPy for GPU acceleration. The computation is performed in batches to handle large datasets efficiently. + + .. seealso:: + For CPU function see :func:`~simba.mixins.FeatureExtractionMixin.framewise_euclidean_distance`. + For CUDA JIT function see :func:`~simba.data_processors.cuda.statistics.get_euclidean_distance_cuda`. + :param np.ndarray x: A 2D NumPy array with shape (n, 2), where each row represents a point in a 2D space. :param np.ndarray y: A 2D NumPy array with shape (n, 2), where each row represents a point in a 2D space. The shape of `y` must match the shape of `x`. :param Optional[int] batch_size: The number of points to process in a single batch. This parameter controls memory usage and can be adjusted based on available GPU memory. The default value is large (`3.5e10 + 7`) to maximize GPU utilization, but it can be lowered if memory issues arise. @@ -212,14 +239,18 @@ def sliding_mean(x: np.ndarray, time_window: float, sample_rate: int) -> np.ndar """ Computes the mean of values within a sliding window over a 1D numpy array `x` using CUDA for acceleration. - .. image:: _static/img/sliding_mean_cuda.png - :width: 500 + .. csv-table:: + :header: EXPECTED RUNTIMES + :file: ../../../docs/tables/sliding_mean.csv + :widths: 10, 90 :align: center + :header-rows: 1 :param np.ndarray x: The input 1D numpy array of floats. The array over which the sliding window sum is computed. - :param float time_window:The size of the sliding window in seconds. This window slides over the array `x` to compute the sum. + :param float time_window: The size of the sliding window in seconds. This window slides over the array `x` to compute the sum. :param int sample_rate: The number of samples per second in the array `x`. This is used to convert the time-based window size into the number of samples. - :return np.ndarray: A numpy array containing the sum of values within each position of the sliding window. + :return: A numpy array containing the sum of values within each position of the sliding window. + :rtype: np.ndarray :example: >>> x = np.random.randint(1, 11, (100, )).astype(np.float32) @@ -259,14 +290,18 @@ def sliding_min(x: np.ndarray, time_window: float, sample_rate: int) -> np.ndarr """ Computes the minimum value within a sliding window over a 1D numpy array `x` using CUDA for acceleration. - .. image:: _static/img/sliding_min_cuda.png - :width: 500 + .. csv-table:: + :header: EXPECTED RUNTIMES + :file: ../../../docs/tables/sliding_min.csv + :widths: 10, 90 :align: center + :header-rows: 1 :param np.ndarray x: Input 1D numpy array of floats. The array over which the sliding window minimum is computed. :param float time_window: The size of the sliding window in seconds. - :param intsample_rate: The sampling rate of the data, which determines the number of samples per second. + :param int sample_rate: The sampling rate of the data, which determines the number of samples per second. :return: A numpy array containing the minimum value for each position of the sliding window. + :rtype: np.ndarray :example: >>> x = np.arange(0, 10000000) @@ -296,15 +331,14 @@ def sliding_spearmans_rank(x: np.ndarray, over sliding windows of size `time_window * sample_rate`. The computation is performed in batches to optimize memory usage, leveraging GPU acceleration with CuPy. - .. math:: - \rho = 1 - \frac{6 \sum d_i^2}{n_w(n_w^2 - 1)} + .. seealso:: + + For CPU function see :func:`~simba.mixins.statistics.StatisticsMixin.sliding_spearman_rank_correlation`. - .. math:: - The function uses CuPy to perform GPU-accelerated calculations. Ensure that your environment - supports GPU computation with CuPy installed. + :math:`\\rho = 1 - \\frac{6 \\sum d_i^2}{n_w(n_w^2 - 1)}` Where: - - \( \rho \) is the Spearman's rank correlation coefficient. + - \( \\rho \) is the Spearman's rank correlation coefficient. - \( d_i \) is the difference between the ranks of corresponding elements in the sliding window. - \( n_w \) is the size of the sliding window. @@ -322,7 +356,6 @@ def sliding_spearmans_rank(x: np.ndarray, >>> sliding_spearmans_rank(x, y, time_window=0.5, sample_rate=2) """ - window_size = int(np.ceil(time_window * sample_rate)) n = x.shape[0] results = cp.full(n, -1, dtype=cp.float32) @@ -378,9 +411,10 @@ def sliding_std(x: np.ndarray, time_window: float, sample_rate: int) -> np.ndarr """ :param np.ndarray x: The input 1D numpy array of floats. The array over which the sliding window sum is computed. - :param float time_window:The size of the sliding window in seconds. This window slides over the array `x` to compute the sum. + :param float time_window: The size of the sliding window in seconds. This window slides over the array `x` to compute the sum. :param int sample_rate: The number of samples per second in the array `x`. This is used to convert the time-based window size into the number of samples. - :return np.ndarray: A numpy array containing the sum of values within each position of the sliding window. + :return: A numpy array containing the sum of values within each position of the sliding window. + :rtype: np.ndarray :example: >>> x = np.random.randint(1, 11, (100, )).astype(np.float32) @@ -418,9 +452,10 @@ def sliding_sum(x: np.ndarray, time_window: float, sample_rate: int) -> np.ndarr Computes the sum of values within a sliding window over a 1D numpy array `x` using CUDA for acceleration. :param np.ndarray x: The input 1D numpy array of floats. The array over which the sliding window sum is computed. - :param float time_window:The size of the sliding window in seconds. This window slides over the array `x` to compute the sum. + :param float time_window: The size of the sliding window in seconds. This window slides over the array `x` to compute the sum. :param int sample_rate: The number of samples per second in the array `x`. This is used to convert the time-based window size into the number of samples. - :return np.ndarray: A numpy array containing the sum of values within each position of the sliding window. + :return: A numpy array containing the sum of values within each position of the sliding window. + :rtype: np.ndarray :example: >>> x = np.random.randint(1, 11, (100, )).astype(np.float32) diff --git a/simba/mixins/circular_statistics.py b/simba/mixins/circular_statistics.py index f4139a070..bf8446ad7 100644 --- a/simba/mixins/circular_statistics.py +++ b/simba/mixins/circular_statistics.py @@ -65,6 +65,11 @@ def mean_resultant_vector_length(data: np.ndarray) -> float: where \(N\) is the number of data points, \(\theta_i\) is the angle of the ith data point, and \(\bar{\theta}\) is the mean angle. + .. seealso:: + :func:`simba.data_processors.cuda.circular_statistics.sliding_resultant_vector_length`, + :func:`simba.mixins.circular_statistics.CircularStatisticsMixin.sliding_mean_resultant_vector_length` + + :parameter np.ndarray data: 1D array of size len(frames) representing angles in degrees. :returns float: The mean resultant vector of the angles. 1 represents tendency towards a single point. 0 represents no central point. @@ -94,6 +99,10 @@ def sliding_mean_resultant_vector_length(data: np.ndarray, fps: float, time_wind :width: 600 :align: center + .. seealso:: + :func:`simba.data_processors.cuda.circular_statistics.sliding_resultant_vector_length`, + :func:`simba.mixins.circular_statistics.CircularStatisticsMixin.mean_resultant_vector_length` + :parameter np.ndarray data: 1D array of size len(data) representing degrees. :parameter np.ndarray time_window: Rolling time-window as float in seconds. :parameter int fps: fps of the recorded video @@ -130,6 +139,10 @@ def circular_mean(data: np.ndarray) -> float: :width: 400 :align: center + .. seealso:: + :func:`simba.data_processors.cuda.circular_statistics.sliding_circular_mean`, + :func:`simba.mixins.circular_statistics.CircularStatisticsMixin.sliding_circular_mean` + :example: >>> data = np.array([50, 90, 70, 60, 20, 90]).astype(np.float32) >>> CircularStatisticsMixin().circular_mean(data=data) @@ -162,6 +175,11 @@ def sliding_circular_mean(data: np.ndarray, time_windows: np.ndarray, fps: int) The returned values represents the angular mean dispersion in the time-window ``[current_frame-time_window->current_frame]``. `-1` is returned when ``current_frame-time_window`` is less than 0. + .. seealso:: + :func:`simba.data_processors.cuda.circular_statistics.sliding_circular_mean`, + :func:`simba.mixins.circular_statistics.CircularStatisticsMixin.circular_mean` + + :example: >>> data = np.random.normal(loc=45, scale=1, size=20).astype(np.float32) >>> CircularStatisticsMixin().sliding_circular_mean(data=data,time_windows=np.array([0.5, 1.0]), fps=10) @@ -191,6 +209,10 @@ def circular_std(data: np.ndarray) -> float: :width: 600 :align: center + .. seealso:: + :func:`simba.data_processors.cuda.circular_statistics.sliding_circular_std`, + :func:`simba.mixins.circular_statistics.CircularStatisticsMixin.sliding_circular_std` + .. math:: \\sigma_{\\text{circular}} = \\text{rad2deg}\\left(\\sqrt{-2 \\cdot \\log\\left(|\text{mean}(\\exp(j \\cdot \\theta))|\\right)}\\right) @@ -220,6 +242,11 @@ def sliding_circular_std( :width: 600 :align: center + .. seealso:: + :func:`simba.data_processors.cuda.circular_statistics.sliding_circular_std`, + :func:`simba.mixins.circular_statistics.CircularStatisticsMixin.circular_std` + + :parameter ndarray data: 1D array of size len(frames) representing degrees. :parameter np.ndarray time_window: Sliding time-window as float in seconds. :parameter int fps: fps of the recorded video @@ -386,9 +413,7 @@ def direction_three_bps(nose_loc: np.ndarray, left_ear_loc: np.ndarray, right_ea @staticmethod @njit("(float32[:, :], float32[:, :])") - def direction_two_bps( - anterior_loc: np.ndarray, posterior_loc: np.ndarray - ) -> np.ndarray: + def direction_two_bps(anterior_loc: np.ndarray, posterior_loc: np.ndarray) -> np.ndarray: """ Jitted method computing degree directionality from two body-parts. E.g., ``nape`` and ``nose``, or ``swim_bladder`` and ``tail``. @@ -397,6 +422,9 @@ def direction_two_bps( :width: 1200 :align: center + .. seealso:: + :func:`simba.data_processors.cuda.circular_statistics.direction_from_two_bps` + :parameter np.ndarray anterior_loc: Size len(frames) x 2 representing x and y coordinates for first body-part. :parameter np.ndarray posterior_loc: Size len(frames) x 2 representing x and y coordinates for second body-part. :return np.ndarray: Frame-wise directionality in degrees. @@ -422,6 +450,7 @@ def direction_two_bps( @staticmethod @njit("(float32[:],)") def rayleigh(data: np.ndarray) -> Tuple[float, float]: + """ Jitted compute of Rayleigh Z (test of non-uniformity) of single sample of circular data in degrees. @@ -440,6 +469,10 @@ def rayleigh(data: np.ndarray) -> Tuple[float, float]: .. math:: p = e^{\\sqrt{1 + 4n + 4(n^2 - R^2)} - (1 + 2n)} + .. seealso:: + :func:`simba.data_processors.cuda.circular_statistics.sliding_rayleigh_z`, + :func:`simba.mixins.circular_statistics.CircularStatisticsMixin.sliding_rayleigh_z` + :parameter ndarray data: 1D array of size len(frames) representing degrees. :returns Tuple[float, float]: Tuple with Rayleigh Z score and associated probability value. @@ -465,6 +498,10 @@ def sliding_rayleigh_z(data: np.ndarray, time_windows: np.ndarray, fps: int) -> .. note:: Adapted from ``pingouin.circular.circ_rayleigh`` and ``pycircstat.tests.rayleigh``. + .. seealso:: + :func:`simba.data_processors.cuda.circular_statistics.sliding_rayleigh_z`, + :func:`simba.mixins.circular_statistics.CircularStatisticsMixin.rayleigh` + :parameter ndarray data: 1D array of size len(frames) representing degrees. :parameter np.ndarray time_window: Rolling time-window as float in seconds. Two windows of 0.5s and 1s would be represented as np.array([0.5, 1.0]) :parameter int fps: fps of the recorded video @@ -869,6 +906,10 @@ def circular_range(data: np.ndarray) -> float: :width: 600 :align: center + .. seealso:: + :func:`simba.data_processors.cuda.circular_statistics.sliding_circular_range`, + :func:`simba.mixins.circular_statistics.CircularStatisticsMixin.sliding_circular_range` + :parameter ndarray data: 1D array of circular data measured in degrees :return np.ndarray: The circular range in degrees. @@ -899,6 +940,10 @@ def sliding_circular_range(data: np.ndarray, time_windows: np.ndarray, fps: int Output data in the beginning of the series where a full time-window is not satisfied (e.g., first 9 observations when fps equals 10 and time_windows = [1.0], will be populated by ``0``. + .. seealso:: + :func:`simba.data_processors.cuda.circular_statistics.sliding_circular_range`, + :func:`simba.mixins.circular_statistics.CircularStatisticsMixin.circular_range` + :parameter np.ndarray data: 1D array of circular data measured in degrees :parameter np.ndarray time_windows: Size of sliding time window in seconds. E.g., two windows of 0.5s and 1s would be represented as np.array([0.5, 1.0]) :parameter int fps: Frame-rate of recorded video. @@ -936,6 +981,10 @@ def circular_hotspots(data: np.ndarray, bins: np.ndarray) -> np.ndarray: bins = np.array([[270, 0], [1, 90], [91, 180], [181, 269]]) is accepted but bins = np.array([[270, 0], [0, 90], [90, 180], [180, 270]]) is not. + .. seealso:: + :func:`simba.data_processors.cuda.circular_statistics.sliding_circular_hotspots`, + :func:`simba.mixins.circular_statistics.CircularStatisticsMixin.sliding_circular_hotspots` + :parameter ndarray data: 1D array of circular data measured in degrees. :parameter ndarray bins: 2D array of shape representing circular bins defining [start_degree, end_degree] inclusive. :return np.ndarray: 1D array containing the proportion of data points that fall within each specified circular bin. @@ -997,6 +1046,10 @@ def sliding_circular_hotspots( :width: 600 :align: center + .. seealso:: + :func:`simba.data_processors.cuda.circular_statistics.circular_hotspots`, + :func:`simba.mixins.circular_statistics.CircularStatisticsMixin.sliding_circular_hotspots` + :example: >>> data = np.array([270, 360, 10, 20, 90, 91, 180, 185, 260, 265]).astype(np.float32) >>> bins = np.array([[270, 90], [91, 269]]) diff --git a/simba/mixins/feature_extraction_mixin.py b/simba/mixins/feature_extraction_mixin.py index b96b8f866..f8dc94e41 100644 --- a/simba/mixins/feature_extraction_mixin.py +++ b/simba/mixins/feature_extraction_mixin.py @@ -155,9 +155,14 @@ def convex_hull_calculator_mp(arr: np.ndarray, px_per_mm: float) -> float: """ Calculate single frame convex hull perimeter length in millimeters. + .. note:: + For acceptable run-time, call using parallel processing. + .. seealso:: - For acceptable run-time, call using ``parallel.delayed``. - For large data, use :meth:`simba.feature_extractors.perimeter_jit.jitted_hull` which returns perimiter length OR area. + + For large data, consider :func:`~simba.feature_extractors.perimeter_jit.jitted_hull`, + :func:`~simba.data_processors.cuda.geometry.get_convex_hull`, + :func:`~ simba.mixins.geometry_mixin.GeometryMixin.bodyparts_to_polygon`. .. image:: _static/img/framewise_perim_length.png :width: 300 @@ -196,9 +201,13 @@ def count_values_in_range(data: np.ndarray, ranges: np.ndarray) -> np.ndarray: :width: 300 :align: center + .. seealso:: + :func:`simba.data_processors.cuda.statistics.count_values_in_ranges` + :parameter np.ndarray data: 2D numpy array with frames on X. :parameter np.ndarray ranges: 2D numpy array representing the brackets. E.g., [[0, 0.1], [0.1, 0.5]] - :return np.ndarray: 2D numpy array of size data.shape[0], ranges.shape[1] + :return: 2D numpy array of size data.shape[0], ranges.shape[1] + :rtype: np.ndarray :example: >>> FeatureExtractionMixin.count_values_in_range(data=np.random.random((3,10)), ranges=np.array([[0.0, 0.25], [0.25, 0.5]])) @@ -255,9 +264,7 @@ def framewise_euclidean_distance_roi( @staticmethod @jit(nopython=True) - def framewise_inside_rectangle_roi( - bp_location: np.ndarray, roi_coords: np.ndarray - ) -> np.ndarray: + def framewise_inside_rectangle_roi(bp_location: np.ndarray, roi_coords: np.ndarray) -> np.ndarray: """ Jitted helper for frame-wise analysis if animal is inside static rectangular ROI. @@ -265,6 +272,9 @@ def framewise_inside_rectangle_roi( :width: 300 :align: center + .. seealso:: + :func:`simba.data_processors.cuda.geometry.is_inside_rectangle` + :parameter np.ndarray bp_location: 2d numeric np.ndarray size len(frames) x 2 :parameter np.ndarray roi_coords: 2d numeric np.ndarray size 2x2 (top left[x, y], bottom right[x, y]) :return ndarray: 2d numeric boolean np.ndarray size len(frames) x 1 with 0 representing outside the rectangle and 1 representing inside the rectangle @@ -303,6 +313,9 @@ def framewise_inside_polygon_roi(bp_location: np.ndarray, roi_coords: np.ndarray :width: 300 :align: center + .. seealso:: + :func:`simba.data_processors.cuda.geometry.is_inside_polygon` + :parameter np.ndarray bp_location: 2d numeric np.ndarray size len(frames) x 2 :parameter np.ndarray roi_coords: 2d numeric np.ndarray size len(polygon points) x 2 @@ -743,12 +756,17 @@ def framewise_euclidean_distance( :width: 300 :align: center + .. seealso:: + :func:`simba.data_processors.cuda.statistics.get_euclidean_distance_cupy`, + :func:`simba.data_processors.cuda.statistics.get_euclidean_distance_cuda` + + :parameter ndarray location_1: 2D array of size len(frames) x 2. :parameter ndarray location_1: 2D array of size len(frames) x 2. :parameter float px_per_mm: The pixels per millimeter in the video. :parameter bool centimeter: If true, the value in centimeters is returned. Else the value in millimeters. - - :return np.ndarray: 1D array of size location_1.shape[0] + :return: 1D array of size location_1.shape[0] + :rtype: np.ndarray. :example: >>> loc_1 = np.random.randint(1, 200, size=(6, 2)).astype(np.float32) @@ -756,8 +774,7 @@ def framewise_euclidean_distance( >>> FeatureExtractionMixin.framewise_euclidean_distance(location_1=loc_1, location_2=loc_2, px_per_mm=4.56, centimeter=False) >>> [49.80098657, 46.54963644, 49.60650394, 70.35919993, 37.91069901, 71.95422524] """ - # if not px_per_mm and centimeter: - # raise InvalidInputError(msg='To calculate centimeters, provide a pixel per millimeter value') + results = np.full((location_1.shape[0]), np.nan) for i in prange(location_1.shape[0]): results[i] = np.linalg.norm(location_1[i] - location_2[i]) / px_per_mm diff --git a/simba/mixins/statistics_mixin.py b/simba/mixins/statistics_mixin.py index 24aaed850..b6d0eef1d 100644 --- a/simba/mixins/statistics_mixin.py +++ b/simba/mixins/statistics_mixin.py @@ -14,8 +14,7 @@ from typing_extensions import Literal import numpy as np -from numba import (bool_, float32, float64, int8, jit, njit, objmode, optional, - prange, typed, types) +from numba import (bool_, float32, float64, int8, jit, njit, prange, typed, types) from scipy import stats from scipy.stats.distributions import chi2 from sklearn.covariance import EllipticEnvelope @@ -1364,6 +1363,10 @@ def spearman_rank_correlation(sample_1: np.ndarray, sample_2: np.ndarray) -> flo # - \( d_i \) is the difference between the ranks of corresponding elements in sample_1 and sample_2. # - \( n \) is the number of observations. + .. seealso:: + :func:`simba.data_processors.cuda.statistics.sliding_spearman_rank_correlation`, + :func:`simba.mixins.statistics.StatisticsMixin.sliding_spearman_rank_correlation` + :param np.ndarray sample_1: First 1D array containing feature values. :param np.ndarray sample_2: Second 1D array containing feature values. :return float: Spearman's rank correlation coefficient. From ad5a4292c8af84e9b1e44fe893dd426928cdf7a7 Mon Sep 17 00:00:00 2001 From: sronilsson Date: Sun, 1 Sep 2024 19:40:35 +0000 Subject: [PATCH 3/5] cleaned --- simba/mixins/statistics_mixin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/simba/mixins/statistics_mixin.py b/simba/mixins/statistics_mixin.py index b6d0eef1d..5f86077e2 100644 --- a/simba/mixins/statistics_mixin.py +++ b/simba/mixins/statistics_mixin.py @@ -14,7 +14,8 @@ from typing_extensions import Literal import numpy as np -from numba import (bool_, float32, float64, int8, jit, njit, prange, typed, types) +from numba import (bool_, float32, float64, int8, jit, njit, prange, typed, + types) from scipy import stats from scipy.stats.distributions import chi2 from sklearn.covariance import EllipticEnvelope From e2f29b8884da4167a3d8ed8c17886a9735273cb5 Mon Sep 17 00:00:00 2001 From: simon Date: Sun, 1 Sep 2024 15:59:45 -0400 Subject: [PATCH 4/5] cuda docs --- README.md | 2 +- docs/environment.yml | 2 ++ docs/index.rst | 20 ++++++++++++++++---- docs/project_description.md | 5 +++-- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 461f8d0a0..ea1981982 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ [![Downloads](https://pepy.tech/badge/simba-uw-tf-dev/month)](https://pepy.tech/project/simba-uw-tf-dev) [![Downloads](https://pepy.tech/badge/simba-uw-tf-dev)](https://pepy.tech/project/simba-uw-tf-dev) - +**Manuscript: [Simple Behavioral Analysis (SimBA) as a platform for explainable machine learning in behavioral neuroscience](https://www.nature.com/articles/s41593-024-01649-9)** **Pre-print: [Simple Behavioral Analysis (SimBA) – an open source toolkit for computer classification of complex social behaviors in experimental animals](https://www.biorxiv.org/content/10.1101/2020.04.19.049452v2)**

diff --git a/docs/environment.yml b/docs/environment.yml index 58f983459..e48c4a4f5 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -15,6 +15,8 @@ dependencies: - sphinx-togglebutton==0.3.2 - sphinx_rtd_theme - simba-uw-tf-dev + - sphinx-mathjax-offline + - sphinxemoji - nbsphinx - umap-learn==0.5.2 - pyvis==0.3.2 diff --git a/docs/index.rst b/docs/index.rst index 2ad2c4931..2148a5fb5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,19 +7,31 @@ Welcome to SimBA's documentation! :align: center + These docs are under development. For detailed tutorials, code, and more extensive documentation see the `SimBA GitHub repository `_. For support, see `SimBA Gitter channel `_. -`Pre-print `_ - -**Installation** - +INSTALLATION +------------------------ .. code:: bash pip install simba-uw-tf-dev + +MORE INFORMATION +------------------------ +See below for raison d'être, detailed API, tutorials, data, documentation, support, and walkthroughs: + +- GitHub: [https://github.com/sgoldenlab/simba](https://github.com/sgoldenlab/simba) +- API: [https://simba-uw-tf-dev.readthedocs.io/en/latest/api.html](https://simba-uw-tf-dev.readthedocs.io/en/latest/api.html) +- Gitter Chat: [https://app.gitter.im/#/room/#SimBA-Resource_community:gitter.im](https://app.gitter.im/#/room/#SimBA-Resource_community:gitter.im) +- bioRxiv preprint: [https://www.biorxiv.org/content/10.1101/2020.04.19.049452v2](https://www.biorxiv.org/content/10.1101/2020.04.19.049452v2) +- Nature Neuroscience paper: [https://www.nature.com/articles/s41593-024-01649-9](https://www.nature.com/articles/s41593-024-01649-9) +- Open Science Framework (OSF) data buckets: [https://osf.io/tmu6y/](https://osf.io/tmu6y/) +- Python Package Index (PyPI): [https://pypi.org/project/Simba-UW-tf-dev/](https://pypi.org/project/Simba-UW-tf-dev/) + .. toctree:: :maxdepth: 3 :caption: API REFERENCE: diff --git a/docs/project_description.md b/docs/project_description.md index 16f99a646..061456739 100644 --- a/docs/project_description.md +++ b/docs/project_description.md @@ -10,8 +10,9 @@ See below for raison d'être, detailed API, tutorials, data, documentation, supp - Documentation readthedocs: [https://simba-uw-tf-dev.readthedocs.io/en/latest/](https://simba-uw-tf-dev.readthedocs.io/en/latest/) - API: [https://simba-uw-tf-dev.readthedocs.io/en/latest/api.html](https://simba-uw-tf-dev.readthedocs.io/en/latest/api.html) - Gitter Chat: [https://app.gitter.im/#/room/#SimBA-Resource_community:gitter.im](https://app.gitter.im/#/room/#SimBA-Resource_community:gitter.im) -- biorxiv preprint: [https://www.biorxiv.org/content/10.1101/2020.04.19.049452v2](https://www.biorxiv.org/content/10.1101/2020.04.19.049452v2) -- OSF: [https://osf.io/tmu6y/](https://osf.io/tmu6y/) +- bioRxiv preprint: [https://www.biorxiv.org/content/10.1101/2020.04.19.049452v2](https://www.biorxiv.org/content/10.1101/2020.04.19.049452v2) +- Nature Neuroscience paper: [https://www.nature.com/articles/s41593-024-01649-9](https://www.nature.com/articles/s41593-024-01649-9) +- Open Science Framework (OSF) data buckets: [https://osf.io/tmu6y/](https://osf.io/tmu6y/) ### Installation To install SimBA, use the following command: From 033a6cdba9f1a9940f6d02e834dd5dce6d10ed02 Mon Sep 17 00:00:00 2001 From: simon Date: Sun, 1 Sep 2024 16:16:52 -0400 Subject: [PATCH 5/5] cuda docs --- docs/index.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 2148a5fb5..8618f8c46 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -24,13 +24,13 @@ MORE INFORMATION ------------------------ See below for raison d'être, detailed API, tutorials, data, documentation, support, and walkthroughs: -- GitHub: [https://github.com/sgoldenlab/simba](https://github.com/sgoldenlab/simba) -- API: [https://simba-uw-tf-dev.readthedocs.io/en/latest/api.html](https://simba-uw-tf-dev.readthedocs.io/en/latest/api.html) -- Gitter Chat: [https://app.gitter.im/#/room/#SimBA-Resource_community:gitter.im](https://app.gitter.im/#/room/#SimBA-Resource_community:gitter.im) -- bioRxiv preprint: [https://www.biorxiv.org/content/10.1101/2020.04.19.049452v2](https://www.biorxiv.org/content/10.1101/2020.04.19.049452v2) -- Nature Neuroscience paper: [https://www.nature.com/articles/s41593-024-01649-9](https://www.nature.com/articles/s41593-024-01649-9) -- Open Science Framework (OSF) data buckets: [https://osf.io/tmu6y/](https://osf.io/tmu6y/) -- Python Package Index (PyPI): [https://pypi.org/project/Simba-UW-tf-dev/](https://pypi.org/project/Simba-UW-tf-dev/) +- GitHub: `https://github.com/sgoldenlab/simba `_ +- API: `https://simba-uw-tf-dev.readthedocs.io/en/latest/api.html `_ +- Gitter Chat: `https://app.gitter.im/#/room/#SimBA-Resource_community:gitter.im `_ +- bioRxiv preprint: `https://www.biorxiv.org/content/10.1101/2020.04.19.049452v2 `_ +- Nature Neuroscience paper: `https://www.nature.com/articles/s41593-024-01649-9 `_ +- Open Science Framework (OSF) data buckets: `https://osf.io/tmu6y/ `_ +- Python Package Index (PyPI): `https://pypi.org/project/Simba-UW-tf-dev/ `_ .. toctree:: :maxdepth: 3