Geometry transformations๏ƒ

Geometry mixin๏ƒ

class simba.mixins.geometry_mixin.GeometryMixin[source]๏ƒ

Methods to perform geometry transformation of pose-estimation data. This includes creating bounding boxes, line objects, circles etc. from pose-estimated body-parts and computing metric representations of the relationships between created shapes or their attributes (sizes, distances etc.).

Relies heavily on shapley.

Note

These methods, generally, do not involve visualizations - they mainly generate geometry data-objects or metrics. To create visualizations with geometries overlay on videos, pass returned shapes to simba.plotting.geometry_plotter.GeometryPlotter().

static adjust_geometry_locations(geometries, shift, pixels_per_mm=None, minimum=(0, 0), maximum=(inf, inf))[source]๏ƒ

Shift a set of geometries specified distance in the x and/or y-axis.

Adjust geometry locations
Parameters
  • geometries (List[Polygon]) โ€“ List of shapely.geometry.Polygon objects which locations are to be adjusted.

  • shift (Tuple[int, int]) โ€“ Tuple specifying the shift distances in the x and y-axis. Interpreted as pixels if pixels_per_mm is None. Else interpreted as millimeter.

  • pixels_per_mm (float) โ€“ Pixel per millimeter conversion factor.

  • minimum (Optional[Tuple[int, int]]) โ€“ Minimim allowed coordinates of Polygon points on x and y axes. Default: (0,0).

  • maximum (Optional[Tuple[int, int]]) โ€“ Maximum allowed coordinates of Polygon points on x and y axes. Default: (np.inf, np.inf).

Return List[Polygon]

List of adjusted polygons.

Example

>>> shapes = GeometryMixin().adjust_geometry_locations(geometries=shapes, shift=(0, 333))
static area(shape, pixels_per_mm)[source]๏ƒ

Calculate the area of a geometry in square millimeters.

Note

If certain that the input data is a valid Polygon, consider using simba.feature_extractors.perimeter_jit.jitted_hull() or simba.data_processors.cuda.geometry.poly_area() for numba jit and CUDA acceleration, respectively.

Parameters
  • shape (Union[MultiPolygon, Polygon]) โ€“ The geometry (MultiPolygon or Polygon) for which to calculate the area.

  • pixels_per_mm (float) โ€“ The pixel-to-millimeter conversion factor.

Returns

The area of the geometry in square millimeters.

Return type

float

Example

>>> polygon = GeometryMixin().bodyparts_to_polygon(np.array([[10, 10], [10, 100], [100, 10], [100, 100]]))
>>> GeometryMixin().area(shape=polygon[0], pixels_per_mm=4.9)
>>> 1701.556313816644
static bodyparts_to_circle(data, parallel_offset=1, pixels_per_mm=1, verbose=True)[source]๏ƒ

Create circle geometries from body-part (x,y) coordinates.

Creates circular polygons around body-part coordinates. Can handle single coordinates (1D array) or multiple coordinates (2D array). The radius is calculated by dividing parallel_offset by pixels_per_mm.

Bodyparts to circle
Parameters
  • data (np.ndarray) โ€“ Body-part coordinate(s) as 1D array [x, y] or 2D array [[x1, y1], [x2, y2], โ€ฆ]. E.g., np.array([364, 308]) or np.array([[364, 308], [100, 200]]).

  • parallel_offset (float) โ€“ The radius of the resultant circle(s). Default: 1.

  • pixels_per_mm (float) โ€“ The pixels per millimeter conversion factor. If 1, radius is in pixels. Default: 1.

Returns

Single Shapely Polygon for 1D input, or List[Polygon] for 2D input.

Return type

Union[Polygon, List[Polygon]]

Example

>>> # Single coordinate
>>> data = np.array([364, 308])
>>> polygon = GeometryMixin.bodyparts_to_circle(data=data, parallel_offset=10, pixels_per_mm=4)
>>> # Multiple coordinates
>>> data = np.array([[364, 308], [100, 200]])
>>> polygons = GeometryMixin.bodyparts_to_circle(data=data, parallel_offset=10, pixels_per_mm=4)
static bodyparts_to_line(data, buffer=None, px_per_mm=None)[source]๏ƒ

Convert body-part coordinates to a Linestring.

Note

If buffer and px_per_mm is provided, then the returned object will be linestring buffered to a 2D object rectangle with specificed area.

Bodyparts to line
Parameters
  • data (np.ndarray) โ€“ 2D dataframe representing the locations of the body-parts in a single image.

  • buffer (Optional[int]) โ€“ Optional buffer for the linestring in millimeters. Default None.

  • px_per_mm (Optional[float]) โ€“ Optional conversion factor. Pass if buffer is passed.

Returns

Return type

Union[Polygon, LineString]

Example

>>> data = np.array([[364, 308],[383, 323], [403, 335],[423, 351]])
>>> line = GeometryMixin().bodyparts_to_line(data=data)
>>> GeometryMixin().bodyparts_to_line(data=data, buffer=10, px_per_mm=4)
static bodyparts_to_multistring_skeleton(data)[source]๏ƒ

Create a multistring skeleton from a 3d array where each 2d array represents start and end coordinates of a linewithin the skeleton.

Parameters

data (np.ndarray) โ€“ A 3D numpy array where each 2D array represents the start position and end position of each LineString.

Returns

Shapely MultiLineString representing animal skeleton.

Return type

MultiLineString

Bodyparts to multistring skeleton
Example

>>> skeleton = np.array([[[5, 5], [1, 10]], [[5, 5], [9, 10]], [[9, 10], [1, 10]], [[9, 10], [9, 25]], [[1, 10], [1, 25]], [[9, 25], [5, 50]], [[1, 25], [5, 50]]])
>>> shape_multistring = GeometryMixin().bodyparts_to_multistring_skeleton(data=skeleton)
static bodyparts_to_points(data, buffer=None, px_per_mm=None)[source]๏ƒ

Convert body-parts coordinate to Point geometries.

Note

If buffer and px_per_mm is not None, then the points will be buffered and a 2D share polygon created with the specified buffered area. If buffer is provided, then also provide px_per_mm for accurate conversion factor between pixels and millimeters.

See also

If having a large number of body-parts, consider using simba.mixins.geometry_mixin.GeometryMixin.multiframe_bodypart_to_point() which uses CPU multiprocessing.

Parameters
  • data (np.ndarray) โ€“ 2D array with body-part coordinates where rows are frames and columns are x and y coordinates.

  • buffer (Optional[int]) โ€“ If not None, then the area of the Point. Thus, if not None, then returns Polygons representing the Points.

  • px_per_mm (Optional[int]) โ€“ Pixels to millimeter conversion factor. Required if buffer is not None.

Returns

List of shapely Points (or polygons if buffer is not None).

Return type

List[Union[Point, Polygon]]

Example

>>> data = np.random.randint(0, 100, (1, 2))
>>> GeometryMixin().bodyparts_to_points(data=data)
static bodyparts_to_polygon(data, cap_style='round', parallel_offset=1, pixels_per_mm=1, simplify_tolerance=2, preserve_topology=True, convex_hull=True)[source]๏ƒ

Converts the body-part points into polygonal representations.

Bodyparts to polygon
Parameters
  • data (np.ndarray) โ€“ 3D array with body-part coordinates where rows are frames and columns are x and y coordinates.

  • cap_style (Literal["round", "square", "flat"]) โ€“ How intersections between lines are handled in the polygon. Default: round.

  • parallel_offset (int) โ€“ How much to โ€œbufferโ€ the polygon from the original size in millimeters. Default: 1.

  • pixels_per_mm (int) โ€“ The pixels per millimeter conversion factor used for buffering. Default: 1.

  • simplify_tolerance (float) โ€“ The higher this value, the smaller the number of vertices in the resulting polygon. Default 2.

  • preserve_topology (bool) โ€“ If True, operation will avoid creating invalid geometries (checking for collapses, ring-intersections, etc). Default True.

  • convex_hull (bool) โ€“ If True, creates the convex hull of the shape, which is the smallest convex polygon that encloses the shape. Default True.

Returns

List of polygons, with one entry for every 2D input array.

Example

>>> data = [[[364, 308],[383, 323],[403, 335], [423, 351]]]
>>> GeometryMixin().bodyparts_to_polygon(data=data)
static bucket_img_into_grid_hexagon(bucket_size_mm, img_size, px_per_mm, verbose=True)[source]๏ƒ

Bucketize an image into hexagons and return a dictionary of polygons representing the hexagon locations.

Bucket img into grid hexagon
Parameters
  • bucket_size_mm (float) โ€“ The width/height of each hexagon bucket in millimeters.

  • img_size (Tuple[int, int]) โ€“ Tuple representing the width and height of the image in pixels.

  • px_per_mm (float) โ€“ Pixels per millimeter conversion factor.

  • verbose (bool) โ€“ If True, prints progress. Default True.

Returns

First value is a dictionary where keys are (row, column) indices of the bucket, and values are Shapely Polygon objects representing the corresponding hexagon buckets. Second value is the aspect ratio of the hexagonal grid.

Return type

Tuple[Dict[Tuple[int, int], Polygon], float]

Example

>>> polygons, aspect_ratio = GeometryMixin().bucket_img_into_grid_hexagon(bucket_size_mm=10, img_size=(800, 600), px_per_mm=5.0, add_correction=True)
static bucket_img_into_grid_points(point_distance, px_per_mm, img_size, border_sites=True)[source]๏ƒ

Create a grid of evenly spaced points within an image. Use for creating spatial markers within an arena.

Bucket img into grid points
Parameters
  • point_distance (int) โ€“ Distance between adjacent points in millimeters.

  • px_per_mm (float) โ€“ Pixels per millimeter conversion factor.

  • img_size (Tuple[int, int]) โ€“ Size of the image in pixels (width, height).

  • border_sites (Optional[bool]) โ€“ If True, includes points on the border of the image. Default is True.

Returns

Dictionary where keys are (row, column) indices of the point, and values are Shapely Point objects.

Return type

Dict[Tuple[int, int], Point]

Example

>>> GeometryMixin.bucket_img_into_grid_points(point_distance=20, px_per_mm=4, img_size=img.shape, border_sites=False)
static bucket_img_into_grid_square(img_size, bucket_grid_size_mm=None, bucket_grid_size=None, px_per_mm=None, add_correction=True, verbose=False)[source]๏ƒ

Segment an image into squares and return a dictionary of polygons representing the bucket locations.

Bucket img into grid square 3
Parameters
  • img_size (Iterable[int]) โ€“ 2-value tuple, list or array representing the width and height of the image in pixels.

  • bucket_grid_size_mm (Optional[float]) โ€“ The width/height of each square bucket in millimeters. E.g., 50 will create 5cm by 5cm squares. If None, then buckets will by defined by bucket_grid_size argument.

  • bucket_grid_size (Optional[Iterable[int, int]]) โ€“ 2-value tuple, list or array representing the grid square in number of horizontal squares x number of vertical squares. If None, then buckets will be defined by the bucket_size_mm argument.

  • px_per_mm (Optional[float]) โ€“ Pixels per millimeter conversion factor. Necessery if buckets are defined by bucket_size_mm argument.

  • add_correction (Optional[bool]) โ€“ If True, performs correction by adding extra columns or rows to cover any remaining space if using bucket_size_mm. Default True.

  • verbose (Optional[bool]) โ€“ If True, prints progress / completion information. Default False.

Returns

Size-2 Tuple with (i) Dictionary where the segment index is the key and polygon is the value, and (ii) float representing aspect ratio of each bucket.

Example

>>> img = cv2.imread('/Users/simon/Desktop/Screenshot 2024-01-21 at 10.15.55 AM.png', 1)
>>> polygons = GeometryMixin().bucket_img_into_grid_square(bucket_grid_size=(10, 5), bucket_grid_size_mm=None, img_size=(img.shape[1], img.shape[0]), px_per_mm=5.0)
>>> for k, v in polygons[0].items(): cv2.polylines(img, [np.array(v.exterior.coords).astype(int)], True, (255, 0, 133), 2)
>>> cv2.imshow('img', img)
>>> cv2.waitKey()
static buffer_shape(shape, size_mm, pixels_per_mm, resolution=16, join_style=1, cap_style='round')[source]๏ƒ

Create a buffered shape by applying a buffer operation to the input polygon or linestring.

Buffer shape

See also

simba.mixins.geometry_mixin.GeometryMixin.parallel_offset_polygon() scales vertices from the centroid (sharp corners, fixed vertex count); buffer_shape is a true constant-distance buffer (rounds corners, works on LineStrings).

Parameters
  • shape (Union[Polygon, LineString]) โ€“ The input Polygon or LineString to be buffered. Or a list of Polygons or LineStrings to be buffered.

  • size_mm (int) โ€“ The size of the buffer in millimeters. Use a negative value for an inward buffer.

  • pixels_per_mm (float) โ€“ The conversion factor from millimeters to pixels.

  • cap_style (Literal['round', 'square', 'flat']) โ€“ The cap style for the buffer. Valid values are โ€˜roundโ€™, โ€˜squareโ€™, or โ€˜flatโ€™. Defaults to โ€˜roundโ€™.

Returns

The buffered shape.

Return type

Polygon

Example

>>> polygon = GeometryMixin().bodyparts_to_polygon(np.array([[100, 110],[100, 100],[110, 100],[110, 110]]))
>>> buffered_polygon = GeometryMixin().buffer_shape(shape=polygon[0], size_mm=-1, pixels_per_mm=1)
static compute_pct_shape_overlap(shapes, denominator='difference')[source]๏ƒ

Compute the percentage of overlap between two shapes.

Compute pct shape overlap
Parameters
  • shapes (List[Union[LineString, Polygon]]) โ€“ A 2D array, where each sub-array has two Polygon or LineString shapes.

  • denominator (Optional[Literal['union', 'shape_1', 'shape_2']]) โ€“ If difference, then percent overlap is calculated using non-intersection area as denominator. If shape_1, percent overlap is calculated using the area of the first shape as denominator. If shape_2, percent overlap is calculated using the area of the second shape as denominator. Default: difference.

Returns

The percentage of overlap between the two shapes (0 - 100) as integer.

Return type

int

Example

>>> polygon_1 = GeometryMixin().bodyparts_to_polygon(np.array([[364, 308],[383, 323],[403, 335],[423, 351]]))
>>> polygon_2 = GeometryMixin().bodyparts_to_polygon(np.array([[356, 307],[376, 319],[396, 331],[419, 347]]))
>>> polygon_1 = [polygon_1 for x in range(100)]
>>> polygon_2 = [polygon_2 for x in range(100)]
>>> data = np.column_stack((polygon_1, polygon_2))
>>> results = GeometryMixin.compute_pct_shape_overlap(shapes=data)
static compute_shape_overlap(shapes, verbose=False)[source]๏ƒ

Computes if two geometrical shapes (Polygon or LineString) overlaps or are disjoint.

See also

For multicore method, see simba.mixins.geometry_mixin.GeometryMixin.multiframe_compute_shape_overlap() Only returns if two shapes are overlapping or not overlapping. If the amount of overlap is required, use GeometryMixin().compute_shape_overlap().

Compute overlap
Parameters

shapes (List[Union[LineString, Polygon, None]]) โ€“ A 2d array of Polygon or LineString shapes. If the array contains a row with None, no overlap will be returned for that row.

Returns

Returns 1 if the two shapes overlap, otherwise returns 0.

Return type

np.ndarray

static contours_to_geometries(contours, force_rectangles=True, convex_hull=False)[source]๏ƒ

Convert a list of contours to a list of geometries.

E.g., convert a list of contours detected with simba.mixins.image_mixin.ImageMixin.find_contours() to a list of Shapely geometries that can be used within the simba.mixins.geometry_mixin.GeometryMixin().

Parameters
  • contours (List[np.ndarray]) โ€“ List of contours represented as 2D arrays.

  • force_rectangles โ€“ If True, then force the resulting geometries to be rectangular.

  • convex_hull (bool) โ€“ If True, creates the convex hull of the shape, which is the smallest convex polygon that encloses the shape. Default True.

Returns

List of Shapley Polygons.

Return type

List[Polygon]

Example

>>> video_frm = read_frm_of_video(video_path='/Users/simon/Desktop/envs/platea_featurizer/data/video/3D_Mouse_5-choice_MouseTouchBasic_s9_a4_grayscale.mp4')
>>> contours = ImageMixin.find_contours(img=video_frm)
>>> GeometryMixin.contours_to_geometries(contours=contours)
static crosses(shapes)[source]๏ƒ

Check if two LineString objects cross each other.

Are lines crossing
Parameters

shapes (List[LineString]) โ€“ A list containing two LineString objects.

Returns

True if the LineStrings cross each other, False otherwise.

Return type

bool

Example

>>> line_1 = GeometryMixin().bodyparts_to_line(np.array([[10, 10],[20, 10],[30, 10],[40, 10]]))
>>> line_2 = GeometryMixin().bodyparts_to_line(np.array([[25, 5],[25, 20],[25, 30],[25, 40]]))
>>> GeometryMixin().crosses(shapes=[line_1, line_2])
>>> True
cumsum_animal_geometries_grid(data, grid, fps=None, core_cnt=- 1, verbose=True, pool=None)[source]๏ƒ

Compute the cumulative time the animal has spent in each geometry.

Note

The time is computed based on if any part of the animal is inside a specific geometry. Thus, if the hull is larger than the individual geometries, then the total time in the matrix can exceed the time of the video.

See also

To calculate the time / count of boolean events in geometries, see simba.mixins.geometry_mixin.GeometryMixin.cumsum_bool_geometries(). To calculate the cumulative time the animal has spent in each geometry using a single key-point, see simba.mixins.geometry_mixin.GeometryMixin.cumsum_coord_geometries().

For each frame a grid cell is counted when the animal convex hull overlaps it; accumulating over all frames and dividing by fps yields the cumulative seconds spent in each cell
Parameters
Returns

Matrix of size (frames x horizontal bins x vertical bins) with values representing time in seconds (if fps passed) or frames (if fps not passed)

Return type

np.ndarray

Return type

np.ndarray

cumsum_bool_geometries(data, geometries, bool_data, fps=None, verbose=True, core_cnt=- 1, pool=None)[source]๏ƒ

Compute the cumulative sums of boolean events within polygon geometries over time using multiprocessing. For example, compute the cumulative bout count of classified events within spatial locations at all time-points of the video.

Cumsum bool geometries

See also

Parameters
  • data (np.ndarray) โ€“ Array containing spatial data with shape (n, 2). E.g., 2D-array with body-part coordinates.

  • geometries (Dict[Tuple[int, int], Polygon]) โ€“ Dictionary of polygons representing spatial regions. E.g., created by simba.mixins.geometry_mixin.GeometryMixin.bucket_img_into_grid_square() or simba.mixins.geometry_mixin.GeometryMixin.bucket_img_into_grid_hexagon().

  • bool_data (np.ndarray) โ€“ Boolean array with shape (data.shape[0],) or (data.shape[0], 1) indicating the presence or absence in each frame.

  • fps (Optional[float]) โ€“ Frames per second. If provided, the result is normalized by the frame rate.

  • verbose (bool) โ€“ If true, prints progress. Default: True.

  • core_cnt (Optional[int]) โ€“ Number of CPU cores to use for parallel processing. Default is -1, which means using all available cores. Ignored if pool is provided.

  • pool (Optional[multiprocessing.Pool]) โ€“ Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.

Returns

Matrix of size (frames x horizontal bins x vertical bins) with times in seconds (if fps passed) or frames (if fps not passed)

Return type

np.ndarray

Return type

np.ndarray

Example

>>> geometries = GeometryMixin.bucket_img_into_grid_square(bucket_size_mm=50, img_size=(800, 800) , px_per_mm=5.0)[0]
>>> coord_data = np.random.randint(0, 800, (500, 2))
>>> bool_data = np.random.randint(0, 2, (500,))
>>> x = GeometryMixin().cumsum_bool_geometries(data=coord_data, geometries=geometries, bool_data=bool_data, fps=15)
>>> x.shape
>>> (500, 4, 4)
cumsum_coord_geometries(data, geometries, fps=None, core_cnt=- 1, verbose=True, pool=None)[source]๏ƒ

Compute the cumulative time a body-part has spent inside a grid of geometries using multiprocessing.

Cumsum coord geometries
Parameters
  • data (np.ndarray) โ€“ Input data array where rows represent frames and columns represent body-part x and y coordinates.

  • geometries (Dict[Tuple[int, int], Polygon]) โ€“ Dictionary of polygons representing spatial regions. E.g., created by simba.mixins.geometry_mixin.GeometryMixin.bucket_img_into_grid_square() or simba.mixins.geometry_mixin.GeometryMixin.bucket_img_into_grid_hexagon().

  • fps (Optional[int]) โ€“ Frames per second (fps) for time normalization. If None, cumulative sum of frame count is returned.

  • core_cnt (Optional[int]) โ€“ Number of CPU cores to use for parallel processing. Default is -1 which is all available cores. Ignored if pool is provided.

  • verbose (Optional[bool]) โ€“ If True, prints progress.

  • pool (Optional[multiprocessing.Pool]) โ€“ Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.

Returns

Matrix of size (frames x horizontal bins x vertical bins) with cumulative time values.

Return type

np.ndarray

Example

>>> img_geometries = GeometryMixin.bucket_img_into_grid_square(img_size=(640, 640), bucket_grid_size=(10, 10), px_per_mm=1)
>>> bp_arr = np.random.randint(0, 640, (5000, 2))
>>> geo_data = GeometryMixin().cumsum_coord_geometries(data=bp_arr, geometries=img_geometries[0], verbose=False, fps=1)
static delaunay_triangulate_keypoints(data)[source]๏ƒ

Triangulates a set of 2D keypoints. E.g., use to polygonize animal hull, or triangulate a gridpoint areana.

This method takes a 2D numpy array representing a set of keypoints and triangulates them using the Delaunay triangulation algorithm. The input array should have two columns corresponding to the x and y coordinates of the keypoints.

Delaunay triangulate keypoints
Delaunay triangulate keypoints 2
Parameters

data (np.ndarray) โ€“ NumPy array of body part coordinates. Each subarray represents the coordinates of a body part.

Returns

A list of Polygon objects representing the triangles formed by the Delaunay triangulation.

Return type

List[Polygon]

Example

>>> data = np.array([[126, 122],[152, 116],[136,  85],[167, 172],[161, 206],[197, 193],[191, 237]])
>>> triangulated_hull = GeometryMixin().delaunay_triangulate_keypoints(data=data)
static difference(shapes=typing.List[typing.Union[shapely.geometry.linestring.LineString, shapely.geometry.polygon.Polygon, shapely.geometry.multipolygon.MultiPolygon]])[source]๏ƒ

Calculate the difference between a shape and one or more potentially overlapping shapes.

Difference Difference 1
Parameters

shapes (List[Union[LineString, Polygon, MultiPolygon]]) โ€“ A list of geometries.

Returns

The first geometry in shapes is returned where all parts that overlap with the other geometries in ``shapes have been removed.

Return type

Polygon

Example

>>> polygon_1 = GeometryMixin().bodyparts_to_polygon(np.array([[10, 10], [10, 100], [100, 10], [100, 100]]))
>>> polygon_2 = GeometryMixin().bodyparts_to_polygon(np.array([[25, 25],[25, 75],[90, 25],[90, 75]]))
>>> polygon_3 = GeometryMixin().bodyparts_to_polygon(np.array([[1, 25],[1, 75],[110, 25],[110, 75]]))
>>> difference = GeometryMixin().difference(shapes = [polygon_1, polygon_2, polygon_3])
static extend_line_to_bounding_box_edges(line_points, bounding_box)[source]๏ƒ

Jitted extend a line segment defined by two points to fit within a bounding box.

Extend line to bounding box edges
Parameters
  • line_points (np.ndarray) โ€“ Coordinates of the line segmentโ€™s two points. Two rows and each row represents a point (x, y).

  • bounding_box (np.ndarray) โ€“ Bounding box coordinates in the format (min_x, min_y, max_x, max_y).

Returns

Intersection points where the extended line crosses the bounding box edges. The shape of the array is (2, 2), where each row represents a point (x, y).

Return type

np.ndarray

Example

>>> line_points = np.array([[25, 25], [45, 25]]).astype(np.float32)
>>> bounding_box = np.array([0, 0, 50, 50]).astype(np.float32)
>>> GeometryMixin().extend_line_to_bounding_box_edges(line_points, bounding_box)
>>> [[ 0. 25.] [50. 25.]]
static filter_low_p_bps_for_shapes(x, p, threshold)[source]๏ƒ

Filter body-part data for geometry construction while maintaining valid geometry arrays.

Having a 3D array representing body-parts across time, and a second 3D array representing probabilities of those body-parts across time, we want to โ€œremoveโ€ body-parts with low detection probabilities whilst also keeping the array sizes intact and suitable for geometry construction. To do this, we find body-parts with detection probabilities below the threshold, and replace these with a body-part that doesnโ€™t fall below the detection probability threshold within the same frame. However, to construct a geometry, we need >= 3 unique key-point locations. Thus, no substitution can be made to when there are less than three unique body-part locations within a frame that falls above the threshold.

Example

>>> x = np.random.randint(0, 500, (18000, 7, 2))
>>> p = np.random.random(size=(18000, 7, 1))
>>> x = GeometryMixin.filter_low_p_bps_for_shapes(x=x, p=p, threshold=0.1)
>>> x = x.reshape(x.shape[0], int(x.shape[1] * 2))
static geometries_to_exterior_keypoints(geometries, core_cnt=- 1, pool=None)[source]๏ƒ

Extract exterior keypoints from a list of Polygon geometries in parallel, with optional core count specification for multiprocessing.

Geometries to exterior keypoints
Parameters
  • geometries (List[Polygon]) โ€“ A list of Shapely Polygon objects representing geometries whose exterior keypoints will be extracted.

  • core_cnt (Optional[int]) โ€“ The number of CPU cores to use for multiprocessing. If -1, it uses the maximum number of available cores. Ignored if pool is provided.

  • pool (Optional[multiprocessing.Pool]) โ€“ Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.

Returns

A numpy array of exterior keypoints extracted from the input geometries.

Return type

np.ndarray

Example

>>> data_path = r"C:/troubleshooting/mitra/project_folder/csv/outlier_corrected_movement_location/FRR_gq_Saline_0624.csv"
>>> animal_data = read_df(file_path=data_path, file_type='csv', usecols=['Nose_x', 'Nose_y', 'Tail_base_x', 'Tail_base_y', 'Left_side_x', 'Left_side_y', 'Right_side_x', 'Right_side_y']).values.reshape(-1, 4, 2)[0:20].astype(np.int32)
>>> animal_polygons = GeometryMixin().bodyparts_to_polygon(data=animal_data)
>>> GeometryMixin.geometries_to_exterior_keypoints(geometries=animal_polygons)
static geometry_contourcomparison(imgs, geometry=None, method='all', canny=True)[source]๏ƒ

Compare contours between a geometry in two images using shape matching.

Geometry contourcomparison

Important

If there is non-pose related noise in the environment (e.g., there are non-experiment related intermittant light or shade sources that goes on and off, this will negatively affect the reliability of contour comparisons.

Used to pick up very subtle changes around pose-estimated body-part locations.

Parameters
  • imgs (List[Union[np.ndarray, Tuple[cv2.VideoCapture, int]]]) โ€“ List of two input images. Can be either be two images in numpy array format OR a two tuples with cv2.VideoCapture object and the frame index.

  • geometry (Optional[Polygon]) โ€“ If Polygon, then the geometry in the two images that should be compared. If None, then entire images will be contourcompared.

  • method (Literal['all', 'exterior']) โ€“ The method used for contour comparison.

  • canny (Optional[bool]) โ€“ If True, applies Canny edge detection before contour comparison. Helps reduce noise and enhance contours. Default is True.

Returns

Contour matching score between the two images. Lower scores indicate higher similarity.

Return type

float

Example

>>> img_1 = cv2.imread('/Users/simon/Desktop/envs/troubleshooting/Emergence/project_folder/videos/Example_1_frames/1978.png').astype(np.uint8)
>>> img_2 = cv2.imread('/Users/simon/Desktop/envs/troubleshooting/Emergence/project_folder/videos/Example_1_frames/1977.png').astype(np.uint8)
>>> data = pd.read_csv('/Users/simon/Desktop/envs/troubleshooting/Emergence/project_folder/csv/outlier_corrected_movement_location/Example_1.csv', nrows=1, usecols=['Nose_x', 'Nose_y']).fillna(-1).values.astype(np.int64)
>>> geometry = GeometryMixin().bodyparts_to_circle(data[0, :], 100)
>>> GeometryMixin().geometry_contourcomparison(imgs=[img_1, img_2], geometry=geometry, canny=True, method='exterior')
>>> 22.54
static geometry_histocomparison(imgs, geometry=None, method='correlation', absolute=True)[source]๏ƒ

Retrieve histogram similarities within a geometry inside two images.

For example, the polygon may represent an area around a rodents head. While the front paws are not pose-estimated, computing the histograms of the geometry in two sequential images gives indication of non-freezing.

Note

If shapes is None, the entire two images passed as imgs will be compared.

Documentation.

Important

If there is non-pose related noise in the environment (e.g., there are non-experiment related light sources that goes on and off, or waving window curtains causing changes in histgram values w/o affecting pose) this will negatively affect the realiability of histogram comparisons.

Geometry histocomparison
Parameters
  • imgs (List[Union[np.ndarray, Tuple[cv2.VideoCapture, int]]]) โ€“ List of two input images. Can be either an two image in numpy array format OR a two tuples with cv2.VideoCapture object and the frame index.

  • geometry (Optional[Polygon]) โ€“ If Polygon, then the geometry in the two images that should be compared. If None, then entire images will be histocompared.

  • method (Literal['correlation', 'chi_square']) โ€“ The method used for comparison. E.g., if correlation, then small output values suggest large differences between the current versus prior image. If chi_square, then large output values suggest large differences between the geometries.

  • absolute (Optional[bool]) โ€“ If True, the absolute difference between the two histograms. If False, then (image2 histogram) - (image1 histogram)

Returns

Value representing the histogram similarities between the geometry in the two images.

Return type

float

Example

>>> img_1 = cv2.imread('/Users/simon/Desktop/envs/troubleshooting/Emergence/project_folder/videos/Example_1_frames/1.png')
>>> img_2 = cv2.imread('/Users/simon/Desktop/envs/troubleshooting/Emergence/project_folder/videos/Example_1_frames/2.png')
>>> data_path = '/Users/simon/Desktop/envs/troubleshooting/Emergence/project_folder/csv/outlier_corrected_movement_location/Example_1.csv'
>>> data = pd.read_csv(data_path, nrows=1, usecols=['Nose_x', 'Nose_y']).fillna(-1).values.astype(np.int64)
>>> polygon = GeometryMixin().bodyparts_to_circle(data[0], 100)
>>> GeometryMixin().geometry_histocomparison(imgs=[img_1, img_2], geometry=polygon, method='correlation')
>>> 0.9999769684923543
>>> img_2 = cv2.imread('/Users/simon/Desktop/envs/troubleshooting/Emergence/project_folder/videos/Example_1_frames/41411.png')
>>> GeometryMixin().geometry_histocomparison(imgs=[img_1, img_2], geometry=polygon, method='correlation')
>>> 0.6732792208872572
>>> img_1 = (cv2.VideoCapture('/Users/simon/Desktop/envs/troubleshooting/Emergence/project_folder/videos/Example_1.mp4'), 1)
>>> img_2 = (cv2.VideoCapture('/Users/simon/Desktop/envs/troubleshooting/Emergence/project_folder/videos/Example_1.mp4'), 2)
>>> GeometryMixin().geometry_histocomparison(imgs=[img_1, img_2], geometry=polygon, method='correlation')
>>> 0.9999769684923543
static geometry_transition_probabilities(data, grid, core_cnt=- 1, verbose=False, pool=None)[source]๏ƒ

Calculate geometry transition probabilities based on spatial transitions between grid cells.

Computes transition probabilities between pairs of spatial grid cells, represented as polygons. For each cell, it calculates the likelihood of transitioning to other cells.

Geometry transition probabilities
Parameters
  • data (np.ndarray) โ€“ A 2D array where each row represents a point in space with two coordinates [x, y].

  • grid (Dict[Tuple[int, int], Polygon]) โ€“ A dictionary mapping grid cell identifiers (tuple of int, int) to their corresponding polygon objects. Each grid cell is represented by a tuple key (e.g., (row, col)) and its spatial boundaries as a Polygon. Can be computed with E.g., created by simba.mixins.geometry_mixin.GeometryMixin.bucket_img_into_grid_square() or simba.mixins.geometry_mixin.GeometryMixin.bucket_img_into_grid_hexagon().

  • core_cnt (Optional[int]) โ€“ The number of cores to use for parallel processing. Default is -1, which uses the maximum available cores. Ignored if pool is provided.

  • verbose (Optional[bool]) โ€“ If True, the function will print additional information, including the elapsed time for processing.

  • pool (Optional[multiprocessing.Pool]) โ€“ Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.

Returns

A tuple containing two dictionaries: - A dictionary of transition probabilities between grid cells, where each key is a grid cell tuple (row, col),

and each value is another dictionary representing the transition probabilities to other cells.

  • A dictionary of transition counts between grid cells, where each key is a grid cell tuple (row, col), and each value is another dictionary representing the transition counts to other cells.

Return type

Tuple[Dict[Tuple[int, int], Dict[Tuple[int, int], float]], Dict[Tuple[int, int], Dict[Tuple[int, int], int]]]

Example

>>> video_meta_data = get_video_meta_data(video_path=r"C:/troubleshooting/mitra/project_folder/videos/708_MA149_Gq_CNO_0515.mp4")
>>> w, h = video_meta_data['width'], video_meta_data['height']
>>> grid = GeometryMixin().bucket_img_into_grid_square(bucket_grid_size=(5, 5), bucket_grid_size_mm=None, img_size=(h, w), verbose=False)[0]
>>> data = read_df(file_path=r'C:/troubleshooting/mitra/project_folder/csv/outlier_corrected_movement_location/708_MA149_Gq_CNO_0515.csv', file_type='csv')[['Nose_x', 'Nose_y']].values
>>> transition_probabilities, _ = geometry_transition_probabilities(data=data, grid=grid)
static geometry_video(shapes, size, save_path=None, fps=10, verbose=False, bg_img=None, bg_clr=None, circle_size=None, thickness=2)[source]๏ƒ

Helper to create a geometry video from a list of shapes.

See also

  • If more aesthetic videos are needed, overlaid on video, then use func:simba.plotting.geometry_plotter.GeometryPlotter

  • If single images of geometries are needed, then use simba.mixins.geometry_mixin.view_shapes()

Parameters
  • shapes (List[List[Union[LineString, Polygon, MultiPolygon, MultiPoint, MultiLineString]]]) โ€“ List of lists containing geometric shapes to be included in the video. Each sublist represents a frame, and each element within the sublist represents a shape for that frame.

  • save_path (Union[str, os.PathLike]) โ€“ Path where the resulting video will be saved.

  • size (Optional[Tuple[int]]) โ€“ Tuple specifying the size of the output video in pixels (width, height).

  • fps (Optional[int]) โ€“ Frames per second of the output video. Defaults to 10.

  • verbose (Optional[bool]) โ€“ If True, then prints progress frmae-by-frame. Default: False.

  • bg_img (Optional[np.ndarray]) โ€“ Background image to be used as the canvas for drawing shapes. Defaults to None. Could be e.g., a low opacity image of the arena.

  • bg_clr (Optional[Tuple[int]]) โ€“ Background color specified as a tuple of RGB values. Defaults to white.

Returns

None

static get_center(shape)[source]๏ƒ

Get the center coordinate of a shape or a list of shapes.

Get center
Parameters

shape (Union[LineString, Polygon, MultiPolygon, List[Union[LineString, Polygon, MultiPolygon]]]) โ€“ A single geometry or a list of geometries. If None, then None is returned.

Returns

Array sith x, y coordinates of shape centers.

Return type

np.ndarray

Example

>>> multipolygon = MultiPolygon([Polygon([[200, 110],[200, 100],[200, 100],[200, 110]]), Polygon([[70, 70],[70, 60],[10, 50],[1, 70]])])
>>> GeometryMixin().get_center(shape=multipolygon)
>>> [33.96969697, 62.32323232]
static get_geometry_brightness_intensity(img, geometries, ignore_black=True)[source]๏ƒ

Calculate the average brightness intensity within a geometry region-of-interest of an image.

E.g., can be used with hardcoded thresholds or model kmeans in simba.mixins.statistics_mixin.Statistics.kmeans_1d to detect if a light source is ON or OFF state.

Get geometry brightness intensity

See also

For direct image brightness comparisons without geometry slicing, see simba.mixins.image_mixin.ImageMixin.brightness_intensity(). For GPU acceleration, see simba.data_processors.cuda.image.img_stack_brightness()

Parameters
  • img (np.ndarray) โ€“ Either an image in numpy array format OR a tuple with cv2.VideoCapture object and the frame index.

  • geometries (List[Union[Polygon, np.ndarray]]) โ€“ A list of shapes either as vertices in a numpy array, or as shapely Polygons.

  • ignore_black (Optional[bool]) โ€“ If non-rectangular geometries, then pixels that donโ€™t belong to the geometry are masked in black. If True, then these pixels will be ignored when computing averages.

Returns

List of geometry brighness values.

Return type

List[float]

Example

>>> img = cv2.imread('/Users/simon/Desktop/envs/troubleshooting/Emergence/project_folder/videos/Example_1_frames/1.png').astype(np.uint8)
>>> data_path = '/Users/simon/Desktop/envs/troubleshooting/Emergence/project_folder/csv/outlier_corrected_movement_location/Example_1.csv'
>>> data = pd.read_csv(data_path, usecols=['Nose_x', 'Nose_y']).sample(n=3).fillna(1).values.astype(np.int64)
>>> geometries = []
>>> for frm_data in data: geometries.append(GeometryMixin().bodyparts_to_circle(frm_data, 100))
>>> GeometryMixin().get_geometry_brightness_intensity(img=img, geometries=geometries, ignore_black=False)
>>> [125.0, 113.0, 118.0]
static get_shape_statistics(shapes)[source]๏ƒ

Calculate the lengths and widths of the minimum bounding rectangles of polygons.

Get shape statistics
Parameters

shapes (Union[List[Polygon], Polygon]) โ€“ A single Polygon, a list of Polygons, or 3d array of vertices, for which the MBR dimensions are calculated.

Returns

A dictionary containing: - โ€˜lengthsโ€™: A list of the lengths (longer side of the MBR) for each polygon. - โ€˜widthsโ€™: A list of the widths (shorter side of the MBR) for each polygon. - โ€˜areasโ€™: A list of the MBR areas (length x width) for each polygon. - โ€˜centersโ€™: A list of the [x, y] centroid of each polygon. - โ€˜max_lengthโ€™ / โ€˜min_lengthโ€™: The maximum / minimum length found among all polygons. - โ€˜max_widthโ€™ / โ€˜min_widthโ€™: The maximum / minimum width found among all polygons. - โ€˜max_areaโ€™ / โ€˜min_areaโ€™: The maximum / minimum MBR area found among all polygons.

Return type

Dict[str, Any]

static hausdorff_distance(geometries)[source]๏ƒ

The Hausdorff distance measure of the similarity between time-series sequential geometries. It is defined as the maximum of the distances from each point in one set to the nearest point in the other set.

Hausdorff distance can be used to measure the similarity of the geometry in one frame relative to the geometry in the next frame. Larger values indicate that the animal has a different shape than in the preceding shape.

Hausdorff distance
Parameters

geometries (List[List[Union[Polygon, LineString]]]) โ€“ List of list where each list has two geometries.

Returns

1D array of hausdorff distances of geometries in each list.

Return type

np.ndarray

Example

>>> x = Polygon([[0,1], [0, 2], [1,1]])
>>> y = Polygon([[0,1], [0, 2], [0,1]])
>>> GeometryMixin.hausdorff_distance(geometries=[[x, y]])
>>> [1.]
static is_containing(shapes=typing.Iterable[typing.Union[shapely.geometry.linestring.LineString, shapely.geometry.polygon.Polygon]])[source]๏ƒ

Check if the first shape in a list contains a second shape in the same list.

Is containing
Example

>>> polygon1 = Polygon([(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)])
>>> polygon2 = Polygon([(3, 3), (7, 3), (7, 7), (3, 7), (3, 3)])
>>> GeometryMixin.is_containing(shapes=[polygon1, polygon2])
static is_shape_covered(shapes)[source]๏ƒ

Check if one geometry fully covers another.

Is line covered
Parameters

shapes (Union[LineString, Polygon, MultiPolygon, MultiPoint]) โ€“ List of 2 geometries, checks if the second geometry fully covers the first geometry.

Returns

True if the second geometry fully covers the first geometry, otherwise False.

Return type

bool

>>> polygon_1 = GeometryMixin().bodyparts_to_polygon(np.array([[10, 10], [10, 100], [100, 10], [100, 100]]))
>>> polygon_2 = GeometryMixin().bodyparts_to_polygon(np.array([[25, 25], [25, 75], [90, 25], [90, 75]]))
>>> GeometryMixin().is_shape_covered(shapes=[polygon_2[0], polygon_1[0]])
>>> True
static is_touching(shapes=typing.List[typing.Union[shapely.geometry.polygon.Polygon, shapely.geometry.linestring.LineString]])[source]๏ƒ

Check if two geometries touch each other.

Touches

Note

Different from GeometryMixin().crosses: Touches requires a common boundary, and does not require the sharing of interior space.

Parameters

shapes (List[Union[LineString, Polygon]]) โ€“ A list containing two LineString or Polygon geometries.

Returns

True if the geometries touch each other, False otherwise.

Return type

bool

Example

>>> rectangle_1 = Polygon(np.array([[0, 0], [10, 10], [0, 10], [10, 0]]))
>>> rectangle_2 = Polygon(np.array([[20, 20], [30, 30], [20, 30], [30, 20]]))
>>> GeometryMixin().is_touching(shapes=[rectangle_1, rectangle_2])
>>> False
static keypoints_to_axis_aligned_bounding_box(keypoints)[source]๏ƒ

Computes the axis-aligned bounding box for each set of keypoints.

Each set of keypoints consists of a 2D array of coordinates representing points. The function calculates the minimum and maximum x and y values from the keypoints to form a rectangle (bounding box) aligned with the x and y axes.

Keypoints to axis aligned bounding box
Parameters

keypoints (np.ndarray) โ€“ A 3D array of shape (N, M, 2) where N is the number of observations, and each observation contains M points in 2D space (x, y).

Returns

A 3D array of shape (N, 4, 2), where each entry represents the four corners of the axis-aligned bounding box corresponding to each set of keypoints.

Return type

np.ndarray

Example

>>> data = np.random.randint(0, 360, (30000, 7, 2))
>>> results = GeometryMixin.keypoints_to_axis_aligned_bounding_box(keypoints=data)
static length(shape, pixels_per_mm, unit='mm')[source]๏ƒ

Calculate the length of a LineString geometry.

Length
Parameters
  • shape (LineString) โ€“ The LineString geometry for which the length is to be calculated.

  • unit (Literal['mm', 'cm', 'dm', 'm']) โ€“ The desired unit for the length measurement (โ€˜mmโ€™, โ€˜cmโ€™, โ€˜dmโ€™, โ€˜mโ€™).

Returns

The length of the LineString geometry in the specified unit.

Return type

float

Example

>>> line_1 = GeometryMixin().bodyparts_to_line(np.array([[10, 70],[20, 60],[30, 50],[40, 70]]))
>>> GeometryMixin().length(shape=line_1, pixels_per_mm=1.0)
>>> 50.6449510224598
static line_split_bounding_box(intersections, bounding_box)[source]๏ƒ

Split a bounding box into two parts using an extended line.

Note

Extended line can be found by body-parts using simba.mixins.geometry_mixin.GeometryMixin.extend_line_to_bounding_box_edges().

Line split bounding box
Parameters
  • line_points (np.ndarray) โ€“ Intersection points where the extended line crosses the bounding box edges. The shape of the array is (2, 2), where each row represents a point (x, y).

  • bounding_box (np.ndarray) โ€“ Bounding box coordinates in the format (min_x, min_y, max_x, max_y).

Returns

A collection of polygons resulting from splitting the bounding box with the extended line.

Return type

GeometryCollection

Example

>>> line_points = np.array([[25, 25], [45, 25]]).astype(np.float32)
>>> bounding_box = np.array([0, 0, 50, 50]).astype(np.float32)
>>> intersection_points = GeometryMixin().extend_line_to_bounding_box_edges(line_points, bounding_box)
>>> GeometryMixin().line_split_bounding_box(intersections=intersection_points, bounding_box=bounding_box)
static linear_frechet_distance(x, y, sample=100)[source]๏ƒ

Jitted compute the Linear Frรฉchet Distance between two trajectories.

The Frรฉchet Distance measures the dissimilarity between two continuous curves or trajectories represented as sequences of points in a 2-dimensional space.

Linear frechet distance
Parameters
  • x (ndarray) โ€“ First 2D array of size len(frames) representing body-part coordinates x and y.

  • y (ndarray) โ€“ Second 2D array of size len(frames) representing body-part coordinates x and y.

  • sample (int) โ€“ The downsampling factor for the trajectories (default is 100If sample > 1, the trajectories are downsampled by selecting every sample-th point.

Returns

Linear Frรฉchet Distance between two trajectories

Return type

float

Note

Modified from Joรฃo Paulo Figueira

Example

>>> x = np.random.randint(0, 100, (10000, 2)).astype(np.float32)
>>> y = np.random.randint(0, 100, (10000, 2)).astype(np.float32)
>>> distance = GeometryMixin.linear_frechet_distance(x=x, y=y, sample=100)
static locate_line_point(path, geometry, px_per_mm=1, fps=1, core_cnt=- 1, distance_min=True, time_prior=True)[source]๏ƒ

Compute the time and distance travelled along a path to reach the most proximal point in reference to a second geometry.

Note

  1. To compute the time and distance travelled to along a path to reach the most distal point to a second geometry, pass distance_min = False.

  2. To compute the time and distance travelled along a path after reaching the most distal or proximal point to a second geometry, pass time_prior = False.

Locate line point
Parameters
  • path (Union[LineString, np.ndarray]) โ€“ A LineString or a 2D array with keypoints across time.

  • geometry (Union[LineString, Polygon, Point]) โ€“ A geometry of intrest.

  • px_per_mm (float) โ€“ Pixels per millimeter conversion factor.

  • sample_rate (Optional[Union[float, int]]) โ€“ The FPS of the recording used as conversion factor for time.

  • core_cnt (Optional[int]) โ€“ The number of cores to use for parallel processing. Default is -1, which uses the maximum available cores.

  • distance_min (Optional[bool]) โ€“ If True, uses the minimim distance between the geometry and the path as the reference point. Else, uses the maximum. Default: True.

  • time_prior (Optional[bool]) โ€“ If True, returns the time/distance BEFORE reaching the proximal/distal point in relation to the geometry. Else, returns the time/distance AFTER reaching the proximal/distal point in relation to the geometry. Default True.

Return type

Dict[str, float]

Example

>>> line = LineString([[10, 10], [7.5, 7.5], [15, 15], [7.5, 7.5]])
>>> polygon = Polygon([[0, 5], [0, 0], [5, 0], [5, 5]])
>>> GeometryMixin.locate_line_point(path=line, geometry=polygon)
>>> {'distance_value': 3.5355339059327378, 'distance_travelled': 3.5355339059327378, 'time_travelled': 1.0, 'distance_index': 1}
static minimum_rotated_rectangle(shape, buffer=None, return_type='geometry')[source]๏ƒ

Calculate the minimum rotated rectangle that bounds a given polygon or set of points.

The minimum rotated rectangle, also known as the minimum bounding rectangle (MBR) or oriented bounding box (OBB), is the smallest rectangle that can fully contain a given polygon or set of points while allowing rotation. It is defined by its center, dimensions (length and width), and rotation angle.

Minimum rotated rectangle
Parameters
  • shape (Union[Polygon, np.ndarray]) โ€“ The polygon or points to bound. A Shapely Polygon or a 2D array of shape (N, 2) with at least 3 vertices (x, y). Arrays are converted to the convex hull of the points before computing the rectangle.

  • buffer (Optional[int]) โ€“ If not None, a buffer in pixels to expand the polygon area with prior to computing the minimum rotated rectangle. Must be >= 1.

  • return_type (Literal['array', 'geometry']) โ€“ If 'geometry' (default), return a Shapely Polygon. If 'array', return the rectangle as an integer array of shape (4, 2) or (5, 2) (exterior coords, last point may repeat first).

Returns

The minimum rotated rectangle as a Polygon or np.ndarray of corner coordinates, depending on return_type.

Return type

Polygon | np.ndarray

Example

>>> polygon = GeometryMixin().bodyparts_to_polygon(np.array([[364, 308],[383, 323],[403, 335],[423, 351]]))
>>> rectangle = GeometryMixin().minimum_rotated_rectangle(shape=polygon[0])
multiframe_area(shapes, pixels_per_mm=1.0, core_cnt=- 1, verbose=False, video_name=False, animal_names=False, pool=None)[source]๏ƒ

Calculate the area of geometries in square millimeters using multiprocessing.

Note

If certain that the input data are valid Polygons, consider using simba.feature_extractors.perimeter_jit.jitted_hull() or simba.data_processors.cuda.geometry.poly_area() for numba jit and CUDA acceleration, respectively.

Parameters
  • shapes (List[Union[MultiPolygon, Polygon]]) โ€“ List of polygons of Multipolygons.

  • pixels_per_mm (float) โ€“ Pixel per millimeter conversion factor. Default: 1.0.

  • core_cnt (Optional[int]) โ€“ The number of CPU cores to use for parallel processing. Default is -1, which automatically detects the available cores. Ignored if pool is provided.

  • verbose (Optional[bool]) โ€“ If True, prints progress.

  • video_name (Optional[str]) โ€“ If string, prints video name string during progress if verbose.

  • animal_names (Optional[str]) โ€“ If string, prints animal name during progress if verbose.

  • pool (Optional[multiprocessing.Pool]) โ€“ Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.

Returns

List of length len(shapes) with area values.

Return type

List[float]

static multiframe_bodypart_to_point(data, core_cnt=- 1, buffer=None, px_per_mm=None, pool=None)[source]๏ƒ

Process multiple frames of body part data in parallel and convert them to shapely Points.

This function takes a multi-frame body part data represented as an array and converts it into points. It utilizes multiprocessing for parallel processing.

Parameters
  • data (np.ndarray) โ€“ 2D or 3D array with body-part coordinates where rows are frames and columns are x and y coordinates.

  • core_cnt (Optional[int]) โ€“ The number of cores to use. If -1, then all available cores. Ignored if pool is provided.

  • buffer (Optional[int]) โ€“ If not None, then the area of the Point. Thus, if not None, then returns Polygons representing the Points.

  • px_per_mm (Optional[int]) โ€“ Pixels to millimeter conversion factor. Required if buffer is not None.

  • pool (Optional[multiprocessing.Pool]) โ€“ Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.

Return Union[List[Point], List[List[Point]]]

If input is a 2D array, then list of Points. If 3D array, then list of list of Points.

Note

If buffer and px_per_mm is not None, then the points will be buffered and a 2D share polygon created with the specified buffered area. If buffer is provided, then also provide px_per_mm for accurate conversion factor between pixels and millimeters.

Example

>>> data = np.random.randint(0, 100, (100, 2))
>>> points_lst = GeometryMixin().multiframe_bodypart_to_point(data=data, buffer=10, px_per_mm=4)
>>> data = np.random.randint(0, 100, (10, 10, 2))
>>> point_lst_of_lst = GeometryMixin().multiframe_bodypart_to_point(data=data)
multiframe_bodyparts_to_circle(data, parallel_offset=1, core_cnt=- 1, verbose=True, pixels_per_mm=1, pool=None)[source]๏ƒ

Convert a set of pose-estimated key-points to circles with specified radius using multiprocessing.

Parameters
  • data (np.ndarray) โ€“ The body-part coordinates xy as a 2d array where rows are frames and columns represent x and y coordinates . E.g., np.array([[364, 308], [369, 309]])

  • parallel_offset (int) โ€“ The radius of the resultant circle in millimeters.

  • core_cnt (int) โ€“ Number of CPU cores to use. Defaults to -1 meaning all available cores will be used. Ignored if pool is provided.

  • verbose (bool) โ€“ If True, prints progress messages. Default True.

  • pixels_per_mm (int) โ€“ The pixels per millimeter of the video. If not passed, 1 will be used meaning revert to radius in pixels rather than millimeters.

  • pool (Optional[multiprocessing.Pool]) โ€“ Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.

Returns

List of shapely Polygons of circular shape of size data.shape[0].

Return type

List[Polygon]

Example

>>> data = np.random.randint(0, 100, (100, 2))
>>> circles = GeometryMixin().multiframe_bodyparts_to_circle(data=data)
multiframe_bodyparts_to_line(data, buffer=None, px_per_mm=None, core_cnt=- 1, pool=None)[source]๏ƒ

Convert multiframe body-parts data to a list of LineString objects using multiprocessing.

Parameters
  • data (np.ndarray) โ€“ Input array representing multiframe body-parts data. It should be a 3D array with dimensions (frames, points, coordinates).

  • buffer (Optional[int]) โ€“ If not None, then the linestring will be expanded into a 2D geometry polygon with area buffer.

  • px_per_mm (Optional[float]) โ€“ If buffer if not None, then provide the pixels to millimeter conversion factor.

  • core_cnt (Optional[int]) โ€“ Number of CPU cores to use for parallel processing. If set to -1, the function will automatically determine the available core count. Ignored if pool is provided.

  • pool (Optional[multiprocessing.Pool]) โ€“ Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.

Returns

A list of LineString objects representing the body-parts trajectories.

Return type

List[LineString]

Example

>>> data = np.random.randint(0, 100, (100, 2))
>>> data = data.reshape(50,-1, data.shape[1])
>>> lines = GeometryMixin().multiframe_bodyparts_to_line(data=data)
multiframe_bodyparts_to_multistring_skeleton(data_df, skeleton, core_cnt=- 1, verbose=False, video_name=False, animal_names=False, pool=None)[source]๏ƒ

Convert body parts to LineString skeleton representations in a videos using multiprocessing.

Parameters
  • data_df (pd.DataFrame) โ€“ Pose-estimation data.

  • skeleton (Iterable[str]) โ€“ Iterable of body part pairs defining the skeleton structure. Eg., [[โ€˜Centerโ€™, โ€˜Lat_leftโ€™], [โ€˜Centerโ€™, โ€˜Lat_rightโ€™], [โ€˜Centerโ€™, โ€˜Noseโ€™], [โ€˜Centerโ€™, โ€˜Tail_baseโ€™]]

  • core_cnt (Optional[int]) โ€“ Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores. Ignored if pool is provided.

  • verbose (Optional[bool]) โ€“ If True, print progress information during computation. Default is False.

  • video_name (Optional[str]) โ€“ If string, include video name in progress information.

  • animal_names (Optional[str]) โ€“ If string, include animal names in progress information.

  • pool (Optional[multiprocessing.Pool]) โ€“ Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.

Returns

List of LineString or MultiLineString objects representing the computed skeletons.

Return type

List[Union[LineString, MultiLineString]]

Example

>>> df = pd.read_csv('/Users/simon/Desktop/envs/troubleshooting/Rat_NOR/project_folder/csv/machine_results/08102021_DOT_Rat7_8(2).csv', nrows=500).fillna(0).astype(int)
>>> skeleton = [['Center', 'Lat_left'], ['Center', 'Lat_right'], ['Center', 'Nose'], ['Center', 'Tail_base'], ['Lat_left', 'Tail_base'], ['Lat_right', 'Tail_base'], ['Nose', 'Ear_left'], ['Nose', 'Ear_right'], ['Ear_left', 'Lat_left'], ['Ear_right', 'Lat_right']]
>>> geometries = GeometryMixin().multiframe_bodyparts_to_multistring_skeleton(data_df=df, skeleton=skeleton, core_cnt=2, verbose=True)
multiframe_bodyparts_to_polygon(data, video_name=None, animal_name=None, verbose=False, cap_style='round', parallel_offset=1, pixels_per_mm=None, simplify_tolerance=2, preserve_topology=True, core_cnt=- 1, pool=None)[source]๏ƒ

Convert multidimensional NumPy array representing body part coordinates to a list of Polygons.

Note

To convert single frame animal body-part coordinates to polygon, use single core method simba.mixins.geometry_mixin.GeometryMixin.bodyparts_to_polygon()

Parameters
  • data (np.ndarray) โ€“ NumPy array of body part coordinates. Each subarray represents the coordinates of a geometry in a frame.

  • video_name (Optional[str]) โ€“ Optional video name for progress messages.

  • animal_name (Optional[str]) โ€“ Optional animal name for progress messages.

  • verbose (Optional[bool]) โ€“ If True, prints progress messages. Default False.

  • cap_style (Literal['round', 'square', 'flat']) โ€“ Style of line cap for parallel offset. Options: โ€˜roundโ€™, โ€˜squareโ€™, โ€˜flatโ€™.

  • parallel_offset (int) โ€“ Offset distance for parallel lines. Default is 1.

  • pixels_per_mm (Optional[float]) โ€“ Pixels per millimeter conversion factor.

  • simplify_tolerance (float) โ€“ Tolerance parameter for simplifying geometries. Default is 2.

  • preserve_topology (bool) โ€“ If True, preserves topology during simplification. Default True.

  • core_cnt (int) โ€“ Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores. Ignored if pool is provided.

  • pool (Optional[multiprocessing.Pool]) โ€“ Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.

Returns

A list of polygons with length data.shape[0]

Return type

List[Polygon]

Example

>>> data = np.array([[[364, 308], [383, 323], [403, 335], [423, 351]],[[356, 307], [376, 319], [396, 331], [419, 347]]])
>>> GeometryMixin().multiframe_bodyparts_to_polygon(data=data)
static multiframe_buffer_shapes(geometries, size_mm, pixels_per_mm, core_cnt=- 1, cap_style='round', pool=None)[source]๏ƒ

Buffer shapes by a specified size using multiprocessing.

Multiframe buffer shapes

See also

For single core method, see simba.mixins.geometry_mixin.GeometryMixin.buffer_shape()

Parameters
  • geometries (List[Union[Polygon, LineString]]) โ€“ List of geometries to buffer.

  • size_mm (int) โ€“ Buffer size in millimeters.

  • pixels_per_mm (float) โ€“ Pixels per millimeter conversion factor.

  • core_cnt (int) โ€“ Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores. Ignored if pool is provided.

  • cap_style (Literal["round", "square", "flat"]) โ€“ Style of line cap for buffering. Options: โ€˜roundโ€™, โ€˜squareโ€™, โ€˜flatโ€™. Default โ€˜roundโ€™.

  • pool (Optional[multiprocessing.Pool]) โ€“ Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.

Returns

List of buffered polygons.

Return type

List[Polygon]

multiframe_compute_pct_shape_overlap(shape_1, shape_2, core_cnt=- 1, video_name=None, verbose=False, animal_names=None, denominator='difference', pool=None)[source]๏ƒ

Compute the percentage overlap between corresponding Polygons in two lists.

Multiframe compute pct shape overlap
Parameters
  • shape_1 (List[Polygon]) โ€“ List of Polygons.

  • shape_2 (List[Polygon]) โ€“ List of Polygons with the same length as shape_1.

  • core_cnt (int) โ€“ Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores. Ignored if pool is provided.

  • video_name (Optional[str]) โ€“ If not None, then the name of the video being processed for interpretable progress msgs.

  • verbose (Optional[bool]) โ€“ If True, then prints interpretable progress msgs.

  • animal_names (Optional[Tuple[str]]) โ€“ If not None, then a two-tuple of animal names (or alternative shape names) interpretable progress msgs.

  • denominator (Optional[Literal["difference", "shape_1", "shape_2"]]) โ€“ Denominator for percentage calculation. โ€œdifferenceโ€ uses union minus intersection, โ€œshape_1โ€ uses shape_1 area, โ€œshape_2โ€ uses shape_2 area. Default: โ€œdifferenceโ€.

  • pool (Optional[multiprocessing.Pool]) โ€“ Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.

Returns

List of length shape_1 with percentage overlap between corresponding Polygons.

Return type

np.ndarray

Example

>>> df = read_df(file_path=r"C:/troubleshooting/two_black_animals_14bp/project_folder/csv/outlier_corrected_movement_location/Together_2.csv", file_type='csv').astype(int)
>>> animal_1_cols = [x for x in df.columns if '_1_' in x and not '_p' in x]
>>> animal_2_cols = [x for x in df.columns if '_2_' in x and not '_p' in x]
>>> animal_1_arr = df[animal_1_cols].values.reshape(len(df), int(len(animal_1_cols)/ 2), 2)
>>> animal_2_arr = df[animal_2_cols].values.reshape(len(df), int(len(animal_1_cols)/ 2), 2)
>>> animal_1_geo = GeometryMixin.bodyparts_to_polygon(data=animal_1_arr)
>>> animal_2_geo = GeometryMixin.bodyparts_to_polygon(data=animal_2_arr)
>>> GeometryMixin().multiframe_compute_pct_shape_overlap(shape_1=animal_1_geo, shape_2=animal_2_geo)
multiframe_compute_shape_overlap(shape_1, shape_2, core_cnt=- 1, verbose=False, names=None, pool=None)[source]๏ƒ

Multiprocess compute overlap between corresponding Polygons in two lists.

Note

This function only returns Boolean if two shapes are overlapping or not overlapping. If the amount of overlap is required, use simba.mixins.geometry_mixin.GeometryMixin.compute_pct_shape_overlap() of simba.mixins.geometry_mixin.GeometryMixin.multiframe_compute_pct_shape_overlap().

Note that shape_1 and shape_2 entries can be NoneType. If so no overlap will be in the output for that observation.

Parameters
  • shape_1 (List[Union[Polygon, LineString, None]]) โ€“ List of Polygons, LineStrings, or None.

  • shape_2 (List[Union[Polygon, LineString, None]]) โ€“ List of Polygons, LineStrings, or None with the same length as shape_1.

  • core_cnt (int) โ€“ Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores. Ignored if pool is provided.

  • verbose (Optional[bool]) โ€“ If True, prints progress messages. Default False.

  • names (Optional[Tuple[str]]) โ€“ Optional tuple of names for progress messages.

  • pool (Optional[multiprocessing.Pool]) โ€“ Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.

Return List[int]

List of overlap between corresponding Polygons. If overlap 1, else 0.

Example

>>> df = read_df(file_path=r"C:/troubleshooting/two_black_animals_14bp/project_folder/csv/outlier_corrected_movement_location/Together_2.csv", file_type='csv').astype(int)
>>> animal_1_cols = [x for x in df.columns if '_1_' in x and not '_p' in x]
>>> animal_2_cols = [x for x in df.columns if '_2_' in x and not '_p' in x]
>>> animal_1_arr = df[animal_1_cols].values.reshape(len(df), int(len(animal_1_cols)/ 2), 2)
>>> animal_2_arr = df[animal_2_cols].values.reshape(len(df), int(len(animal_1_cols)/ 2), 2)
>>> animal_1_geo = GeometryMixin.bodyparts_to_polygon(data=animal_1_arr)
>>> animal_2_geo = GeometryMixin.bodyparts_to_polygon(data=animal_2_arr)
>>> GeometryMixin().multiframe_compute_shape_overlap(shape_1=animal_1_geo, shape_2=animal_2_geo)
multiframe_delaunay_triangulate_keypoints(data, core_cnt=- 1, pool=None)[source]๏ƒ

Triangulates a set of 2D keypoints. E.g., can be used to polygonize animal hull, or triangulate a gridpoint arena.

Parameters
  • data (np.ndarray) โ€“ 3D array of keypoints where shape is (frames, keypoints, coordinates).

  • core_cnt (int) โ€“ Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores. Ignored if pool is provided.

  • pool (Optional[multiprocessing.Pool]) โ€“ Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.

Returns

List of lists of Polygon objects representing triangles.

Return type

List[List[Polygon]]

Example

>>> data_path = '/Users/simon/Desktop/envs/troubleshooting/Rat_NOR/project_folder/csv/machine_results/08102021_DOT_Rat7_8(2).csv'
>>> data = pd.read_csv(data_path, index_col=0).head(1000).iloc[:, 0:21]
>>> data = data[data.columns.drop(list(data.filter(regex='_p')))]
>>> animal_data = data.values.reshape(len(data), -1, 2).astype(int)
>>> tri = GeometryMixin().multiframe_delaunay_triangulate_keypoints(data=animal_data)
multiframe_difference(shapes, core_cnt=- 1, verbose=False, animal_names=None, video_name=None, pool=None)[source]๏ƒ

Compute the multi-frame difference for a collection of shapes using parallel processing.

See also

For single core method, see simba.mixins.geometry_mixin.GeometryMixin.difference()

Parameters
  • shapes (Iterable[Union[LineString, Polygon, MultiPolygon]]) โ€“ A collection of shapes, where each shape is a list containing two geometries.

  • core_cnt (int) โ€“ The number of CPU cores to use for parallel processing. Default is -1, which automatically detects the available cores. Ignored if pool is provided.

  • verbose (Optional[bool]) โ€“ If True, print progress messages during computation. Default is False.

  • animal_names (Optional[str]) โ€“ Optional string representing the names of animals for informative messages.

  • video_name (Optional[str]) โ€“ Optional string representing the name of the video for informative messages.

  • pool (Optional[multiprocessing.Pool]) โ€“ Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.

Returns

A list of geometries representing the multi-frame difference.

Return type

List[Union[Polygon, MultiPolygon]]

multiframe_hausdorff_distance(geometries, lag=1, sample_rate=1, core_cnt=- 1, pool=None)[source]๏ƒ

The Hausdorff distance measure of the similarity between sequential time-series geometries.

Parameters
  • geometries (List[Union[Polygon, LineString]]) โ€“ List of geometries.

  • lag (Optional[Union[float, int]]) โ€“ If int, then the number of frames preceeding the current frame to compare the geometry with. Eg., 1 compares the geometry to the immediately preceeding geometry. If float, then evaluated as seconds. E.g., 1 compares the geometry to the geometry 1s prior in the geometries list.

  • sample_rate (Optional[Union[float, int]]) โ€“ The FPS of the recording. Used as conversion factor if lag is a float.

  • core_cnt (Optional[int]) โ€“ The number of cores to use for parallel processing. Default is -1, which uses the maximum available cores. Ignored if pool is provided.

  • pool (Optional[multiprocessing.Pool]) โ€“ Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.

Returns

List of Hausdorff distance measures.

Return type

List[float]

Example

>>> df = read_df(file_path='/Users/simon/Desktop/envs/simba/troubleshooting/mouse_open_field/project_folder/csv/outlier_corrected_movement_location/SI_DAY3_308_CD1_PRESENT.csv', file_type='csv')
>>> cols = [x for x in df.columns if not x.endswith('_p')]
>>> data = df[cols].values.reshape(len(df), -1 , 2).astype(np.int)
>>> geometries = GeometryMixin().multiframe_bodyparts_to_polygon(data=data, pixels_per_mm=1, parallel_offset=1, verbose=False, core_cnt=-1)
>>> hausdorff_distances = GeometryMixin.multiframe_hausdorff_distance(geometries=geometries)
multiframe_is_shape_covered(shape_1, shape_2, core_cnt=- 1, pool=None)[source]๏ƒ

For each shape in time-series of shapes, check if another shape in the same time-series fully covers the first shape.

Multiframe is shape covered
Parameters
  • shape_1 (List[Union[LineString, Polygon, MultiPolygon]]) โ€“ List of geometries to check if covered.

  • shape_2 (List[Union[LineString, Polygon, MultiPolygon]]) โ€“ List of geometries that may cover shape_1.

  • core_cnt (Optional[int]) โ€“ Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores. Ignored if pool is provided.

  • pool (Optional[multiprocessing.Pool]) โ€“ Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.

Returns

List of booleans indicating if each shape_1 is covered by corresponding shape_2.

Return type

List[bool]

Example

>>> shape_1 = GeometryMixin().multiframe_bodyparts_to_polygon(data=np.random.randint(0, 200, (100, 6, 2)))
>>> shape_2 = [Polygon([[0, 0], [20, 20], [20, 10], [10, 20]]) for x in range(len(shape_1))]
>>> GeometryMixin.multiframe_is_shape_covered(shape_1=shape_1, shape_2=shape_2, core_cnt=3)
multiframe_length(shapes, pixels_per_mm, core_cnt=- 1, unit='mm', pool=None)[source]๏ƒ

Calculate the length of LineStrings using multiprocessing.

See also

For single core process, see simba.mixins.geometry_mixin.GeometryMixin.length()

Parameters
  • shapes (List[Union[LineString, MultiLineString]]) โ€“ List of LineString or MultiLineString geometries.

  • pixels_per_mm (float) โ€“ Pixels per millimeter conversion factor.

  • core_cnt (int) โ€“ Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores. Ignored if pool is provided.

  • unit (Literal["mm", "cm", "dm", "m"]) โ€“ Unit of measurement for the result. Options: โ€˜mmโ€™, โ€˜cmโ€™, โ€˜dmโ€™, โ€˜mโ€™. Default: โ€˜mmโ€™.

  • pool (Optional[multiprocessing.Pool]) โ€“ Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.

Returns

List of lengths in the specified unit.

Return type

List[float]

Example

>>> data = np.random.randint(0, 100, (5000, 2))
>>> data = data.reshape(2500,-1, data.shape[1])
>>> lines = GeometryMixin().multiframe_bodyparts_to_line(data=data)
>>> lengths = GeometryMixin().multiframe_length(shapes=lines, pixels_per_mm=1.0)
multiframe_minimum_rotated_rectangle(shapes, video_name=None, verbose=False, animal_name=None, core_cnt=- 1, pool=None)[source]๏ƒ

Compute the minimum rotated rectangle for each Polygon in a list using multiprocessing.

Parameters
  • shapes (List[Polygon]) โ€“ List of Polygons.

  • video_name (Optional[str]) โ€“ Optional video name to print (if verbose is True).

  • animal_name (Optional[str]) โ€“ Optional animal name to print (if verbose is True).

  • verbose (Optional[bool]) โ€“ If True, prints progress.

  • core_cnt (int) โ€“ Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores. Ignored if pool is provided.

  • pool (Optional[multiprocessing.Pool]) โ€“ Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.

Returns

A list of rotated rectangles of size len(shapes).

Return type

List[Polygon]

Example

>>> df = read_df(file_path=r"C:/troubleshooting/two_black_animals_14bp/project_folder/csv/outlier_corrected_movement_location/Together_2.csv", file_type='csv').astype(int)
>>> animal_1_cols = [x for x in df.columns if '_1_' in x and not '_p' in x]
>>> animal_1_arr = df[animal_1_cols].values.reshape(len(df), int(len(animal_1_cols)/ 2), 2)
>>> animal_1_geo = GeometryMixin.bodyparts_to_polygon(data=animal_1_arr)
>>> GeometryMixin().multiframe_minimum_rotated_rectangle(shapes=animal_1_geo)
multiframe_shape_distance(shapes_a, shapes_b, pixels_per_mm=1, unit='mm', verbose=False, core_cnt=- 1, maxchildpertask=8000, shape_names=None, pool=None)[source]๏ƒ

Compute shape distances between corresponding shapes in two lists of LineString or Polygon geometries for multiple frames.

Note

The distance is the minimum Euclidean distance between any point on geometry A and any point on geometry B.

See also

For single core method, see simba.mixins.geometry_mixin.GeometryMixin.shape_distance()

Multiframe shape distance
Parameters
  • shapes_a (List[Union[LineString, Polygon]]) โ€“ List of LineString or Polygon geometries.

  • shapes_b (List[Union[LineString, Polygon]]) โ€“ List of LineString or Polygon geometries with the same length as shapes_a.

  • pixels_per_mm (Optional[float]) โ€“ Conversion factor from pixels to millimeters. Default 1.

  • unit (Literal['mm', 'cm', 'dm', 'm']) โ€“ Unit of measurement for the result. Options: โ€˜mmโ€™, โ€˜cmโ€™, โ€˜dmโ€™, โ€˜mโ€™. Default: โ€˜mmโ€™.

  • verbose (bool) โ€“ If True, prints progress information during computation. Default False.

  • core_cnt (int) โ€“ Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores. Ignored if pool is provided.

  • maxchildpertask (int) โ€“ Maximum number of tasks per child process before restarting. Default is from Defaults.MAXIMUM_MAX_TASK_PER_CHILD. Ignored if pool is provided.

  • shape_names (Optional[str]) โ€“ Optional name identifier for the shapes being compared, used in verbose output. Default None.

  • pool (Optional[multiprocessing.Pool]) โ€“ Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.

Returns

List of shape distances between corresponding shapes in passed unit.

Return type

List[float]

Example

>>> df = read_df(file_path=r"C:/troubleshooting/two_black_animals_14bp/project_folder/csv/outlier_corrected_movement_location/Together_2.csv", file_type='csv').astype(int)
>>> animal_1_cols = [x for x in df.columns if '_1_' in x and not '_p' in x]
>>> animal_2_cols = [x for x in df.columns if '_2_' in x and not '_p' in x]
>>> animal_1_arr = df[animal_1_cols].values.reshape(len(df), int(len(animal_1_cols)/ 2), 2)
>>> animal_2_arr = df[animal_2_cols].values.reshape(len(df), int(len(animal_1_cols)/ 2), 2)
>>> animal_1_geo = GeometryMixin.bodyparts_to_polygon(data=animal_1_arr)
>>> animal_2_geo = GeometryMixin.bodyparts_to_polygon(data=animal_2_arr)
>>> GeometryMixin().multiframe_shape_distance(shapes_a=animal_1_geo, shapes_b=animal_2_geo, pixels_per_mm=2.12, unit='cm')
multiframe_symmetric_difference(shapes, core_cnt=- 1, pool=None)[source]๏ƒ

Compute the symmetric differences between corresponding LineString or MultiLineString geometries using multiprocessing.

Computes a new geometry consisting of the parts that are exclusive to each input geometry.

In other words, it includes the parts that are unique to each geometry while excluding the parts that are common to both.

Parameters
  • shapes (Iterable[Union[LineString, MultiLineString, Polygon]]) โ€“ Iterable collection of shapes where each element is a list containing two geometries.

  • core_cnt (int) โ€“ Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores. Ignored if pool is provided.

  • pool (Optional[multiprocessing.Pool]) โ€“ Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.

Returns

List of symmetric difference geometries.

Return type

List[Union[LineString, MultiLineString, Polygon]]

Example

>>> data_1 = np.random.randint(0, 100, (5000, 2)).reshape(1000,-1, 2)
>>> data_2 = np.random.randint(0, 100, (5000, 2)).reshape(1000,-1, 2)
>>> polygon_1 = GeometryMixin().multiframe_bodyparts_to_polygon(data=data_1)
>>> polygon_2 = GeometryMixin().multiframe_bodyparts_to_polygon(data=data_2)
>>> data = np.array([polygon_1, polygon_2]).T
>>> symmetric_differences = GeometryMixin().multiframe_symmetric_difference(shapes=data)
multiframe_union(shapes, core_cnt=- 1, pool=None)[source]๏ƒ

Join multiple shapes frame-wise into a single shape/

Can be useful to get more accurate representation of animal by joining animal hull (Polygon) together with animal tail (Linestring or second polygon).

See also

For single core method, see simba.mixins.geometry_mixin.GeometryMixin.union()

Parameters
  • shapes (Iterable[Union[LineString, MultiLineString, Polygon]]) โ€“ Iterable collection of shapes (LineString, MultiLineString, or Polygon) to be merged. E.g, of size NxM where N is the number of frames and M is the number of shapes in each frame.

  • core_cnt (int) โ€“ The number of CPU cores to use for parallel processing; defaults to -1, which uses all available cores. Ignored if pool is provided.

  • pool (Optional[multiprocessing.Pool]) โ€“ Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.

Returns

An iterable of merged shapes, where each frame is combined into a single shape.

Return type

List[Union[LineString, MultiLineString, Polygon]]

Example

>>> data_1 = np.random.randint(0, 100, (5000, 2)).reshape(1000,-1, 2)
>>> data_2 = np.random.randint(0, 100, (5000, 2)).reshape(1000,-1, 2)
>>> polygon_1 = GeometryMixin().multiframe_bodyparts_to_polygon(data=data_1)
>>> polygon_2 = GeometryMixin().multiframe_bodyparts_to_polygon(data=data_2)
>>> data = np.array([polygon_1, polygon_2]).T
>>> unions = GeometryMixin().multiframe_union(shapes=data)
multifrm_geometry_histocomparison(video_path, data, shape_type, lag=2, core_cnt=- 1, pixels_per_mm=1, parallel_offset=1, pool=None)[source]๏ƒ

Perform geometry histocomparison on multiple video frames using multiprocessing.

Note

Comparions are made using the intersections of the two image geometries, meaning that the same experimental area of the image and arena is used in the comparison and shifts in animal location cannot account for variability.

Parameters
  • video_path (Union[str, os.PathLike]) โ€“ Path to the video file.

  • data (np.ndarray) โ€“ Input data, typically containing coordinates of one or several body-parts.

  • shape_type (Literal['rectangle', 'circle', 'line']) โ€“ Type of shape for comparison.

  • lag (Optional[int]) โ€“ Number of frames to lag between comparisons. Default is 2.

  • core_cnt (Optional[int]) โ€“ Number of CPU cores to use for parallel processing. Default is -1 which is all available cores. Ignored if pool is provided.

  • pixels_per_mm (int) โ€“ Pixels per millimeter for conversion. Default is 1.

  • parallel_offset (int) โ€“ Size of the geometry ROI in millimeters. Default 1.

  • pool (Optional[multiprocessing.Pool]) โ€“ Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.

Returns

The difference between the successive geometry histograms.

Return type

np.ndarray

Example

>>> data = pd.read_csv('/Users/simon/Desktop/envs/troubleshooting/Emergence/project_folder/csv/outlier_corrected_movement_location/Example_1.csv', nrows=2100, usecols=['Nose_x', 'Nose_y']).fillna(-1).values.astype(np.int64)
>>> results = GeometryMixin().multifrm_geometry_histocomparison(video_path='/Users/simon/Desktop/envs/troubleshooting/Emergence/project_folder/videos/Example_1.mp4', data= data, shape_type='circle', pixels_per_mm=1, parallel_offset=100)
>>> data = pd.read_csv('/Users/simon/Desktop/envs/troubleshooting/Emergence/project_folder/csv/outlier_corrected_movement_location/Example_2.csv', nrows=2100, usecols=['Nose_x', 'Nose_y', 'Tail_base_x' , 'Tail_base_y', 'Center_x' , 'Center_y']).fillna(-1).values.astype(np.int64)
>>> results = GeometryMixin().multifrm_geometry_histocomparison(video_path='/Users/simon/Desktop/envs/troubleshooting/Emergence/project_folder/videos/Example_1.mp4', data= data, shape_type='rectangle', pixels_per_mm=1, parallel_offset=1)
static parallel_offset_polygon(polygon, size_mm, pixels_per_mm)[source]๏ƒ

Offset polygon by scaling from centroid while preserving the exact vertex count.

This is a simple method that moves each vertex along the line from the centroid, preserving the vertex count and shape proportions.

Parallel offset polygon
Parameters
  • polygon (Polygon) โ€“ The input polygon to offset.

  • size_mm (Union[int, float]) โ€“ The offset distance in millimeters. Positive for outward, negative for inward.

  • pixels_per_mm (float) โ€“ The conversion factor from millimeters to pixels.

Returns

The offset polygon with the same vertex count.

Return type

Polygon

Example

>>> polygon = Polygon([(0, 0), (10, 0), (10, 10), (0, 10)])
>>> offset = GeometryMixin().parallel_offset_polygon(polygon=polygon, size_mm=2.0, pixels_per_mm=1.0)
static point_lineside(lines, points)[source]๏ƒ

Determine the relative position of a point (left vs right) with respect to a lines in each frame.

Point lineside
Parameters
  • lines (numpy.ndarray) โ€“ An array of shape (N, 2, 2) representing N lines, where each line is defined by two points. The first point that denotes the beginning of the line, the second point denotes the end of the line.

  • point (numpy.ndarray) โ€“ An array of shape (N, 2) representing N points.

Return np.ndarray

An array of length N containing the results for each line. 2 if the point is on the right side of the line. 1 if the point is on the left side of the line. 0 if the point is on the line.

Example

>>> lines = np.array([[[25, 25], [25, 20]], [[15, 25], [15, 20]], [[15, 25], [50, 20]]]).astype(np.float32)
>>> points = np.array([[20, 0], [15, 20], [90, 0]]).astype(np.float32)
>>> GeometryMixin().point_lineside(lines=lines, points=points)
>>> [1., 0., 1.]
static points_in_polygon(x)[source]๏ƒ

Finds the points that fall inside the respective polygons.

Simba.mixins.geometry mixin.Geometry Mixin.points in polygon
Parameters

x (numba.typed.List) โ€“ List of numpy arrays of size Nx2 representing polygon vertices.

Returns

List of size len(x) of numpy arrays with coordinates representing points inside the polygons.

Return type

numba.typed.List[numba.types.Array]

Example

>>> data_path = r"/mnt/c/troubleshooting/two_black_animals_14bp/project_folder/csv/outlier_corrected_movement_location/Together_1.csv" # PATH TO A DATA FILE
>>> array_1 = read_df(file_path=data_path, file_type='csv', usecols=['Nose_1_x', 'Nose_1_y', 'Tail_base_1_x', 'Tail_base_1_y', 'Lat_left_1_x', 'Lat_left_1_y', 'Lat_right_1_x', 'Lat_right_1_y', 'Ear_left_1_x', 'Ear_left_1_y', 'Ear_right_1_x', 'Ear_right_1_y']).values.reshape(-1, 6, 2)[0:150]## READ THE BODY-PART THAT DEFINES THE HULL AND CONVERT TO ARRAY
>>> polygons = GeometryMixin().multiframe_bodyparts_to_polygon(data=array_1, parallel_offset=50, simplify_tolerance=2, preserve_topology=True)
>>> polygons_lst = typed.List()
>>> for i in polygons: polygons_lst.append(np.array(i.exterior.coords).astype(np.int32))
>>> results = points_in_polygon(polygons_lst)
static rank_shapes(shapes, method, deviation=False, descending=True)[source]๏ƒ

Rank a list of polygon geometries based on a specified method. E.g., order the list of geometries according to sizes or distances to each other or from left to right etc.

Rank shapes
Parameters
  • shapes (List[Polygon]) โ€“ List of Shapely polygons to be ranked. List has to contain two or more shapes.

  • method (Literal["area", "min_distance", "max_distance", "mean_distance", "left_to_right", "top_to_bottom"]) โ€“ The ranking method to use.

  • deviation (Optional[bool]) โ€“ If True, rank based on absolute deviation from the mean. Default: False.

  • descending (Optional[bool]) โ€“ If True, rank in descending order; otherwise, rank in ascending order. Default: False.

Returns

A list of Shapely polygons sorted according to the specified ranking method.

Return type

List[Polygon]

static shape_distance(shapes, pixels_per_mm, unit='mm')[source]๏ƒ

Calculate the distance between two lists of geometries in specified units.

The distance method will compute the shortest distance between the boundaries of the two shapes. If the shapes overlap, the distance will be zero.

Parameters
  • shapes (List[Union[LineString, Polygon, Point]]) โ€“ I 2d numpy array with 2 columns. Rows represent frames and columns represents the shapes to be compared. Or A list of list where each list has two LineString, Polygon or Point geometries.

  • pixels_per_mm (float) โ€“ The conversion factor from pixels to millimeters.

  • unit (Literal['mm', 'cm', 'dm', 'm']) โ€“ The desired unit for the distance calculation. Options: โ€˜mmโ€™, โ€˜cmโ€™, โ€˜dmโ€™, โ€˜mโ€™. Defaults to โ€˜mmโ€™.

Returns

A list of distances between corresponding geometries in the specified unit.

Return type

List[float]

Note

The distance is the minimum Euclidean distance between any point on geometry A and any point on geometry B.

Shape distance Multiframe shape distance
>>> from shapely.geometry import Polygon
>>> shape_1 = Polygon([(0, 0), (10, 0), (10, 10), (0, 10)])
>>> shape_2 = Polygon([(5, 5), (15, 5), (15, 15), (5, 15)])
>>> GeometryMixin.shape_distance(shapes_a=[shape_1], shapes_b=[shape_2], pixels_per_mm=1.0)
[0.0]
static simba_roi_to_geometries(rectangles_df=None, circles_df=None, polygons_df=None, color=False)[source]๏ƒ

Convert SimBA dataframes holding ROI geometries to nested dictionary holding Shapley polygons.

Example

>>> config_path = '/Users/simon/Desktop/envs/simba/troubleshooting/spontenous_alternation/project_folder/project_config.ini'
>>> config = ConfigReader(config_path=config_path)
>>> config.read_roi_data()
>>> GeometryMixin.simba_roi_to_geometries(rectangles_df=config.rectangles_df, circles_df=config.circles_df, polygons_df=config.polygon_df)
static sleap_csv_to_geometries(data, buffer=50, save_path=None, by_track=True)[source]๏ƒ

Convert SLEAP CSV tracking data to polygon geometries for each track and frame.

This function reads SLEAP-exported CSV files containing pose estimation data and converts the body part coordinates into polygon geometries. The polygons are created by connecting the body parts with a specified buffer around the animalโ€™s body outline.

param Union[str, os.PathLike] data

Path to SLEAP CSV file containing tracking data with columns โ€˜trackโ€™, โ€˜frame_idxโ€™, and body part coordinates.

param int buffer

Buffer size in pixels to add around the body part polygon. Default: 10.

param Optional[bool] by_track

If True, create one circle per animal track. If False, then one circle per body-part. Default False.

param Optional[Union[str, os.PathLike]] save_path

Optional path to save the results as a pickle file. If None, returns the data directly.

return

Dictionary with track IDs (or body-part ID) or body-part as keys and frame-to-polygon mappings as values.

rtype

Union[None, Dict[Any, dict]]

example I
>>> results = GeometryMixin.sleap_csv_to_geometries(data=r"C:/troubleshooting/ants/pose_data/ant.csv")
>>> # Results structure: {track_id: {frame_idx: Polygon, ...}, ...}

:example II >>> data_path = rโ€/Users/simon/Desktop/envs/simba/troubleshooting/ant/ant.csvโ€ >>> save_path = rโ€/Users/simon/Desktop/envs/simba/troubleshooting/ant/ant_geometries.pickleโ€ >>> results = GeometryMixin.sleap_csv_to_geometries(data=data_path, save_path=save_path)

static smooth_geometry_bspline(data, smooth_factor=1.0, points=50)[source]๏ƒ

Smooths the geometry of polygons or coordinate arrays using B-spline interpolation.

Accepts an input geometry, which can be a NumPy array, a single Polygon, or a list of Polygons, and applies a B-spline smoothing operation. The degree of smoothing is controlled by smooth_factor, and the number of interpolated points along the new smoothed boundary is determined by points.

Smooth geometry bspline
Parameters
  • data (Union[np.ndarray, Polygon, List[Polygon]]) โ€“ The input geometry to be smoothed. This can be: A NumPy array of shape (N, 2) representing a single polygon. A NumPy array of shape (M, N, 2) representing multiple polygons. A Polygon object from Shapely. A list of Polygon objects.

  • smooth_factor (float) โ€“ The smoothing factor for the B-spline. Higher values result in smoother curves. Must be >= 0.1.

  • points (int) โ€“ The number of interpolated points used to redefine the smoothed polygon boundary. Must be >= 3.

Returns

A list of smoothed polygons obtained by applying B-spline interpolation to the input geometry.

Return type

List[Polygon]

Example

>>> polygon = np.array([[0, 0], [2, 1], [3, 3], [1, 4], [0, 3], [0, 0]])
>>> polygon = Polygon(polygon)
>>> smoothed_polygon = smooth_geometry_bspline(polygon)
static static_point_lineside(lines, point)[source]๏ƒ

Determine the relative position (left vs right) of a static point with respect to multiple lines.

Static point lineside

Note

Modified from rayryeng.

Parameters
  • lines (numpy.ndarray) โ€“ An array of shape (N, 2, 2) representing N lines, where each line is defined by two points. The first point that denotes the beginning of the line, the second point denotes the end of the line.

  • point (numpy.ndarray) โ€“ A 2-element array representing the coordinates of the static point.

Returns

An array of length N containing the results for each line. 2 if the point is on the right side of the line, 1 if the point is on the left side of the line, and 0 if the point is on the line.

Return type

np.ndarray

Example

>>> line = np.array([[[25, 25], [25, 20]], [[15, 25], [15, 20]], [[15, 25], [50, 20]]]).astype(np.float32)
>>> point = np.array([20, 0]).astype(np.float64)
>>> GeometryMixin().static_point_lineside(lines=line, point=point)
>>> [1. 2. 1.]
static symmetric_difference(shapes)[source]๏ƒ

Computes a new geometry consisting of the parts that are exclusive to each input geometry.

In other words, it includes the parts that are unique to each geometry while excluding the parts that are common to both.

Symmetric difference
Parameters

shapes (List[Union[LineString, Polygon, MultiPolygon]]) โ€“ A list of LineString, Polygon, or MultiPolygon geometries to find the symmetric difference.

Returns

A list containing the resulting geometries after performing symmetric difference operations.

Return type

List[Union[Polygon, MultiPolygon]]

Example

>>> polygon_1 = GeometryMixin().bodyparts_to_polygon(np.array([[10, 10], [10, 100], [100, 10], [100, 100]]))
>>> polygon_2 = GeometryMixin().bodyparts_to_polygon(np.array([[1, 25], [1, 75], [110, 25], [110, 75]]))
>>> symmetric_difference = symmetric_difference(shapes=[polygon_1, polygon_2])
static to_linestring(data)[source]๏ƒ

Convert a 2D array of x and y coordinates to a shapely linestring.

Note

Linestrings are useful for representing an animal path, and to answer questions like (i) โ€œHow far along the animals paths was the animal most proximal to geometry Xโ€? โ€œHow far had the animal travelled at time T?โ€ โ€œWhen does the animal path intersect geometry X?โ€

Parameters

data (np.ndarray) โ€“ 2D array with floats or ints of size Nx2 representing body-part coordinates.

Return type

LineString

Example

>>> data = np.load('/Users/simon/Desktop/envs/simba/simba/simba/sandbox/data.npy')
>>> linestring = GeometryMixin.to_linestring(data=data)
static union(shapes)[source]๏ƒ

Compute the union of multiple geometries.

Union
Parameters

shapes (List[Union[LineString, Polygon, MultiPolygon]]) โ€“ A list of LineString, Polygon, or MultiPolygon geometries to be unioned.

Returns

The resulting geometry after performing the union operation.

Return type

Union[MultiPolygon, Polygon]

Example

>>> polygon_1 = GeometryMixin().bodyparts_to_polygon(np.array([[10, 10], [10, 100], [100, 10], [100, 100]]))
>>> polygon_2 = GeometryMixin().bodyparts_to_polygon(np.array([[1, 25],[1, 75],[110, 25],[110, 75]]))
>>> union = GeometryMixin().union(shape = polygon_1, overlap_shapes=[polygon_2, polygon_2])
static view_shapes(shapes, bg_img=None, bg_clr=None, size=None, color_palette='Set1', fill_shapes=False, thickness=2, pixel_buffer=200, circle_size=2)[source]๏ƒ

Draws geometrical shapes (such as LineString, Polygon, MultiPolygon, and MultiLineString) on a white canvas or a specified background image.

This function is useful for quick visual troubleshooting by allowing the inspection of geometrical shapes.

Parameters
  • shapes (List[Union[LineString, Polygon, MultiPolygon, MultiLineString]]) โ€“ A list of geometrical shapes to be drawn. The shapes can be of type LineString, Polygon, MultiPolygon, or MultiLineString.

  • bg_img (Optional[np.ndarray]) โ€“ Optional. An image array (in np.ndarray format) to use as the background. If not provided, a blank canvas will be created.

  • bg_clr (Optional[Tuple[int, int, int]]) โ€“ A tuple representing the RGB color of the background (e.g., (255, 255, 255) for white). This is ignored if bg_img is provided. If None the background is white.

  • size (Optional[int]) โ€“ Optional. An integer to specify the size of the canvas (width and height). Only applicable if bg_img is not provided.

  • color_palette (Optional[str]) โ€“ Optional. A string specifying the color palette to be used for the shapes. Default is โ€˜Set1โ€™, which uses distinct colors. Alternatively, a list of RGB value tuples of same length as shapes.

  • thickness (Optional[int]) โ€“ Optional. An integer specifying the thickness of the lines when rendering LineString or Polygon borders. Default is 2.

  • pixel_buffer (Optional[int]) โ€“ Optional. An integer specifying the number of pixels to add around the bounding box of the shapes for padding. Default is 200.

Returns

An image (np.ndarray) with the rendered shapes.

Return type

np.ndarray

Example

>>> multipolygon_1 = MultiPolygon([Polygon([[200, 110],[200, 100],[200, 100],[200, 110]]), Polygon([[70, 70],[70, 60],[10, 50],[1, 70]])])
>>> polygon_1 = GeometryMixin().bodyparts_to_polygon(np.array([[100, 110],[100, 100],[110, 100],[110, 110]]))
>>> line_1 = GeometryMixin().bodyparts_to_line(np.array([[10, 70],[20, 60],[30, 50],[40, 70]]))
>>> img = GeometryMixin.view_shapes(shapes=[line_1, polygon_1, multipolygon_1])

Geometry GPU methods๏ƒ

simba.data_processors.cuda.geometry.directionality_to_nonstatic_target(left_ear, right_ear, nose, target, verbose=False)[source]๏ƒ

GPU method to calculate if an animal is directing towards a moving point location given the target location and the left ear, right ear, and nose coordinates of the observer.

EXPECTED RUNTIMES

Observations Frames

Mean (s)

Std (s)

Min (s)

Max (s)

10,000

0.147

0.2064

0.0008

0.4389

100,000

0.0056

0.0005

0.0052

0.0063

1,000,000

0.0438

0.0235

0.0239

0.0768

10,000,000

0.2133

0.0046

0.2083

0.2195

50,000,000

1.5716

0.2608

1.2548

1.8936

NVIDIA GeForce RTX 4070

3 ITERATIONS

Directing moving targets
Parameters
  • left_ear (np.ndarray) โ€“ 2D array of size len(frames) x 2 with the coordinates of the observer animals left ear

  • right_ear (np.ndarray) โ€“ 2D array of size len(frames) x 2 with the coordinates of the observer animals right ear

  • nose (np.ndarray) โ€“ 2D array of size len(frames) x 2 with the coordinates of the observer animals nose

  • target (np.ndarray) โ€“ 1D array of with x,y of target location

Returns

2D array of size len(frames) x 4. First column represent the side of the observer that the target is in view. 0 = Left side, 1 = Right side, 2 = Not in view. Second and third column represent the x and y location of the observer animals eye (half-way between the ear and the nose). Fourth column represent if target is view (bool).

Return type

np.ndarray

Example

>>> left_ear = np.random.randint(0, 500, (100, 2))
>>> right_ear = np.random.randint(0, 500, (100, 2))
>>> nose = np.random.randint(0, 500, (100, 2))
>>> target = np.random.randint(0, 500, (100, 2))
>>> directionality_to_nonstatic_target(left_ear=left_ear, right_ear=right_ear, nose=nose, target=target)
simba.data_processors.cuda.geometry.directionality_to_static_targets(left_ear, right_ear, nose, target, verbose=False)[source]๏ƒ

GPU helper to calculate if an animal is directing towards a static location (e.g., ROI centroid), given the target location and the left ear, right ear, and nose coordinates of the observer.

Note

Input left ear, right ear, and nose coordinates of the observer is returned by simba.mixins.feature_extraction_mixin.FeatureExtractionMixin.check_directionality_viable()

Directing static targets

EXPECTED RUNTIMES

FRAMES (MILLIONS)

GPU TIME (S)

GPU TIME (STEV)

10

0.1005

0.02

20

0.1918

0.04

40

0.4382

0.094

80

0.912

0.2904

160

2.8023

1.2692

240

5.7812

3.85148

NVIDIA GeForce RTX 4070

3 ITERATIONS

Parameters
  • left_ear (np.ndarray) โ€“ 2D array of size len(frames) x 2 with the coordinates of the observer animals left ear

  • right_ear (np.ndarray) โ€“ 2D array of size len(frames) x 2 with the coordinates of the observer animals right ear

  • nose (np.ndarray) โ€“ 2D array of size len(frames) x 2 with the coordinates of the observer animals nose

  • target (np.ndarray) โ€“ 1D array of with x,y of target location

Returns

2D array of size len(frames) x 4. First column represent the side of the observer that the target is in view. 0 = Left side, 1 = Right side, 2 = Not in view. Second and third column represent the x and y location of the observer animals eye (half-way between the ear and the nose). Fourth column represent if target is view (bool).

Return type

np.ndarray

Example

>>> left_ear = np.random.randint(0, 500, (100, 2))
>>> right_ear = np.random.randint(0, 500, (100, 2))
>>> nose = np.random.randint(0, 500, (100, 2))
>>> target = np.random.randint(0, 500, (2))
>>> directionality_to_static_targets(left_ear=left_ear, right_ear=right_ear, nose=nose, target=target)
simba.data_processors.cuda.geometry.find_midpoints(x, y, percentile=0.5, batch_size=15000000)[source]๏ƒ

Calculate the midpoints between corresponding points in arrays x and y based on a given percentile using GPU acceleration.

For example, calculate the midpoint between the animal ears (to get presumed nape) or lateral sides (to get presumed center of mass), or nose and left ear (to get left eye) etc.

This function computes the midpoints between each pair of points (x[i], y[i]) from the input arrays x and y. The midpoint is calculated by taking a weighted sum of the differences along each axis, where the weight is determined by the specified percentile. The computation is performed in batches to handle large datasets efficiently.

Find midpoints

EXPECTED RUNTIMES

OBSERVATIONS (MILLIONS)

TIME (S)

STD (S)

2

0.014

0.004

4

0.022

0.015

8

0.039

0.025

16

0.073

0.057

32

0.161

0.102

64

0.290

0.205

128

0.609

0.477

512

2.100

0.9

Parameters
  • x (np.ndarray) โ€“ An array of shape (n, 2) representing the x-coordinates of n points.

  • y (np.ndarray) โ€“ An array of shape (n, 2) representing the y-coordinates of n points.

  • percentile โ€“ A float value between 0 and 1 indicating the percentile to use when calculating the midpoints. The default value is 0.5, which corresponds to the middle.

  • batch_size (Optional[int]) โ€“ An integer specifying the batch size for processing the input arrays. Larger batch sizes will use more memory but may be faster. The default value is 15 million (1.5e+7).

Returns

An array of shape (n, 2) containing the calculated midpoints for each pair of corresponding points in x and y.

Return type

np.ndarray

Example

>>> x = np.random.randint(0, 100, (100, 2)).astype(np.int8)
>>> y = np.random.randint(0, 100, (100, 2)).astype(np.int8)
>>> p = find_midpoints(x=x, y=y)
simba.data_processors.cuda.geometry.get_convex_hull(pts)[source]๏ƒ

Compute the convex hull for each set of 2D points in parallel using CUDA and the Jarvis March algorithm. This function processes a batch of 2D point sets (frames) and computes the convex hull for each set. The convex hull of a set of points is the smallest convex polygon that contains all the points.

The function uses a variant of the Gift Wrapping algorithm (Jarvis March) to compute the convex hull. It finds the leftmost point, then iteratively determines the next point on the hull by checking the orientation of the remaining points. The results are stored in the results array, where each row corresponds to a frame and contains the indices of the points forming the convex hull. Points not on the hull are marked with -1.

EXPECTED RUNTIMES

FRAMES

TIME (S)

110k

0.009

181k

0.014

327k

0.026

620k

0.049

1.2m

0.095

2.4m

0.18

4.7m

0.351

9m

0.865

17.9m

1.452

35.8m

5.695

71.6m

7.243

NVIDIA GeForce RTX 4070

7 body-parts

Note

Implements the Jarvis March (gift-wrapping) convex-hull algorithm (Jarvis, R. A., 1973, Information Processing Letters 2(1): 18-21).

Parameters

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.

Returns

An upated 3D numpy array of shape (M, N, 2) consisting of the points in the hull.

Return type

np.ndarray

Example

>>> video_path = r"/mnt/c/troubleshooting/mitra/project_folder/videos/501_MA142_Gi_CNO_0514.mp4"
>>> data_path = r"/mnt/c/troubleshooting/mitra/project_folder/csv/outlier_corrected_movement_location/501_MA142_Gi_CNO_0514 - test.csv"
>>> df = read_df(file_path=data_path, file_type='csv')
>>> frame_data = df.values.reshape(len(df), -1, 2)
>>> x = get_convex_hull(frame_data)
simba.data_processors.cuda.geometry.is_inside_circle(x, y, r)[source]๏ƒ

Determines whether points in array x are inside the circle with center y and radius r

Is inside circle

EXPECTED RUNTIMES

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

Parameters
  • x (np.ndarray) โ€“ 2d numeric np.ndarray size (N, 2).

  • y (np.ndarray) โ€“ 2d numeric np.ndarray size (1, 2) representing the center of the circle.

  • r (float) โ€“ The radius of the circle.

Returns

2d numeric boolean (N, 1) with 1s representing the point being inside the circle and 0 if the point is outside the rectangle.

Return type

1d np.ndarray vector.

simba.data_processors.cuda.geometry.is_inside_polygon(x, y)[source]๏ƒ

Determines whether points in array x are inside the polygon defined by the vertices in array y.

This function uses GPU acceleration to perform the point-in-polygon test. The points in x are tested against 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.

Simba.data processors.cuda.geometry.is inside polygon

EXPECTED RUNTIMES

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

See also

For jitted CPU function see framewise_inside_polygon_roi()

Parameters
  • x (np.ndarray) โ€“ An array of shape (N, 2) where each row represents a point in 2D space. The points are checked against the polygon.

  • y (np.ndarray) โ€“ An array of shape (M, 2) where each row represents a vertex of the polygon in 2D space.

Returns

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 type

np.ndarray

Example

>>> x = np.random.randint(0, 200, (i, 2)).astype(np.int8)
>>> y = np.random.randint(0, 200, (4, 2)).astype(np.int8)
>>> results = is_inside_polygon(x=x, y=y)
>>> print(results)
>>> [1 0 1 0 1 1 0 0 1 0]
simba.data_processors.cuda.geometry.is_inside_rectangle(x, y)[source]๏ƒ

Determines whether points in array x are inside the rectangle defined by the top left and bottom right vertices in array y. ๐Ÿ˜

Simba.data processors.cuda.geometry.is inside rectangle

EXPECTED RUNTIMES

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

See also

For numba CPU function see framewise_inside_rectangle_roi()

Parameters
  • x (np.ndarray) โ€“ 2d numeric np.ndarray size (N, 2).

  • y (np.ndarray) โ€“ 2d numeric np.ndarray size (2, 2) (top left[x, y], bottom right[x, y])

Returns

2d numeric boolean (N, 1) with 1s representing the point being inside the rectangle and 0 if the point is outside the rectangle.

Return type

np.ndarray

simba.data_processors.cuda.geometry.poly_area(data, pixels_per_mm=1.0, batch_size=5000000)[source]๏ƒ

Compute the area of a polygon using GPU acceleration.

This function calculates the area of polygons defined by sets of points in a 3D array. Each 2D slice along the first dimension represents a polygon, with each row corresponding to a point in the polygon and each column representing the x and y coordinates.

The computation is done in batches to handle large datasets efficiently.

See also

jitted_hull().

Simba.data processors.cuda.geometry.poly area cuda
Parameters
  • 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.

  • pixels_per_mm โ€“ Optional scaling factor to convert the area from pixels squared to square millimeters. Default is 1.0.

  • batch_size โ€“ Optional batch size for processing the data in chunks to fit in memory. Default is 0.5e+7.

Returns

A 1D numpy array of shape (N,) containing the computed area of each polygon in square millimeters.

Return type

np.ndarray