From 5f915a84f32405dc8bddae4ecbf95f4745af6fbc Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Wed, 28 Feb 2024 13:57:31 +0100 Subject: More work on TimestampedGazePositions and GazeMovements. --- .../DispersionThresholdIdentification.py | 4 +- .../VelocityThresholdIdentification.py | 4 +- src/argaze.test/GazeFeatures.py | 122 ++++++++++----------- 3 files changed, 63 insertions(+), 67 deletions(-) (limited to 'src/argaze.test') diff --git a/src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py b/src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py index b7475b5..f0d286a 100644 --- a/src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py +++ b/src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py @@ -47,7 +47,7 @@ def build_gaze_fixation(size: int, center: tuple, deviation_max: float, min_time else: - gaze_position = GazeFeatures.UnvalidGazePosition() + gaze_position = GazeFeatures.GazePosition() # Store gaze position ts = time.time() - start_time + start_ts @@ -85,7 +85,7 @@ def build_gaze_saccade(size: int, center_A: tuple, center_B: tuple, min_time: fl else: - gaze_position = GazeFeatures.UnvalidGazePosition() + gaze_position = GazeFeatures.GazePosition() # Store gaze position ts = time.time() - start_time + start_ts diff --git a/src/argaze.test/GazeAnalysis/VelocityThresholdIdentification.py b/src/argaze.test/GazeAnalysis/VelocityThresholdIdentification.py index 425d592..24f2e3c 100644 --- a/src/argaze.test/GazeAnalysis/VelocityThresholdIdentification.py +++ b/src/argaze.test/GazeAnalysis/VelocityThresholdIdentification.py @@ -53,7 +53,7 @@ def build_gaze_fixation(size: int, start_position: tuple, deviation_max: float, else: - gaze_position = GazeFeatures.UnvalidGazePosition() + gaze_position = GazeFeatures.GazePosition() # Store gaze position ts = time.time() - start_time + start_ts @@ -91,7 +91,7 @@ def build_gaze_saccade(size: int, center_A: tuple, center_B: tuple, min_time: fl else: - gaze_position = GazeFeatures.UnvalidGazePosition() + gaze_position = GazeFeatures.GazePosition() # Store gaze position ts = time.time() - start_time + start_ts diff --git a/src/argaze.test/GazeFeatures.py b/src/argaze.test/GazeFeatures.py index fdc140d..7d18976 100644 --- a/src/argaze.test/GazeFeatures.py +++ b/src/argaze.test/GazeFeatures.py @@ -39,28 +39,27 @@ def random_gaze_positions(size, frame_dimension: tuple[float, float] = (1, 1)): return ts_gaze_positions -@dataclass(frozen=True) class TestFixation(GazeFeatures.Fixation): """Define basic fixation class for test.""" - def __post_init__(self): + def __init__(self, positions: GazeFeatures.TimeStampedGazePositions = (), finished: bool = False, message: str = None, **kwargs): - super().__post_init__() + super().__init__(positions, finished, message, **kwargs) - points = self.positions.values() - points_x, points_y = [p[0] for p in points], [p[1] for p in points] - points_array = numpy.column_stack([points_x, points_y]) - centroid_array = numpy.array([numpy.mean(points_x), numpy.mean(points_y)]) + if positions: - # Update frozen focus attribute using centroid - object.__setattr__(self, 'focus', (centroid_array[0], centroid_array[1])) + positions_array = numpy.asarray(self.values()) + centroid = numpy.mean(positions_array, axis=0) + + # Update focus attribute using centroid + self.focus = (centroid[0], centroid[1]) -@dataclass(frozen=True) class TestSaccade(GazeFeatures.Saccade): """Define basic saccade for test.""" - def __post_init__(self): - super().__post_init__() + def __init__(self, positions: GazeFeatures.TimeStampedGazePositions = (), finished: bool = False, message: str = None, **kwargs): + + super().__init__(positions, finished, message, **kwargs) class TestGazePositionClass(unittest.TestCase): """Test GazePosition class.""" @@ -265,11 +264,11 @@ class TestTimeStampedGazePositionsClass(unittest.TestCase): self.assertEqual(ts_gaze_positions[0].precision, 15) self.assertEqual(bool(ts_gaze_positions[0]), True) - # Check third gaze position is correctly stored and accessible as a UnvalidGazePosition + # Check third gaze position is correctly stored and accessible as a GazePosition self.assertIsInstance(ts_gaze_positions[2], GazeFeatures.GazePosition) self.assertEqual(numpy.isnan(ts_gaze_positions[2].precision), True) self.assertEqual(bool(ts_gaze_positions[2]), False) - + def test_as_dataframe(self): """Test inherited as_dataframe method.""" @@ -309,19 +308,19 @@ class TestTimeStampedGazePositionsClass(unittest.TestCase): class TestGazeMovementClass(unittest.TestCase): """Test GazeMovement class.""" - @unittest.skip("DEBUG") + 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(len(abstract_gaze_movement), 0) + self.assertEqual(abstract_gaze_movement.duration, 0) + self.assertEqual(abstract_gaze_movement.amplitude, 0) + self.assertEqual(bool(abstract_gaze_movement), False) self.assertEqual(abstract_gaze_movement.finished, False) - @unittest.skip("DEBUG") + def test_finish(self): """Test GazeMovement finishing.""" @@ -340,25 +339,22 @@ class TestGazeMovementClass(unittest.TestCase): self.assertEqual(abstract_gaze_movement.finished, True) self.assertEqual(abstract_gaze_movement_ref.finished, True) -class TestUnvalidGazeMovementClass(unittest.TestCase): - """Test UnvalidGazeMovement class.""" - @unittest.skip("DEBUG") - def test_new(self): - """Test UnvalidGazeMovement creation.""" + def test_message(self): + """Test GazeMovement creation with message only.""" - unvalid_gaze_movement = GazeFeatures.UnvalidGazeMovement('test') + gaze_movement = GazeFeatures.GazeMovement(message='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') + # Check GazeMovement + self.assertEqual(len(gaze_movement), 0) + self.assertEqual(gaze_movement.duration, 0) + self.assertEqual(gaze_movement.amplitude, 0) + self.assertEqual(bool(gaze_movement), False) + self.assertEqual(gaze_movement.finished, False) + self.assertEqual(gaze_movement.message, 'test') class TestScanStepClass(unittest.TestCase): """Test ScanStep class.""" - @unittest.skip("DEBUG") + def test_new(self): """Test ScanStep creation.""" @@ -371,7 +367,7 @@ class TestScanStepClass(unittest.TestCase): self.assertEqual(scan_step.first_fixation, fixation) self.assertEqual(scan_step.last_saccade, saccade) self.assertGreater(scan_step.duration, 0) - + def build_scan_path(size, frame_dimension: tuple[float, float] = (1, 1)): """Build scan path""" @@ -380,18 +376,18 @@ def build_scan_path(size, frame_dimension: tuple[float, float] = (1, 1)): for i in range(size): fixation = TestFixation(random_gaze_positions(10, frame_dimension)) - ts, _ = fixation.positions.first + ts, _ = fixation.first scan_path.append_fixation(ts, fixation) saccade = TestSaccade(random_gaze_positions(2, frame_dimension)) - ts, _ = saccade.positions.first + ts, _ = saccade.first scan_path.append_saccade(ts, saccade) return scan_path class TestScanPathClass(unittest.TestCase): """Test ScanPath class.""" - @unittest.skip("DEBUG") + def test_new(self): """Test ScanPath creation.""" @@ -400,7 +396,7 @@ class TestScanPathClass(unittest.TestCase): self.assertEqual(len(scan_path), 0) self.assertEqual(scan_path.duration, 0) - @unittest.skip("DEBUG") + def test_append(self): """Test ScanPath append methods.""" @@ -408,7 +404,7 @@ class TestScanPathClass(unittest.TestCase): # Append a saccade that should be ignored saccade = TestSaccade(random_gaze_positions(2)) - ts, _ = saccade.positions.first + ts = saccade[0].timestamp new_step = scan_path.append_saccade(ts, saccade) @@ -419,7 +415,7 @@ class TestScanPathClass(unittest.TestCase): # Append first fixation fixation_A = TestFixation(random_gaze_positions(10)) - ts, _ = fixation_A.positions.first + ts = fixation_A[0].timestamp new_step = scan_path.append_fixation(ts, fixation_A) @@ -430,7 +426,7 @@ class TestScanPathClass(unittest.TestCase): # Append consecutive saccade saccade_A = TestSaccade(random_gaze_positions(2)) - ts, _ = saccade_A.positions.first + ts = saccade_A[0].timestamp new_step_A = scan_path.append_saccade(ts, saccade_A) @@ -443,7 +439,7 @@ class TestScanPathClass(unittest.TestCase): # Append 2 consecutive fixations then a saccade fixation_B1 = TestFixation(random_gaze_positions(10)) - ts, _ = fixation_B1.positions.first + ts = fixation_B1[0].timestamp new_step = scan_path.append_fixation(ts, fixation_B1) @@ -453,7 +449,7 @@ class TestScanPathClass(unittest.TestCase): self.assertEqual(new_step, None) fixation_B2 = TestFixation(random_gaze_positions(10)) - ts, _ = fixation_B2.positions.first + ts = fixation_B2[0].timestamp new_step = scan_path.append_fixation(ts, fixation_B2) @@ -463,7 +459,7 @@ class TestScanPathClass(unittest.TestCase): self.assertEqual(new_step, None) saccade_B = TestSaccade(random_gaze_positions(2)) - ts, _ = saccade_B.positions.first + ts = saccade_B[0].timestamp new_step_B = scan_path.append_saccade(ts, saccade_B) @@ -476,19 +472,19 @@ class TestScanPathClass(unittest.TestCase): class TestAOIScanStepClass(unittest.TestCase): """Test AOIScanStep class.""" - @unittest.skip("DEBUG") + def test_new(self): """Test AOIScanStep creation.""" movements = GazeFeatures.TimeStampedGazeMovements() fixation = TestFixation(random_gaze_positions(10)) - ts, _ = fixation.positions.first - movements[ts] = fixation + ts = fixation[0].timestamp + movements.append(fixation) saccade = TestSaccade(random_gaze_positions(2)) - ts, _ = saccade.positions.first - movements[ts] = saccade + ts = saccade[0].timestamp + movements.append(saccade) aoi_scan_step = GazeFeatures.AOIScanStep(movements, 'Test') @@ -505,12 +501,12 @@ class TestAOIScanStepClass(unittest.TestCase): movements = GazeFeatures.TimeStampedGazeMovements() saccade = TestSaccade(random_gaze_positions(2)) - ts, _ = saccade.positions.first - movements[ts] = saccade + ts = saccade[0].timestamp + movements.append(saccade) fixation = TestFixation(random_gaze_positions(10)) - ts, _ = fixation.positions.first - movements[ts] = fixation + ts = fixation[0].timestamp + movements.append(fixation) # Check that aoi scan step creation fail with self.assertRaises(GazeFeatures.AOIScanStepError): @@ -528,11 +524,11 @@ def build_aoi_scan_path(expected_aoi, aoi_path): for aoi in aoi_path: fixation = TestFixation(random_gaze_positions(10)) - ts, _ = fixation.positions.first + ts, _ = fixation.first aoi_scan_path.append_fixation(ts, fixation, aoi) saccade = TestSaccade(random_gaze_positions(2)) - ts, _ = saccade.positions.first + ts, _ = saccade.first aoi_scan_path.append_saccade(ts, saccade) return aoi_scan_path @@ -555,7 +551,7 @@ class TestAOIScanPathClass(unittest.TestCase): # Append fixation on A aoi fixation_A = TestFixation(random_gaze_positions(10)) - ts, _ = fixation_A.positions.first + ts, _ = fixation_A.first new_step = aoi_scan_path.append_fixation(ts, fixation_A, 'Foo') @@ -566,7 +562,7 @@ class TestAOIScanPathClass(unittest.TestCase): # Append saccade saccade_A = TestSaccade(random_gaze_positions(2)) - ts, _ = saccade_A.positions.first + ts, _ = saccade_A.first new_step = aoi_scan_path.append_saccade(ts, saccade_A) @@ -577,7 +573,7 @@ class TestAOIScanPathClass(unittest.TestCase): # Append fixation on B aoi fixation_B = TestFixation(random_gaze_positions(10)) - ts, _ = fixation_B.positions.first + ts, _ = fixation_B.first new_step_A = aoi_scan_path.append_fixation(ts, fixation_B, 'Bar') @@ -588,8 +584,8 @@ class TestAOIScanPathClass(unittest.TestCase): 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 + first_ts, _ = fixation_A.first + last_ts, _ = saccade_A.last self.assertEqual(new_step_A.duration, last_ts - first_ts) @@ -603,7 +599,7 @@ class TestAOIScanPathClass(unittest.TestCase): # Append fixation on A aoi fixation = TestFixation(random_gaze_positions(10)) - ts, _ = fixation.positions.first + ts, _ = fixation.first new_step = aoi_scan_path.append_fixation(ts, fixation, 'Foo') @@ -613,7 +609,7 @@ class TestAOIScanPathClass(unittest.TestCase): # Append fixation on B aoi fixation = TestFixation(random_gaze_positions(10)) - ts, _ = fixation.positions.first + ts, _ = fixation.first # Check that aoi scan step creation fail when fixation is appened after another fixation with self.assertRaises(GazeFeatures.AOIScanStepError): -- cgit v1.1