From 4a83037c3cd1d98cf16f98efd26e7e2a8d356b25 Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Wed, 31 May 2023 11:35:13 +0200 Subject: Renaming DispersionThresholdIdentification test file. --- .../DispersionBasedGazeMovementIdentifier.py | 313 --------------------- .../DispersionThresholdIdentification.py | 313 +++++++++++++++++++++ 2 files changed, 313 insertions(+), 313 deletions(-) delete mode 100644 src/argaze.test/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py create mode 100644 src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py diff --git a/src/argaze.test/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py b/src/argaze.test/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py deleted file mode 100644 index dc4284c..0000000 --- a/src/argaze.test/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py +++ /dev/null @@ -1,313 +0,0 @@ -#!/usr/bin/env python - -""" """ - -__author__ = "Théo de la Hogue" -__credits__ = [] -__copyright__ = "Copyright 2023, Ecole Nationale de l'Aviation Civile (ENAC)" -__license__ = "BSD" - -import unittest -import random -import time - -from argaze import GazeFeatures -from argaze.GazeAnalysis import DispersionThresholdIdentification - -import numpy - -def build_gaze_fixation(size: int, center: tuple, deviation_max: float, start_time: float, min_time: float, max_time: float, validity: list = []): - """ Generate N TimeStampedGazePsoitions dispersed around a center point for testing purpose. - Timestamps are current time after random sleep (second). - GazePositions are random values. - """ - ts_gaze_positions = GazeFeatures.TimeStampedGazePositions() - - for i in range(0, size): - - # Check position validity - valid = True - if len(validity) > i: - - valid = validity[i] - - if valid: - - # Edit gaze position - random_x = center[0] + deviation_max * (random.random() - 0.5) - random_y = center[1] + deviation_max * (random.random() - 0.5) - gaze_position = GazeFeatures.GazePosition((random_x, random_y)) - - else: - - gaze_position = GazeFeatures.UnvalidGazePosition() - - # Store gaze position - ts = time.time() - start_time - ts_gaze_positions[ts] = gaze_position - - # Sleep a random time - sleep_time = random.random() * (max_time - min_time) + min_time - time.sleep(sleep_time) - - return ts_gaze_positions - -def build_gaze_saccade(size: int, center_A: tuple, center_B: tuple, start_time: float, min_time: float, max_time: float, validity: list = []): - """ Generate N TimeStampedGazePsoitions between 2 center points for testing purpose. - Timestamps are current time after random sleep (second). - GazePositions are random values. - """ - ts_gaze_positions = GazeFeatures.TimeStampedGazePositions() - - for i in range(0, size): - - # Check position validity - valid = True - if len(validity) > i: - - valid = validity[i] - - if valid: - - # Edit gaze position - move_x = center_A[0] + (center_B[0] - center_A[0]) * (i / size) - move_y = center_A[1] + (center_B[1] - center_A[1]) * (i / size) - gaze_position = GazeFeatures.GazePosition((move_x, move_y)) - - else: - - gaze_position = GazeFeatures.UnvalidGazePosition() - - # Store gaze position - ts = time.time() - start_time - ts_gaze_positions[ts] = gaze_position - - # Sleep a random time - sleep_time = random.random() * (max_time - min_time) + min_time - time.sleep(sleep_time) - - return ts_gaze_positions - -class TestDispersionThresholdIdentificationClass(unittest.TestCase): - """Test DispersionThresholdIdentification class.""" - - def test_fixation_identification(self): - """Test DispersionThresholdIdentification fixation identification.""" - - size = 10 - center = (0, 0) - deviation_max = 10 - start_time = time.time() - min_time = 0.01 - max_time = 0.1 - - ts_gaze_positions = build_gaze_fixation(size, center, deviation_max, start_time, min_time, max_time) - 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) - - # Check result size - self.assertEqual(len(ts_fixations), 1) - self.assertEqual(len(ts_saccades), 0) - self.assertEqual(len(ts_status), size) - - # Check fixation - ts, fixation = ts_fixations.pop_first() - - self.assertEqual(len(fixation.positions.keys()), size) - self.assertLessEqual(fixation.deviation_max, deviation_max) - self.assertGreaterEqual(fixation.duration, size * min_time) - self.assertLessEqual(fixation.duration, size * max_time) - - def test_fixation_and_direct_saccade_identification(self): - """Test DispersionThresholdIdentification fixation and saccade identification.""" - - size = 10 - center_A = (0, 0) - center_B = (50, 50) - deviation_max = 10 - start_time = time.time() - min_time = 0.01 - max_time = 0.1 - - ts_gaze_positions_A = build_gaze_fixation(size, center_A, deviation_max, start_time, min_time, max_time) - ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, start_time, min_time, max_time) - - 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) - ts_fixations, ts_saccades, ts_status = gaze_movement_identifier.browse(ts_gaze_positions) - - # Check result size - self.assertEqual(len(ts_fixations), 2) - self.assertEqual(len(ts_saccades), 1) - self.assertEqual(len(ts_status), size*2) - - # Check first fixation - ts, fixation = ts_fixations.pop_first() - - self.assertEqual(len(fixation.positions.keys()), size) - self.assertLessEqual(fixation.deviation_max, deviation_max) - self.assertGreaterEqual(fixation.duration, size * min_time) - self.assertLessEqual(fixation.duration, size * max_time) - - # Check first saccade - ts, saccade = ts_saccades.pop_first() - - self.assertEqual(len(saccade.positions.keys()), 1) - self.assertGreaterEqual(saccade.duration, 0.) - self.assertLessEqual(saccade.duration, 0.) - - # Check second fixation - ts, fixation = ts_fixations.pop_first() - - self.assertEqual(len(fixation.positions.keys()), size-1) - self.assertLessEqual(fixation.deviation_max, deviation_max) - self.assertGreaterEqual(fixation.duration, size * min_time) - self.assertLessEqual(fixation.duration, size * max_time) - ''' - def test_fixation_and_short_saccade_identification(self): - """Test DispersionThresholdIdentification fixation and saccade identification.""" - - size = 10 - move = 2 - center_A = (0, 0) - out_A = (20, 20) - center_B = (50, 50) - deviation_max = 10 - start_time = time.time() - min_time = 0.01 - max_time = 0.1 - - ts_gaze_positions_A = build_gaze_fixation(size, center_A, deviation_max, start_time, min_time, max_time) - ts_move_positions = build_gaze_saccade(move, out_A, center_B, start_time, min_time, min_time) - ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, start_time, min_time, max_time) - - print(ts_move_positions) - - ts_gaze_positions = ts_gaze_positions_A.append(ts_move_positions).append(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) - - # Check result size - self.assertEqual(len(ts_fixations), 2) - self.assertEqual(len(ts_saccades), 1) - self.assertEqual(len(ts_status), size*2 + move) - - # Check first fixation - ts, fixation = ts_fixations.pop_first() - - self.assertEqual(len(fixation.positions.keys()), size-1) - self.assertLessEqual(fixation.deviation_max, deviation_max) - self.assertGreaterEqual(fixation.duration, size * min_time) - self.assertLessEqual(fixation.duration, size * max_time) - - # Check first saccade - ts, saccade = ts_saccades.pop_first() - - self.assertEqual(len(saccade.positions.keys()), move + 2) - self.assertGreaterEqual(saccade.duration, min_time) - self.assertLessEqual(saccade.duration, max_time) - - # Check second fixation - ts, fixation = ts_fixations.pop_first() - - self.assertEqual(len(fixation.positions.keys()), size-1) - self.assertLessEqual(fixation.deviation_max, deviation_max) - self.assertGreaterEqual(fixation.duration, size * min_time) - self.assertLessEqual(fixation.duration, size * max_time) - ''' - def test_invalid_gaze_position(self): - """Test DispersionThresholdIdentification fixation and saccade identification with invalid gaze position.""" - - size = 15 - center = (0, 0) - deviation_max = 10 - start_time = time.time() - min_time = 0.01 - max_time = 0.1 - validity = [True, True, True, True, False, True, True, True, False, False, True, True, True, True, True] - - ts_gaze_positions = build_gaze_fixation(size, center, deviation_max, start_time, min_time, max_time, validity) - - 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) - - # Check result size - self.assertEqual(len(ts_fixations), 1) - self.assertEqual(len(ts_saccades), 0) - self.assertEqual(len(ts_status), size-3) - - # Check fixation - ts, fixation = ts_fixations.pop_first() - - self.assertEqual(len(fixation.positions.keys()), size-3) - self.assertLessEqual(fixation.deviation_max, deviation_max) - self.assertGreaterEqual(fixation.duration, size * min_time) - self.assertLessEqual(fixation.duration, size * max_time) - - @unittest.skip("Fixation overlapping is broken.") - def test_fixation_overlapping(self): - """Test Fixation overlap function.""" - - size = 10 - center_A = (0, 0) - center_B = (50, 50) - center_C = (55, 55) - deviation_max = 10 - start_time = time.time() - min_time = 0.01 - max_time = 0.1 - - ts_gaze_positions_A = build_gaze_fixation(size, center_A, deviation_max, start_time, min_time, max_time) - ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, start_time, min_time, max_time) - ts_gaze_positions_C = build_gaze_fixation(size, center_C, deviation_max, start_time, min_time, max_time) - - fixation_A = DispersionThresholdIdentification.Fixation(ts_gaze_positions_A) - fixation_B = DispersionThresholdIdentification.Fixation(ts_gaze_positions_B) - fixation_C = DispersionThresholdIdentification.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)) - - @unittest.skip("Fixation overlapping identification is broken.") - def test_fixation_overlapping_identification(self): - """Test DispersionThresholdIdentification identification when fixations overlap.""" - - size = 50 - center_A = (-5, 0) - center_B = (5, 0) - deviation_max = 15 - start_time = time.time() - min_time = 0.01 - max_time = 0.1 - - ts_gaze_positions_A = build_gaze_fixation(size, center_A, deviation_max, start_time, min_time, max_time) - ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, start_time, min_time, max_time) - - 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) - ts_fixations, ts_saccades, ts_status = gaze_movement_identifier.browse(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.deviation_max, deviation_max) - 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.test/GazeAnalysis/DispersionThresholdIdentification.py b/src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py new file mode 100644 index 0000000..dc4284c --- /dev/null +++ b/src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py @@ -0,0 +1,313 @@ +#!/usr/bin/env python + +""" """ + +__author__ = "Théo de la Hogue" +__credits__ = [] +__copyright__ = "Copyright 2023, Ecole Nationale de l'Aviation Civile (ENAC)" +__license__ = "BSD" + +import unittest +import random +import time + +from argaze import GazeFeatures +from argaze.GazeAnalysis import DispersionThresholdIdentification + +import numpy + +def build_gaze_fixation(size: int, center: tuple, deviation_max: float, start_time: float, min_time: float, max_time: float, validity: list = []): + """ Generate N TimeStampedGazePsoitions dispersed around a center point for testing purpose. + Timestamps are current time after random sleep (second). + GazePositions are random values. + """ + ts_gaze_positions = GazeFeatures.TimeStampedGazePositions() + + for i in range(0, size): + + # Check position validity + valid = True + if len(validity) > i: + + valid = validity[i] + + if valid: + + # Edit gaze position + random_x = center[0] + deviation_max * (random.random() - 0.5) + random_y = center[1] + deviation_max * (random.random() - 0.5) + gaze_position = GazeFeatures.GazePosition((random_x, random_y)) + + else: + + gaze_position = GazeFeatures.UnvalidGazePosition() + + # Store gaze position + ts = time.time() - start_time + ts_gaze_positions[ts] = gaze_position + + # Sleep a random time + sleep_time = random.random() * (max_time - min_time) + min_time + time.sleep(sleep_time) + + return ts_gaze_positions + +def build_gaze_saccade(size: int, center_A: tuple, center_B: tuple, start_time: float, min_time: float, max_time: float, validity: list = []): + """ Generate N TimeStampedGazePsoitions between 2 center points for testing purpose. + Timestamps are current time after random sleep (second). + GazePositions are random values. + """ + ts_gaze_positions = GazeFeatures.TimeStampedGazePositions() + + for i in range(0, size): + + # Check position validity + valid = True + if len(validity) > i: + + valid = validity[i] + + if valid: + + # Edit gaze position + move_x = center_A[0] + (center_B[0] - center_A[0]) * (i / size) + move_y = center_A[1] + (center_B[1] - center_A[1]) * (i / size) + gaze_position = GazeFeatures.GazePosition((move_x, move_y)) + + else: + + gaze_position = GazeFeatures.UnvalidGazePosition() + + # Store gaze position + ts = time.time() - start_time + ts_gaze_positions[ts] = gaze_position + + # Sleep a random time + sleep_time = random.random() * (max_time - min_time) + min_time + time.sleep(sleep_time) + + return ts_gaze_positions + +class TestDispersionThresholdIdentificationClass(unittest.TestCase): + """Test DispersionThresholdIdentification class.""" + + def test_fixation_identification(self): + """Test DispersionThresholdIdentification fixation identification.""" + + size = 10 + center = (0, 0) + deviation_max = 10 + start_time = time.time() + min_time = 0.01 + max_time = 0.1 + + ts_gaze_positions = build_gaze_fixation(size, center, deviation_max, start_time, min_time, max_time) + 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) + + # Check result size + self.assertEqual(len(ts_fixations), 1) + self.assertEqual(len(ts_saccades), 0) + self.assertEqual(len(ts_status), size) + + # Check fixation + ts, fixation = ts_fixations.pop_first() + + self.assertEqual(len(fixation.positions.keys()), size) + self.assertLessEqual(fixation.deviation_max, deviation_max) + self.assertGreaterEqual(fixation.duration, size * min_time) + self.assertLessEqual(fixation.duration, size * max_time) + + def test_fixation_and_direct_saccade_identification(self): + """Test DispersionThresholdIdentification fixation and saccade identification.""" + + size = 10 + center_A = (0, 0) + center_B = (50, 50) + deviation_max = 10 + start_time = time.time() + min_time = 0.01 + max_time = 0.1 + + ts_gaze_positions_A = build_gaze_fixation(size, center_A, deviation_max, start_time, min_time, max_time) + ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, start_time, min_time, max_time) + + 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) + ts_fixations, ts_saccades, ts_status = gaze_movement_identifier.browse(ts_gaze_positions) + + # Check result size + self.assertEqual(len(ts_fixations), 2) + self.assertEqual(len(ts_saccades), 1) + self.assertEqual(len(ts_status), size*2) + + # Check first fixation + ts, fixation = ts_fixations.pop_first() + + self.assertEqual(len(fixation.positions.keys()), size) + self.assertLessEqual(fixation.deviation_max, deviation_max) + self.assertGreaterEqual(fixation.duration, size * min_time) + self.assertLessEqual(fixation.duration, size * max_time) + + # Check first saccade + ts, saccade = ts_saccades.pop_first() + + self.assertEqual(len(saccade.positions.keys()), 1) + self.assertGreaterEqual(saccade.duration, 0.) + self.assertLessEqual(saccade.duration, 0.) + + # Check second fixation + ts, fixation = ts_fixations.pop_first() + + self.assertEqual(len(fixation.positions.keys()), size-1) + self.assertLessEqual(fixation.deviation_max, deviation_max) + self.assertGreaterEqual(fixation.duration, size * min_time) + self.assertLessEqual(fixation.duration, size * max_time) + ''' + def test_fixation_and_short_saccade_identification(self): + """Test DispersionThresholdIdentification fixation and saccade identification.""" + + size = 10 + move = 2 + center_A = (0, 0) + out_A = (20, 20) + center_B = (50, 50) + deviation_max = 10 + start_time = time.time() + min_time = 0.01 + max_time = 0.1 + + ts_gaze_positions_A = build_gaze_fixation(size, center_A, deviation_max, start_time, min_time, max_time) + ts_move_positions = build_gaze_saccade(move, out_A, center_B, start_time, min_time, min_time) + ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, start_time, min_time, max_time) + + print(ts_move_positions) + + ts_gaze_positions = ts_gaze_positions_A.append(ts_move_positions).append(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) + + # Check result size + self.assertEqual(len(ts_fixations), 2) + self.assertEqual(len(ts_saccades), 1) + self.assertEqual(len(ts_status), size*2 + move) + + # Check first fixation + ts, fixation = ts_fixations.pop_first() + + self.assertEqual(len(fixation.positions.keys()), size-1) + self.assertLessEqual(fixation.deviation_max, deviation_max) + self.assertGreaterEqual(fixation.duration, size * min_time) + self.assertLessEqual(fixation.duration, size * max_time) + + # Check first saccade + ts, saccade = ts_saccades.pop_first() + + self.assertEqual(len(saccade.positions.keys()), move + 2) + self.assertGreaterEqual(saccade.duration, min_time) + self.assertLessEqual(saccade.duration, max_time) + + # Check second fixation + ts, fixation = ts_fixations.pop_first() + + self.assertEqual(len(fixation.positions.keys()), size-1) + self.assertLessEqual(fixation.deviation_max, deviation_max) + self.assertGreaterEqual(fixation.duration, size * min_time) + self.assertLessEqual(fixation.duration, size * max_time) + ''' + def test_invalid_gaze_position(self): + """Test DispersionThresholdIdentification fixation and saccade identification with invalid gaze position.""" + + size = 15 + center = (0, 0) + deviation_max = 10 + start_time = time.time() + min_time = 0.01 + max_time = 0.1 + validity = [True, True, True, True, False, True, True, True, False, False, True, True, True, True, True] + + ts_gaze_positions = build_gaze_fixation(size, center, deviation_max, start_time, min_time, max_time, validity) + + 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) + + # Check result size + self.assertEqual(len(ts_fixations), 1) + self.assertEqual(len(ts_saccades), 0) + self.assertEqual(len(ts_status), size-3) + + # Check fixation + ts, fixation = ts_fixations.pop_first() + + self.assertEqual(len(fixation.positions.keys()), size-3) + self.assertLessEqual(fixation.deviation_max, deviation_max) + self.assertGreaterEqual(fixation.duration, size * min_time) + self.assertLessEqual(fixation.duration, size * max_time) + + @unittest.skip("Fixation overlapping is broken.") + def test_fixation_overlapping(self): + """Test Fixation overlap function.""" + + size = 10 + center_A = (0, 0) + center_B = (50, 50) + center_C = (55, 55) + deviation_max = 10 + start_time = time.time() + min_time = 0.01 + max_time = 0.1 + + ts_gaze_positions_A = build_gaze_fixation(size, center_A, deviation_max, start_time, min_time, max_time) + ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, start_time, min_time, max_time) + ts_gaze_positions_C = build_gaze_fixation(size, center_C, deviation_max, start_time, min_time, max_time) + + fixation_A = DispersionThresholdIdentification.Fixation(ts_gaze_positions_A) + fixation_B = DispersionThresholdIdentification.Fixation(ts_gaze_positions_B) + fixation_C = DispersionThresholdIdentification.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)) + + @unittest.skip("Fixation overlapping identification is broken.") + def test_fixation_overlapping_identification(self): + """Test DispersionThresholdIdentification identification when fixations overlap.""" + + size = 50 + center_A = (-5, 0) + center_B = (5, 0) + deviation_max = 15 + start_time = time.time() + min_time = 0.01 + max_time = 0.1 + + ts_gaze_positions_A = build_gaze_fixation(size, center_A, deviation_max, start_time, min_time, max_time) + ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, start_time, min_time, max_time) + + 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) + ts_fixations, ts_saccades, ts_status = gaze_movement_identifier.browse(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.deviation_max, deviation_max) + 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 -- cgit v1.1