-
Notifications
You must be signed in to change notification settings - Fork 126
Description
Is your feature request related to a problem? Please describe.
I'll like to raise PRs for adding ROI statistics which are currently lacking in movement, starting with function for computing entries/exits.
What : Event Statistics for each region and individual which may include(non-exhaustively)
- entry and exits between regions
- dwell time
- co-occupancy, etc
Why : Used in research like 1, and as discussed in
#418 conditional arrays proposal
Describe the solution you'd like
For now, i've considered implementing entry/exit count first, then move to other events
You can find the demo use of the compute_entry_exits here - github-gist
notice how using more keypoints with mode = 'all', decrease entires/exits
Describe solution you've considered
Possible Usage
entries_and_exits = compute_entry_exits(
data=positions_filtered.isel(time=slice(2000, 2500)),
keypoints=["centre", "snout", "tail_base"],
mode="all",
regions=[
center_region,
open_left_region,
open_right_region,
closed_top_region,
closed_bottom_region,
],
min_frames_transition=10,
)See
github-gist OR
github-gist in interactive binder env for more detailed demo:
Planned implementation: compute_entry_exits
#compute_entry_exits: (see below discussion for location of this function)
def compute_entry_exits(
data: xr.DataArray,
keypoints: str,
mode: Literal["centroid", "all", "any", "majority"],
regions: ROICollection,
min_frames_transition: int,
) -> xr.DataArray:
if isinstance(keypoints, str):
keypoints = [keypoints]
data_sel = data.sel(keypoints=keypoints) #('time', 'space', 'keypoints', 'individuals')
assert not np.isnan(data_sel).any(), (
"Expecting NaN values handled beforehand"
)
region_names = []
entry_list = []
exit_list = []
#temporal_occupancies = {} #single array maybe useful for dwelltime, en/ex, co-occupancy
for r in regions:
name = r.name
region_names.append(name)
if mode == "centroid":
centroid = data_sel.mean(dim="keypoints")
occupancies = r.contains_point(centroid)
else:
raw_occ = r.contains_point(data_sel)
if mode == "all":
occupancies = raw_occ.all(dim="keypoints")
elif mode == "any":
occupancies = raw_occ.any(dim="keypoints")
elif mode == "majority":
occupancies = raw_occ.mean(dim="keypoints") > 0.5
else:
raise ValueError(
"mode must be one of: centroid, all, any, majority" #or maybe top-k out of n-keypoints
)
occupancies = occupancies.astype(int)
# temporal filtering to suppress noisy transitions check
occupancies = (
occupancies
.rolling(time=min_frames_transition)
.sum()
>= min_frames_transition
).astype(int)
diff_occupancies = occupancies.diff("time").astype(int) #('time', 'individuals')
# print(np.unique(diff_occupancies.values))# values be -1,0,1
entry_counts = (diff_occupancies == 1).sum(dim="time") #( individuals)
exit_counts = (diff_occupancies == -1).sum(dim="time")
entry_list.append(entry_counts)
exit_list.append(exit_counts)
# stack regions
entries = xr.concat(entry_list, dim="region")
exits = xr.concat(exit_list, dim="region")
entries = entries.assign_coords(region=region_names)
exits = exits.assign_coords(region=region_names)
# add event dimension
entries = entries.expand_dims(event=["entry"])
exits = exits.expand_dims(event=["exit"])
result = xr.concat([entries, exits], dim="event")
result.name = "entry_exit_counts"
return resultAdditional questions / Discussion
Design choice:
Case 1: roi.conditions.compute_entry_exits:
- Putting compute_entry_exits in roi.conditions implies compute_entry_exits returns some kinda boolean array as implied from the name conditions but it is rather a data array involving integer value information about some aspect of the ROI and not like 'compute_region_occupancy'....should I create a new module named 'events' withing roi?
Case 2: separate roi.events file, with roi.events.compute_entry_exits or rather an roi.event class :
- isolated module concerned with events happening with ROIs
- class might help to share common data array ( ex, temporal region occupancies req to calculate other aspects like : en/ex events (diff along time, min_transition_window)
- dwell time(sum occupancy along 'time'),
- co-occupancy(AND operation btw occupancies of selected individuals, and sum result along'time'), etc)
- may help in adding more events mentioned above.
Also thought of instead storing entry/exit events instead of occupancy but occupancy seem to be more useful for different event rather than just entry/exit times
Challenges:
- multiple keypoints transition scenario, as mentioned by @niksirbi here- zulip discussion
soln
- to condense the keypoints dim to single value based on mode provided by user(all/any/majority/centroid)
- implementation similar to above single keypoint, with just 1 diff, i do an additionl step using over the multi-keypoint -> single value (see the code for compute_entry_exits above)
- Sensitivity to noisy detections at borders
soln
- add a window check to filter noisy transitions
- should we do a window check like in above code or assume(and mention) that user does approp. filtering/smoothening?
Would like to know @niksirbi , if there are any suggestions before raising a PR. Thanks !
Related Issues
#418: conditional arrays proposal |
Implementation Plan
.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status