aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py14
-rw-r--r--src/argaze.test/GazeAnalysis/VelocityThresholdIdentification.py9
-rw-r--r--src/argaze.test/GazeFeatures.py31
-rw-r--r--src/argaze/GazeAnalysis/DispersionThresholdIdentification.py11
-rw-r--r--src/argaze/GazeAnalysis/VelocityThresholdIdentification.py11
-rw-r--r--src/argaze/GazeFeatures.py9
6 files changed, 83 insertions, 2 deletions
diff --git a/src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py b/src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py
index eba80c8..f1d02d6 100644
--- a/src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py
+++ b/src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py
@@ -120,6 +120,7 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase):
self.assertLessEqual(fixation.deviation_max, deviation_max)
self.assertGreaterEqual(fixation.duration, size * min_time)
self.assertLessEqual(fixation.duration, size * max_time)
+ self.assertLessEqual(fixation.finished, True)
def test_fixation_and_direct_saccade_identification(self):
"""Test DispersionThresholdIdentification fixation and saccade identification."""
@@ -151,6 +152,7 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase):
self.assertLessEqual(fixation.deviation_max, deviation_max)
self.assertGreaterEqual(fixation.duration, size * min_time)
self.assertLessEqual(fixation.duration, size * max_time)
+ self.assertLessEqual(fixation.finished, True)
# Check first saccade
ts, saccade = ts_saccades.pop_first()
@@ -158,6 +160,7 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase):
self.assertEqual(len(saccade.positions.keys()), 1)
self.assertGreaterEqual(saccade.duration, 0.)
self.assertLessEqual(saccade.duration, 0.)
+ self.assertLessEqual(saccade.finished, True)
# Check second fixation
ts, fixation = ts_fixations.pop_first()
@@ -166,6 +169,7 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase):
self.assertLessEqual(fixation.deviation_max, deviation_max)
self.assertGreaterEqual(fixation.duration, size * min_time)
self.assertLessEqual(fixation.duration, size * max_time)
+ self.assertLessEqual(fixation.finished, True)
def test_fixation_and_short_saccade_identification(self):
"""Test DispersionThresholdIdentification fixation and saccade identification."""
@@ -200,6 +204,7 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase):
self.assertLessEqual(fixation.deviation_max, deviation_max)
self.assertGreaterEqual(fixation.duration, size * min_time)
self.assertLessEqual(fixation.duration, size * max_time)
+ self.assertLessEqual(fixation.finished, True)
# Check first saccade
ts, saccade = ts_saccades.pop_first()
@@ -207,6 +212,7 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase):
self.assertEqual(len(saccade.positions.keys()), move)
self.assertGreaterEqual(saccade.duration, min_time)
self.assertLessEqual(saccade.duration, max_time)
+ self.assertLessEqual(saccade.finished, True)
# Check second fixation
ts, fixation = ts_fixations.pop_first()
@@ -215,6 +221,7 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase):
self.assertLessEqual(fixation.deviation_max, deviation_max)
self.assertGreaterEqual(fixation.duration, size * min_time)
self.assertLessEqual(fixation.duration, size * max_time)
+ self.assertLessEqual(fixation.finished, True)
def test_invalid_gaze_position(self):
"""Test DispersionThresholdIdentification fixation and saccade identification with invalid gaze position."""
@@ -243,14 +250,16 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase):
self.assertLessEqual(fixation.deviation_max, deviation_max)
self.assertGreaterEqual(fixation.duration, 7 * min_time)
self.assertLessEqual(fixation.duration, 7 * max_time)
+ self.assertLessEqual(fixation.finished, True)
- # Check seconde fixation
+ # Check second fixation
ts, fixation = ts_fixations.pop_first()
self.assertEqual(len(fixation.positions.keys()), 5)
self.assertLessEqual(fixation.deviation_max, deviation_max)
self.assertGreaterEqual(fixation.duration, 5 * min_time)
self.assertLessEqual(fixation.duration, 5 * max_time)
+ self.assertLessEqual(fixation.finished, True)
def test_fixation_overlapping(self):
"""Test Fixation overlap function."""
@@ -310,6 +319,7 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase):
#self.assertGreaterEqual(fixation.deviation_max, deviation_max)
self.assertGreaterEqual(fixation.duration, 2 * size * min_time)
self.assertLessEqual(fixation.duration, 2 * size * max_time)
+ self.assertLessEqual(fixation.finished, True)
def test_identification_generator(self):
"""Test DispersionThresholdIdentification identification using generator."""
@@ -338,6 +348,7 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase):
self.assertLessEqual(gaze_movement.deviation_max, deviation_max)
self.assertGreaterEqual(gaze_movement.duration, size * min_time)
self.assertLessEqual(gaze_movement.duration, size * max_time)
+ self.assertLessEqual(gaze_movement.finished, True)
fixation_count += 1
@@ -346,6 +357,7 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase):
self.assertEqual(len(gaze_movement.positions.keys()), 1)
self.assertGreaterEqual(gaze_movement.duration, 0.)
self.assertLessEqual(gaze_movement.duration, 0.)
+ self.assertLessEqual(gaze_movement.finished, True)
if __name__ == '__main__':
diff --git a/src/argaze.test/GazeAnalysis/VelocityThresholdIdentification.py b/src/argaze.test/GazeAnalysis/VelocityThresholdIdentification.py
index 4cf3a81..71a8daf 100644
--- a/src/argaze.test/GazeAnalysis/VelocityThresholdIdentification.py
+++ b/src/argaze.test/GazeAnalysis/VelocityThresholdIdentification.py
@@ -127,6 +127,7 @@ class TestVelocityThresholdIdentificationClass(unittest.TestCase):
self.assertEqual(len(fixation.positions.keys()), size-1)
self.assertGreaterEqual(fixation.duration, size * min_time)
self.assertLessEqual(fixation.duration, size * max_time)
+ self.assertLessEqual(fixation.finished, True)
def test_fixation_and_direct_saccade_identification(self):
"""Test VelocityThresholdIdentification fixation and saccade identification."""
@@ -158,6 +159,7 @@ class TestVelocityThresholdIdentificationClass(unittest.TestCase):
self.assertEqual(len(fixation.positions.keys()), size-1)
self.assertGreaterEqual(fixation.duration, size * min_time)
self.assertLessEqual(fixation.duration, size * max_time)
+ self.assertLessEqual(fixation.finished, True)
# Check first saccade
ts, saccade = ts_saccades.pop_first()
@@ -165,6 +167,7 @@ class TestVelocityThresholdIdentificationClass(unittest.TestCase):
self.assertEqual(len(saccade.positions.keys()), 1)
self.assertGreaterEqual(saccade.duration, 0.)
self.assertLessEqual(saccade.duration, 0.)
+ self.assertLessEqual(saccade.finished, True)
# Check second fixation
ts, fixation = ts_fixations.pop_first()
@@ -172,6 +175,7 @@ class TestVelocityThresholdIdentificationClass(unittest.TestCase):
self.assertEqual(len(fixation.positions.keys()), size-1)
self.assertGreaterEqual(fixation.duration, size * min_time)
self.assertLessEqual(fixation.duration, size * max_time)
+ self.assertLessEqual(fixation.finished, True)
def test_fixation_and_short_saccade_identification(self):
"""Test VelocityThresholdIdentification fixation and saccade identification."""
@@ -206,6 +210,7 @@ class TestVelocityThresholdIdentificationClass(unittest.TestCase):
self.assertEqual(len(fixation.positions.keys()), size-1)
self.assertGreaterEqual(fixation.duration, size * min_time)
self.assertLessEqual(fixation.duration, size * max_time)
+ self.assertLessEqual(fixation.finished, True)
# Check first saccade
ts, saccade = ts_saccades.pop_first()
@@ -213,6 +218,7 @@ class TestVelocityThresholdIdentificationClass(unittest.TestCase):
self.assertEqual(len(saccade.positions.keys()), move+1)
self.assertGreaterEqual(saccade.duration, min_time)
self.assertLessEqual(saccade.duration, max_time)
+ self.assertLessEqual(saccade.finished, True)
# Check second fixation
ts, fixation = ts_fixations.pop_first()
@@ -220,6 +226,7 @@ class TestVelocityThresholdIdentificationClass(unittest.TestCase):
self.assertEqual(len(fixation.positions.keys()), size-1)
self.assertGreaterEqual(fixation.duration, size * min_time)
self.assertLessEqual(fixation.duration, size * max_time)
+ self.assertLessEqual(fixation.finished, True)
def test_invalid_gaze_position(self):
"""Test VelocityThresholdIdentification fixation and saccade identification with invalid gaze position."""
@@ -248,6 +255,7 @@ class TestVelocityThresholdIdentificationClass(unittest.TestCase):
self.assertEqual(len(fixation.positions.keys()), 6)
self.assertGreaterEqual(fixation.duration, 6 * min_time)
self.assertLessEqual(fixation.duration, 6 * max_time)
+ self.assertLessEqual(fixation.finished, True)
# Check second fixation
ts, fixation = ts_fixations.pop_first()
@@ -255,6 +263,7 @@ class TestVelocityThresholdIdentificationClass(unittest.TestCase):
self.assertEqual(len(fixation.positions.keys()), 4)
self.assertGreaterEqual(fixation.duration, 4 * min_time)
self.assertLessEqual(fixation.duration, 4 * max_time)
+ self.assertLessEqual(fixation.finished, True)
if __name__ == '__main__':
diff --git a/src/argaze.test/GazeFeatures.py b/src/argaze.test/GazeFeatures.py
index dd3f1c0..b8d173c 100644
--- a/src/argaze.test/GazeFeatures.py
+++ b/src/argaze.test/GazeFeatures.py
@@ -287,6 +287,37 @@ class TestTimeStampedGazePositionsClass(unittest.TestCase):
self.assertEqual(ts_gaze_positions_dataframe["value"].dtype, 'object')
self.assertEqual(ts_gaze_positions_dataframe["precision"].dtype, 'O') # Python object type
+class TestGazeMovementClass(unittest.TestCase):
+ """Test GazeMovement class."""
+
+ def test_new(self):
+ """Test GazeMovement creation."""
+
+ abstract_gaze_movement = GazeFeatures.GazeMovement(random_gaze_positions(0))
+
+ # Check abstract GazeMovement
+ self.assertEqual(len(abstract_gaze_movement.positions), 0)
+ self.assertEqual(abstract_gaze_movement.duration, -1)
+ self.assertEqual(abstract_gaze_movement.amplitude, -1)
+ self.assertEqual(abstract_gaze_movement.valid, False)
+ self.assertEqual(abstract_gaze_movement.finished, False)
+
+class TestUnvalidGazeMovementClass(unittest.TestCase):
+ """Test UnvalidGazeMovement class."""
+
+ def test_new(self):
+ """Test UnvalidGazeMovement creation."""
+
+ unvalid_gaze_movement = GazeFeatures.UnvalidGazeMovement('test')
+
+ # Check UnvalidGazeMovement
+ self.assertEqual(len(unvalid_gaze_movement.positions), 0)
+ self.assertEqual(unvalid_gaze_movement.duration, -1)
+ self.assertEqual(unvalid_gaze_movement.amplitude, -1)
+ self.assertEqual(unvalid_gaze_movement.valid, False)
+ self.assertEqual(unvalid_gaze_movement.finished, False)
+ self.assertEqual(unvalid_gaze_movement.message, 'test')
+
class TestScanStepClass(unittest.TestCase):
"""Test ScanStep class."""
diff --git a/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py b/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py
index 147046a..3015d51 100644
--- a/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py
+++ b/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py
@@ -107,7 +107,7 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier):
duration_min_threshold: int|float
"""Minimal duration allowed to consider a gaze movement as a fixation.
- It is also used as maximal duration allowed to consider a gaze movement as a saccade."""
+ It is also used as maximal duration allowed to wait valid gaze positions."""
def __post_init__(self):
@@ -136,6 +136,9 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier):
# Get last movement
last_movement = self.current_saccade if len(self.__fixation_positions) == 0 else self.current_fixation
+ # Set last movement as finished
+ last_movement.finish()
+
# Clear all former gaze positions
self.__valid_positions = GazeFeatures.TimeStampedGazePositions()
self.__fixation_positions = GazeFeatures.TimeStampedGazePositions()
@@ -165,6 +168,9 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier):
# Store last saccade
last_saccade = self.current_saccade
+ # Set last saccade as finished
+ last_saccade.finish()
+
# Clear saccade positions
self.__saccade_positions = GazeFeatures.TimeStampedGazePositions()
@@ -180,6 +186,9 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier):
# Store last fixation
last_fixation = self.current_fixation
+ # Set last fixation as finished
+ last_fixation.finish()
+
# Start saccade positions with current gaze position
self.__saccade_positions[ts] = gaze_position
diff --git a/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py b/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py
index 7131373..9a645c7 100644
--- a/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py
+++ b/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py
@@ -153,6 +153,9 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier):
# Get last movement
last_movement = self.current_saccade if len(self.__fixation_positions) == 0 else self.current_fixation
+ # Set last movement as finished
+ last_movement.finish()
+
# Clear all former gaze positions
self.__fixation_positions = GazeFeatures.TimeStampedGazePositions()
self.__saccade_positions = GazeFeatures.TimeStampedGazePositions()
@@ -176,8 +179,12 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier):
# Does last fixation exist?
if len(self.__fixation_positions) > 0:
+ # Create last fixation
last_fixation = Fixation(self.__fixation_positions)
+ # Set last fixation as finished
+ last_fixation.finish()
+
# Clear fixation positions
self.__fixation_positions = GazeFeatures.TimeStampedGazePositions()
@@ -198,8 +205,12 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier):
# Does last saccade exist?
if len(self.__saccade_positions) > 0:
+ # Create last saccade
last_saccade = Saccade(self.__saccade_positions)
+ # Set last saccade as finished
+ last_saccade.finish()
+
# Clear fixation positions
self.__saccade_positions = GazeFeatures.TimeStampedGazePositions()
diff --git a/src/argaze/GazeFeatures.py b/src/argaze/GazeFeatures.py
index 85b167f..855b90b 100644
--- a/src/argaze/GazeFeatures.py
+++ b/src/argaze/GazeFeatures.py
@@ -222,6 +222,9 @@ class GazeMovement():
amplitude: float = field(init=False)
"""Inferred amplitude from first and last positions."""
+ finished: bool = field(init=False, default=False)
+ """Is the movement finished?"""
+
def __post_init__(self):
if self.valid:
@@ -267,6 +270,12 @@ class GazeMovement():
return len(self.positions) > 0
+ def finish(self):
+ """Set gaze movement as finished"""
+
+ # Update frozen finished attribute
+ object.__setattr__(self, 'finished', True)
+
def draw_positions(self, image: numpy.array, color=(0, 55, 55)):
"""Draw gaze movement positions"""