Skip to content

Commit

Permalink
Reorder dimensions (#351)
Browse files Browse the repository at this point in the history
* Draft reorder poses dimensions

* Draft reorder bboxes dimensions

* Set dim order in Validators

* Remove TestLoadPoses class

* Simplify transpose

* Drop transpose from test fixtures

* Reduce test code duplication

* Add docstring for `valid_dataset` helper

* Update docstrings + code comments

* Fix up rebase merge mistake
  • Loading branch information
lochhh authored Dec 6, 2024
1 parent f7539b9 commit e0cdf8e
Show file tree
Hide file tree
Showing 14 changed files with 595 additions and 579 deletions.
3 changes: 2 additions & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
"dependencies": ["environment.yml"],
},
"reference_url": {"movement": None},
"default_thumb_file": "source/_static/data_icon.png", # default thumbnail image
"default_thumb_file": "source/_static/data_icon.png", # default thumbnail image
"remove_config_comments": True,
# do not render config params set as # sphinx_gallery_config [= value]
}
Expand Down Expand Up @@ -206,6 +206,7 @@
intersphinx_mapping = {
"xarray": ("https://docs.xarray.dev/en/stable/", None),
"scipy": ("https://docs.scipy.org/doc/scipy/reference/", None),
"pandas": ("https://pandas.pydata.org/pandas-docs/stable/", None),
}


Expand Down
4 changes: 2 additions & 2 deletions docs/source/user_guide/input_output.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ with three keypoints each: ``snout``, ``centre``, and ``tail_base``. These keypo
import numpy as np

ds = load_poses.from_numpy(
position_array=np.random.rand((100, 2, 3, 2)),
position_array=np.random.rand(100, 2, 3, 2),
confidence_array=np.ones((100, 2, 3)),
individual_names=["Alice", "Bob"],
keypoint_names=["snout", "centre", "tail_base"],
Expand Down Expand Up @@ -256,7 +256,7 @@ with open(filepath, mode="w", newline="") as file:
writer.writerow([frame, individual, x, y, width, height, confidence])

```
Alternatively, we can convert the `movement` bounding boxes' dataset to a pandas DataFrame with the {func}`.xarray.DataArray.to_dataframe()` method, wrangle the dataframe as required, and then apply the {func}`.pandas.DataFrame.to_csv()` method to save the data as a .csv file.
Alternatively, we can convert the `movement` bounding boxes' dataset to a pandas DataFrame with the {meth}`xarray.DataArray.to_dataframe` method, wrangle the dataframe as required, and then apply the {meth}`pandas.DataFrame.to_csv` method to save the data as a .csv file.


(target-sample-data)=
Expand Down
38 changes: 19 additions & 19 deletions docs/source/user_guide/movement_dataset.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,15 @@ print(ds)
and we would obtain an output such as:
```
<xarray.Dataset> Size: 27kB
Dimensions: (time: 601, individuals: 3, keypoints: 1, space: 2)
Dimensions: (time: 601, space: 2, keypoints: 1, individuals: 3)
Coordinates:
* time (time) float64 5kB 0.0 0.02 0.04 0.06 ... 11.96 11.98 12.0
* individuals (individuals) <U10 120B 'AEON3B_NTP' 'AEON3B_TP1' 'AEON3B_TP2'
* keypoints (keypoints) <U8 32B 'centroid'
* space (space) <U1 8B 'x' 'y'
* keypoints (keypoints) <U8 32B 'centroid'
* individuals (individuals) <U10 120B 'AEON3B_NTP' 'AEON3B_TP1' 'AEON3B_TP2'
Data variables:
position (time, individuals, keypoints, space) float32 14kB 770.3 ......
confidence (time, individuals, keypoints) float32 7kB nan nan ... nan nan
position (time, space, keypoints, individuals) float32 14kB 770.3 ......
confidence (time, keypoints, individuals) float32 7kB nan nan ... nan nan
Attributes:
fps: 50.0
time_unit: seconds
Expand All @@ -78,14 +78,14 @@ print(ds)
and the last command would print out:
```
<xarray.Dataset> Size: 19kB
Dimensions: (time: 5, individuals: 86, space: 2)
Dimensions: (time: 5, space: 2, individuals: 86)
Coordinates:
* time (time) int64 40B 0 1 2 3 4
* individuals (individuals) <U5 2kB 'id_1' 'id_2' 'id_3' ... 'id_89' 'id_90'
* space (space) <U1 8B 'x' 'y'
* individuals (individuals) <U5 2kB 'id_1' 'id_2' 'id_3' ... 'id_89' 'id_90'
Data variables:
position (time, individuals, space) float64 7kB 871.8 ... 905.3
shape (time, individuals, space) float64 7kB 60.0 53.0 ... 51.0 36.0
position (time, space, individuals) float64 7kB 901.8 ... 923.3
shape (time, space, individuals) float64 7kB 60.0 30.0 ... 72.0 36.0
confidence (time, individuals) float64 3kB nan nan nan nan ... nan nan nan
Attributes:
fps: None
Expand Down Expand Up @@ -114,25 +114,25 @@ the labelled "ticks" along each axis are called **coordinates** (`coords`).
:::{tab-item} Poses dataset
A `movement` poses dataset has the following **dimensions**:
- `time`, with size equal to the number of frames in the video.
- `individuals`, with size equal to the number of tracked individuals/instances.
- `keypoints`, with size equal to the number of tracked keypoints per individual.
- `space`, which is the number of spatial dimensions. Currently, we support only 2D poses.
- `keypoints`, with size equal to the number of tracked keypoints per individual.
- `individuals`, with size equal to the number of tracked individuals/instances.
:::

:::{tab-item} Bounding boxes' dataset
A `movement` bounding boxes dataset has the following **dimensions**s:
- `time`, with size equal to the number of frames in the video.
- `individuals`, with size equal to the number of tracked individuals/instances.
- `space`, which is the number of spatial dimensions. Currently, we support only 2D bounding boxes data.
- `individuals`, with size equal to the number of tracked individuals/instances.
Notice that these are the same dimensions as for a poses dataset, except for the `keypoints` dimension.
:::
::::

In both cases, appropriate **coordinates** are assigned to each **dimension**.
- `individuals` are labelled with a list of unique names (e.g. `mouse1`, `mouse2`, etc. or `id_0`, `id_1`, etc.).
- `keypoints` are likewise labelled with a list of unique body part names, e.g. `snout`, `right_ear`, etc. Note that this dimension only exists in the poses dataset.
- `space` is labelled with either `x`, `y` (2D) or `x`, `y`, `z` (3D). Note that bounding boxes datasets are restricted to 2D space.
- `time` is labelled in seconds if `fps` is provided, otherwise the **coordinates** are expressed in frames (ascending 0-indexed integers).
- `space` is labelled with either `x`, `y` (2D) or `x`, `y`, `z` (3D). Note that bounding boxes datasets are restricted to 2D space.
- `keypoints` are likewise labelled with a list of unique body part names, e.g. `snout`, `right_ear`, etc. Note that this dimension only exists in the poses dataset.
- `individuals` are labelled with a list of unique names (e.g. `mouse1`, `mouse2`, etc. or `id_0`, `id_1`, etc.).

:::{dropdown} Additional dimensions
:color: info
Expand All @@ -156,14 +156,14 @@ The specific data variables stored are slightly different between a `movement` p
::::{tab-set}
:::{tab-item} Poses dataset
A `movement` poses dataset contains two **data variables**:
- `position`: the 2D or 3D locations of the keypoints over time, with shape (`time`, `individuals`, `keypoints`, `space`).
- `confidence`: the confidence scores associated with each predicted keypoint (as reported by the pose estimation model), with shape (`time`, `individuals`, `keypoints`).
- `position`: the 2D or 3D locations of the keypoints over time, with shape (`time`, `space`, `keypoints`, `individuals`).
- `confidence`: the confidence scores associated with each predicted keypoint (as reported by the pose estimation model), with shape (`time`, `keypoints`, `individuals`).
:::

:::{tab-item} Bounding boxes' dataset
A `movement` bounding boxes dataset contains three **data variables**:
- `position`: the 2D locations of the bounding boxes' centroids over time, with shape (`time`, `individuals`, `space`).
- `shape`: the width and height of the bounding boxes over time, with shape (`time`, `individuals`, `space`).
- `position`: the 2D locations of the bounding boxes' centroids over time, with shape (`time`, `space`, `individuals`).
- `shape`: the width and height of the bounding boxes over time, with shape (`time`, `space`, `individuals`).
- `confidence`: the confidence scores associated with each predicted bounding box, with shape (`time`, `individuals`).
:::
::::
Expand Down
26 changes: 13 additions & 13 deletions movement/io/load_bboxes.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ def from_numpy(
Parameters
----------
position_array : np.ndarray
Array of shape (n_frames, n_individuals, n_space)
Array of shape (n_frames, n_space, n_individuals)
containing the tracks of the bounding boxes' centroids.
It will be converted to a :class:`xarray.DataArray` object
named "position".
shape_array : np.ndarray
Array of shape (n_frames, n_individuals, n_space)
Array of shape (n_frames, n_space, n_individuals)
containing the shape of the bounding boxes. The shape of a bounding
box is its width (extent along the x-axis of the image) and height
(extent along the y-axis of the image). It will be converted to a
Expand All @@ -56,7 +56,7 @@ def from_numpy(
If None (default), bounding boxes are assigned names based on the size
of the ``position_array``. The names will be in the format of
``id_<N>``, where <N> is an integer from 0 to
``position_array.shape[1]-1`` (i.e., "id_0", "id_1"...).
``position_array.shape[-1]-1`` (i.e., "id_0", "id_1"...).
frame_array : np.ndarray, optional
Array of shape (n_frames, 1) containing the frame numbers for which
bounding boxes are defined. If None (default), frame numbers will
Expand Down Expand Up @@ -376,9 +376,9 @@ def _numpy_arrays_from_via_tracks_file(
The extracted numpy arrays are returned in a dictionary with the following
keys:
- position_array (n_frames, n_individuals, n_space):
- position_array (n_frames, n_space, n_individuals):
contains the trajectories of the bounding boxes' centroids.
- shape_array (n_frames, n_individuals, n_space):
- shape_array (n_frames, n_space, n_individuals):
contains the shape of the bounding boxes (width and height).
- confidence_array (n_frames, n_individuals):
contains the confidence score of each bounding box.
Expand Down Expand Up @@ -428,11 +428,11 @@ def _numpy_arrays_from_via_tracks_file(
df[map_key_to_columns[key]].to_numpy(),
indices_id_switch, # indices along axis=0
)
array_dict[key] = np.stack(list_arrays, axis=1)
array_dict[key] = np.stack(list_arrays, axis=-1)

# squeeze only last dimension if it is 1
if array_dict[key].shape[-1] == 1:
array_dict[key] = array_dict[key].squeeze(axis=-1)
if array_dict[key].shape[1] == 1:
array_dict[key] = array_dict[key].squeeze(axis=1)

# Transform position_array to represent centroid of bbox,
# rather than top-left corner
Expand Down Expand Up @@ -677,21 +677,21 @@ def _ds_from_valid_data(data: ValidBboxesDataset) -> xr.Dataset:
time_unit = "seconds"

# Convert data to an xarray.Dataset
# with dimensions ('time', 'individuals', 'space')
# with dimensions ('time', 'space', 'individuals')
DIM_NAMES = ValidBboxesDataset.DIM_NAMES
n_space = data.position_array.shape[-1]
n_space = data.position_array.shape[1]
return xr.Dataset(
data_vars={
"position": xr.DataArray(data.position_array, dims=DIM_NAMES),
"shape": xr.DataArray(data.shape_array, dims=DIM_NAMES),
"confidence": xr.DataArray(
data.confidence_array, dims=DIM_NAMES[:-1]
data.confidence_array, dims=DIM_NAMES[:1] + DIM_NAMES[2:]
),
},
coords={
DIM_NAMES[0]: time_coords,
DIM_NAMES[1]: data.individual_names,
DIM_NAMES[2]: ["x", "y", "z"][:n_space],
DIM_NAMES[1]: ["x", "y", "z"][:n_space],
DIM_NAMES[2]: data.individual_names,
},
attrs={
"fps": data.fps,
Expand Down
Loading

0 comments on commit e0cdf8e

Please sign in to comment.