From 8a4888ace0ff5b4da4cd1d426f14c410c6d00b47 Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Fri, 9 Dec 2022 01:06:27 +0100 Subject: Adding and testing fixation overlap function. --- .../DispersionBasedGazeMovementIdentifier.py | 66 +++++++++++++++++++++- .../DispersionBasedGazeMovementIdentifier.py | 18 ++++++ 2 files changed, 81 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/argaze.test/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py b/src/argaze.test/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py index 01b2aad..497a23f 100644 --- a/src/argaze.test/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py +++ b/src/argaze.test/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py @@ -47,7 +47,7 @@ def build_gaze_fixation(size: int, center: tuple, dispersion: float, start_time: class TestDispersionBasedGazeMovementIdentifierClass(unittest.TestCase): """Test DispersionBasedGazeMovementIdentifier class.""" - + def test_fixation_identification(self): """Test DispersionBasedGazeMovementIdentifier fixation identification.""" @@ -74,7 +74,7 @@ class TestDispersionBasedGazeMovementIdentifierClass(unittest.TestCase): self.assertLessEqual(fixation.dispersion, dispersion) self.assertGreaterEqual(fixation.duration, size * min_time) self.assertLessEqual(fixation.duration, size * max_time) - + def test_fixation_and_saccade_identification(self): """Test DispersionBasedGazeMovementIdentifier fixation and saccade identification.""" @@ -121,7 +121,7 @@ class TestDispersionBasedGazeMovementIdentifierClass(unittest.TestCase): self.assertLessEqual(fixation.dispersion, dispersion) self.assertGreaterEqual(fixation.duration, size * min_time) self.assertLessEqual(fixation.duration, size * max_time) - + def test_invalid_gaze_position(self): """Test DispersionBasedGazeMovementIdentifier fixation and saccade identification with invalid gaze position.""" @@ -150,7 +150,67 @@ class TestDispersionBasedGazeMovementIdentifierClass(unittest.TestCase): self.assertLessEqual(fixation.dispersion, dispersion) self.assertGreaterEqual(fixation.duration, size * min_time) self.assertLessEqual(fixation.duration, size * max_time) + + def test_fixation_overlapping(self): + """Test Fixation overlap function.""" + + size = 10 + center_A = (0, 0) + center_B = (50, 50) + center_C = (55, 55) + dispersion = 10 + start_time = time.time() + min_time = 0.01 + max_time = 0.1 + + ts_gaze_positions_A = build_gaze_fixation(size, center_A, dispersion, start_time, min_time, max_time) + ts_gaze_positions_B = build_gaze_fixation(size, center_B, dispersion, start_time, min_time, max_time) + ts_gaze_positions_C = build_gaze_fixation(size, center_C, dispersion, start_time, min_time, max_time) + + fixation_A = DispersionBasedGazeMovementIdentifier.Fixation(ts_gaze_positions_A) + fixation_B = DispersionBasedGazeMovementIdentifier.Fixation(ts_gaze_positions_B) + fixation_C = DispersionBasedGazeMovementIdentifier.Fixation(ts_gaze_positions_C) + + # Check that fixation doesn't overlap + self.assertFalse(fixation_A.overlap(fixation_B)) + self.assertFalse(fixation_B.overlap(fixation_A)) + + # Check that fixation overlaps + self.assertTrue(fixation_B.overlap(fixation_C)) + self.assertTrue(fixation_C.overlap(fixation_B)) + ''' + def test_fixation_overlapping_identification(self): + """Test DispersionBasedGazeMovementIdentifier identification when fixations overlap.""" + + size = 50 + center_A = (-5, 0) + center_B = (5, 0) + dispersion = 15 + start_time = time.time() + min_time = 0.01 + max_time = 0.1 + ts_gaze_positions_A = build_gaze_fixation(size, center_A, dispersion, start_time, min_time, max_time) + ts_gaze_positions_B = build_gaze_fixation(size, center_B, dispersion, start_time, min_time, max_time) + + ts_gaze_positions = ts_gaze_positions_A.append(ts_gaze_positions_B) + + gaze_movement_identifier = DispersionBasedGazeMovementIdentifier.GazeMovementIdentifier(dispersion_threshold=dispersion, duration_threshold=min_time*2) + ts_fixations, ts_saccades, ts_status = gaze_movement_identifier.identify(ts_gaze_positions) + + # Check result size + self.assertEqual(len(ts_fixations), 1) + self.assertEqual(len(ts_saccades), 0) + self.assertEqual(len(ts_status), size*2) + + # Check unique fixation + ts, fixation = ts_fixations.pop_first() + + self.assertEqual(len(fixation.positions.keys()), size*2) + #self.assertGreaterEqual(fixation.dispersion, dispersion) + self.assertGreaterEqual(fixation.duration, 2 * size * min_time) + self.assertLessEqual(fixation.duration, 2 * size * max_time) + ''' if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/src/argaze/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py b/src/argaze/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py index 4fd5aab..8498f84 100644 --- a/src/argaze/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py +++ b/src/argaze/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py @@ -52,6 +52,20 @@ class Fixation(GazeFeatures.Fixation): # Update frozen centroid attribute object.__setattr__(self, 'centroid', (cx, cy)) + def overlap(self, fixation) -> float: + """Does this fixation overlap another fixation?""" + + dist = (self.centroid[0] - fixation.centroid[0])**2 + (self.centroid[1] - fixation.centroid[1])**2 + dist = numpy.sqrt(dist) + + return dist < (self.dispersion + fixation.dispersion) + + def merge(self, fixation) -> float: + """Merge another fixation into this fixation.""" + + self.positions.append(fixation.positions) + self.__post_init__() + @dataclass(frozen=True) class Saccade(GazeFeatures.Saccade): """Define dispersion based saccade.""" @@ -91,6 +105,7 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier): # Ignore non valid start position if not gaze_position_start.valid: + self.__ts_gaze_positions.pop_first() continue @@ -129,6 +144,7 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier): # remove selected gaze positions for gp in ts_gaze_positions: + self.__ts_gaze_positions.pop_first() # extend fixation position from a copy @@ -143,7 +159,9 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier): # Ignore non valid position # Should we consider invalid position to not break fixation ? if not gaze_position_next.valid: + remaining_ts_gaze_positions.pop_first() + self.__ts_gaze_positions.pop_first() continue ts_gaze_positions_extension[ts_next] = gaze_position_next -- cgit v1.1