aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/argaze.test/GazeFeatures.py53
-rw-r--r--src/argaze/GazeFeatures.py78
2 files changed, 101 insertions, 30 deletions
diff --git a/src/argaze.test/GazeFeatures.py b/src/argaze.test/GazeFeatures.py
index 85ba362..a6709cb 100644
--- a/src/argaze.test/GazeFeatures.py
+++ b/src/argaze.test/GazeFeatures.py
@@ -379,6 +379,7 @@ class TestScanPathClass(unittest.TestCase):
scan_path = GazeFeatures.ScanPath()
self.assertEqual(len(scan_path), 0)
+ self.assertEqual(scan_path.duration, 0)
def test_append(self):
"""Test ScanPath append methods."""
@@ -393,6 +394,7 @@ class TestScanPathClass(unittest.TestCase):
# Check that no scan step have been created yet
self.assertEqual(len(scan_path), 0)
+ self.assertEqual(scan_path.duration, 0)
self.assertEqual(new_step, None)
# Append first fixation
@@ -403,18 +405,21 @@ class TestScanPathClass(unittest.TestCase):
# Check that no scan step have been created yet
self.assertEqual(len(scan_path), 0)
+ self.assertEqual(scan_path.duration, 0)
self.assertEqual(new_step, None)
# Append consecutive saccade
saccade_A = TestSaccade(random_gaze_positions(2))
ts, _ = saccade_A.positions.first
- new_step = scan_path.append_saccade(ts, saccade_A)
+ new_step_A = scan_path.append_saccade(ts, saccade_A)
# Check that new scan step have been created
self.assertEqual(len(scan_path), 1)
- self.assertEqual(new_step.first_fixation, fixation_A)
- self.assertEqual(new_step.last_saccade, saccade_A)
+ self.assertEqual(scan_path.duration, new_step_A.duration)
+ self.assertEqual(new_step_A.first_fixation, fixation_A)
+ self.assertEqual(new_step_A.last_saccade, saccade_A)
+ self.assertEqual(new_step_A.duration, fixation_A.duration + saccade_A.duration)
# Append 2 consecutive fixations then a saccade
fixation_B1 = TestFixation(random_gaze_positions(10))
@@ -424,6 +429,7 @@ class TestScanPathClass(unittest.TestCase):
# Check that no scan step have been created yet
self.assertEqual(len(scan_path), 1)
+ self.assertEqual(scan_path.duration, new_step_A.duration)
self.assertEqual(new_step, None)
fixation_B2 = TestFixation(random_gaze_positions(10))
@@ -433,17 +439,20 @@ class TestScanPathClass(unittest.TestCase):
# Check that no scan step have been created yet
self.assertEqual(len(scan_path), 1)
+ self.assertEqual(scan_path.duration, new_step_A.duration)
self.assertEqual(new_step, None)
saccade_B = TestSaccade(random_gaze_positions(2))
ts, _ = saccade_B.positions.first
- new_step = scan_path.append_saccade(ts, saccade_B)
+ new_step_B = scan_path.append_saccade(ts, saccade_B)
# Check that new scan step have been created
self.assertEqual(len(scan_path), 2)
- self.assertEqual(new_step.first_fixation, fixation_B2)
- self.assertEqual(new_step.last_saccade, saccade_B)
+ self.assertEqual(scan_path.duration, new_step_A.duration + new_step_B.duration)
+ self.assertEqual(new_step_B.first_fixation, fixation_B2)
+ self.assertEqual(new_step_B.last_saccade, saccade_B)
+ self.assertEqual(new_step_B.duration, fixation_B2.duration + saccade_B.duration)
class TestAOIScanStepClass(unittest.TestCase):
"""Test AOIScanStep class."""
@@ -525,36 +534,44 @@ class TestAOIScanPathClass(unittest.TestCase):
aoi_scan_path = GazeFeatures.AOIScanPath(['Foo', 'Bar'])
# Append fixation on A aoi
- fixation = TestFixation(random_gaze_positions(10))
- ts, _ = fixation.positions.first
+ fixation_A = TestFixation(random_gaze_positions(10))
+ ts, _ = fixation_A.positions.first
- new_step = aoi_scan_path.append_fixation(ts, fixation, 'Foo')
+ new_step = aoi_scan_path.append_fixation(ts, fixation_A, 'Foo')
# Check that no aoi scan step have been created yet
self.assertEqual(len(aoi_scan_path), 0)
+ self.assertEqual(aoi_scan_path.duration, 0)
self.assertEqual(new_step, None)
# Append saccade
- saccade = TestSaccade(random_gaze_positions(2))
- ts, _ = saccade.positions.first
+ saccade_A = TestSaccade(random_gaze_positions(2))
+ ts, _ = saccade_A.positions.first
- new_step = aoi_scan_path.append_saccade(ts, saccade)
+ new_step = aoi_scan_path.append_saccade(ts, saccade_A)
# Check that no aoi scan step have been created yet
self.assertEqual(len(aoi_scan_path), 0)
+ self.assertEqual(aoi_scan_path.duration, 0)
self.assertEqual(new_step, None)
# Append fixation on B aoi
- fixation = TestFixation(random_gaze_positions(10))
- ts, _ = fixation.positions.first
+ fixation_B = TestFixation(random_gaze_positions(10))
+ ts, _ = fixation_B.positions.first
- new_step = aoi_scan_path.append_fixation(ts, fixation, 'Bar')
+ new_step_A = aoi_scan_path.append_fixation(ts, fixation_B, 'Bar')
# Check a first aoi scan step have been created once a new fixation is appened
self.assertEqual(len(aoi_scan_path), 1)
- self.assertEqual(len(new_step.movements), 2)
- self.assertEqual(new_step.aoi, 'Foo')
- self.assertEqual(new_step.letter, 'A')
+ self.assertEqual(aoi_scan_path.duration, new_step_A.duration)
+ self.assertEqual(len(new_step_A.movements), 2)
+ self.assertEqual(new_step_A.aoi, 'Foo')
+ self.assertEqual(new_step_A.letter, 'A')
+
+ first_ts, _ = fixation_A.positions.first
+ last_ts, _ = saccade_A.positions.last
+
+ self.assertEqual(new_step_A.duration, last_ts - first_ts)
# Check letter affectation
self.assertEqual(aoi_scan_path.get_letter_aoi('A'), 'Foo')
diff --git a/src/argaze/GazeFeatures.py b/src/argaze/GazeFeatures.py
index 315aaf8..8afe2eb 100644
--- a/src/argaze/GazeFeatures.py
+++ b/src/argaze/GazeFeatures.py
@@ -539,8 +539,10 @@ class ScanStepError(Exception):
@dataclass(frozen=True)
class ScanStep():
"""Define a scan step as a fixation and a consecutive saccade.
- .. warning::
- Scan step have to start by a fixation and then end by a saccade."""
+
+ !!! warning
+ Scan step have to start by a fixation and then end by a saccade.
+ """
first_fixation: Fixation
"""A fixation that comes before the next saccade."""
@@ -561,16 +563,24 @@ class ScanStep():
raise ScanStepError('Last step movement is not a saccade')
@property
- def duration(self):
- """Time spent on AOI."""
+ def fixation_duration(self) -> int|float:
+ """Time spent on AOI
- # Timestamp of first position of first fixation
- first_ts, _ = self.first_fixation.positions.first
+ Returns:
+ fixation duration
+ """
- # Timestamp of first position of last saccade
- last_ts, _ = self.last_saccade.positions.first
+ return self.first_fixation.duration
- return last_ts - first_ts
+ @property
+ def duration(self) -> int|float:
+ """Time spent on AOI and time spent to go to next AOI
+
+ Returns:
+ duration
+ """
+
+ return self.first_fixation.duration + self.last_saccade.duration
ScanPathType = TypeVar('ScanPathType', bound="ScanPathType")
# Type definition for type annotation convenience
@@ -583,6 +593,17 @@ class ScanPath(list):
super().__init__()
self.__last_fixation = None
+ self.__duration = 0
+
+ @property
+ def duration(self) -> int|float:
+ """Sum of all scan steps duration
+
+ Returns:
+ duration
+ """
+
+ return self.__duration
def append_saccade(self, ts, saccade) -> ScanStepType:
"""Append new saccade to scan path and return last new scan step if one have been created."""
@@ -598,6 +619,9 @@ class ScanPath(list):
# Append new step
super().append(new_step)
+ # Update duration
+ self.__duration += new_step.duration
+
# Return new step
return new_step
@@ -609,7 +633,7 @@ class ScanPath(list):
def append_fixation(self, ts, fixation):
"""Append new fixation to scan path.
!!! warning
- Consecutives fixations are ignored keeping the last fixation"""
+ Consecutives fixations are ignored keeping the last fixation"""
self.__last_fixation = fixation
@@ -706,8 +730,12 @@ class AOIScanStep():
return last_movement
@property
- def duration(self):
- """Time spent on AOI."""
+ def fixation_duration(self) -> int|float:
+ """Time spent on AOI
+
+ Returns:
+ fixation duration
+ """
# Timestamp of first position of first fixation
first_ts, _ = self.first_fixation.positions.first
@@ -717,6 +745,22 @@ class AOIScanStep():
return last_ts - first_ts
+ @property
+ def duration(self) -> int|float:
+ """Time spent on AOI and time spent to go to next AOI
+
+ Returns:
+ duration
+ """
+
+ # Timestamp of first position of first fixation
+ first_ts, _ = self.first_fixation.positions.first
+
+ # Timestamp of last position of last saccade
+ last_ts, _ = self.last_saccade.positions.last
+
+ return last_ts - first_ts
+
AOIScanPathType = TypeVar('AOIScanPathType', bound="AOIScanPathType")
# Type definition for type annotation convenience
@@ -728,12 +772,19 @@ class AOIScanPath(list):
super().__init__()
self.expected_aois = expected_aois
+ self.__duration = 0
def __repr__(self):
"""String representation."""
return str(super())
+ @property
+ def duration(self) -> float:
+ """Sum of all scan steps duration"""
+
+ return self.__duration
+
def __get_aoi_letter(self, aoi):
try :
@@ -839,6 +890,9 @@ class AOIScanPath(list):
# Append new step
super().append(new_step)
+ # Update duration
+ self.__duration += new_step.duration
+
# Return new step
return new_step