Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
sronilsson committed Oct 19, 2024
2 parents b9aebfa + c5c8196 commit ae1e470
Show file tree
Hide file tree
Showing 11 changed files with 92 additions and 60 deletions.
7 changes: 7 additions & 0 deletions docs/FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,13 @@ Then try to relaunch simba by typing `simba`.

</details>

### 28. I'm on a mac M2 computer and SimBA won't install properly!

<details>
<summary>Show solutions!</summary>
<br/><br/>

Sorry - I don't have a straightforward installation process for M2's. But [THIS](https://github.com/sgoldenlab/simba/issues/372#issuecomment-2313396518) procedure is reported to work.



Expand Down
Binary file added docs/_static/img/flip_videos.webm
Binary file not shown.
3 changes: 0 additions & 3 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ Welcome to SimBA's documentation!
:width: 1000
:align: center




These docs are under development. For detailed tutorials, code, and more extensive documentation
see the `SimBA GitHub repository <https://github.com/sgoldenlab/simba>`_.

Expand Down
Binary file added docs/tutorials_rst/img/index/landing_1.webm
Binary file not shown.
1 change: 0 additions & 1 deletion simba/SimBA.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from tkinter.messagebox import askyesno

import PIL.Image
from PIL import ImageTk

from simba.bounding_box_tools.boundary_menus import BoundaryMenus
from simba.cue_light_tools.cue_light_menues import CueLightAnalyzerMenu
Expand Down
6 changes: 3 additions & 3 deletions simba/data_processors/cuda/circular_statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ def _instantaneous_angular_velocity(x, stride, results):


def instantaneous_angular_velocity(x: np.ndarray, stride: Optional[int] = 1) -> np.ndarray:
"""
r"""
Calculate the instantaneous angular velocity between angles in a given array.
This function uses CUDA to perform parallel computations on the GPU.
Expand Down Expand Up @@ -650,8 +650,8 @@ def sliding_angular_diff(x: np.ndarray,
\text{difference} = \pi - |\pi - |a_1 - a_2||
Where:
- \( a_1 \) is the angle at position `x`.
- \( a_2 \) is the angle at position `x - \text{stride}`.
- :math:`a_1` is the angle at position `x`.
- :math:`a_2` is the angle at position `x - \text{stride}`.
:param np.ndarray x: 1D array of angles in degrees.
:param np.ndarray time_windows: 1D array of time windows in seconds to determine the stride (distance in frames) between angles.
Expand Down
2 changes: 1 addition & 1 deletion simba/data_processors/cuda/statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ def dunn_index(x: np.ndarray, y: np.ndarray) -> float:
.. math::
Dunn\ Index = \\frac{\\min_{i \\neq j} \\delta(c_i, c_j)}{\\max_k \\Delta(c_k)}
Dunn\ Index = \frac{\\min_{i \\neq j} \\delta(c_i, c_j)}{\\max_k \\Delta(c_k)}
Where:
- :math:`\\delta(c_i, c_j)` is the distance between clusters :math:`c_i` and :math:`c_j`.
Expand Down
78 changes: 50 additions & 28 deletions simba/mixins/circular_statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def mean_resultant_vector_length(data: np.ndarray) -> float:
R = \\frac{{\\sqrt{{\\sum_{{i=1}}^N \\cos(\\theta_i - \\bar{\theta})^2 + \\sum_{{i=1}}^N \\sin(\theta_i - \\bar{\\theta})^2}}}}{{N}}
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.
where :math:`N` is the number of data points, :math:`\\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`,
Expand Down Expand Up @@ -131,16 +131,19 @@ def sliding_mean_resultant_vector_length(data: np.ndarray, fps: float, time_wind
@staticmethod
@njit("(float32[:],)")
def circular_mean(data: np.ndarray) -> float:
"""

r"""
Jitted compute of the circular mean of single sample.
.. math::
\mu = \text{atan2}\left(\frac{1}{N} \sum_{i=1}^{N} \sin(\theta_i), \frac{1}{N} \sum_{i=1}^{N} \cos(\theta_i)\right)
\\mu = \\text{atan2}\\left(\\frac{1}{N} \\sum_{i=1}^{N} \\sin(\\theta_i), \\frac{1}{N} \\sum_{i=1}^{N} \\cos(\\theta_i)\\right)
Where:
- :math:`\mu` is the circular mean in degrees.
- :math:`\theta_i` are the individual angles in degrees.
- :math:`N` is the number of samples.
- :math:`\\theta_i` are the angles in radians within the sliding window
- :math:`N` is the number of samples in the window
:param np.ndarray data: 1D array of size len(frames) representing angles in degrees.
:returns: The circular mean of the angles in degrees.
Expand Down Expand Up @@ -226,10 +229,10 @@ def circular_std(data: np.ndarray) -> float:
\\sigma_{\\text{circular}} = \\text{rad2deg}\\left(\\sqrt{-2 \\cdot \\log\\left(|\text{mean}(\\exp(j \\cdot \\theta))|\\right)}\\right)
where \\(\\theta\\) represents the angles in radians
where :math:`\\theta` represents the angles in radians
:parameter ndarray data: 1D array of size len(frames) with angles in degrees
:param ndarray data: 1D array of size len(frames) with angles in degrees
:returns: The standard deviation of the data sample in degrees.
:rtype: float
Expand All @@ -249,7 +252,8 @@ def circular_std(data: np.ndarray) -> float:
def sliding_circular_std(
data: np.ndarray, fps: int, time_windows: np.ndarray
) -> np.ndarray:
"""

r"""
Compute standard deviation of angular data in sliding time windows.
.. image:: _static/img/angle_stdev.png
Expand All @@ -261,9 +265,9 @@ def 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
:param ndarray data: 1D array of size len(frames) representing degrees.
:param np.ndarray time_window: Sliding time-window as float in seconds.
:param int fps: fps of the recorded video
:returns: Size data.shape[0] x time_windows.shape[0] with angular standard deviations in rolling time windows in degrees.
:rtype: np.ndarray
Expand Down Expand Up @@ -564,11 +568,21 @@ def sliding_rayleigh_z(data: np.ndarray, time_windows: np.ndarray, fps: int) ->
@staticmethod
@njit("(float32[:], float32[:],)")
def circular_correlation(sample_1: np.ndarray, sample_2: np.ndarray) -> float:
"""

r"""
Jitted compute of circular correlation coefficient of two samples using the cross-correlation coefficient.
Ranges from -1 to 1: 1 indicates perfect positive correlation, -1 indicates perfect negative correlation, 0
indicates no correlation.
.. math::
R = \\frac{\\sum \\sin(\\theta_1 - \\bar{\\theta}_1) \\sin(\\theta_2 - \\bar{\\theta}_2)}
{\\sqrt{\\sum \\sin^2(\\theta_1 - \\bar{\\theta}_1) \\sum \\sin^2(\\theta_2 - \\bar{\\theta}_2)}}
where:
- :math:`\\theta_1` and :math:`\\theta_2` are the angles (in radians) from `sample_1` and `sample_2`, respectively.
- :math:`\\bar{\\theta}_1` and :math:`\\bar{\\theta}_2` are the mean directions of `sample_1` and `sample_2`, respectively.
.. note::
Adapted from ``astropy.stats.circstats.circcorrcoef``.
Expand All @@ -579,9 +593,10 @@ def circular_correlation(sample_1: np.ndarray, sample_2: np.ndarray) -> float:
.. seealso:
:func:`simba.mixins.circular_statistics.CircularStatisticsMixin.sliding_circular_correlation`
:parameter np.ndarray sample_1: Angular data for e.g., Animal 1
:parameter np.ndarray sample_1: Angular data for e.g., Animal 2
:parameter float circular_correlation: The correlation between the two distributions.
:param np.ndarray sample_1: Angular data for e.g., Animal 1
:param np.ndarray sample_1: Angular data for e.g., Animal 2
:return: The correlation between the two distributions.
:rtype: float
:example:
>>> sample_1 = np.array([50, 90, 20, 60, 20, 90]).astype(np.float32)
Expand All @@ -594,15 +609,13 @@ def circular_correlation(sample_1: np.ndarray, sample_2: np.ndarray) -> float:
m1 = np.arctan2(np.mean(np.sin(sample_1)), np.mean(np.cos(sample_1)))
m2 = np.arctan2(np.mean(np.sin(sample_2)), np.mean(np.cos(sample_2)))
sin_1, sin_2 = np.sin(sample_1 - m1), np.sin(sample_2 - m2)
return np.abs(
np.sum(sin_1 * sin_2)
/ np.sqrt(np.sum(sin_1 * sin_1) * np.sum(sin_2 * sin_2))
)
return np.abs(np.sum(sin_1 * sin_2) / np.sqrt(np.sum(sin_1 * sin_1) * np.sum(sin_2 * sin_2)))

@staticmethod
@njit("(float32[:], float32[:], float64[:], int64)")
def sliding_circular_correlation(sample_1: np.ndarray, sample_2: np.ndarray, time_windows: np.ndarray, fps: float) -> np.ndarray:
"""

r"""
Jitted compute of correlations between two angular distributions in sliding time-windows
using the cross-correlation coefficient.
Expand Down Expand Up @@ -803,9 +816,9 @@ def sliding_rao_spacing(data: np.ndarray, time_windows: np.ndarray, fps: int) ->
"""
Jitted compute of the uniformity of a circular dataset in sliding windows.
:parameter ndarray data: 1D array of size len(frames) representing degrees.
:parameter np.ndarray time_window: Rolling time-window as float in seconds.
:parameter int fps: fps of the recorded video
:param ndarray data: 1D array of size len(frames) representing degrees.
:param np.ndarray time_window: Rolling time-window as float in seconds.
:param int fps: fps of the recorded video
:return np.ndarray: representing rao-spacing U in every sliding windows [-window:n]
.. image:: _static/img/raospacing.png
Expand Down Expand Up @@ -868,7 +881,7 @@ def kuipers_two_sample_test(sample_1: np.ndarray, sample_2: np.ndarray) -> float
Where:
- :math:`F_1(\theta)` and :math:`F_2(\theta)` are the empirical cumulative distribution functions (CDFs) of the two circular samples.
- :math:`\theta` are the sorted angles in the two samples.
- :math:`\\theta` are the sorted angles in the two samples.
.. note::
Adapted from `Kuiper <https://github.com/aarchiba/kuiper/tree/master>`__ by `Anne Archibald <https://github.com/aarchiba>`_.
Expand Down Expand Up @@ -979,20 +992,29 @@ def watsons_u(data: np.ndarray):
@staticmethod
@njit("(float32[:],)")
def circular_range(data: np.ndarray) -> float:
"""
r"""
Jitted compute of circular range in degrees. The range is defined as the angular span of the
shortest arc that can contain all the data points. A smaller range indicates a more concentrated
distribution, while a larger range suggests a more dispersed distribution.
.. math::
\text{Range} = \min \left( 2\pi - \max(\delta \theta_i), \theta_{\text{max}} - \theta_{\text{min}} \right)
where:
- :math:`\delta \theta_i` is the difference between consecutive angular data points.
- :math:`\theta_{\text{max}}` and :math:`\theta_{\text{min}}` are the maximum and minimum angles in the data.
.. image:: _static/img/circular_range.png
:width: 600
:width: 400
: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
:param ndarray data: 1D array of circular data measured in degrees
:return: The circular range in degrees.
:rtype: np.ndarray
Expand Down
13 changes: 7 additions & 6 deletions simba/mixins/statistics_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ def independent_samples_t(
sample_2: np.ndarray,
critical_values: Optional[np.ndarray] = None,
) -> (float, Union[None, bool]):
"""

r"""
Jitted compute independent-samples t-test statistic and boolean significance between two distributions.
.. note::
Expand Down Expand Up @@ -3781,10 +3782,10 @@ def adjusted_rand(x: np.ndarray, y: np.ndarray) -> float:
ARI = \\frac{TP + TN}{TP + FP + FN + TN}
where:
- TP (True Positive) is the number of pairs of elements that are in the same cluster in both x and y,
- FP (False Positive) is the number of pairs of elements that are in the same cluster in y but not in x,
- FN (False Negative) is the number of pairs of elements that are in the same cluster in x but not in y,
- TN (True Negative) is the number of pairs of elements that are in different clusters in both x and y.
- :math:`TP` (True Positive) is the number of pairs of elements that are in the same cluster in both x and y,
- :math:`FP` (False Positive) is the number of pairs of elements that are in the same cluster in y but not in x,
- :math:`FN` (False Negative) is the number of pairs of elements that are in the same cluster in x but not in y,
- :math:`TN` (True Negative) is the number of pairs of elements that are in different clusters in both x and y.
The ARI value ranges from -1 to 1. A value of 1 indicates perfect clustering agreement, 0 indicates random clustering, and negative values indicate disagreement between the clusterings.
Expand Down Expand Up @@ -4052,7 +4053,7 @@ def wave_hedges_distance(self, x: np.ndarray, y: np.ndarray) -> float:
:example:
>>> x = np.random.randint(0, 500, (1000,))
>>> y = np.random.randint(0, 500, (1000,))
>>> wave_hedges_distance(x=x, y=y)
>>> Statistics().wave_hedges_distance(x=x, y=y)
"""

check_valid_array(data=x, source=f'{Statistics.wave_hedges_distance.__name__} x', accepted_ndims=(1,), accepted_dtypes=Formats.NUMERIC_DTYPES.value)
Expand Down
36 changes: 21 additions & 15 deletions simba/mixins/timeseries_features_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -701,8 +701,8 @@ def permutation_entropy(data: np.ndarray, dimension: int, delay: int) -> float:
PE = - \\sum(p_i \\log(p_i))
where:
- PE is the permutation entropy.
- p_i is the probability of each unique order pattern.
:math:`PE` is the permutation entropy.
:math:`p_i` is the probability of each unique order pattern.
:param numpy.ndarray data: The time series data for which permutation entropy is calculated.
:param int dimension: It specifies the length of the order patterns to be considered.
Expand Down Expand Up @@ -762,9 +762,9 @@ def line_length(data: np.ndarray) -> float:
LL = \sum_{i=1}^{N-1} |x[i] - x[i-1]|
where:
- LL is the line length.
- N is the number of elements in the input data array.
- x[i] represents the value of the data at index i.
:math:`LL` is the line length.
:math:`N` is the number of elements in the input data array.
:math:`x[i]` represents the value of the data at index i.
.. image:: _static/img/line_length.png
Expand Down Expand Up @@ -882,12 +882,20 @@ def sliding_descriptive_statistics(data: np.ndarray, window_sizes: np.ndarray, s
:return np.ndarray: Array containing the selected descriptive statistics for each window size, data point, and statistic type. The shape of the result array is (len(statistics), data.shape[0], window_sizes.shape[0).
.. note::
- The `statistics` parameter should be a list containing one or more of the following statistics:
'var' (variance), 'max' (maximum), 'min' (minimum), 'std' (standard deviation), 'median' (median),
'mean' (mean), 'mad' (median absolute deviation), 'sum' (sum), 'mac' (mean absolute change),
'rms' (root mean square), 'absenergy' (absolute energy).
- If the statistics list is ['var', 'max', 'mean'], the
3rd dimension order in the result array will be: [variance, maximum, mean]
The `statistics` parameter should be a list containing one or more of the following statistics:
* 'var' (variance)
* 'max' (maximum)
* 'min' (minimum)
* 'std' (standard deviation)
* 'median' (median)
* 'mean' (mean)
* 'mad' (median absolute deviation)
* 'sum' (sum)
* 'mac' (mean absolute change)
* 'rms' (root mean square)
* 'absenergy' (absolute energy)
E.g., If the statistics list is ['var', 'max', 'mean'], the 3rd dimension order in the result array will be: [variance, maximum, mean]
:example:
>>> data = np.array([1, 4, 2, 3, 5, 6, 8, 7, 9, 10]).astype(np.float32)
Expand Down Expand Up @@ -1792,7 +1800,7 @@ def sliding_pct_in_top_n(
Compute the percentage of elements in the top 'n' frequencies in sliding windows of the input array.
.. note::
To compute percentage of elements in the top 'n' frequencies in entire array, use ``simba.mixins.statistics_mixin.Statistics.pct_in_top_n``.
To compute percentage of elements in the top 'n' frequencies in entire array, use :func:`simba.mixins.statistics_mixin.Statistics.pct_in_top_n()`.
:param np.ndarray x: Input 1D array.
:param np.ndarray windows: Array of window sizes in seconds.
Expand Down Expand Up @@ -1831,9 +1839,7 @@ def sliding_pct_in_top_n(
results = np.full((x.shape[0], windows.shape[0]), -1.0)
for i in range(windows.shape[0]):
W_s = int(windows[i] * fps)
for cnt, (l, r) in enumerate(
zip(range(0, x.shape[0] + 1), range(W_s, x.shape[0] + 1))
):
for cnt, (l, r) in enumerate(zip(range(0, x.shape[0] + 1), range(W_s, x.shape[0] + 1))):
sample = x[l:r]
cnts = np.sort(np.unique(sample, return_counts=True)[1])[-n:]
results[int(r - 1), i] = np.sum(cnts) / sample.shape[0]
Expand Down
6 changes: 3 additions & 3 deletions simba/video_processors/video_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def convert_to_jpeg(path: Union[str, os.PathLike, List[Union[str, os.PathLike]]]
Quality above 95 should be avoided; 100 disables portions of the JPEG compression algorithm, and results in large files with hardly any gain in image quality
.. image:: _static/img/convert_to_jpeg.jpeg
:width: 600
:width: 400
:align: center
:parameter Union[str, os.PathLike] directory: Path to directory holding image files, a single image file, or a list of paths to image files.
Expand Down Expand Up @@ -323,7 +323,7 @@ def convert_to_webp(path: Union[str, os.PathLike],
Convert the file type of all image files within a directory to WEBP format of passed quality.
.. image:: _static/img/convert_to_webp.webp
:width: 600
:width: 400
:align: center
:parameter Union[str, os.PathLike] directory: Path to directory holding image files
Expand Down Expand Up @@ -4259,7 +4259,7 @@ def flip_videos(video_path: Union[str, os.PathLike],
Flip a video or directory of videos horizontally, vertically, or both, and save them to the specified directory.
.. video:: _static/img/flip_videos.webm
:width: 900
:width: 700
:loop:
:autoplay:
Expand Down

0 comments on commit ae1e470

Please sign in to comment.