Skip to content

Commit

Permalink
tracking: only remove tracks fully in the selector
Browse files Browse the repository at this point in the history
  • Loading branch information
JoepVanlier committed Feb 24, 2023
1 parent fa255e2 commit 2843eb0
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 24 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
* Plotting: When creating a plot providing `axes` and an `image_handle`, a `ValueError` is raised when those `axes` do not belong to the `image_handle` provided.
* Widefield: Attempting to open multiple `TIFF` as a single ImageStack will now raise a `ValueError` if the alignment matrices of the individual `TIFF` are different.
* PowerSpectrum: Attempting to replace the power spectral values of a `PowerSpectrum` using `with_spectrum` using a vector of incorrect length will raise a `ValueError`.
* When removing tracks with the kymotracking widget, only tracks that are entirely in the selection rectangle will be removed. Prior to this change, any tracks intersecting with the selection rectangle would be removed.

#### New features

Expand Down
22 changes: 15 additions & 7 deletions lumicks/pylake/kymotracker/kymotrack.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,17 +227,24 @@ def _check_ends_are_defined(self):
"""Checks if beginning and end of the track are not in the first/last frame."""
return self.time_idx[0] > 0 and self.time_idx[-1] < self._image.shape[1] - 1

def in_rect(self, rect):
"""Check whether any point of this KymoTrack falls in the rect given in rect.
def in_rect(self, rect, all_points=False):
"""Check whether points of this KymoTrack fall in the given rect.
Parameters
----------
rect : Tuple[Tuple[float, float], Tuple[float, float]]
Coordinates should be given as ((min_time, min_coord), (max_time, max_coord)).
all_points : bool
Require that all points fall inside the rectangle.
Returns
-------
is_inside : bool
"""
time_match = np.logical_and(self.seconds < rect[1][0], self.seconds >= rect[0][0])
position_match = np.logical_and(self.position < rect[1][1], self.position >= rect[0][1])
return np.any(np.logical_and(time_match, position_match))
criterion = np.all if all_points else np.any
return criterion(np.logical_and(time_match, position_match))

def interpolate(self):
"""Interpolate KymoTrack to whole pixel values"""
Expand Down Expand Up @@ -757,22 +764,23 @@ def extend(self, other):
def remove_lines_in_rect(self, rect):
self.remove_tracks_in_rect(rect)

def remove_tracks_in_rect(self, rect):
"""Removes tracks that fall in a particular region. Note that if any point on a track falls
inside the selected region it will be removed.
def remove_tracks_in_rect(self, rect, all_points=False):
"""Removes tracks that fall in a particular region.
Parameters
----------
rect : array_like
Array of 2D coordinates in time and space units (not pixels)
all_points : bool
Only remove tracks that are completely inside the rectangle.
"""
if rect[0][0] > rect[1][0]:
rect[0][0], rect[1][0] = rect[1][0], rect[0][0]

if rect[0][1] > rect[1][1]:
rect[0][1], rect[1][1] = rect[1][1], rect[0][1]

self._src = [track for track in self._src if not track.in_rect(rect)]
self._src = [track for track in self._src if not track.in_rect(rect, all_points)]

def __repr__(self):
return f"{self.__class__.__name__}(N={len(self)})"
Expand Down
40 changes: 25 additions & 15 deletions lumicks/pylake/kymotracker/tests/test_kymotrack.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,26 +73,36 @@ def test_kymotrack_selection_non_unit_calibration():
assert KymoTrack(time, pos, kymo, "red").in_rect(((4, 6 * 5), (6, 8 * 5)))


def test_kymotracks_removal(blank_kymo):
k1 = KymoTrack(np.array([1, 2, 3]), np.array([1, 1, 1]), blank_kymo, "red")
k2 = KymoTrack(np.array([2, 3, 4]), np.array([2, 2, 2]), blank_kymo, "red")
k3 = KymoTrack(np.array([3, 4, 5]), np.array([3, 3, 3]), blank_kymo, "red")
@pytest.mark.parametrize(
"rect, remaining_lines, fully_in_rect",
[
([[5, 3], [6, 4]], [True, True, False], False),
([[6, 3], [5, 4]], [True, True, False], False),
([[6, 5], [5, 3]], [True, True, False], False),
([[1, 1], [4, 2]], [False, True, True], True),
([[1, 1], [4, 2]], [False, True, True], False),
([[1, 1], [3, 2]], [True, True, True], True), # Not fully inside
([[1, 1], [3, 2]], [False, True, True], False),
([[0, 0], [5, 5]], [False, False, False], False),
([[15, 3], [16, 4]], [True, True, True], False),
],
)
def test_kymotracks_removal(blank_kymo, rect, remaining_lines, fully_in_rect):
"""Tests removal of KymoTracks within a particular rectangle.
Note that the rectangle is given as (min time, min coord) to (max time, max coord)"""
k0 = KymoTrack(np.array([1, 2, 3]), np.array([1, 1, 1]), blank_kymo, "red")
k1 = KymoTrack(np.array([2, 3, 4]), np.array([2, 2, 2]), blank_kymo, "red")
k2 = KymoTrack(np.array([3, 4, 5]), np.array([3, 3, 3]), blank_kymo, "red")
tracks = [k0, k1, k2]

def verify(rect, resulting_tracks):
k = KymoTrackGroup([k1, k2, k3])
k.remove_tracks_in_rect(rect)
k = KymoTrackGroup(tracks)
k.remove_tracks_in_rect(rect, fully_in_rect)
assert len(k._src) == len(resulting_tracks)
assert all([l1 == l2 for l1, l2 in zip(k._src, resulting_tracks)])

verify([[5, 3], [6, 4]], [k1, k2])
verify([[6, 3], [5, 4]], [k1, k2])
verify([[6, 5], [5, 3]], [k1, k2])
verify([[0, 0], [5, 5]], [])
verify([[15, 3], [16, 4]], [k1, k2, k3])

with pytest.warns(DeprecationWarning):
k = KymoTrackGroup([k1, k2, k3])
k.remove_lines_in_rect([[5, 3], [6, 4]])
verify(rect, [track for track, should_remain in zip(tracks, remaining_lines) if should_remain])


def test_kymotrackgroup(blank_kymo):
Expand Down
2 changes: 1 addition & 1 deletion lumicks/pylake/nb_widgets/kymotracker_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def _track_kymo(self, click, release):

# Explicit copy to make modifications. Current state pushed to undo stack on assignment.
tracks = copy(self.tracks)
tracks.remove_tracks_in_rect([p1, p2])
tracks.remove_tracks_in_rect([p1, p2], not self._adding)

if self._adding:
new_tracks = self._track(rect=[p1, p2])
Expand Down
8 changes: 7 additions & 1 deletion lumicks/pylake/nb_widgets/tests/test_kymotracker_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,15 @@ def test_track_kymo(kymograph, region_select):
kymo_widget._track_all()
assert len(kymo_widget.tracks) == 3

# Use remove, but don't actually remove the track because it is only partially inside the
# rectangle.
kymo_widget._adding = False
kymo_widget._track_kymo(*region_select(in_s(12), in_um(8), in_s(15), in_um(9)))
assert len(kymo_widget.tracks) == 3

# Remove a single track
kymo_widget._adding = False
kymo_widget._track_kymo(*region_select(in_s(15), in_um(8), in_s(20), in_um(9)))
kymo_widget._track_kymo(*region_select(in_s(10), in_um(8), in_s(15), in_um(9)))
assert len(kymo_widget.tracks) == 2


Expand Down

0 comments on commit 2843eb0

Please sign in to comment.