Regions of interest
Once the localized or tracked data are loaded as a dataframe, regions of interest can be selected at particle density peaks, for example as square areas centered at these peak locations.
The tramway.analyzer.roi.utils
module exports a simple density_based_roi
function to do so, as an example. Its usage is better illustrated in another notebook (section ROI definition), part of the TRamWAy project.
The time support for each space ROI can also be restricted to a time segment with relatively sustained density.
TRamWAy does not encourage a specific approach, but the general rule is to select regions that contain enough data for the estimation techniques involved at the inference stage to work properly.
Considering coordinates named 'x'
, 'y'
(space), 't'
(time), regions of interest can be defined as bounding boxes, in a dataframe with one row per ROI, and columns 'x min'
, 'x max'
, 'y min'
, 'y max'
, 't min'
, 't max'
, etc.
import pandas as pd
my_single_roi = pd.DataFrame({'x min': [0], 'x max': [1], 'y min': [0], 'y max': [1]})
Such a dataframe can be passed to the roi.from_bounding_boxes
function exported by the tramway.analyzer
module, and the returned value be assigned to the roi
attribute of an SPTDataItem
object.
from tramway.analyzer import *
a = RWAnalyzer()
a.spt_data = spt_data.from_ascii_files('../data/*_traj.rwa') # this actually matches a single file
for file in a.spt_data:
file.roi = roi.from_bounding_boxes(my_single_roi)
Below is an example function to tighten the time support to the highest density series of contiguous time segments, for a given sliding time window. Data df
are assumed to be cropped for a space ROI:
import numpy as np
def time_support(df, dt, win_dur, win_shift, min_n_seg=5, absolute_min_n_transloc=20, triggering_min_n_transloc=50):
"""
Define a contiguous time support for data `df`.
Dataframe `df` should feature a column labelled :const:`'t'`.
The resulting time support is adjusted for a sliding time window with duration `win_dur` and shift `win_shift`.
Time segments are selected based on the number of associated rows in `df` (points).
Time support selection operates in three steps:
* the first and last time segments to exhibit `triggering_min_n_transloc` points are sought for, at frame interval (`dt`) resolution;
* the sliding time window is applied to the overall time interval from such a segment to the other, so as to define a series of time segments;
the series is split into possibly multiple "fragments", discarding the time segments with less than `absolute_min_n_transloc` points;
* the longest fragment (or contiguous series of pre-selected time segments) is selected and the corresponding start and stop times are returned.
If no such time segments can be found, or fewer than `min_n_seg`, :const:`None` is returned instead.
"""
t = df['t'].values
t0, t1 = t.min(), t.max()
# redefine support [t0, t1] so that first and last segments satisfy triggering_min_n_transloc
ts = np.arange(t0, t1+.5*dt, dt)
cum_n = np.array([ np.sum((t_-win_dur-.5*dt<t)&(t<t_+.5*dt)) for t_ in ts ])
ok = np.flatnonzero(triggering_min_n_transloc <= cum_n)
if ok.size < 2:
return None
i, j = ok[0], ok[-1]
t0, t1 = ts[i]-win_dur, ts[j]
n_seg = np.round((t1 - t0 - win_dur) / win_shift) + 1
if n_seg < min_n_seg:
return None
t_tot = (n_seg - 1) * win_shift + win_dur
t_wasted = (t1 - t0) - t_tot
if 0 < t_wasted:
t0 += t_wasted / 2
t1 -= t_wasted / 2
# fragment the [t0, t1] support discarding the segments with less than absolute_min_n_transloc points
seg_start = np.arange(t0, t1-win_dur+.5*dt, win_shift)
seg = np.hstack((seg_start[:,None], seg_start[:,None]+win_dur))
transloc_count = np.array([ np.sum((t0_-.5*dt<t)&(t<t1_+.5*dt)) for t0_, t1_ in seg ])
ok = absolute_min_n_transloc <= transloc_count
fragment_start = np.flatnonzero( np.r_[True, ~ok[:-1]] & ok )
fragment_stop = np.flatnonzero( ok & np.r_[~ok[1:], True] )
assert fragment_start.size == fragment_stop.size
# select the longest fragment (= series of successive segments)
fragment_len = fragment_stop - fragment_start
longest_fragment = np.argmax(fragment_len)
if fragment_len[longest_fragment] < min_n_seg:
return None
i, j = fragment_start[longest_fragment], fragment_stop[longest_fragment]
t0, t1 = seg[i,0], seg[j,1]
return t0, t1