diff options
Diffstat (limited to 'src/argaze.test')
-rw-r--r-- | src/argaze.test/ArUcoMarkers/ArUcoCamera.py | 83 | ||||
-rw-r--r-- | src/argaze.test/ArUcoMarkers/utils/aoi_3d.obj | 7 | ||||
-rw-r--r-- | src/argaze.test/ArUcoMarkers/utils/aruco_camera.json | 39 | ||||
-rw-r--r-- | src/argaze.test/DataFeatures.py | 464 | ||||
-rw-r--r-- | src/argaze.test/DataLog/FileWriter.py | 37 | ||||
-rw-r--r-- | src/argaze.test/DataLog/__init__.py | 0 | ||||
-rw-r--r-- | src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py | 154 | ||||
-rw-r--r-- | src/argaze.test/GazeAnalysis/VelocityThresholdIdentification.py | 127 | ||||
-rw-r--r-- | src/argaze.test/GazeFeatures.py | 358 | ||||
-rw-r--r-- | src/argaze.test/PupillFeatures.py | 60 | ||||
-rw-r--r-- | src/argaze.test/utils/ts_buffer.json | 1 | ||||
-rw-r--r-- | src/argaze.test/utils/ts_data_file.json | 82 |
12 files changed, 714 insertions, 698 deletions
diff --git a/src/argaze.test/ArUcoMarkers/ArUcoCamera.py b/src/argaze.test/ArUcoMarkers/ArUcoCamera.py index 6145f40..a3c5943 100644 --- a/src/argaze.test/ArUcoMarkers/ArUcoCamera.py +++ b/src/argaze.test/ArUcoMarkers/ArUcoCamera.py @@ -25,49 +25,46 @@ class TestArUcoCameraClass(unittest.TestCase): json_filepath = os.path.join(current_directory, 'utils/aruco_camera.json') # Load test aruco camera - aruco_camera = ArUcoCamera.ArUcoCamera.from_json(json_filepath) - - # Check aruco camera meta data - self.assertEqual(aruco_camera.name, "TestArUcoCamera") - - # Check ArUco detector - self.assertEqual(aruco_camera.aruco_detector.dictionary.name, "DICT_ARUCO_ORIGINAL") - self.assertEqual(aruco_camera.aruco_detector.marker_size, 3.0) - self.assertEqual(aruco_camera.aruco_detector.parameters.cornerRefinementMethod, 3) - self.assertEqual(aruco_camera.aruco_detector.parameters.aprilTagQuadSigma, 2) - self.assertEqual(aruco_camera.aruco_detector.parameters.aprilTagDeglitch, 1) - - # Check ArUco detector optic parameters - self.assertEqual(aruco_camera.aruco_detector.optic_parameters.rms, 1.0) - self.assertIsNone(numpy.testing.assert_array_equal(aruco_camera.aruco_detector.optic_parameters.dimensions, [1920, 1080])) - self.assertIsNone(numpy.testing.assert_array_equal(aruco_camera.aruco_detector.optic_parameters.K, [[1.0, 0.0, 1.0], [0.0, 1.0, 1.0], [0.0, 0.0, 1.0]])) - self.assertIsNone(numpy.testing.assert_array_equal(aruco_camera.aruco_detector.optic_parameters.D, [-1.0, -0.5, 0.0, 0.5, 1.0])) - - # Check camera scenes - self.assertEqual(len(aruco_camera.scenes), 2) - self.assertIsNone(numpy.testing.assert_array_equal(list(aruco_camera.scenes.keys()), ["TestSceneA", "TestSceneB"])) - - # Load test scene - ar_scene = aruco_camera.scenes["TestSceneA"] - - # Check Aruco scene - self.assertEqual(len(ar_scene.aruco_markers_group.places), 2) - self.assertIsNone(numpy.testing.assert_array_equal(ar_scene.aruco_markers_group.places[0].translation, [1, 0, 0])) - self.assertIsNone(numpy.testing.assert_array_equal(ar_scene.aruco_markers_group.places[0].rotation, [[1.,0.,0.],[0.,1.,0.],[0.,0.,1.]])) - self.assertEqual(ar_scene.aruco_markers_group.places[0].marker.identifier, 0) - - self.assertIsNone(numpy.testing.assert_array_equal(ar_scene.aruco_markers_group.places[1].translation, [0, 1, 0])) - self.assertIsNone(numpy.testing.assert_array_almost_equal(ar_scene.aruco_markers_group.places[1].rotation, [[0.,0.,1.],[0., 1.,0.],[-1.,0.,0.]])) - self.assertEqual(ar_scene.aruco_markers_group.places[1].marker.identifier, 1) - - # Check AOI scene - self.assertEqual(len(ar_scene.aoi_scene.items()), 1) - self.assertEqual(ar_scene.aoi_scene['Test'].points_number, 4) - self.assertIsNone(numpy.testing.assert_array_equal(ar_scene.aoi_scene['Test'].size, [1., 1., 0.])) - - # Check ArScene - self.assertEqual(ar_scene.angle_tolerance, 1.0) - self.assertEqual(ar_scene.distance_tolerance, 2.0) + with ArUcoCamera.ArUcoCamera.from_json(json_filepath) as aruco_camera: + + # Check aruco camera meta data + self.assertEqual(aruco_camera.name, "TestArUcoCamera") + + # Check ArUco detector + self.assertEqual(aruco_camera.aruco_detector.dictionary.name, "DICT_ARUCO_ORIGINAL") + self.assertEqual(aruco_camera.aruco_detector.parameters.cornerRefinementMethod, 3) + self.assertEqual(aruco_camera.aruco_detector.parameters.aprilTagQuadSigma, 2) + self.assertEqual(aruco_camera.aruco_detector.parameters.aprilTagDeglitch, 1) + + # Check ArUco detector optic parameters + self.assertEqual(aruco_camera.aruco_detector.optic_parameters.rms, 1.0) + self.assertIsNone(numpy.testing.assert_array_equal(aruco_camera.aruco_detector.optic_parameters.dimensions, [1920, 1080])) + self.assertIsNone(numpy.testing.assert_array_equal(aruco_camera.aruco_detector.optic_parameters.K, [[1.0, 0.0, 1.0], [0.0, 1.0, 1.0], [0.0, 0.0, 1.0]])) + self.assertIsNone(numpy.testing.assert_array_equal(aruco_camera.aruco_detector.optic_parameters.D, [-1.0, -0.5, 0.0, 0.5, 1.0])) + + # Check camera scenes + self.assertEqual(len(aruco_camera.scenes), 2) + self.assertIsNone(numpy.testing.assert_array_equal(list(aruco_camera.scenes.keys()), ["TestSceneA", "TestSceneB"])) + + # Load test scene + ar_scene = aruco_camera.scenes["TestSceneA"] + + # Check Aruco scene + self.assertEqual(len(ar_scene.aruco_markers_group.places), 2) + self.assertIsNone(numpy.testing.assert_allclose(ar_scene.aruco_markers_group.places[0].corners[0], [-0.5, 1.5, 0.], rtol=0, atol=1e-3)) + self.assertEqual(ar_scene.aruco_markers_group.places[0].marker.identifier, 0) + + self.assertIsNone(numpy.testing.assert_allclose(ar_scene.aruco_markers_group.places[1].corners[0], [0., 2.5, -1.5], rtol=0, atol=1e-3)) + self.assertEqual(ar_scene.aruco_markers_group.places[1].marker.identifier, 1) + + # Check layers and AOI scene + self.assertEqual(len(ar_scene.layers.items()), 1) + self.assertEqual(len(ar_scene.layers["Main"].aoi_scene), 1) + self.assertEqual(ar_scene.layers["Main"].aoi_scene['Test'].points_number, 4) + + # Check ArScene + self.assertEqual(ar_scene.angle_tolerance, 1.0) + self.assertEqual(ar_scene.distance_tolerance, 2.0) if __name__ == '__main__': diff --git a/src/argaze.test/ArUcoMarkers/utils/aoi_3d.obj b/src/argaze.test/ArUcoMarkers/utils/aoi_3d.obj new file mode 100644 index 0000000..92e85bd --- /dev/null +++ b/src/argaze.test/ArUcoMarkers/utils/aoi_3d.obj @@ -0,0 +1,7 @@ +o Test +v 0.000000 0.000000 0.000000 +v 25.000000 0.000000 0.000000 +v 0.000000 14.960000 0.000000 +v 25.000000 14.960000 0.000000 +s off +f 1 2 4 3 diff --git a/src/argaze.test/ArUcoMarkers/utils/aruco_camera.json b/src/argaze.test/ArUcoMarkers/utils/aruco_camera.json index 7648916..980dc9f 100644 --- a/src/argaze.test/ArUcoMarkers/utils/aruco_camera.json +++ b/src/argaze.test/ArUcoMarkers/utils/aruco_camera.json @@ -1,10 +1,10 @@ { "name": "TestArUcoCamera", + "size": [1920, 1080], "aruco_detector": { "dictionary": { "name": "DICT_ARUCO_ORIGINAL" }, - "marker_size": 3.0, "optic_parameters": { "rms": 1.0, "dimensions": [ @@ -45,45 +45,54 @@ "scenes": { "TestSceneA" : { "aruco_markers_group": { - "marker_size": 3.0, - "dictionary": { - "name": "DICT_ARUCO_ORIGINAL" - }, + "dictionary": "DICT_ARUCO_ORIGINAL", "places": { "0": { "translation": [1, 0, 0], - "rotation": [0, 0, 0] + "rotation": [0, 0, 0], + "size": 3.0 }, "1": { "translation": [0, 1, 0], - "rotation": [0, 90, 0] + "rotation": [0, 90, 0], + "size": 3.0 } } }, - "aoi_scene": "aoi.obj", + "layers": { + "Main" : { + "aoi_scene": "aoi_3d.obj" + } + }, "angle_tolerance": 1.0, "distance_tolerance": 2.0 }, "TestSceneB" : { "aruco_markers_group": { - "marker_size": 3.0, - "dictionary": { - "name": "DICT_ARUCO_ORIGINAL" - }, + "dictionary": "DICT_ARUCO_ORIGINAL", "places": { "0": { "translation": [1, 0, 0], - "rotation": [0, 0, 0] + "rotation": [0, 0, 0], + "size": 3.0 }, "1": { "translation": [0, 1, 0], - "rotation": [0, 90, 0] + "rotation": [0, 90, 0], + "size": 3.0 } } }, - "aoi_scene": "aoi.obj", + "layers": { + "Main" : { + "aoi_scene": "aoi_3d.obj" + } + }, "angle_tolerance": 1.0, "distance_tolerance": 2.0 } + }, + "layers": { + "Main": {} } }
\ No newline at end of file diff --git a/src/argaze.test/DataFeatures.py b/src/argaze.test/DataFeatures.py index b30c560..c64ad4c 100644 --- a/src/argaze.test/DataFeatures.py +++ b/src/argaze.test/DataFeatures.py @@ -16,334 +16,360 @@ from argaze import DataFeatures import pandas import numpy -def random_data_buffer(size, data_keys): - """ Generate a random TimeStampedBuffer for testing purpose. +class BasicDataClass(DataFeatures.TimestampedObject): + """Define a basic dataclass for testing purpose.""" + + def __init__(self, value: tuple = (), message: str = None, **kwargs): + + DataFeatures.TimestampedObject.__init__(self, **kwargs) + + self.__value = value + self.__message = message + + @property + def value(self): + return self.__value + + @property + def message(self): + return self.__message + +def random_data_list(size): + """ Generate a random TimestampedObjectsList for testing purpose. Timestamps are current time. Values are tuples containing an expected value and a random value. """ - import random import time - ts_buffer = DataFeatures.TimeStampedBuffer() + data_list = [] for i in range(0, size): # Edit data - random_data = {} - for key in data_keys: - random_data[key] = (i, random.random()) + random_data = BasicDataClass((i, random.random()), f'test_{i}') + + # Timestamp data + random_data.timestamp = time.time() # Store data - ts_buffer[time.time()] = random_data + data_list.append(random_data) time.sleep(0.0001) - return ts_buffer + return DataFeatures.TimestampedObjectsList(BasicDataClass, data_list) -@dataclass() -class BasicDataClass(): - """Define a basic dataclass for testing purpose.""" - - value: tuple +# DEBUG +''' +print('test_as_dataframe: export ts_data_file.json') +current_directory = os.path.dirname(os.path.abspath(__file__)) +json_filepath = os.path.join(current_directory, 'utils/ts_data_file.json') +random_data_list(10).to_json(json_filepath) +''' -class TestTimeStampedBufferClass(unittest.TestCase): - """Test TimeStampedBuffer class.""" +class TestTimestampedObjectsListClass(unittest.TestCase): + """Test TimestampedObjectsList class.""" def test_new(self): - """Test TimeStampedBuffer creation.""" + """Test TimestampedObjectsList creation.""" + + # Check TimestampedObjectsList length after creation + self.assertEqual(len(DataFeatures.TimestampedObjectsList(BasicDataClass)), 0) + + # Check TimestampedObjectsList timestamps after creation + self.assertEqual(DataFeatures.TimestampedObjectsList(BasicDataClass).timestamps(), []) + + # Check TimestampedObjectsList items after creation + self.assertEqual(DataFeatures.TimestampedObjectsList(BasicDataClass), []) + + # Check that TimestampedObjectsList creation fails when data are not timestamped + with self.assertRaises(ValueError): + + data_list = [BasicDataClass((0, 0))] + DataFeatures.TimestampedObjectsList(BasicDataClass, data_list) - # Check TimeStampedBuffer length after creation - self.assertEqual(len(DataFeatures.TimeStampedBuffer()), 0) - self.assertEqual(len(DataFeatures.TimeStampedBuffer({0: ""})), 1) - self.assertEqual(len(DataFeatures.TimeStampedBuffer({0.1: ""})), 1) - self.assertEqual(len(DataFeatures.TimeStampedBuffer({0: "A", 1: "B"})), 2) + def test_as_dataframe(self): + """Test TimestampedObjectsList as_dataframe method.""" - # Check TimeStampedBuffer keys after creation - self.assertEqual(list(DataFeatures.TimeStampedBuffer().keys()), []) - self.assertEqual(list(DataFeatures.TimeStampedBuffer({0: ""}).keys()), [0]) - self.assertEqual(list(DataFeatures.TimeStampedBuffer({0.1: ""}).keys()), [0.1]) - self.assertEqual(list(DataFeatures.TimeStampedBuffer({0: "A", 1: "B"}).keys()), [0, 1]) + data_frame = random_data_list(10).as_dataframe() - # Check TimeStampedBuffer items after creation - self.assertEqual(list(DataFeatures.TimeStampedBuffer().items()), []) - self.assertEqual(list(DataFeatures.TimeStampedBuffer({0: ""}).items()), [(0, "")]) - self.assertEqual(list(DataFeatures.TimeStampedBuffer({0.1: ""}).items()), [(0.1, "")]) - self.assertEqual(list(DataFeatures.TimeStampedBuffer({0: "A", 1: "B"}).items()), [(0, "A"), (1, "B")]) + # Check dataframe conversion + self.assertEqual(data_frame.index.name, "timestamp") + self.assertEqual(data_frame.index.size, 10) + self.assertEqual(data_frame.columns.size, 2) + self.assertEqual(data_frame.index.dtype, 'float64') + self.assertEqual(data_frame["value"].dtype, 'object') + self.assertEqual(data_frame["message"].dtype, 'object') - # Check that TimeStampedBuffer creation fails when keys are not numbers - with self.assertRaises(AssertionError): + # Check data exclusion option + data_frame = random_data_list(10).as_dataframe(exclude=["value"]) - DataFeatures.TimeStampedBuffer({"first": ""}) + self.assertEqual(data_frame.index.name, "timestamp") + self.assertEqual(data_frame.index.size, 10) + self.assertEqual(data_frame.columns.size, 1) + # Check dataframe split option + data_frame = random_data_list(10).as_dataframe(split={"value": ["value_0", "value_1"]}) + + self.assertEqual(data_frame.index.name, "timestamp") + self.assertEqual(data_frame.index.size, 10) + self.assertEqual(data_frame.columns.size, 3) + self.assertEqual(data_frame["value_0"].dtype, 'int64') + self.assertEqual(data_frame["value_1"].dtype, 'float64') + def test_from_dataframe(self): - """Test TimeStampedBuffer creation from pandas dataframe.""" + """Test TimestampedObjectsList creation from pandas dataframe.""" - ts_buffer = random_data_buffer(10, ["data_A", "data_B", "data_C"]) + data_frame = random_data_list(10).as_dataframe() # Check dataframe conversion - ts_buffer_from_df = DataFeatures.TimeStampedBuffer.from_dataframe(ts_buffer.as_dataframe()) - - self.assertEqual(len(ts_buffer_from_df), 10) + data_list = DataFeatures.TimestampedObjectsList.from_dataframe(BasicDataClass, data_frame) + self.assertEqual(len(data_list), 10) + def test_from_json(self): - """Test TimeStampedBuffer creation from json file.""" + """Test TimestampedObjectsList creation from json file.""" # Edit dataframe csv file path current_directory = os.path.dirname(os.path.abspath(__file__)) - json_filepath = os.path.join(current_directory, 'utils/ts_buffer.json') + json_filepath = os.path.join(current_directory, 'utils/ts_data_file.json') - # Load TimeStampedBuffer from json file - ts_buffer = DataFeatures.TimeStampedBuffer.from_json(json_filepath) - - self.assertEqual(len(ts_buffer), 3) + # Load TimestampedObjectsList from json file + data_list = DataFeatures.TimestampedObjectsList.from_json(BasicDataClass, json_filepath) + self.assertEqual(len(data_list), 10) + self.assertEqual(type(data_list[0]), BasicDataClass) + def test___repr__(self): - """Test TimeStampedBuffer string representation.""" + """Test TimestampedObjectsList string representation.""" - self.assertEqual(repr(DataFeatures.TimeStampedBuffer()), "{}") - self.assertEqual(repr(DataFeatures.TimeStampedBuffer({0: ""})), "{\"0\": \"\"}") - self.assertEqual(repr(DataFeatures.TimeStampedBuffer({0.1: ""})), "{\"0.1\": \"\"}") + self.assertEqual(repr(DataFeatures.TimestampedObjectsList(BasicDataClass)), "[]") - data = BasicDataClass((123, 456)) - ts_buffer = DataFeatures.TimeStampedBuffer({0: data}) - - self.assertEqual(repr(ts_buffer), "{\"0\": {\"value\": [123, 456]}}") - - array = numpy.zeros(3) - ts_buffer = DataFeatures.TimeStampedBuffer({0: array}) - - self.assertEqual(repr(ts_buffer), "{\"0\": [0.0, 0.0, 0.0]}") + data_list = [BasicDataClass((0, 0), 'test', timestamp=0)] + self.assertEqual(repr(DataFeatures.TimestampedObjectsList(BasicDataClass, data_list)), "[{\"value\": [0, 0], \"message\": \"test\", \"timestamp\": 0}]") def test___str__(self): - """Test TimeStampedBuffer string representation.""" - - self.assertEqual(str(DataFeatures.TimeStampedBuffer()), "{}") - self.assertEqual(str(DataFeatures.TimeStampedBuffer({0: ""})), "{\"0\": \"\"}") - self.assertEqual(str(DataFeatures.TimeStampedBuffer({0.1: ""})), "{\"0.1\": \"\"}") + """Test TimestampedObjectsList string representation.""" - data = BasicDataClass((123, 456)) - ts_buffer = DataFeatures.TimeStampedBuffer({0: data}) + self.assertEqual(str(DataFeatures.TimestampedObjectsList(BasicDataClass)), "[]") - self.assertEqual(str(ts_buffer), "{\"0\": {\"value\": [123, 456]}}") - - array = numpy.zeros(3) - ts_buffer = DataFeatures.TimeStampedBuffer({0: array}) - - self.assertEqual(str(ts_buffer), "{\"0\": [0.0, 0.0, 0.0]}") + data_list = [BasicDataClass((0, 0), 'test', timestamp=0)] + self.assertEqual(str(DataFeatures.TimestampedObjectsList(BasicDataClass, data_list)), "[{\"value\": [0, 0], \"message\": \"test\", \"timestamp\": 0}]") def test_append(self): - """Test TimeStampedBuffer append method.""" - - ts_buffer = DataFeatures.TimeStampedBuffer({0: "A", 1: "B"}) - ts_buffer_next = DataFeatures.TimeStampedBuffer({2: "C", 3: "D"}) - - self.assertEqual(len(ts_buffer.append(ts_buffer_next)), 4) - self.assertEqual(list(ts_buffer.append(ts_buffer_next).keys()), [0, 1, 2, 3]) + """Test TimestampedObjectsList append method.""" - def test_first(self): - """Test TimeStampedBuffer first property.""" + data_list = DataFeatures.TimestampedObjectsList(BasicDataClass) + next_data = BasicDataClass((0, 0), 'test', timestamp=0) - self.assertEqual(DataFeatures.TimeStampedBuffer({0: "A", 1: "B"}).first, (0, "A")) + data_list.append(next_data) - # Check that accessing to first item of an empty TimeStampedBuffer fails - with self.assertRaises(IndexError): + self.assertEqual(len(data_list), 1) + self.assertEqual(data_list.timestamps(), [0]) + + def test_pop_first(self): + """Test TimestampedObjectsList pop_first method.""" - DataFeatures.TimeStampedBuffer().first + data_list = DataFeatures.TimestampedObjectsList(BasicDataClass, \ + [ + BasicDataClass(message="A", timestamp=0), + BasicDataClass(message="B", timestamp=1) + ]) - def test_pop_first(self): - """Test TimeStampedBuffer pop_first method.""" + first = data_list.pop(0) - ts_buffer = DataFeatures.TimeStampedBuffer({0: "A", 1: "B"}) + self.assertEqual(len(data_list), 1) + self.assertEqual(first.message, "A") + self.assertEqual(first.timestamp, 0) + self.assertEqual(data_list[0].message, "B") + self.assertEqual(data_list[0].timestamp, 1) - self.assertEqual(ts_buffer.pop_first(), (0, "A")) - self.assertEqual(len(ts_buffer), 1) - self.assertEqual(ts_buffer.first, (1, "B")) - def test_pop_last_until(self): - """Test TimeStampedBuffer pop_last_until method.""" + """Test TimestampedObjectsList pop_last_until method.""" - ts_buffer = DataFeatures.TimeStampedBuffer({0: "A", 1: "B", 2: "C", 3: "D"}) + data_list = DataFeatures.TimestampedObjectsList(BasicDataClass, \ + [ + BasicDataClass(message="A", timestamp=0), + BasicDataClass(message="B", timestamp=1), + BasicDataClass(message="C", timestamp=2), + BasicDataClass(message="D", timestamp=3) + ]) # Check pop until an existing timestamp - pop_last_until_2 = ts_buffer.pop_last_until(2) + pop_last_until_2 = data_list.pop_last_until(2) - self.assertEqual(pop_last_until_2, (2, "C")) - self.assertEqual(len(ts_buffer), 2) - self.assertEqual(ts_buffer.first, (2, "C")) + self.assertEqual(pop_last_until_2.message, "C") + self.assertEqual(pop_last_until_2.timestamp, 2) + self.assertEqual(len(data_list), 2) + self.assertEqual(data_list[0].message, "C") + self.assertEqual(data_list[0].timestamp, 2) # Check first until an none existing timestamp - ts_buffer = DataFeatures.TimeStampedBuffer({0: "A", 1: "B", 2: "C", 3: "D"}) - - pop_last_until_1dot5 = ts_buffer.pop_last_until(1.5) + data_list = DataFeatures.TimestampedObjectsList(BasicDataClass, \ + [ + BasicDataClass(message="A", timestamp=0), + BasicDataClass(message="B", timestamp=1), + BasicDataClass(message="C", timestamp=2), + BasicDataClass(message="D", timestamp=3) + ]) + + pop_last_until_1dot5 = data_list.pop_last_until(1.5) + + self.assertEqual(pop_last_until_1dot5.message, "B") + self.assertEqual(pop_last_until_1dot5.timestamp, 1) + self.assertEqual(len(data_list), 3) + self.assertEqual(data_list[0].message, "B") + self.assertEqual(data_list[0].timestamp, 1) - self.assertEqual(pop_last_until_1dot5, (1, "B")) - self.assertEqual(len(ts_buffer), 3) - self.assertEqual(ts_buffer.first, (1, "B")) - def test_pop_last_before(self): - """Test TimeStampedBuffer pop_last_before method.""" + """Test TimestampedObjectsList pop_last_before method.""" - ts_buffer = DataFeatures.TimeStampedBuffer({0: "A", 1: "B", 2: "C", 3: "D"}) + data_list = DataFeatures.TimestampedObjectsList(BasicDataClass, \ + [ + BasicDataClass(message="A", timestamp=0), + BasicDataClass(message="B", timestamp=1), + BasicDataClass(message="C", timestamp=2), + BasicDataClass(message="D", timestamp=3) + ]) # Check pop until an existing timestamp - last_before_2 = ts_buffer.pop_last_before(2) + last_before_2 = data_list.pop_last_before(2) - self.assertEqual(last_before_2, (1, "B")) - self.assertEqual(len(ts_buffer), 2) - self.assertEqual(ts_buffer.first, (2, "C")) + self.assertEqual(last_before_2.message, "B") + self.assertEqual(last_before_2.timestamp, 1) + self.assertEqual(len(data_list), 2) + self.assertEqual(data_list[0].message, "C") + self.assertEqual(data_list[0].timestamp, 2) # Check pop until an none existing timestamp - ts_buffer = DataFeatures.TimeStampedBuffer({0: "A", 1: "B", 2: "C", 3: "D"}) - - first_until_1dot5 = ts_buffer.pop_last_before(1.5) - - self.assertEqual(first_until_1dot5, (1, "B")) - self.assertEqual(len(ts_buffer), 2) - self.assertEqual(ts_buffer.first, (2, "C")) - - def test_last(self): - """Test TimeStampedBuffer last property.""" - - self.assertEqual(DataFeatures.TimeStampedBuffer({0: "A", 1: "B"}).last, (1, "B")) - - # Check that accessing to last item of an empty TimeStampedBuffer fails - with self.assertRaises(IndexError): - - DataFeatures.TimeStampedBuffer().last + data_list = DataFeatures.TimestampedObjectsList(BasicDataClass, \ + [ + BasicDataClass(message="A", timestamp=0), + BasicDataClass(message="B", timestamp=1), + BasicDataClass(message="C", timestamp=2), + BasicDataClass(message="D", timestamp=3) + ]) + + first_until_1dot5 = data_list.pop_last_before(1.5) + + self.assertEqual(first_until_1dot5.message, "B") + self.assertEqual(first_until_1dot5.timestamp, 1) + self.assertEqual(len(data_list), 2) + self.assertEqual(data_list[0].message, "C") + self.assertEqual(data_list[0].timestamp, 2) def test_pop_last(self): - """Test TimeStampedBuffer pop_last method.""" + """Test TimestampedObjectsList pop_last method.""" + + data_list = DataFeatures.TimestampedObjectsList(BasicDataClass, \ + [ + BasicDataClass(message="A", timestamp=0), + BasicDataClass(message="B", timestamp=1) + ]) - ts_buffer = DataFeatures.TimeStampedBuffer({0: "A", 1: "B"}) + last = data_list.pop(-1) - self.assertEqual(ts_buffer.pop_last(), (1, "B")) - self.assertEqual(len(ts_buffer), 1) - self.assertEqual(ts_buffer.last, (0, "A")) + self.assertEqual(len(data_list), 1) + self.assertEqual(last.message, "B") + self.assertEqual(last.timestamp, 1) + self.assertEqual(data_list[0].message, "A") + self.assertEqual(data_list[0].timestamp, 0) def test_get_first_from(self): - """Test TimeStampedBuffer get_first_from method.""" + """Test TimestampedObjectsList get_first_from method.""" - ts_buffer = DataFeatures.TimeStampedBuffer({0: "A", 1: "B", 2: "C", 3: "D"}) + data_list = DataFeatures.TimestampedObjectsList(BasicDataClass, \ + [ + BasicDataClass(message="A", timestamp=0), + BasicDataClass(message="B", timestamp=1), + BasicDataClass(message="C", timestamp=2), + BasicDataClass(message="D", timestamp=3) + ]) - get_first_from_1 = ts_buffer.get_first_from(1) + get_first_from_1 = data_list.get_first_from(1) - self.assertEqual(get_first_from_1, (1, "B")) - self.assertEqual(len(ts_buffer), 4) + self.assertEqual(get_first_from_1.message, "B") + self.assertEqual(get_first_from_1.timestamp, 1) + self.assertEqual(len(data_list), 4) - get_first_from_1dot5 = ts_buffer.get_first_from(1.5) + get_first_from_1dot5 = data_list.get_first_from(1.5) - self.assertEqual(get_first_from_1dot5, (2, "C")) + self.assertEqual(get_first_from_1dot5.message, "C") + self.assertEqual(get_first_from_1dot5.timestamp, 2) - get_first_from_0 = ts_buffer.get_first_from(0) + get_first_from_0 = data_list.get_first_from(0) - self.assertEqual(get_first_from_0, (0, "A")) + self.assertEqual(get_first_from_0.message, "A") + self.assertEqual(get_first_from_0.timestamp, 0) # Check that accessing to lately timestamp fails with self.assertRaises(KeyError): - ts_buffer.get_first_from(4) + data_list.get_first_from(4) def test_get_last_before(self): - """Test TimeStampedBuffer get_last_before method.""" + """Test TimestampedObjectsList get_last_before method.""" - ts_buffer = DataFeatures.TimeStampedBuffer({0: "A", 1: "B", 2: "C", 3: "D"}) + data_list = DataFeatures.TimestampedObjectsList(BasicDataClass, \ + [ + BasicDataClass(message="A", timestamp=0), + BasicDataClass(message="B", timestamp=1), + BasicDataClass(message="C", timestamp=2), + BasicDataClass(message="D", timestamp=3) + ]) - get_last_before_2 = ts_buffer.get_last_before(2) + get_last_before_2 = data_list.get_last_before(2) - self.assertEqual(get_last_before_2, (1, "B")) - self.assertEqual(len(ts_buffer), 4) + self.assertEqual(get_last_before_2.message, "B") + self.assertEqual(get_last_before_2.timestamp, 1) + self.assertEqual(len(data_list), 4) - get_last_before_1dot5 = ts_buffer.get_last_before(1.5) + get_last_before_1dot5 = data_list.get_last_before(1.5) - self.assertEqual(get_last_before_1dot5, (1, "B")) + self.assertEqual(get_last_before_1dot5.message,"B") + self.assertEqual(get_last_before_1dot5.timestamp, 1) - get_last_before_4 = ts_buffer.get_last_before(4) + get_last_before_4 = data_list.get_last_before(4) - self.assertEqual(get_last_before_4, (3, "D")) + self.assertEqual(get_last_before_4.message, "D") + self.assertEqual(get_last_before_4.timestamp, 3) # Check that accessing to early timestamp fails with self.assertRaises(KeyError): - ts_buffer.get_last_before(-1) - - + data_list.get_last_before(-1) + def test_get_last_until(self): - """Test TimeStampedBuffer get_last_until method.""" + """Test TimestampedObjectsList get_last_until method.""" - ts_buffer = DataFeatures.TimeStampedBuffer({0: "A", 1: "B", 2: "C", 3: "D"}) + data_list = DataFeatures.TimestampedObjectsList(BasicDataClass, \ + [ + BasicDataClass(message="A", timestamp=0), + BasicDataClass(message="B", timestamp=1), + BasicDataClass(message="C", timestamp=2), + BasicDataClass(message="D", timestamp=3) + ]) - get_last_until_2 = ts_buffer.get_last_until(2) + get_last_until_2 = data_list.get_last_until(2) - self.assertEqual(get_last_until_2, (2, "C")) - self.assertEqual(len(ts_buffer), 4) + self.assertEqual(get_last_until_2.message, "C") + self.assertEqual(get_last_until_2.timestamp, 2) + self.assertEqual(len(data_list), 4) - get_last_until_1dot5 = ts_buffer.get_last_until(1.5) + get_last_until_1dot5 = data_list.get_last_until(1.5) - self.assertEqual(get_last_until_1dot5, (1, "B")) + self.assertEqual(get_last_until_1dot5.message, "B") + self.assertEqual(get_last_until_1dot5.timestamp, 1) - get_last_until_4 = ts_buffer.get_last_until(4) + get_last_until_4 = data_list.get_last_until(4) - self.assertEqual(get_last_until_4, (3, "D")) + self.assertEqual(get_last_until_4.message, "D") + self.assertEqual(get_last_until_4.timestamp, 3) # Check that accessing to early timestamp fails with self.assertRaises(KeyError): - ts_buffer.get_last_until(-1) - - def test_as_dataframe(self): - """Test TimeStampedBuffer as_dataframe method.""" - - ts_buffer = random_data_buffer(10, ["data_A", "data_B", "data_C"]) - - # Check dataframe conversion - ts_buffer_dataframe = ts_buffer.as_dataframe() - - self.assertEqual(ts_buffer_dataframe.index.name, "timestamp") - self.assertEqual(ts_buffer_dataframe.index.size, 10) - - self.assertEqual(ts_buffer_dataframe.columns.size, 3) - self.assertEqual(ts_buffer_dataframe.columns[0], "data_A") - self.assertEqual(ts_buffer_dataframe.columns[1], "data_B") - self.assertEqual(ts_buffer_dataframe.columns[2], "data_C") - - self.assertEqual(ts_buffer_dataframe.index.dtype, 'float64') - self.assertEqual(ts_buffer_dataframe["data_A"].dtype, 'object') - self.assertEqual(ts_buffer_dataframe["data_B"].dtype, 'object') - self.assertEqual(ts_buffer_dataframe["data_C"].dtype, 'object') - - # Check data exclusion option - ts_buffer_dataframe = ts_buffer.as_dataframe(exclude=["data_B"]) - - self.assertEqual(ts_buffer_dataframe.index.name, "timestamp") - self.assertEqual(ts_buffer_dataframe.index.size, 10) - - self.assertEqual(ts_buffer_dataframe.columns.size, 2) - self.assertEqual(ts_buffer_dataframe.columns[0], "data_A") - self.assertEqual(ts_buffer_dataframe.columns[1], "data_C") - - # Check dataframe split option - ts_buffer_dataframe = ts_buffer.as_dataframe(split={"data_B": ["data_B0", "data_B1"]}) - - self.assertEqual(ts_buffer_dataframe.index.name, "timestamp") - self.assertEqual(ts_buffer_dataframe.index.size, 10) - - self.assertEqual(ts_buffer_dataframe.columns.size, 4) - self.assertEqual(ts_buffer_dataframe.columns[0], "data_A") - self.assertEqual(ts_buffer_dataframe.columns[1], "data_B0") - self.assertEqual(ts_buffer_dataframe.columns[2], "data_B1") - self.assertEqual(ts_buffer_dataframe.columns[3], "data_C") - - # Check dataframe conversion with dataclass - data = BasicDataClass((123, 456)) - ts_buffer_dataframe = DataFeatures.TimeStampedBuffer({0: data}).as_dataframe() - - self.assertEqual(ts_buffer_dataframe.index.name, "timestamp") - self.assertEqual(ts_buffer_dataframe.index.size, 1) - - self.assertEqual(ts_buffer_dataframe.columns.size, 1) - self.assertEqual(ts_buffer_dataframe.columns[0], "value") - + data_list.get_last_until(-1) if __name__ == '__main__': diff --git a/src/argaze.test/DataLog/FileWriter.py b/src/argaze.test/DataLog/FileWriter.py deleted file mode 100644 index 648385c..0000000 --- a/src/argaze.test/DataLog/FileWriter.py +++ /dev/null @@ -1,37 +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 os - -from argaze import DataFeatures -from argaze.DataLog import FileWriter -from argaze.utils import UtilsFeatures - -DataFeaturesTest = UtilsFeatures.importFromTestPackage('DataFeatures') - -class TestTimeStampedDataLogger(unittest.TestCase): - """Test DataLogger class.""" - - def test_creation(self): - """Test logger creation.""" - - file_writer = FileWriter.TimeStampedDataLogger(path='./_export/logs/data.txt', separator=',') - - # Check file creation - self.assertEqual(os.path.exists('./_export/logs/data.txt'), True) - - # Write into file - file_writer.emit(0, 'A') - file_writer.emit(1, 'B') - file_writer.emit(2, 'C') - -if __name__ == '__main__': - - unittest.main()
\ No newline at end of file diff --git a/src/argaze.test/DataLog/__init__.py b/src/argaze.test/DataLog/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/src/argaze.test/DataLog/__init__.py +++ /dev/null diff --git a/src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py b/src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py index b7475b5..156f6f1 100644 --- a/src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py +++ b/src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py @@ -47,11 +47,13 @@ def build_gaze_fixation(size: int, center: tuple, deviation_max: float, min_time else: - gaze_position = GazeFeatures.UnvalidGazePosition() + 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 @@ -85,11 +87,13 @@ def build_gaze_saccade(size: int, center_A: tuple, center_B: tuple, min_time: fl else: - gaze_position = GazeFeatures.UnvalidGazePosition() + 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,14 +119,14 @@ 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.assertEqual(len(fixation), size) self.assertLessEqual(fixation.deviation_max, deviation_max) self.assertGreaterEqual(fixation.duration, (size - 1) * min_time) self.assertLessEqual(fixation.duration, (size - 1) * max_time) self.assertLessEqual(fixation.finished, True) - + def test_fixation_and_direct_saccade_identification(self): """Test DispersionThresholdIdentification fixation and saccade identification.""" @@ -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,45 +151,39 @@ 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.assertEqual(len(fixation), size) self.assertLessEqual(fixation.deviation_max, deviation_max) self.assertGreaterEqual(fixation.duration, (size - 1) * min_time) self.assertLessEqual(fixation.duration, (size - 1) * max_time) 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.assertEqual(len(saccade), 2) self.assertGreaterEqual(saccade.duration, min_time) self.assertLessEqual(saccade.duration, max_time) 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.assertEqual(len(fixation), size) self.assertLessEqual(fixation.deviation_max, deviation_max) self.assertGreaterEqual(fixation.duration, (size - 1) * min_time) self.assertLessEqual(fixation.duration, (size - 1) * max_time) 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,47 +211,41 @@ 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.assertEqual(len(fixation), size) self.assertLessEqual(fixation.deviation_max, deviation_max) self.assertGreaterEqual(fixation.duration, (size - 1) * min_time) self.assertLessEqual(fixation.duration, (size - 1) * max_time) 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.assertEqual(len(saccade), move + 2) self.assertGreaterEqual(saccade.duration, (move + 1) * min_time) self.assertLessEqual(saccade.duration, (move + 1) * max_time) 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.assertEqual(len(fixation), size) self.assertLessEqual(fixation.deviation_max, deviation_max) self.assertGreaterEqual(fixation.duration, (size - 1) * min_time) self.assertLessEqual(fixation.duration, (size - 1) * max_time) 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) - - def test_invalid_gaze_position(self): - """Test DispersionThresholdIdentification fixation and saccade identification with invalid gaze position.""" + self.assertEqual(saccade[-1].timestamp, fixation[0].timestamp) + self.assertEqual(saccade[-1].value, fixation[0].value) + + def test_empty_gaze_position(self): + """Test DispersionThresholdIdentification fixation and saccade identification with empty gaze position.""" size = 15 center = (0, 0) @@ -273,23 +265,23 @@ 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.assertEqual(len(fixation), 7) self.assertLessEqual(fixation.deviation_max, deviation_max) 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() + fixation = ts_fixations.pop(0) - self.assertEqual(len(fixation.positions.keys()), 5) + self.assertEqual(len(fixation), 5) self.assertLessEqual(fixation.deviation_max, deviation_max) self.assertGreaterEqual(fixation.duration, 4 * min_time) self.assertLessEqual(fixation.duration, 4 * max_time) self.assertLessEqual(fixation.finished, True) - + def test_fixation_overlapping(self): """Test Fixation overlap function.""" @@ -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,14 +334,14 @@ 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.assertEqual(len(fixation), size * 2) #self.assertGreaterEqual(fixation.deviation_max, deviation_max) self.assertGreaterEqual(fixation.duration, (2 * size - 1) * min_time) self.assertLessEqual(fixation.duration, (2 * size - 1) * max_time) self.assertLessEqual(fixation.finished, True) - + def test_identification_browsing(self): """Test DispersionThresholdIdentification identification browsing.""" @@ -361,23 +353,20 @@ 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) - # Get last ts to terminate identification on last gaze position - last_ts, _ = ts_gaze_positions.last - # 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 == ts_gaze_positions[-1].timestamp)) if GazeFeatures.is_fixation(finished_gaze_movement): - self.assertEqual(len(finished_gaze_movement.positions.keys()), size) + self.assertEqual(len(finished_gaze_movement), size) self.assertLessEqual(finished_gaze_movement.deviation_max, deviation_max) self.assertGreaterEqual(finished_gaze_movement.duration, (size-1) * min_time) self.assertLessEqual(finished_gaze_movement.duration, (size-1) * max_time) @@ -385,29 +374,20 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase): elif GazeFeatures.is_saccade(finished_gaze_movement): - self.assertEqual(len(finished_gaze_movement.positions.keys()), 2) + self.assertEqual(len(finished_gaze_movement), 2) self.assertGreaterEqual(finished_gaze_movement.duration, min_time) self.assertLessEqual(finished_gaze_movement.duration, max_time) self.assertLessEqual(finished_gaze_movement.finished, True) - # 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 - - self.assertNotEqual(last_ts, ts) - # 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 current_gaze_movement = gaze_movement_identifier.current_gaze_movement - if current_gaze_movement.valid: + if current_gaze_movement: if GazeFeatures.is_fixation(current_gaze_movement): - last_ts, _ = current_gaze_movement.positions.last - - self.assertEqual(last_ts, ts) - + self.assertEqual(current_gaze_movement[-1].timestamp, gaze_position.timestamp) + def test_identification_generator(self): """Test DispersionThresholdIdentification identification using generator.""" @@ -419,17 +399,17 @@ 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) - for ts, finished_gaze_movement in gaze_movement_identifier(ts_gaze_positions): + for finished_gaze_movement in gaze_movement_identifier(ts_gaze_positions): if GazeFeatures.is_fixation(finished_gaze_movement): - self.assertEqual(len(finished_gaze_movement.positions.keys()), size) + self.assertEqual(len(finished_gaze_movement), size) self.assertLessEqual(finished_gaze_movement.deviation_max, deviation_max) self.assertGreaterEqual(finished_gaze_movement.duration, size * min_time) self.assertLessEqual(finished_gaze_movement.duration, size * max_time) @@ -437,7 +417,7 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase): elif GazeFeatures.is_saccade(finished_gaze_movement): - self.assertEqual(len(finished_gaze_movement.positions.keys()), 2) + self.assertEqual(len(finished_gaze_movement), 2) self.assertGreaterEqual(finished_gaze_movement.duration, 2 * min_time) self.assertLessEqual(finished_gaze_movement.duration, 2 * max_time) self.assertLessEqual(finished_gaze_movement.finished, True) diff --git a/src/argaze.test/GazeAnalysis/VelocityThresholdIdentification.py b/src/argaze.test/GazeAnalysis/VelocityThresholdIdentification.py index 425d592..262cfc0 100644 --- a/src/argaze.test/GazeAnalysis/VelocityThresholdIdentification.py +++ b/src/argaze.test/GazeAnalysis/VelocityThresholdIdentification.py @@ -17,8 +17,8 @@ from argaze.GazeAnalysis import VelocityThresholdIdentification import numpy -def build_gaze_fixation(size: int, start_position: tuple, deviation_max: float, min_time: float, max_time: float, start_ts: float = 0., validity: list = []): - """ Generate N TimeStampedGazePositions strating from a starting position for testing purpose. +def build_gaze_fixation(size: int, center: tuple, deviation_max: float, min_time: float, max_time: float, start_ts: float = 0., 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. """ @@ -26,8 +26,6 @@ def build_gaze_fixation(size: int, start_position: tuple, deviation_max: float, start_time = time.time() - last_valid_position = start_position - for i in range(0, size): # Sleep a random time @@ -43,21 +41,19 @@ def build_gaze_fixation(size: int, start_position: tuple, deviation_max: float, if valid: # Edit gaze position - random_x = last_valid_position[0] + deviation_max * (random.random() - 0.5) / math.sqrt(2) - random_y = last_valid_position[1] + deviation_max * (random.random() - 0.5) / math.sqrt(2) - + random_x = center[0] + deviation_max * (random.random() - 0.5) / math.sqrt(2) + random_y = center[1] + deviation_max * (random.random() - 0.5) / math.sqrt(2) gaze_position = GazeFeatures.GazePosition((random_x, random_y)) - # Remember last valid gaze position - last_valid_position = gaze_position.value - else: - gaze_position = GazeFeatures.UnvalidGazePosition() + 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 @@ -91,11 +87,13 @@ def build_gaze_saccade(size: int, center_A: tuple, center_B: tuple, min_time: fl else: - gaze_position = GazeFeatures.UnvalidGazePosition() + 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 @@ -122,9 +120,9 @@ class TestVelocityThresholdIdentificationClass(unittest.TestCase): self.assertEqual(len(ts_status), size - 1) # Check fixation - ts, fixation = ts_fixations.pop_first() + fixation = ts_fixations.pop(0) - self.assertEqual(len(fixation.positions.keys()), size - 1) + self.assertEqual(len(fixation), size - 1) self.assertGreaterEqual(fixation.duration, (size - 2) * min_time) self.assertLessEqual(fixation.duration, (size - 2) * max_time) self.assertLessEqual(fixation.finished, True) @@ -141,9 +139,9 @@ class TestVelocityThresholdIdentificationClass(unittest.TestCase): velocity_max = deviation_max / min_time 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 = VelocityThresholdIdentification.GazeMovementIdentifier(velocity_max_threshold=velocity_max, duration_min_threshold=max_time*2) ts_fixations, ts_saccades, ts_status = gaze_movement_identifier.browse(ts_gaze_positions) @@ -154,42 +152,36 @@ class TestVelocityThresholdIdentificationClass(unittest.TestCase): self.assertEqual(len(ts_status), size * 2 - 1) # Check first fixation - ts, fixation = ts_fixations.pop_first() + fixation = ts_fixations.pop(0) - self.assertEqual(len(fixation.positions.keys()), size - 1) + self.assertEqual(len(fixation), size - 1) self.assertGreaterEqual(fixation.duration, (size - 2) * min_time) self.assertLessEqual(fixation.duration, (size - 2) * max_time) 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.assertEqual(len(saccade), 2) self.assertGreaterEqual(saccade.duration, min_time) self.assertLessEqual(saccade.duration, max_time) 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.assertEqual(len(fixation), size) self.assertGreaterEqual(fixation.duration, (size - 1) * min_time) self.assertLessEqual(fixation.duration, (size - 1) * max_time) 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 VelocityThresholdIdentification fixation and saccade identification.""" @@ -205,10 +197,10 @@ class TestVelocityThresholdIdentificationClass(unittest.TestCase): velocity_max = deviation_max / min_time 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 = VelocityThresholdIdentification.GazeMovementIdentifier(velocity_max_threshold=velocity_max, duration_min_threshold=max_time*2) ts_fixations, ts_saccades, ts_status = gaze_movement_identifier.browse(ts_gaze_positions) @@ -219,42 +211,36 @@ class TestVelocityThresholdIdentificationClass(unittest.TestCase): self.assertEqual(len(ts_status), 2 * size + move - 1) # Check first fixation - ts, fixation = ts_fixations.pop_first() + fixation = ts_fixations.pop(0) - self.assertEqual(len(fixation.positions.keys()), size - 1) # BUG: NOT ALWAYS TRUE !!! + self.assertEqual(len(fixation), size - 1) # BUG: NOT ALWAYS TRUE !!! self.assertGreaterEqual(fixation.duration, (size - 2) * min_time) self.assertLessEqual(fixation.duration, (size - 2) * max_time) 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.assertEqual(len(saccade), move + 2) self.assertGreaterEqual(saccade.duration, (move + 1) * min_time) self.assertLessEqual(saccade.duration, (move + 1) * max_time) 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.assertEqual(len(fixation), size) self.assertGreaterEqual(fixation.duration, (size - 1) * min_time) self.assertLessEqual(fixation.duration, (size - 1) * max_time) 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], fixation[0]) + self.assertEqual(saccade[-1].value, fixation[0].value) def test_invalid_gaze_position(self): """Test VelocityThresholdIdentification fixation and saccade identification with invalid gaze position.""" @@ -278,17 +264,17 @@ class TestVelocityThresholdIdentificationClass(unittest.TestCase): self.assertEqual(len(ts_status), len(validity)-5) # Check first fixation - ts, fixation = ts_fixations.pop_first() + fixation = ts_fixations.pop(0) - self.assertEqual(len(fixation.positions.keys()), 6) + self.assertEqual(len(fixation), 6) self.assertGreaterEqual(fixation.duration, 5 * min_time) self.assertLessEqual(fixation.duration, 5 * max_time) 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()), 4) + self.assertEqual(len(fixation), 4) self.assertGreaterEqual(fixation.duration, 3 * min_time) self.assertLessEqual(fixation.duration, 3 * max_time) self.assertLessEqual(fixation.finished, True) @@ -305,34 +291,27 @@ class TestVelocityThresholdIdentificationClass(unittest.TestCase): velocity_max = deviation_max / min_time 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 = VelocityThresholdIdentification.GazeMovementIdentifier(velocity_max_threshold=velocity_max, duration_min_threshold=max_time*2) - - # Get last ts to terminate identification on last gaze position - last_ts, _ = ts_gaze_positions.last # 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 == ts_gaze_positions[-1])) # Check that last gaze position date is not equal to given gaze position date - if finished_gaze_movement.valid: + if finished_gaze_movement: - last_ts, _ = finished_gaze_movement.positions.last - - self.assertNotEqual(last_ts, ts) + self.assertNotEqual(finished_gaze_movement[-1].timestamp, gaze_position.timestamp) # Check that last gaze position date of current movement is equal to given gaze position date current_gaze_movement = gaze_movement_identifier.current_gaze_movement - if current_gaze_movement.valid: - - last_ts, _ = current_gaze_movement.positions.last + if current_gaze_movement: - self.assertEqual(last_ts, ts) + self.assertEqual(current_gaze_movement[-1].timestamp, gaze_position.timestamp) if __name__ == '__main__': diff --git a/src/argaze.test/GazeFeatures.py b/src/argaze.test/GazeFeatures.py index b41c7c7..035c76a 100644 --- a/src/argaze.test/GazeFeatures.py +++ b/src/argaze.test/GazeFeatures.py @@ -29,79 +29,79 @@ def random_gaze_positions(size, frame_dimension: tuple[float, float] = (1, 1)): for i in range(0, size): # Edit gaze position - random_gaze_position = GazeFeatures.GazePosition((random.random() * frame_dimension[0], random.random() * frame_dimension[1])) + random_gaze_position = GazeFeatures.GazePosition((random.random() * frame_dimension[0], random.random() * frame_dimension[1]), precision=5) - # Store gaze position - ts_gaze_positions[time.time()] = random_gaze_position + # Timestamp gaze position + random_gaze_position.timestamp = time.time() + + # Store timestamped gaze position + ts_gaze_positions.append(random_gaze_position) 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().__init__(positions, finished, message, **kwargs) - super().__post_init__() + if positions: - 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)]) + positions_array = numpy.asarray(self.values()) + centroid = numpy.mean(positions_array, axis=0) - # Update frozen focus attribute using centroid - object.__setattr__(self, 'focus', (centroid_array[0], centroid_array[1])) + # 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.""" - + def test_new(self): """Test GazePosition creation.""" # Check empty GazePosition - empty_gaze_position = GazeFeatures.GazePosition() + empty_gaze_position = GazeFeatures.GazePosition(message="empty for test") - self.assertEqual(empty_gaze_position.value, (0, 0)) - self.assertEqual(empty_gaze_position[0], 0) - self.assertEqual(empty_gaze_position[1], 0) - - for v in empty_gaze_position: - self.assertEqual(v, 0) - - self.assertEqual(empty_gaze_position.precision, 0.) - self.assertEqual(len(empty_gaze_position), 2) - self.assertEqual(empty_gaze_position.valid, True) - self.assertEqual(numpy.array(empty_gaze_position).shape, (2,)) + self.assertEqual(empty_gaze_position, ()) + self.assertEqual(empty_gaze_position, ()) + self.assertEqual(empty_gaze_position.precision, None) + self.assertEqual(empty_gaze_position.message, "empty for test") + self.assertEqual(len(empty_gaze_position), 0) + self.assertEqual(bool(empty_gaze_position), False) + self.assertEqual(numpy.array(empty_gaze_position).shape, (0,)) # Check integer GazePosition int_gaze_position = GazeFeatures.GazePosition((123, 456), precision=55) - self.assertEqual(int_gaze_position.value, (123, 456)) + self.assertEqual(int_gaze_position, (123, 456)) self.assertEqual(int_gaze_position[0], 123) self.assertEqual(int_gaze_position[1], 456) + self.assertEqual(int_gaze_position, (123, 456)) self.assertEqual(int_gaze_position.precision, 55) self.assertEqual(len(int_gaze_position), 2) - self.assertEqual(int_gaze_position.valid, True) - self.assertEqual(numpy.array(empty_gaze_position).shape, (2,)) + self.assertEqual(bool(int_gaze_position), True) + self.assertEqual(numpy.array(int_gaze_position).shape, (2,)) # Check float GazePosition float_gaze_position = GazeFeatures.GazePosition((1.23, 4.56), precision=5.5) - self.assertEqual(float_gaze_position.value, (1.23, 4.56)) + self.assertEqual(float_gaze_position, (1.23, 4.56)) self.assertEqual(float_gaze_position[0], 1.23) self.assertEqual(float_gaze_position[1], 4.56) + self.assertEqual(float_gaze_position, (1.23, 4.56)) self.assertEqual(float_gaze_position.precision, 5.5) self.assertEqual(len(float_gaze_position), 2) - self.assertEqual(float_gaze_position.valid, True) - self.assertEqual(numpy.array(empty_gaze_position).shape, (2,)) - + self.assertEqual(bool(float_gaze_position), True) + self.assertEqual(numpy.array(float_gaze_position).shape, (2,)) + def test_properties(self): """Test GazePosition properties cannot be modified after creation.""" @@ -110,11 +110,29 @@ class TestGazePositionClass(unittest.TestCase): # Check that gaze position value setting fails with self.assertRaises(AttributeError): - gaze_position.value = (123, 456) + gaze_position.value = (12, 34) + # WARNING: gaze_position = (12, 34) is possible !!!! + # How to prevent this? - self.assertNotEqual(gaze_position.value, (123, 456)) - self.assertEqual(gaze_position.value, (0, 0)) + self.assertNotEqual(gaze_position, (12, 34)) + self.assertEqual(gaze_position, ()) + # Check that gaze position precision setting fails + with self.assertRaises(AttributeError): + + gaze_position.precision = 5 + + self.assertNotEqual(gaze_position.precision, 5) + self.assertEqual(gaze_position.precision, None) + + # Check that gaze position message setting fails + with self.assertRaises(AttributeError): + + gaze_position.message = "later setting" + + self.assertNotEqual(gaze_position.message, "later setting") + self.assertEqual(gaze_position.message, None) + def test_overlapping(self): """Test GazePosition overlap method.""" @@ -133,83 +151,80 @@ class TestGazePositionClass(unittest.TestCase): self.assertFalse(gaze_position_C.overlap(gaze_position_A)) self.assertFalse(gaze_position_C.overlap(gaze_position_B)) - + def test___repr__(self): """Test GazePosition string representation.""" # Check empty GazePosition representation - self.assertEqual(repr(GazeFeatures.GazePosition()), "{\"value\": [0, 0], \"precision\": 0.0}") + self.assertEqual(repr(GazeFeatures.GazePosition()), "{\"value\": [], \"timestamp\": NaN}") -class TestUnvalidGazePositionClass(unittest.TestCase): - """Test UnvalidGazePosition class.""" + # Check GazePosition representation + self.assertEqual(repr(GazeFeatures.GazePosition((12, 345), precision=50, message="ok")), \ + "{\"value\": [12, 345], \"precision\": 50, \"message\": \"ok\", \"timestamp\": NaN}") +class TestTimeStampedGazePositionsClass(unittest.TestCase): + """Test TimeStampedGazePositions class.""" + def test_new(self): - """Test UnvalidGazePosition creation.""" - - import math - - unvalid_gaze_position = GazeFeatures.UnvalidGazePosition() - - self.assertEqual(unvalid_gaze_position.value, (None, None)) - self.assertEqual(unvalid_gaze_position.precision, None) - self.assertEqual(unvalid_gaze_position.valid, False) + """Test TimeStampedGazePositions creation.""" - def test___repr__(self): - """Test UnvalidGazePosition string representation.""" - - self.assertEqual(repr(GazeFeatures.UnvalidGazePosition()), "{\"message\": null, \"value\": [null, null], \"precision\": null}") + empty_gaze_position = GazeFeatures.GazePosition() + empty_gaze_position.timestamp = 100 -class TestTimeStampedGazePositionsClass(unittest.TestCase): - """Test TimeStampedGazePositions class.""" + gaze_position_with_message = GazeFeatures.GazePosition((12, 345), message="second position") + gaze_position_with_message.timestamp = 200 - def test___setitem__(self): - """Test __setitem__ method.""" + dict_gaze_position = {"timestamp": 300, "value": (0, 0), "precision": 0.} - ts_gaze_positions = GazeFeatures.TimeStampedGazePositions() - ts_gaze_positions[0] = GazeFeatures.GazePosition() - ts_gaze_positions[1] = GazeFeatures.UnvalidGazePosition() - ts_gaze_positions[2] = {"value": (0, 0), "precision": 0.} + ts_gaze_positions = GazeFeatures.TimeStampedGazePositions([empty_gaze_position, gaze_position_with_message, dict_gaze_position]) - # Check GazePosition is correctly stored and accessible as a GazePosition + # Check first GazePosition is correctly stored and accessible as a GazePosition self.assertIsInstance(ts_gaze_positions[0], GazeFeatures.GazePosition) - self.assertEqual(ts_gaze_positions[0].valid, True) + self.assertEqual(bool(ts_gaze_positions[0]), False) + self.assertEqual(ts_gaze_positions[0].timestamp, 100) - # Check UnvalidGazePosition is correctly stored and accessible as a UnvalidGazePosition - self.assertIsInstance(ts_gaze_positions[1], GazeFeatures.UnvalidGazePosition) - self.assertEqual(ts_gaze_positions[1].valid, False) + # Check second GazePosition is correctly stored and accessible as a GazePosition + self.assertIsInstance(ts_gaze_positions[1], GazeFeatures.GazePosition) + self.assertEqual(bool(ts_gaze_positions[1]), True) + self.assertEqual(ts_gaze_positions[1].timestamp, 200) - # Check dict with "value" and "precision" keys is correctly stored and accessible as a GazePosition + # Check third GazePosition from dict is correctly stored and accessible as a GazePosition self.assertIsInstance(ts_gaze_positions[2], GazeFeatures.GazePosition) - self.assertEqual(ts_gaze_positions[2].valid, True) + self.assertEqual(bool(ts_gaze_positions[2]), True) + self.assertEqual(ts_gaze_positions[2].timestamp, 300) # Check that bad data type insertion fails - with self.assertRaises(AssertionError): + with self.assertRaises(TypeError): - ts_gaze_positions[3] = "This string is not a gaze position value." + ts_gaze_positions.append("This string is not a gaze position value.") # Check that dict with bad keys insertion fails - with self.assertRaises(AssertionError): + with self.assertRaises(TypeError): - ts_gaze_positions[4] = {"bad_key": (0, 0), "precision": 0.} + ts_gaze_positions.append({"bad_key": (0, 0), "precision": 0.}) # Check final lenght self.assertEqual(len(ts_gaze_positions), 3) - + def test___repr__(self): """Test inherited string representation.""" ts_gaze_positions = GazeFeatures.TimeStampedGazePositions() - self.assertEqual(repr(GazeFeatures.TimeStampedGazePositions()), "{}") + self.assertEqual(repr(GazeFeatures.TimeStampedGazePositions()), "[]") - ts_gaze_positions[0] = GazeFeatures.GazePosition() - - self.assertEqual(repr(ts_gaze_positions), "{\"0\": {\"value\": [0, 0], \"precision\": 0.0}}") + empty_gaze_position = GazeFeatures.GazePosition() + empty_gaze_position.timestamp = 100 + ts_gaze_positions.append(empty_gaze_position) - ts_gaze_positions[0] = GazeFeatures.UnvalidGazePosition() + self.assertEqual(repr(ts_gaze_positions), "[{\"value\": [], \"timestamp\": 100}]") - self.assertEqual(repr(ts_gaze_positions), "{\"0\": {\"message\": null, \"value\": [null, null], \"precision\": null}}") + full_gaze_position = GazeFeatures.GazePosition((12, 345), precision=50, message="ok") + full_gaze_position.timestamp = 200 + ts_gaze_positions[0] = full_gaze_position + self.assertEqual(repr(ts_gaze_positions), "[{\"value\": [12, 345], \"precision\": 50, \"message\": \"ok\", \"timestamp\": 200}]") + def test_from_dataframe(self): """Test from_dataframe classmethod.""" @@ -226,13 +241,13 @@ class TestTimeStampedGazePositionsClass(unittest.TestCase): # Check first gaze position is correctly stored and accessible as a GazePosition self.assertIsInstance(ts_gaze_positions[0], GazeFeatures.GazePosition) - self.assertEqual(ts_gaze_positions[0].precision, 0) - self.assertEqual(ts_gaze_positions[0].valid, True) + self.assertEqual(ts_gaze_positions[0].precision, None) + self.assertEqual(bool(ts_gaze_positions[0]), True) - # Check third gaze position is correctly stored and accessible as a UnvalidGazePosition - self.assertIsInstance(ts_gaze_positions[2], GazeFeatures.UnvalidGazePosition) + # Check third gaze position is correctly stored and accessible as a GazePosition + self.assertIsInstance(ts_gaze_positions[2], GazeFeatures.GazePosition) self.assertEqual(ts_gaze_positions[2].precision, None) - self.assertEqual(ts_gaze_positions[2].valid, False) + self.assertEqual(bool(ts_gaze_positions[2]), False) data = {'Specific timestamp label': [0, 1, 2, 3, 4], 'Specific gaze position x label': [0, 10, numpy.nan, 30, 40], @@ -249,12 +264,12 @@ class TestTimeStampedGazePositionsClass(unittest.TestCase): # Check first gaze position is correctly stored and accessible as a GazePosition self.assertIsInstance(ts_gaze_positions[0], GazeFeatures.GazePosition) self.assertEqual(ts_gaze_positions[0].precision, 15) - self.assertEqual(ts_gaze_positions[0].valid, True) + self.assertEqual(bool(ts_gaze_positions[0]), True) - # Check third gaze position is correctly stored and accessible as a UnvalidGazePosition - self.assertIsInstance(ts_gaze_positions[2], GazeFeatures.UnvalidGazePosition) - self.assertEqual(ts_gaze_positions[2].precision, None) - self.assertEqual(ts_gaze_positions[2].valid, False) + # 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.""" @@ -265,27 +280,33 @@ class TestTimeStampedGazePositionsClass(unittest.TestCase): self.assertEqual(ts_gaze_positions_dataframe.index.name, "timestamp") self.assertEqual(ts_gaze_positions_dataframe.index.size, 10) - self.assertEqual(ts_gaze_positions_dataframe.columns.size, 2) + self.assertEqual(ts_gaze_positions_dataframe.columns.size, 3) self.assertEqual(ts_gaze_positions_dataframe.columns[0], "value") self.assertEqual(ts_gaze_positions_dataframe.columns[1], "precision") + self.assertEqual(ts_gaze_positions_dataframe.columns[2], "message") self.assertEqual(ts_gaze_positions_dataframe["value"].dtype, 'object') - self.assertEqual(ts_gaze_positions_dataframe["precision"].dtype, 'float64') + self.assertEqual(ts_gaze_positions_dataframe["precision"].dtype, 'int64') + self.assertEqual(ts_gaze_positions_dataframe["message"].dtype, 'O') # Python object type - # Check unvalid position conversion - ts_gaze_positions = GazeFeatures.TimeStampedGazePositions() - ts_gaze_positions[0] = GazeFeatures.UnvalidGazePosition() + # Check empty position conversion + empty_gaze_position = GazeFeatures.GazePosition() + empty_gaze_position.timestamp = 100 + + ts_gaze_positions = GazeFeatures.TimeStampedGazePositions([empty_gaze_position]) ts_gaze_positions_dataframe = ts_gaze_positions.as_dataframe() self.assertEqual(ts_gaze_positions_dataframe.index.name, "timestamp") self.assertEqual(ts_gaze_positions_dataframe.index.size, 1) - self.assertEqual(ts_gaze_positions_dataframe.columns.size, 2) + self.assertEqual(ts_gaze_positions_dataframe.columns.size, 3) self.assertEqual(ts_gaze_positions_dataframe.columns[0], "value") self.assertEqual(ts_gaze_positions_dataframe.columns[1], "precision") + self.assertEqual(ts_gaze_positions_dataframe.columns[2], "message") self.assertEqual(ts_gaze_positions_dataframe["value"].dtype, 'object') self.assertEqual(ts_gaze_positions_dataframe["precision"].dtype, 'O') # Python object type + self.assertEqual(ts_gaze_positions_dataframe["message"].dtype, 'O') # Python object type class TestGazeMovementClass(unittest.TestCase): """Test GazeMovement class.""" @@ -296,10 +317,10 @@ class TestGazeMovementClass(unittest.TestCase): 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) def test_finish(self): @@ -320,25 +341,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.""" - - 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.""" - + def test_new(self): """Test ScanStep creation.""" @@ -351,7 +369,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""" @@ -359,19 +377,17 @@ 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 - scan_path.append_fixation(ts, fixation) + fixation = TestFixation(random_gaze_positions(10, frame_dimension), timestamp=i) + scan_path.append_fixation(fixation) - saccade = TestSaccade(random_gaze_positions(2, frame_dimension)) - ts, _ = saccade.positions.first - scan_path.append_saccade(ts, saccade) + saccade = TestSaccade(random_gaze_positions(2, frame_dimension), timestamp=i+1) + scan_path.append_saccade(saccade) return scan_path class TestScanPathClass(unittest.TestCase): """Test ScanPath class.""" - + def test_new(self): """Test ScanPath creation.""" @@ -380,7 +396,7 @@ class TestScanPathClass(unittest.TestCase): self.assertEqual(len(scan_path), 0) self.assertEqual(scan_path.duration, 0) - + def test_append(self): """Test ScanPath append methods.""" @@ -388,9 +404,7 @@ class TestScanPathClass(unittest.TestCase): # Append a saccade that should be ignored saccade = TestSaccade(random_gaze_positions(2)) - ts, _ = saccade.positions.first - - new_step = scan_path.append_saccade(ts, saccade) + new_step = scan_path.append_saccade(saccade) # Check that no scan step have been created yet self.assertEqual(len(scan_path), 0) @@ -399,9 +413,7 @@ class TestScanPathClass(unittest.TestCase): # Append first fixation fixation_A = TestFixation(random_gaze_positions(10)) - ts, _ = fixation_A.positions.first - - new_step = scan_path.append_fixation(ts, fixation_A) + new_step = scan_path.append_fixation(fixation_A) # Check that no scan step have been created yet self.assertEqual(len(scan_path), 0) @@ -410,9 +422,7 @@ class TestScanPathClass(unittest.TestCase): # Append consecutive saccade saccade_A = TestSaccade(random_gaze_positions(2)) - ts, _ = saccade_A.positions.first - - new_step_A = scan_path.append_saccade(ts, saccade_A) + new_step_A = scan_path.append_saccade(saccade_A) # Check that new scan step have been created self.assertEqual(len(scan_path), 1) @@ -423,9 +433,7 @@ class TestScanPathClass(unittest.TestCase): # Append 2 consecutive fixations then a saccade fixation_B1 = TestFixation(random_gaze_positions(10)) - ts, _ = fixation_B1.positions.first - - new_step = scan_path.append_fixation(ts, fixation_B1) + new_step = scan_path.append_fixation(fixation_B1) # Check that no scan step have been created yet self.assertEqual(len(scan_path), 1) @@ -433,9 +441,7 @@ class TestScanPathClass(unittest.TestCase): self.assertEqual(new_step, None) fixation_B2 = TestFixation(random_gaze_positions(10)) - ts, _ = fixation_B2.positions.first - - new_step = scan_path.append_fixation(ts, fixation_B2) + new_step = scan_path.append_fixation(fixation_B2) # Check that no scan step have been created yet self.assertEqual(len(scan_path), 1) @@ -443,9 +449,7 @@ class TestScanPathClass(unittest.TestCase): self.assertEqual(new_step, None) saccade_B = TestSaccade(random_gaze_positions(2)) - ts, _ = saccade_B.positions.first - - new_step_B = scan_path.append_saccade(ts, saccade_B) + new_step_B = scan_path.append_saccade(saccade_B) # Check that new scan step have been created self.assertEqual(len(scan_path), 2) @@ -456,19 +460,19 @@ class TestScanPathClass(unittest.TestCase): class TestAOIScanStepClass(unittest.TestCase): """Test AOIScanStep class.""" - + 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') @@ -478,19 +482,19 @@ class TestAOIScanStepClass(unittest.TestCase): self.assertEqual(aoi_scan_step.first_fixation, fixation) self.assertEqual(aoi_scan_step.last_saccade, saccade) self.assertGreater(aoi_scan_step.duration, 0) - + def test_error(self): """Test AOIScanStep creation error.""" 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): @@ -505,21 +509,19 @@ def build_aoi_scan_path(expected_aoi, aoi_path): # Append a hidden last step to allow last given step creation aoi_path.append(aoi_path[-2]) - for aoi in aoi_path: + for i, aoi in enumerate(aoi_path): - fixation = TestFixation(random_gaze_positions(10)) - ts, _ = fixation.positions.first - aoi_scan_path.append_fixation(ts, fixation, aoi) + fixation = TestFixation(random_gaze_positions(10), timestamp=i) + aoi_scan_path.append_fixation(fixation, aoi) - saccade = TestSaccade(random_gaze_positions(2)) - ts, _ = saccade.positions.first - aoi_scan_path.append_saccade(ts, saccade) + saccade = TestSaccade(random_gaze_positions(2), timestamp=i+1) + aoi_scan_path.append_saccade(saccade) return aoi_scan_path class TestAOIScanPathClass(unittest.TestCase): """Test AOIScanPath class.""" - + def test_new(self): """Test AOIScanPath creation.""" @@ -527,7 +529,7 @@ class TestAOIScanPathClass(unittest.TestCase): aoi_scan_path = GazeFeatures.AOIScanPath() self.assertEqual(len(aoi_scan_path), 0) - + def test_append(self): """Test AOIScanPath append methods.""" @@ -535,9 +537,7 @@ class TestAOIScanPathClass(unittest.TestCase): # Append fixation on A aoi fixation_A = TestFixation(random_gaze_positions(10)) - ts, _ = fixation_A.positions.first - - new_step = aoi_scan_path.append_fixation(ts, fixation_A, 'Foo') + new_step = aoi_scan_path.append_fixation(fixation_A, 'Foo') # Check that no aoi scan step have been created yet self.assertEqual(len(aoi_scan_path), 0) @@ -546,9 +546,7 @@ class TestAOIScanPathClass(unittest.TestCase): # Append saccade saccade_A = TestSaccade(random_gaze_positions(2)) - ts, _ = saccade_A.positions.first - - new_step = aoi_scan_path.append_saccade(ts, saccade_A) + new_step = aoi_scan_path.append_saccade(saccade_A) # Check that no aoi scan step have been created yet self.assertEqual(len(aoi_scan_path), 0) @@ -557,9 +555,7 @@ class TestAOIScanPathClass(unittest.TestCase): # Append fixation on B aoi fixation_B = TestFixation(random_gaze_positions(10)) - ts, _ = fixation_B.positions.first - - new_step_A = aoi_scan_path.append_fixation(ts, fixation_B, 'Bar') + new_step_A = aoi_scan_path.append_fixation(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) @@ -568,14 +564,11 @@ 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 - - self.assertEqual(new_step_A.duration, last_ts - first_ts) + self.assertEqual(new_step_A.duration, saccade_A[-1].timestamp - fixation_A[0].timestamp) # Check letter affectation self.assertEqual(aoi_scan_path.get_letter_aoi('A'), 'Foo') - + def test_append_error(self): """Test AOIScanPath append error.""" @@ -583,9 +576,7 @@ class TestAOIScanPathClass(unittest.TestCase): # Append fixation on A aoi fixation = TestFixation(random_gaze_positions(10)) - ts, _ = fixation.positions.first - - new_step = aoi_scan_path.append_fixation(ts, fixation, 'Foo') + new_step = aoi_scan_path.append_fixation(fixation, 'Foo') # Check that no aoi scan step have been created yet self.assertEqual(len(aoi_scan_path), 0) @@ -593,18 +584,17 @@ class TestAOIScanPathClass(unittest.TestCase): # Append fixation on B aoi fixation = TestFixation(random_gaze_positions(10)) - ts, _ = fixation.positions.first # Check that aoi scan step creation fail when fixation is appened after another fixation with self.assertRaises(GazeFeatures.AOIScanStepError): - new_step = aoi_scan_path.append_fixation(ts, fixation, 'Bar') + new_step = aoi_scan_path.append_fixation(fixation, 'Bar') # Check that unexpected aoi scan step creation fail with self.assertRaises(GazeFeatures.AOIScanStepError): - new_step = aoi_scan_path.append_fixation(ts, fixation, 'Shu') - + new_step = aoi_scan_path.append_fixation(fixation, 'Shu') + def test_letter_index_and_string_reprentation(self): """Test AOIScanPath letter index and string representation feature.""" @@ -633,7 +623,7 @@ class TestAOIScanPathClass(unittest.TestCase): # Check letter sequence representation self.assertEqual(aoi_scan_path.letter_sequence, 'ABCA') - + def test_transition_matrix(self): """Test AOIScanPath transition matrix feature.""" @@ -652,7 +642,7 @@ class TestAOIScanPathClass(unittest.TestCase): self.assertEqual(aoi_scan_path.transition_matrix['Shu']['Foo'], 0) self.assertEqual(aoi_scan_path.transition_matrix['Shu']['Bar'], 1) - + def test_transition_matrix(self): """Test AOIScanPath fixations count feature.""" diff --git a/src/argaze.test/PupillFeatures.py b/src/argaze.test/PupillFeatures.py index f0e8e1b..9cf26eb 100644 --- a/src/argaze.test/PupillFeatures.py +++ b/src/argaze.test/PupillFeatures.py @@ -8,6 +8,7 @@ __copyright__ = "Copyright 2023, Ecole Nationale de l'Aviation Civile (ENAC)" __license__ = "BSD" import unittest +import math from argaze import PupillFeatures @@ -43,14 +44,12 @@ class TestPupillDiameterClass(unittest.TestCase): # Check empty PupillDiameter empty_pupill_diameter = PupillFeatures.PupillDiameter() - self.assertEqual(empty_pupill_diameter.value, 0.) - self.assertEqual(empty_pupill_diameter.valid, False) + self.assertEqual(empty_pupill_diameter, math.nan) # Check float PupillDiameter float_pupill_diameter = PupillFeatures.PupillDiameter(1.23) - self.assertEqual(float_pupill_diameter.value, 1.23) - self.assertEqual(float_pupill_diameter.valid, True) + self.assertEqual(float_pupill_diameter, 1.23) def test_properties(self): """Test PupillDiameter properties cannot be modified after creation.""" @@ -60,32 +59,16 @@ class TestPupillDiameterClass(unittest.TestCase): # Check that pupill diameter value setting fails with self.assertRaises(AttributeError): - pupill_diameter.value = 123 + pupill_diameter = 123 - self.assertNotEqual(pupill_diameter.value, 123) - self.assertEqual(pupill_diameter.value, 0.) + self.assertNotEqual(pupill_diameter, 123) + self.assertEqual(pupill_diameter, math.nan) def test___repr__(self): """Test PupillDiameter string representation.""" # Check empty PupillDiameter representation - self.assertEqual(repr(PupillFeatures.PupillDiameter()), "{\"value\": 0.0}") - -class TestUnvalidPupillDiameterClass(unittest.TestCase): - """Test UnvalidPupillDiameter class.""" - - def test_new(self): - """Test UnvalidPupillDiameter creation.""" - - unvalid_pupill_diameter = PupillFeatures.UnvalidPupillDiameter() - - self.assertEqual(unvalid_pupill_diameter.value, 0.) - self.assertEqual(unvalid_pupill_diameter.valid, False) - - def test___repr__(self): - """Test UnvalidPupillDiameter string representation.""" - - self.assertEqual(repr(PupillFeatures.UnvalidPupillDiameter()), "{\"message\": null, \"value\": 0.0}") + self.assertEqual(repr(PupillFeatures.PupillDiameter()), "{\"value\": NaN}") class TestTimeStampedPupillDiametersClass(unittest.TestCase): """Test TimeStampedPupillDiameters class.""" @@ -93,22 +76,23 @@ class TestTimeStampedPupillDiametersClass(unittest.TestCase): def test___setitem__(self): """Test __setitem__ method.""" - ts_pupill_diameters = PupillFeatures.TimeStampedPupillDiameters() - ts_pupill_diameters[0] = PupillFeatures.PupillDiameter() - ts_pupill_diameters[1] = PupillFeatures.UnvalidPupillDiameter() - ts_pupill_diameters[2] = {"value": 1.23} + ts_pupill_diameters = PupillFeatures.TimeStampedPupillDiameters([ + PupillFeatures.PupillDiameter(), + PupillFeatures.PupillDiameter(0.63), + {"value": 1.23} + ]) - # Check PupillDiameter is correctly stored and accessible as a PupillDiameter + # Check empty PupillDiameter is correctly stored and accessible as a PupillDiameter self.assertIsInstance(ts_pupill_diameters[0], PupillFeatures.PupillDiameter) - self.assertEqual(ts_pupill_diameters[0].valid, False) + self.assertEqual(ts_pupill_diameters[0], math.nan) - # Check UnvalidPupillDiameter is correctly stored and accessible as a UnvalidPupillDiameter - self.assertIsInstance(ts_pupill_diameters[1], PupillFeatures.UnvalidPupillDiameter) - self.assertEqual(ts_pupill_diameters[1].valid, False) + # Check PupillDiameter is correctly stored and accessible as a PupillDiameter + self.assertIsInstance(ts_pupill_diameters[1], PupillFeatures.PupillDiameter) + self.assertEqual(ts_pupill_diameters[0], 0.63) - # Check dict with "value" and "precision" keys is correctly stored and accessible as a PupillDiameter + # Check dict with "value" key is correctly stored and accessible as a PupillDiameter self.assertIsInstance(ts_pupill_diameters[2], PupillFeatures.PupillDiameter) - self.assertEqual(ts_pupill_diameters[2].valid, True) + self.assertEqual(ts_pupill_diameters[0], 1.23) # Check that bad data type insertion fails with self.assertRaises(AssertionError): @@ -125,11 +109,11 @@ class TestTimeStampedPupillDiametersClass(unittest.TestCase): ts_pupill_diameters = PupillFeatures.TimeStampedPupillDiameters() - self.assertEqual(repr(PupillFeatures.TimeStampedPupillDiameters()), "{}") + self.assertEqual(repr(PupillFeatures.TimeStampedPupillDiameters()), "[]") - ts_pupill_diameters[0] = PupillFeatures.PupillDiameter() + ts_pupill_diameters.append(PupillFeatures.PupillDiameter()) - self.assertEqual(repr(ts_pupill_diameters), "{\"0\": {\"value\": 0.0}}") + self.assertEqual(repr(ts_pupill_diameters), "[{\"value\": NaN, \"timestamp\": 0}]") ts_pupill_diameters[0] = PupillFeatures.UnvalidPupillDiameter() diff --git a/src/argaze.test/utils/ts_buffer.json b/src/argaze.test/utils/ts_buffer.json deleted file mode 100644 index b1433de..0000000 --- a/src/argaze.test/utils/ts_buffer.json +++ /dev/null @@ -1 +0,0 @@ -{"0":{"timestamp":0,"int_value":1,"float_value":0.000001,"string_value":"A","list_value":[[0,0],[0,1],[1,1],[1,0]],"tuple_value":[0,0],"nan_value":1.0,"json_value":{"0":"A","1":"B","2":"C","3":"D"}},"1":{"timestamp":1,"int_value":2,"float_value":0.000002,"string_value":"B","list_value":[[0,0],[0,2],[2,2],[2,0]],"tuple_value":[1,1],"nan_value":null,"json_value":{"0":"A","1":"B","2":"C","3":"D"}},"2":{"timestamp":2,"int_value":3,"float_value":0.000003,"string_value":"C","list_value":[[0,0],[0,3],[3,3],[3,0]],"tuple_value":[2,2],"nan_value":1.0,"json_value":{"0":"A","1":"B","2":"C","3":"D"}}}
\ No newline at end of file diff --git a/src/argaze.test/utils/ts_data_file.json b/src/argaze.test/utils/ts_data_file.json new file mode 100644 index 0000000..d69dd77 --- /dev/null +++ b/src/argaze.test/utils/ts_data_file.json @@ -0,0 +1,82 @@ +[ + { + "value": [ + 0, + 0.04245991513702008 + ], + "message": "test_0", + "timestamp": 1709160858.387703 + }, + { + "value": [ + 1, + 0.6761490271896192 + ], + "message": "test_1", + "timestamp": 1709160858.38784 + }, + { + "value": [ + 2, + 0.046407850274610474 + ], + "message": "test_2", + "timestamp": 1709160858.387973 + }, + { + "value": [ + 3, + 0.9378067398496651 + ], + "message": "test_3", + "timestamp": 1709160858.388105 + }, + { + "value": [ + 4, + 0.4197936347606107 + ], + "message": "test_4", + "timestamp": 1709160858.388236 + }, + { + "value": [ + 5, + 0.26937423401632943 + ], + "message": "test_5", + "timestamp": 1709160858.3883672 + }, + { + "value": [ + 6, + 0.9478731386524537 + ], + "message": "test_6", + "timestamp": 1709160858.3884978 + }, + { + "value": [ + 7, + 0.39010865778889914 + ], + "message": "test_7", + "timestamp": 1709160858.388629 + }, + { + "value": [ + 8, + 0.4100480317631575 + ], + "message": "test_8", + "timestamp": 1709160858.388763 + }, + { + "value": [ + 9, + 0.5900791904864906 + ], + "message": "test_9", + "timestamp": 1709160858.388895 + } +]
\ No newline at end of file |