Open getzze opened 3 months ago
The recent updates enhance the tracking capabilities and usability of the system. Key modifications include streamlined command-line usage, refined configuration parameters, improved inference logic, and better handling of tracking instances. These changes collectively aim to simplify user interactions, optimize performance, and clarify documentation, ensuring a more robust and efficient tracking experience.
Files | Change Summary |
---|---|
docs/guides/cli.md |
Updated command-line examples, replacing --max_tracking with --max_tracks to simplify options. |
sleap/config/pipeline_form.yaml |
Removed tracking.max_tracking and added tracking.save_shifted_instances for improved tracking configuration. |
sleap/gui/learning/runners.py |
Improved handling of inference parameters and tracker name compatibility. |
sleap/instance.py |
Introduced all_disjoint and create_merged_instances functions for better instance processing. |
sleap/nn/inference.py |
Enhanced prediction efficiency and backward compatibility, refined data processing workflow. |
sleap/nn/tracker/components.py |
Added merging logic to cull_frame_instances for better instance management. |
sleap/nn/tracking.py |
Restructured candidate-making logic with new abstract base class for flexibility. |
tests/nn/test_inference.py |
Updated tests to reflect new tracking approach and improved performance handling. |
tests/nn/test_tracker_components.py |
Renamed functions and added parameters for dynamic image scaling in tracking tests. |
tests/nn/test_tracking_integration.py |
Refactored tracking tests for improved encapsulation and clarity. |
sequenceDiagram
participant User
participant CLI
participant Tracker
participant Inference
User->>CLI: Execute tracking command
CLI->>Tracker: Initialize tracker
Tracker->>Inference: Process data
Inference-->>Tracker: Return results
Tracker-->>CLI: Output results
CLI-->>User: Display tracking results
π In the meadow, I hop with glee,
New changes made, oh what a spree!
Tracks refined and paths made clear,
With every hop, I shed a cheer!
Letβs bound ahead, no need to look back,
In our swift dance, weβre on the right track! πΌβ¨
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?
Attention: Patch coverage is 65.46053%
with 105 lines
in your changes missing coverage. Please review.
Project coverage is 75.21%. Comparing base (
7ed1229
) to head (06ef136
). Report is 56 commits behind head on develop.
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
Hi @getzze,
Thanks for this!
We notice that you tried to simplify our tracking method by removing max_tracking
. In SLEAP, we currently support 2 candidate generation methods: Fixed window and Local queues. In Fixed window
method, instances from the last window_size
frames are stored in the tracker queue. This method focuses on recent predictions but can lose a track if no detections are assigned for window_size
frames. In Local queues
method, the last window_size
instances for each track are stored in the tracker queue. This method is better at connecting detections over gaps but doesn't rely on recent predictions. To support both the methods, we use two different data structures (a deque
for fixed window method and a dict[track_id, deque]
for local queues) and also max_tracking
parameter to determine the type of candidate generation method to be used.
The PR refactors the code to implement a fixed window
method with the option of limiting the number of new tracks being created. However, this does not support the local queues
method, which is preferred in some cases (videos where tracks are to be connected after short gaps).
Let us know if you have any questions!
Thanks!
Divya
Hi @gitttt-1234,
Thanks for reviewing the PR!
I chose to remove the Local queues
alternative because I thought its only point was to limit the number of tracks/identities. I didn't get that it could be used if only one instance is hidden for some time. Anyway, I think it's very unlikely that the instance would reappear exactly at the exact same place it disappeared, so it is matched to the track that disappeared. In my tests, I always got tracks that become empty after the instance is lost for window_size
time steps, they are never "caught" again by the track.
Instead I added an (optional) step to assign an instance to a track that has disappeared for more than window_size
time steps, which works better in my tests (two mice).
Something I didn't do in this PR and that should probably be done, is to put back the pre_cull_to_target
option in the GUI, to cap the number of instances before assigning them to tracks (whereas max_tracks
cap them after assigning them to tracks), to be able to get back the pre-#1447 behavior.
Hi @getzze,
Thanks for the contributions! Apologies it's taken us so long to work through it all, but we did have some questions and concerns about the changes. @gitttt-1234 described those inline, but I think maybe there's also some misunderstandings that I want to clarify to make sure we're on the same page -- see below.
Hi @gitttt-1234,
Thanks for reviewing the PR!
I chose to remove the
Local queues
alternative because I thought its only point was to limit the number of tracks/identities.
As @gitttt-1234 mentioned, the point of the local queues is indeed to limit the maximum number of tracks that can be possibly spawned in an entire project, but it does so using a specific algorithmic approach.
By specifying the maximum number of tracks, what we can do is create buffers (up to that limit) that hold the past window_size
detected instances that have been assigned to that track. This means that if we don't see an instance from track_0
for 1000+ frames, we're still able to assign a new instance to track_0
back to that track.
The main thing this solves is that it prevents the explosion of new tracklets being created in scenarios where there is a lot of track fragmentation due to frequent occlusions (or lots of animals in the FOV).
I didn't get that it could be used if only one instance is hidden for some time.
Again, to be clear, with local track queues, the instances can be used no matter how many instances are hidden and no matter the amount of time.
Anyway, I think it's very unlikely that the instance would reappear exactly at the exact same place it disappeared, so it is matched to the track that disappeared.
Using instances from a long time in the past is not great since the detections will be pretty stale, but it can work well when animals are sleeping or hiding under a hut or something else out of view. It does not, however, mean that the instance needs to reappear in the exact same place. It just means that the last detection(s) from that track will still be in consideration during the candidate generation and matching steps.
If we lose a track for a long amount of time, the truly appropriate thing to do would be to explicitly use ReID features, but we don't have that implemented in here yet, so in the meantime, this is a "best effort" approach that doesn't require any new information to be integrated in the algorithm.
In my tests, I always got tracks that become empty after the instance is lost for
window_size
time steps, they are never "caught" again by the track.
Could you elaborate on what you mean by that? How does a track "become empty"?
Again, by design, if an instance is lost for more than window_size
time steps, when using the local queues in simplemaxtracks
or flowmaxtracks
, last few instances that have been assigned to a track can never be lost no matter the gap length since they're stored in queues local to the track (ergo, "track local queues").
Instead I added an (optional) step to assign an instance to a track that has disappeared for more than
window_size
time steps, which works better in my tests (two mice).
The track local queues should handle this explicitly. We don't make a distinction between tracks that are "stale" vs "active", but treat all instances from all queues as valid candidates. I could see an advantage to separating these two (e.g., for preferring active tracks that have recent detections before considering the stale ones), but this is fundamentally a different tracking algorithm and not a replacement for what we have now.
Something I didn't do in this PR and that should probably be done, is to put back the
pre_cull_to_target
option in the GUI, to cap the number of instances before assigning them to tracks (whereasmax_tracks
cap them after assigning them to tracks), to be able to get back the pre-#1447 behavior.
We removed pre-culling as this was superseded by a feature set for limiting the number of instances right at the model inference level. This is exposed via the --max_instances
flag.
We implemented these in:
The advantage is that it allows the users to cull to a specific number of instances regardless of whether tracking will be applied next -- so it's usable during the human-in-the-loop annotation stage as well to filter out some false positives when models aren't trained well enough yet.
We also had a question about the instance merging functionality you added. It makes total sense, but you might imagine how this will have lots of edge cases.
For example: if the two node sets are exactly disjoint, but correctly belong to two different animals, we'd be incorrectly merging them into what we call a "chimera" (top of one mouse connected to the bottom of another mouse).
Another scenario we see often that this wouldn't quite resolve is when we have two almost disjoint sets, except for one node that is maybe off by a couple of pixels. Adding a bit of fuzziness to the keypoint matching to test for "disjoint-ness" before triggering this function could help cover those cases as well.
The thing I'm a bit more confused by is that you're mentioning this as useful when there are two instances on the same frame that also belong to the same track. How is this even possible given any of the current tracking approaches? We always do matching that ensures that no more than one instance is assigned to a given track within a frame.
Establishing that two (partial) instances indeed belong to the same track is the hard problem that's at the crux of this instance merging problem -- and why we haven't implemented this before.
Some clarity on what you had in mind for dealing with this would help us understand the solution.
In general, we're super appreciative of the contributions, but we want to make sure that:
If you prefer to have a chat in realtime, just drop me an email --> talmo@salk.edu
.
Thanks again for all the work! Looking forward to getting this merged in :)
Talmo
Now I understand the use of Local queues.
Maybe the best approach is to make 2 PR, one for bringing the max_tracks
feature to the Window_size
candidate maker and another one to add the merging option. But the name flowmaxtracks
would need to be changed in my opinion, to better reflect that it's using a local queue.
I will try to send a screenshot to explain what I mean by a track becoming empty. But basically, when a track is not detected for window_size
timesteps, no instance is never assigned to it anymore, ever. I think it has to do with the loop in FlowMaxTracksCandidateMaker.get_candidates
, line 408. But I didn't investigate much.
When using pre-culling to 2 instances, it would not happen.
I didn't realize that you could limit the number of instances from inference, thanks. Although I like to try different tracking method with new videos, so I run inference and tracking separately and I would still keep all the instances in the inference stage and pre-cull when tracking. Maybe other people would do that also, I don't know.
Regarding the merging function:
For example: if the two node sets are exactly disjoint, but correctly belong to two different animals, we'd be incorrectly merging them into what we call a "chimera" (top of one mouse connected to the bottom of another mouse).
Yes but this chimera will be less likely to be the best match for the track (but it's not sure).
Another scenario we see often that this wouldn't quite resolve is when we have two almost disjoint sets, except for one node that is maybe off by a couple of pixels. Adding a bit of fuzziness to the keypoint matching to test for "disjoint-ness" before triggering this function could help cover those cases as well.
I also see that. I only implemented merging totally disjoint nodes, but we could think of merging group of nodes with one overlapping node (taking the average position of the duplicate node). It would not be too hard to add.
The thing I'm a bit more confused by is that you're mentioning this as useful when there are two instances on the same frame that also belong to the same track. How is this even possible given any of the current tracking approaches? We always do matching that ensures that no more than one instance is assigned to a given track within a frame.
The problem I tried to solve with this merging function is the following:
So the idea of merging instances was to avoid the step 3, stopping the identity swap.
ONLY AFTER #1894 is merged.
I refactored the
max_tracking
feature introduced in #1447 and simplified the implementation. It may give different results as the goal is similar but implemented differently.One problem I had with the current implementation is that instances cannot be assigned to created but unused tracks. Say we have
max_tracks=2
, and the 2 tracks are created. If a track is not assigned instances fortrack_window
timesteps, it does not contribute to instance candidates anymore, hence instances cannot be assigned to this track anymore, forever. What was happening is that instances stopped being tracked in the middle of the video. Therefore it was unusable for me and the "pre_cull" options are not available from the GUI anymore, so I need to use the CLI.I added an option to reassign unused tracks to unmatched instances if the maximum number of tracks was reached.
I also added an option to merge instances in the "pre_cull" stage. That is because I often have two instances from inference: front of mouse and back of mouse, that could perfectly be merged. The idea is that only "good" merges are kept by the tracking step, but it is not perfect.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests