diff options
-rw-r--r-- | src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py | 94 | ||||
-rw-r--r-- | src/argaze/DataFeatures.py | 9 | ||||
-rw-r--r-- | src/argaze/GazeAnalysis/DispersionThresholdIdentification.py | 2 | ||||
-rw-r--r-- | src/argaze/GazeFeatures.py | 4 |
4 files changed, 55 insertions, 54 deletions
diff --git a/src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py b/src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py index f0d286a..7e74c1d 100644 --- a/src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py +++ b/src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py @@ -49,9 +49,11 @@ def build_gaze_fixation(size: int, center: tuple, deviation_max: float, min_time gaze_position = GazeFeatures.GazePosition() + # Timestamp gaze position + gaze_position.timestamp = time.time() - start_time + start_ts + # Store gaze position - ts = time.time() - start_time + start_ts - ts_gaze_positions[ts] = gaze_position + ts_gaze_positions.append(gaze_position) return ts_gaze_positions @@ -87,9 +89,11 @@ def build_gaze_saccade(size: int, center_A: tuple, center_B: tuple, min_time: fl gaze_position = GazeFeatures.GazePosition() + # Timestamp gaze position + gaze_position.timestamp = time.time() - start_time + start_ts + # Store gaze position - ts = time.time() - start_time + start_ts - ts_gaze_positions[ts] = gaze_position + ts_gaze_positions.append(gaze_position) return ts_gaze_positions @@ -115,7 +119,7 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase): self.assertEqual(len(ts_status), size) # Check fixation - ts, fixation = ts_fixations.pop_first() + fixation = ts_fixations.pop(0) self.assertEqual(len(fixation.positions.keys()), size) self.assertLessEqual(fixation.deviation_max, deviation_max) @@ -134,9 +138,9 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase): max_time = 0.1 ts_gaze_positions_A = build_gaze_fixation(size, center_A, deviation_max, min_time, max_time) - ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, min_time, max_time, start_ts=ts_gaze_positions_A.last[0]) + ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, min_time, max_time, start_ts=ts_gaze_positions_A[-1].timestamp) - ts_gaze_positions = ts_gaze_positions_A.append(ts_gaze_positions_B) + ts_gaze_positions = ts_gaze_positions_A + ts_gaze_positions_B gaze_movement_identifier = DispersionThresholdIdentification.GazeMovementIdentifier(deviation_max_threshold=deviation_max, duration_min_threshold=max_time*2) ts_fixations, ts_saccades, ts_status = gaze_movement_identifier.browse(ts_gaze_positions) @@ -147,7 +151,7 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase): self.assertEqual(len(ts_status), size * 2) # Check first fixation - ts, fixation = ts_fixations.pop_first() + fixation = ts_fixations.pop(0) self.assertEqual(len(fixation.positions.keys()), size) self.assertLessEqual(fixation.deviation_max, deviation_max) @@ -156,7 +160,7 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase): self.assertLessEqual(fixation.finished, True) # Check first saccade - ts, saccade = ts_saccades.pop_first() + saccade = ts_saccades.pop(0) self.assertEqual(len(saccade.positions.keys()), 2) self.assertGreaterEqual(saccade.duration, min_time) @@ -164,14 +168,11 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase): self.assertLessEqual(saccade.finished, True) # Check that last position of a movement is equal to first position of next movement - last_ts, last_position = fixation.positions.last - first_ts, first_position = saccade.positions.first - - self.assertEqual(last_ts, first_ts) - self.assertEqual(last_position.value, first_position.value) + self.assertEqual(fixation[-1].timestamp, saccade[0].timestamp) + self.assertEqual(fixation[-1].value, saccade[0].value) # Check second fixation - ts, fixation = ts_fixations.pop_first() + fixation = ts_fixations.pop(0) self.assertEqual(len(fixation.positions.keys()), size) self.assertLessEqual(fixation.deviation_max, deviation_max) @@ -180,11 +181,8 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase): self.assertLessEqual(fixation.finished, True) # Check that last position of a movement is equal to first position of next movement - last_ts, last_position = saccade.positions.last - first_ts, first_position = fixation.positions.first - - self.assertEqual(last_ts, first_ts) - self.assertEqual(last_position.value, first_position.value) + self.assertEqual(saccade[-1].timestamp, fixation[0].timestamp) + self.assertEqual(saccade[-1].value, fixation[0].value) def test_fixation_and_short_saccade_identification(self): """Test DispersionThresholdIdentification fixation and saccade identification.""" @@ -199,10 +197,10 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase): max_time = 0.1 ts_gaze_positions_A = build_gaze_fixation(size, center_A, deviation_max, min_time, max_time) - ts_move_positions = build_gaze_saccade(move, out_A, center_B, min_time, min_time, start_ts=ts_gaze_positions_A.last[0]) - ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, min_time, max_time, start_ts=ts_move_positions.last[0]) + ts_move_positions = build_gaze_saccade(move, out_A, center_B, min_time, min_time, start_ts=ts_gaze_positions_A[-1].timestamp) + ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, min_time, max_time, start_ts=ts_move_positions[-1].timestamp) - ts_gaze_positions = ts_gaze_positions_A.append(ts_move_positions).append(ts_gaze_positions_B) + ts_gaze_positions = ts_gaze_positions_A + ts_move_positions + ts_gaze_positions_B gaze_movement_identifier = DispersionThresholdIdentification.GazeMovementIdentifier(deviation_max_threshold=deviation_max, duration_min_threshold=max_time*2) ts_fixations, ts_saccades, ts_status = gaze_movement_identifier.browse(ts_gaze_positions) @@ -213,7 +211,7 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase): self.assertEqual(len(ts_status), size * 2 + move) # Check first fixation - ts, fixation = ts_fixations.pop_first() + fixation = ts_fixations.pop(0) self.assertEqual(len(fixation.positions.keys()), size) self.assertLessEqual(fixation.deviation_max, deviation_max) @@ -222,7 +220,7 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase): self.assertLessEqual(fixation.finished, True) # Check first saccade - ts, saccade = ts_saccades.pop_first() + saccade = ts_saccades.pop(0) self.assertEqual(len(saccade.positions.keys()), move + 2) self.assertGreaterEqual(saccade.duration, (move + 1) * min_time) @@ -230,14 +228,11 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase): self.assertLessEqual(saccade.finished, True) # Check that last position of a movement is equal to first position of next movement - last_ts, last_position = fixation.positions.last - first_ts, first_position = saccade.positions.first - - self.assertEqual(last_ts, first_ts) - self.assertEqual(last_position.value, first_position.value) + self.assertEqual(fixation[-1].timestamp, saccade[0].timestamp) + self.assertEqual(fixation[-1].value, saccade[0].value) # Check second fixation - ts, fixation = ts_fixations.pop_first() + fixation = ts_fixations.pop(0) self.assertEqual(len(fixation.positions.keys()), size) self.assertLessEqual(fixation.deviation_max, deviation_max) @@ -246,11 +241,8 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase): self.assertLessEqual(fixation.finished, True) # Check that last position of a movement is equal to first position of next movement - last_ts, last_position = saccade.positions.last - first_ts, first_position = fixation.positions.first - - self.assertEqual(last_ts, first_ts) - self.assertEqual(last_position.value, first_position.value) + self.assertEqual(saccade[-1].timestamp, fixation[0].timestamp) + self.assertEqual(saccade[-1].value, fixation[0].value) def test_invalid_gaze_position(self): """Test DispersionThresholdIdentification fixation and saccade identification with invalid gaze position.""" @@ -273,7 +265,7 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase): self.assertEqual(len(ts_status), size-3) # Check first fixation - ts, fixation = ts_fixations.pop_first() + fixation = ts_fixations.pop(0) self.assertEqual(len(fixation.positions.keys()), 7) self.assertLessEqual(fixation.deviation_max, deviation_max) @@ -282,7 +274,7 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase): self.assertLessEqual(fixation.finished, True) # Check second fixation - ts, fixation = ts_fixations.pop_first() + fixation = ts_fixations.pop(0) self.assertEqual(len(fixation.positions.keys()), 5) self.assertLessEqual(fixation.deviation_max, deviation_max) @@ -302,8 +294,8 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase): max_time = 0.1 ts_gaze_positions_A = build_gaze_fixation(size, center_A, deviation_max, min_time, max_time) - ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, min_time, max_time, start_ts=ts_gaze_positions_A.last[0]) - ts_gaze_positions_C = build_gaze_fixation(size, center_C, deviation_max, min_time, max_time, start_ts=ts_gaze_positions_B.last[0]) + ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, min_time, max_time, start_ts=ts_gaze_positions_A[-1].timestamp) + ts_gaze_positions_C = build_gaze_fixation(size, center_C, deviation_max, min_time, max_time, start_ts=ts_gaze_positions_B[-1].timestamp) fixation_A = DispersionThresholdIdentification.Fixation(ts_gaze_positions_A) fixation_B = DispersionThresholdIdentification.Fixation(ts_gaze_positions_B) @@ -329,7 +321,7 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase): max_time = 0.1 ts_gaze_positions_A = build_gaze_fixation(size, center_A, deviation_max, min_time, max_time) - ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, min_time, max_time, start_ts=ts_gaze_positions_A.last[0]) + ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, min_time, max_time, start_ts=ts_gaze_positions_A[-1].timestamp) ts_gaze_positions = ts_gaze_positions_A.append(ts_gaze_positions_B) @@ -342,7 +334,7 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase): self.assertEqual(len(ts_status), size*2) # Check unique fixation - ts, fixation = ts_fixations.pop_first() + fixation = ts_fixations.pop(0) self.assertEqual(len(fixation.positions.keys()), size * 2) #self.assertGreaterEqual(fixation.deviation_max, deviation_max) @@ -361,19 +353,19 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase): max_time = 0.1 ts_gaze_positions_A = build_gaze_fixation(size, center_A, deviation_max, min_time, max_time) - ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, min_time, max_time, start_ts=ts_gaze_positions_A.last[0]) + ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, min_time, max_time, start_ts=ts_gaze_positions_A[-1].timestamp) ts_gaze_positions = ts_gaze_positions_A.append(ts_gaze_positions_B) gaze_movement_identifier = DispersionThresholdIdentification.GazeMovementIdentifier(deviation_max_threshold=deviation_max, duration_min_threshold=max_time*2) # Get last ts to terminate identification on last gaze position - last_ts, _ = ts_gaze_positions.last + last_ts = ts_gaze_positions[-1].timestamp # Iterate on gaze positions - for ts, gaze_position in ts_gaze_positions.items(): + for gaze_position in ts_gaze_positions: - finished_gaze_movement = gaze_movement_identifier.identify(ts, gaze_position, terminate=(ts == last_ts)) + finished_gaze_movement = gaze_movement_identifier.identify(gaze_position.timestamp, gaze_position, terminate=(gaze_position.timestamp == last_ts)) if GazeFeatures.is_fixation(finished_gaze_movement): @@ -393,9 +385,9 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase): # Check that last gaze position date is not equal to given gaze position date if finished_gaze_movement.valid: - last_ts, _ = finished_gaze_movement.positions.last + last_ts = finished_gaze_movement[-1].timestamp - self.assertNotEqual(last_ts, ts) + self.assertNotEqual(last_ts, gaze_position.timestamp) # Check that last gaze position date of current fixation is equal to given gaze position date # NOTE: This is not true for saccade as, for I-DT, there is a minimal time window while the gaze movement is unknown @@ -404,9 +396,9 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase): if GazeFeatures.is_fixation(current_gaze_movement): - last_ts, _ = current_gaze_movement.positions.last + last_ts = current_gaze_movement[-1].timestamp - self.assertEqual(last_ts, ts) + self.assertEqual(last_ts, gaze_position.timestamp) def test_identification_generator(self): """Test DispersionThresholdIdentification identification using generator.""" @@ -419,7 +411,7 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase): max_time = 0.1 ts_gaze_positions_A = build_gaze_fixation(size, center_A, deviation_max, min_time, max_time) - ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, min_time, max_time, start_ts=ts_gaze_positions_A.last[0]) + ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, min_time, max_time, start_ts=ts_gaze_positions_A[-1].timestamp) ts_gaze_positions = ts_gaze_positions_A.append(ts_gaze_positions_B) diff --git a/src/argaze/DataFeatures.py b/src/argaze/DataFeatures.py index 6909222..6be946f 100644 --- a/src/argaze/DataFeatures.py +++ b/src/argaze/DataFeatures.py @@ -205,6 +205,15 @@ class TimestampedObjectsList(list): super().append(ts_object) + def __add__(self, ts_objects: list = []) -> TimestampedObjectsListType: + """Append timestamped objects list.""" + + for ts_object in ts_objects: + + self.append(ts_object) + + return self + @property def duration(self): """Get inferred duration from first and last timestamps.""" diff --git a/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py b/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py index f928c5a..a452e98 100644 --- a/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py +++ b/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py @@ -227,7 +227,7 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier): self.__valid_positions = GazeFeatures.TimeStampedGazePositions() # Store current gaze position - self.__valid_positions[ts] = gaze_position + self.__valid_positions.append(gaze_position) # Output last fixation return last_fixation if not terminate else self.current_saccade.finish() diff --git a/src/argaze/GazeFeatures.py b/src/argaze/GazeFeatures.py index fb3dceb..71c643e 100644 --- a/src/argaze/GazeFeatures.py +++ b/src/argaze/GazeFeatures.py @@ -612,7 +612,7 @@ class GazeMovementIdentifier(DataFeatures.PipelineStepObject): # Iterate on gaze positions for gaze_position in ts_gaze_positions: - finished_gaze_movement = self.identify(ts, gaze_position, terminate=(gaze_position.timestamp == last_ts)) + finished_gaze_movement = self.identify(gaze_position.timestamp, gaze_position, terminate=(gaze_position.timestamp == last_ts)) if is_fixation(finished_gaze_movement): @@ -685,7 +685,7 @@ class GazeMovementIdentifier(DataFeatures.PipelineStepObject): # Iterate on gaze positions for gaze_position in ts_gaze_positions: - finished_gaze_movement = self.identify(ts, gaze_position, terminate=(gaze_position.timestamp == last_ts)) + finished_gaze_movement = self.identify(gaze_position.timestamp, gaze_position, terminate=(gaze_position.timestamp == last_ts)) if finished_gaze_movement: |