Skip to content

Commit

Permalink
merge develop
Browse files Browse the repository at this point in the history
  • Loading branch information
eberrigan committed Dec 18, 2024
1 parent 32aa5ee commit 6dc7a12
Show file tree
Hide file tree
Showing 26 changed files with 439 additions and 51 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/website.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
# 'main' triggers updates to 'sleap.ai', all others to 'sleap.ai/develop'
- main
- develop
- liezl/update-intallation-docs-1.4.1 # again!
- liezl/add-channels-for-pip-conda-env
paths:
- "docs/**"
- "README.rst"
Expand Down
Binary file added docs/_static/bonsai-connection.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_static/bonsai-filecapture.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_static/bonsai-predictcentroids.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_static/bonsai-predictposeidentities.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_static/bonsai-predictposes.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_static/bonsai-workflow.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
75 changes: 75 additions & 0 deletions docs/guides/bonsai.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
(bonsai)=

# Using Bonsai with SLEAP

Bonsai is a visual language for reactive programming and currently supports SLEAP models.

:::{note}
Currently Bonsai supports only single instance, top-down and top-down-id SLEAP models.
:::

### Exporting a SLEAP trained model

Before we can import a trained model into Bonsai, we need to use the {code}`sleap-export` command to convert the model to a format supported by Bonsai. For example, to export a top-down-id model, the command is as follows:

```bash
sleap-export -m centroid/model/folder/path -m top_down_id/model/folder/path -e exported/model/path
```

Please refer to the {ref}`sleap-export` docs for more details on using the command.

This will generate the necessary `.pb` file and other information files required by Bonsai. In this example, these files were saved to the specified `exported/model/path` folder.

The `exported/model/path` folder will have a structure like the following:

```plaintext
exported/model/path
├── centroid_config.json
├── confmap_config.json
├── frozen_graph.pb
└── info.json
```

### Installing Bonsai and necessary packages

1. Install Bonsai. See the [Bonsai installation instructions](https://bonsai-rx.org/docs/articles/installation.html).

2. Download and add the necessary packages for Bonsai to run with SLEAP. See the official [Bonsai SLEAP documentation](https://github.com/bonsai-rx/sleap?tab=readme-ov-file#bonsai---sleap) for more information.

### Using Bonsai SLEAP modules

Once you have Bonsai installed with the required packages, you should be able to open the Bonsai application. The workflow must have a source module `FileCapture` which can be found in the toolbox search in the workflow editor. Provide the path to the video that was used to train the SLEAP model in the `FileName` field of the module.

![Bonsai FileCapture module](../_static/bonsai-filecapture.jpg)

#### Top-down model
The top-down model requires both the `PredictCentroids` and the `PredictPoses` modules.

The `PredictCentroids` module will predict the centroids of detections. There are two fields inside the `PredictCentroids` module: the `ModelFileName` field and the `TrainingConfig` field. The `TrainingConfig` field expects the path to the training config JSON file for the centroid model. The `ModelFileName` field expects the path to the `frozen_graph.pb` file in the `exported/model/path` folder.

![Bonsai PredictCentroids module](../_static/bonsai-predictcentroids.jpg)

The `PredictPoses` module will predict the instances of detections. Similar to the `PredictCentroid` module, there are two fields inside the `PredictPoses` module: the `ModelFileName` field and the `TrainingConfig` field. The `TrainingConfig` field expects the path to the training config JSON file for the centered instance model. The `ModelFileName` field expects the path to the `frozen_graph.pb` file in the `exported/model/path` folder.

![Bonsai PredictPoses module](../_static/bonsai-predictposes.jpg)

#### Top-Down-ID model
The `PredictPoseIdentities` module will predict the instances with identities. This module has two fields: the `ModelFileName` field and the `TrainingConfig` field. The `TrainingConfig` field expects the path to the training config JSON file for the top-down-id model. The `ModelFileName` field expects the path to the `frozen_graph.pb` file in the `exported/model/path` folder.

![Bonsai PredictPoseIdentities module](../_static/bonsai-predictposeidentities.jpg)

#### Single instance model
The `PredictSinglePose` module will predict the poses for single instance models. This module also has two fields: the `ModelFileName` field and the `TrainingConfig` field. The `TrainingConfig` field expects the path to the training config JSON file for the single instance model. The `ModelFileName` field expects the path to the `frozen_graph.pb` file in the `exported/model/path` folder.

### Connecting the modules
Right-click on the `FileCapture` module and select **Create Connection**. Now click on the required SLEAP module to complete the connection.

![Bonsai module connection ](../_static/bonsai-connection.jpg)

Once it is done, the workflow in Bonsai will look something like the following:

![Bonsai.SLEAP workflow](../_static/bonsai-workflow.jpg)

Now you can click the green start button to run the workflow and you can add more modules to analyze and visualize the results in Bonsai.

For more documentation on various modules and workflows, please refer to the [official Bonsai docs](https://bonsai-rx.org/docs/articles/editor.html).
5 changes: 5 additions & 0 deletions docs/guides/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@

{ref}`remote-inference` when you trained models and you want to run inference on a different machine using a **command-line interface**.

## SLEAP with Bonsai

{ref}`bonsai` when you want to analyze the trained SLEAP model to visualize the poses, centroids and identities for further visual analysis.

```{toctree}
:hidden: true
:maxdepth: 2
Expand All @@ -44,4 +48,5 @@ proofreading
colab
custom-training
remote
bonsai
```
13 changes: 6 additions & 7 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ local:
Installation requires entering commands in a terminal. To open one:
````{tabs}
```{tab} Windows
Open the *Start menu* and search for the *Anaconda Prompt* (if using Miniconda) or the *Command Prompt* if not.
Open the *Start menu* and search for the *Anaconda Prompt* (if using Miniconda) or the *Command Prompt* if not.
```{note}
On Windows, our personal preference is to use alternative terminal apps like [Cmder](https://cmder.net) or [Windows Terminal](https://aka.ms/terminal).
```
Expand Down Expand Up @@ -66,7 +66,6 @@ If you don't have a `conda` package manager installation, here are some quick in

Miniforge is a minimal installer for conda that includes the `conda` package manager and is maintained by the [conda-forge](https://conda-forge.org) community. The only difference between Miniforge and Miniconda is that Miniforge uses the `conda-forge` channel by default, which provides a much wider selection of community-maintained packages.


````{tabs}
```{group-tab} Windows
Open a new PowerShell terminal (does not need to be admin) and enter:
Expand Down Expand Up @@ -135,20 +134,20 @@ This is a minimal installer for conda that includes the `conda` package manager

See the [Miniconda website](https://docs.anaconda.com/free/miniconda/) for up-to-date installation instructions if the above instructions don't work for your system.


(installation-methods)=

## Installation methods

SLEAP can be installed three different ways: via {ref}`conda package<condapackage>`, {ref}`conda from source<condasource>`, or {ref}`pip package<pippackage>`. Select one of the methods below to install SLEAP. We recommend {ref}`conda package<condapackage>`.

````{tabs}
`````{tabs}
```{tab} conda package
**This is the recommended installation method**.
````{tabs}
```{group-tab} Windows and Linux
```bash
conda create -y -n sleap -c conda-forge -c nvidia -c sleap -c anaconda sleap=1.4.1a2
```
```
```{note}
- This comes with CUDA to enable GPU support. All you need is to have an NVIDIA GPU and [updated drivers](https://nvidia.com/drivers).
- If you already have CUDA installed on your system, this will not conflict with it.
Expand Down Expand Up @@ -222,7 +221,7 @@ SLEAP can be installed three different ways: via {ref}`conda package<condapackag
````{tabs}
```{group-tab} NVIDIA GPU
```bash
conda create --name sleap pip python=3.7.12 cudatoolkit=11.3 cudnn=8.2
conda create --name sleap pip python=3.7.12 cudatoolkit=11.3 cudnn=8.2 -c conda-forge -c nvidia
```
```
```{group-tab} CPU or other GPU
Expand Down Expand Up @@ -256,7 +255,7 @@ SLEAP can be installed three different ways: via {ref}`conda package<condapackag
```
````
```
````
`````

## Testing that things are working

Expand Down
13 changes: 13 additions & 0 deletions sleap/config/frame_range_form.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
main:

- name: min_frame_idx
label: Minimum frame index
type: int
range: 1,1000000
default: 1

- name: max_frame_idx
label: Maximum frame index
type: int
range: 1,1000000
default: 1000
19 changes: 15 additions & 4 deletions sleap/gui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -656,17 +656,18 @@ def prev_vid():
key="edge style",
)

# XXX
add_submenu_choices(
menu=viewMenu,
title="Node Marker Size",
options=(1, 2, 4, 6, 8, 12),
options=prefs["node marker sizes"],
key="marker size",
)

add_submenu_choices(
menu=viewMenu,
title="Node Label Size",
options=(6, 12, 18, 24, 36),
options=prefs["node label sizes"],
key="node label size",
)

Expand Down Expand Up @@ -804,6 +805,12 @@ def new_instance_menu_action():
"Delete Predictions beyond Max Instances...",
self.commands.deleteInstanceLimitPredictions,
)
add_menu_item(
labelMenu,
"delete frame limit predictions",
"Delete Predictions beyond Frame Limit...",
self.commands.deleteFrameLimitPredictions,
)

### Tracks Menu ###

Expand Down Expand Up @@ -873,6 +880,8 @@ def new_instance_menu_action():
"Point Displacement (max)",
"Primary Point Displacement (sum)",
"Primary Point Displacement (max)",
"Tracking Score (mean)",
"Tracking Score (min)",
"Instance Score (sum)",
"Instance Score (min)",
"Point Score (sum)",
Expand Down Expand Up @@ -1331,7 +1340,7 @@ def updateStatusMessage(self, message: Optional[str] = None):
message += f" [Hidden] Press '{hide_key}' to toggle."
self.statusBar().setStyleSheet("color: red")
else:
self.statusBar().setStyleSheet("color: black")
self.statusBar().setStyleSheet("")

self.statusBar().showMessage(message)

Expand Down Expand Up @@ -1406,6 +1415,8 @@ def _set_seekbar_header(self, graph_name: str):
"Point Displacement (max)": data_obj.get_point_displacement_series,
"Primary Point Displacement (sum)": data_obj.get_primary_point_displacement_series,
"Primary Point Displacement (max)": data_obj.get_primary_point_displacement_series,
"Tracking Score (mean)": data_obj.get_tracking_score_series,
"Tracking Score (min)": data_obj.get_tracking_score_series,
"Instance Score (sum)": data_obj.get_instance_score_series,
"Instance Score (min)": data_obj.get_instance_score_series,
"Point Score (sum)": data_obj.get_point_score_series,
Expand All @@ -1419,7 +1430,7 @@ def _set_seekbar_header(self, graph_name: str):
else:
if graph_name in header_functions:
kwargs = dict(video=self.state["video"])
reduction_name = re.search("\\((sum|max|min)\\)", graph_name)
reduction_name = re.search("\\((sum|max|min|mean)\\)", graph_name)
if reduction_name is not None:
kwargs["reduction"] = reduction_name.group(1)
series = header_functions[graph_name](**kwargs)
Expand Down
35 changes: 35 additions & 0 deletions sleap/gui/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class which inherits from `AppCommand` (or a more specialized class such as
from sleap.gui.dialogs.merge import MergeDialog, ReplaceSkeletonTableDialog
from sleap.gui.dialogs.message import MessageDialog
from sleap.gui.dialogs.missingfiles import MissingFilesDialog
from sleap.gui.dialogs.frame_range import FrameRangeDialog
from sleap.gui.state import GuiState
from sleap.gui.suggestions import VideoFrameSuggestions
from sleap.instance import Instance, LabeledFrame, Point, PredictedInstance, Track
Expand Down Expand Up @@ -494,6 +495,10 @@ def deleteInstanceLimitPredictions(self):
"""Gui for deleting instances beyond some number in each frame."""
self.execute(DeleteInstanceLimitPredictions)

def deleteFrameLimitPredictions(self):
"""Gui for deleting instances beyond some frame number."""
self.execute(DeleteFrameLimitPredictions)

def completeInstanceNodes(self, instance: Instance):
"""Adds missing nodes to given instance."""
self.execute(AddMissingInstanceNodes, instance=instance)
Expand Down Expand Up @@ -2472,6 +2477,36 @@ def ask(cls, context: CommandContext, params: dict) -> bool:
return super().ask(context, params)


class DeleteFrameLimitPredictions(InstanceDeleteCommand):
@staticmethod
def get_frame_instance_list(context: CommandContext, params: Dict):
"""Called from the parent `InstanceDeleteCommand.ask` method.
Returns:
List of instances to be deleted.
"""
instances = []
# Select the instances to be deleted
for lf in context.labels.labeled_frames:
if lf.frame_idx < (params["min_frame_idx"] - 1) or lf.frame_idx > (
params["max_frame_idx"] - 1
):
instances.extend([(lf, inst) for inst in lf.instances])
return instances

@classmethod
def ask(cls, context: CommandContext, params: Dict) -> bool:
current_video = context.state["video"]
dialog = FrameRangeDialog(
title="Delete Instances in Frame Range...", max_frame_idx=len(current_video)
)
results = dialog.get_results()
if results:
params["min_frame_idx"] = results["min_frame_idx"]
params["max_frame_idx"] = results["max_frame_idx"]
return super().ask(context, params)


class TransposeInstances(EditCommand):
topics = [UpdateTopic.project_instances, UpdateTopic.tracks]

Expand Down
38 changes: 25 additions & 13 deletions sleap/gui/dataviews.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,17 @@
"""

from qtpy import QtCore, QtWidgets, QtGui

import numpy as np
import os

from operator import itemgetter
from pathlib import Path
from typing import Any, Callable, List, Optional

from typing import Any, Callable, Dict, List, Optional, Type
import numpy as np
from qtpy import QtCore, QtGui, QtWidgets

from sleap.gui.state import GuiState
from sleap.gui.commands import CommandContext
from sleap.gui.color import ColorManager
from sleap.io.dataset import Labels
from sleap.instance import LabeledFrame, Instance
from sleap.gui.state import GuiState
from sleap.instance import LabeledFrame
from sleap.skeleton import Skeleton


Expand Down Expand Up @@ -386,10 +383,25 @@ def getSelectedRowItem(self) -> Any:


class VideosTableModel(GenericTableModel):
properties = ("filename", "frames", "height", "width", "channels")

def item_to_data(self, obj, item):
return {key: getattr(item, key) for key in self.properties}
properties = (
"name",
"filepath",
"frames",
"height",
"width",
"channels",
)

def item_to_data(self, obj, item: "Video"):
data = {}
for property in self.properties:
if property == "name":
data[property] = Path(item.filename).name
elif property == "filepath":
data[property] = str(Path(item.filename).parent)
else:
data[property] = getattr(item, property)
return data


class SkeletonNodesTableModel(GenericTableModel):
Expand Down
Loading

0 comments on commit 6dc7a12

Please sign in to comment.