aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorThéo de la Hogue2024-07-02 12:56:58 +0200
committerThéo de la Hogue2024-07-02 12:56:58 +0200
commit54229c63622782d15902d2389350317f2271a8c8 (patch)
tree9ef780ae02624480b7e33d9fc9cb7a89fedb3916 /src
parentb7f28b2d12c65d097607f5941a5d081d94bd83cb (diff)
downloadargaze-main.zip
argaze-main.tar.gz
argaze-main.tar.bz2
argaze-main.tar.xz
Fixing bad admonition formatting.HEADmain
Diffstat (limited to 'src')
-rw-r--r--src/argaze.test/GazeAnalysis/VelocityThresholdIdentification.py514
-rw-r--r--src/argaze/ArFeatures.py16
-rw-r--r--src/argaze/ArUcoMarker/ArUcoDetector.py12
-rw-r--r--src/argaze/ArUcoMarker/ArUcoMarker.py102
-rw-r--r--src/argaze/ArUcoMarker/ArUcoMarkerGroup.py5
-rw-r--r--src/argaze/AreaOfInterest/AOI2DScene.py4
-rw-r--r--src/argaze/AreaOfInterest/AOI3DScene.py10
-rw-r--r--src/argaze/AreaOfInterest/AOIFeatures.py20
-rw-r--r--src/argaze/DataFeatures.py11
-rw-r--r--src/argaze/GazeAnalysis/DeviationCircleCoverage.py258
-rw-r--r--src/argaze/GazeAnalysis/Entropy.py100
-rw-r--r--src/argaze/GazeFeatures.py38
-rw-r--r--src/argaze/__init__.py2
-rw-r--r--src/argaze/utils/UtilsFeatures.py4
-rw-r--r--src/argaze/utils/contexts/TobiiProGlasses2.py4
15 files changed, 548 insertions, 552 deletions
diff --git a/src/argaze.test/GazeAnalysis/VelocityThresholdIdentification.py b/src/argaze.test/GazeAnalysis/VelocityThresholdIdentification.py
index e7431b5..9bb07cb 100644
--- a/src/argaze.test/GazeAnalysis/VelocityThresholdIdentification.py
+++ b/src/argaze.test/GazeAnalysis/VelocityThresholdIdentification.py
@@ -28,301 +28,301 @@ from argaze.GazeAnalysis import VelocityThresholdIdentification
import numpy
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.
- """
- ts_gaze_positions = GazeFeatures.TimeStampedGazePositions()
+ """ Generate N TimeStampedGazePsoitions dispersed around a center point for testing purpose.
+ Timestamps are current time after random sleep (second).
+ GazePositions are random values.
+ """
+ ts_gaze_positions = GazeFeatures.TimeStampedGazePositions()
- start_time = time.time()
+ start_time = time.time()
- for i in range(0, size):
+ for i in range(0, size):
- # Sleep a random time
- sleep_time = random.random() * (max_time - min_time) + min_time
- time.sleep(sleep_time)
+ # Sleep a random time
+ sleep_time = random.random() * (max_time - min_time) + min_time
+ time.sleep(sleep_time)
- # Check position validity
- valid = True
- if len(validity) > i:
+ # Check position validity
+ valid = True
+ if len(validity) > i:
- valid = validity[i]
+ valid = validity[i]
- if valid:
+ if valid:
- # Edit gaze position
- 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))
+ # Edit gaze position
+ 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))
- else:
+ else:
- gaze_position = GazeFeatures.GazePosition()
+ gaze_position = GazeFeatures.GazePosition()
- # Timestamp gaze position
- gaze_position.timestamp = time.time() - start_time + start_ts
+ # Timestamp gaze position
+ gaze_position.timestamp = time.time() - start_time + start_ts
- # Store gaze position
- ts_gaze_positions.append(gaze_position)
+ # Store gaze position
+ ts_gaze_positions.append(gaze_position)
- return ts_gaze_positions
+ return ts_gaze_positions
def build_gaze_saccade(size: int, center_A: tuple, center_B: tuple, min_time: float, max_time: float, start_ts: float = 0., validity: list = []):
- """ Generate N TimeStampedGazePsoitions between 2 center points for testing purpose.
- Timestamps are current time after random sleep (second).
- GazePositions are random values.
- """
- ts_gaze_positions = GazeFeatures.TimeStampedGazePositions()
+ """ Generate N TimeStampedGazePsoitions between 2 center points for testing purpose.
+ Timestamps are current time after random sleep (second).
+ GazePositions are random values.
+ """
+ ts_gaze_positions = GazeFeatures.TimeStampedGazePositions()
- start_time = time.time()
+ start_time = time.time()
- for i in range(0, size):
+ for i in range(0, size):
- # Sleep a random time
- sleep_time = random.random() * (max_time - min_time) + min_time
- time.sleep(sleep_time)
+ # Sleep a random time
+ sleep_time = random.random() * (max_time - min_time) + min_time
+ time.sleep(sleep_time)
- # Check position validity
- valid = True
- if len(validity) > i:
+ # Check position validity
+ valid = True
+ if len(validity) > i:
- valid = validity[i]
+ valid = validity[i]
- if valid:
+ if valid:
- # Edit gaze position
- move_x = center_A[0] + (center_B[0] - center_A[0]) * (i / size)
- move_y = center_A[1] + (center_B[1] - center_A[1]) * (i / size)
- gaze_position = GazeFeatures.GazePosition((move_x, move_y))
+ # Edit gaze position
+ move_x = center_A[0] + (center_B[0] - center_A[0]) * (i / size)
+ move_y = center_A[1] + (center_B[1] - center_A[1]) * (i / size)
+ gaze_position = GazeFeatures.GazePosition((move_x, move_y))
- else:
+ else:
- gaze_position = GazeFeatures.GazePosition()
+ gaze_position = GazeFeatures.GazePosition()
- # Timestamp gaze position
- gaze_position.timestamp = time.time() - start_time + start_ts
+ # Timestamp gaze position
+ gaze_position.timestamp = time.time() - start_time + start_ts
- # Store gaze position
- ts_gaze_positions.append(gaze_position)
+ # Store gaze position
+ ts_gaze_positions.append(gaze_position)
- return ts_gaze_positions
+ return ts_gaze_positions
class TestVelocityThresholdIdentificationClass(unittest.TestCase):
- """Test VelocityThresholdIdentification class."""
-
- def test_fixation_identification(self):
- """Test VelocityThresholdIdentification fixation identification."""
-
- size = 10
- center = (0, 0)
- deviation_max = 10
- min_time = 0.05
- max_time = 0.1
- velocity_max = deviation_max / min_time
-
- ts_gaze_positions = build_gaze_fixation(size, center, deviation_max, min_time, max_time)
- 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)
-
- # Check result size
- self.assertEqual(len(ts_fixations), 1)
- self.assertEqual(len(ts_saccades), 0)
- self.assertEqual(len(ts_status), size - 1)
-
- # Check fixation
- fixation = ts_fixations.pop(0)
-
- 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.is_finished(), True)
-
- def test_fixation_and_direct_saccade_identification(self):
- """Test VelocityThresholdIdentification fixation and saccade identification."""
-
- size = 10
- center_A = (0, 0)
- center_B = (500, 500)
- deviation_max = 10
- min_time = 0.05
- max_time = 0.1
- 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[-1].timestamp)
-
- 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)
-
- # Check result size
- self.assertEqual(len(ts_fixations), 2)
- self.assertEqual(len(ts_saccades), 1)
- self.assertEqual(len(ts_status), size * 2 - 1)
-
- # Check first fixation
- fixation = ts_fixations.pop(0)
-
- 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.is_finished(), True)
-
- # Check first saccade
- saccade = ts_saccades.pop(0)
-
- self.assertEqual(len(saccade), 2)
- self.assertGreaterEqual(saccade.duration, min_time)
- self.assertLessEqual(saccade.duration, max_time)
- self.assertLessEqual(saccade.is_finished(), True)
-
- # Check that last position of a movement is equal to first position of next movement
- self.assertEqual(fixation[-1].timestamp, saccade[0].timestamp)
- self.assertEqual(fixation[-1].value, saccade[0].value)
-
- # Check second fixation
- fixation = ts_fixations.pop(0)
-
- self.assertEqual(len(fixation), size)
- self.assertGreaterEqual(fixation.duration, (size - 1) * min_time)
- self.assertLessEqual(fixation.duration, (size - 1) * max_time)
- self.assertLessEqual(fixation.is_finished(), True)
-
- # Check that last position of a movement is equal to first position of next movement
- 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."""
-
- size = 10
- move = 2
- center_A = (0, 0)
- out_A = (10, 10)
- center_B = (50, 50)
- deviation_max = 10
- min_time = 0.05
- max_time = 0.1
- 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[-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 + 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)
-
- # Check result size
- self.assertEqual(len(ts_fixations), 2)
- self.assertEqual(len(ts_saccades), 1)
- self.assertEqual(len(ts_status), 2 * size + move - 1)
-
- # Check first fixation
- fixation = ts_fixations.pop(0)
-
- 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.is_finished(), True)
-
- # Check first saccade
- saccade = ts_saccades.pop(0)
-
- 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.is_finished(), True)
-
- # Check that last position of a movement is equal to first position of next movement
- self.assertEqual(fixation[-1].timestamp, saccade[0].timestamp)
- self.assertEqual(fixation[-1].value, saccade[0].value)
-
- # Check second fixation
- fixation = ts_fixations.pop(0)
-
- self.assertEqual(len(fixation), size)
- self.assertGreaterEqual(fixation.duration, (size - 1) * min_time)
- self.assertLessEqual(fixation.duration, (size - 1) * max_time)
- self.assertLessEqual(fixation.is_finished(), True)
-
- # Check that last position of a movement is equal to first position of next movement
- 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."""
-
- size = 15
- center = (0, 0)
- deviation_max = 10
- min_time = 0.05
- max_time = 0.1
- velocity_max = deviation_max / min_time
- validity = [True, True, True, True, True, True, True, False, False, False, True, True, True, True, True]
-
- ts_gaze_positions = build_gaze_fixation(size, center, deviation_max, min_time, max_time, validity=validity)
-
- 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)
-
- # Check result size
- self.assertEqual(len(ts_fixations), 2)
- self.assertEqual(len(ts_saccades), 0)
- self.assertEqual(len(ts_status), len(validity)-5)
-
- # Check first fixation
- fixation = ts_fixations.pop(0)
-
- self.assertEqual(len(fixation), 6)
- self.assertGreaterEqual(fixation.duration, 5 * min_time)
- self.assertLessEqual(fixation.duration, 5 * max_time)
- self.assertLessEqual(fixation.is_finished(), True)
+ """Test VelocityThresholdIdentification class."""
+
+ def test_fixation_identification(self):
+ """Test VelocityThresholdIdentification fixation identification."""
+
+ size = 10
+ center = (0, 0)
+ deviation_max = 10
+ min_time = 0.05
+ max_time = 0.1
+ velocity_max = deviation_max / min_time
+
+ ts_gaze_positions = build_gaze_fixation(size, center, deviation_max, min_time, max_time)
+ 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)
+
+ # Check result size
+ self.assertEqual(len(ts_fixations), 1)
+ self.assertEqual(len(ts_saccades), 0)
+ self.assertEqual(len(ts_status), size - 1)
+
+ # Check fixation
+ fixation = ts_fixations.pop(0)
+
+ 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.is_finished(), True)
+
+ def test_fixation_and_direct_saccade_identification(self):
+ """Test VelocityThresholdIdentification fixation and saccade identification."""
+
+ size = 10
+ center_A = (0, 0)
+ center_B = (500, 500)
+ deviation_max = 10
+ min_time = 0.05
+ max_time = 0.1
+ 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[-1].timestamp)
+
+ 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)
+
+ # Check result size
+ self.assertEqual(len(ts_fixations), 2)
+ self.assertEqual(len(ts_saccades), 1)
+ self.assertEqual(len(ts_status), size * 2 - 1)
+
+ # Check first fixation
+ fixation = ts_fixations.pop(0)
+
+ 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.is_finished(), True)
+
+ # Check first saccade
+ saccade = ts_saccades.pop(0)
+
+ self.assertEqual(len(saccade), 2)
+ self.assertGreaterEqual(saccade.duration, min_time)
+ self.assertLessEqual(saccade.duration, max_time)
+ self.assertLessEqual(saccade.is_finished(), True)
+
+ # Check that last position of a movement is equal to first position of next movement
+ self.assertEqual(fixation[-1].timestamp, saccade[0].timestamp)
+ self.assertEqual(fixation[-1].value, saccade[0].value)
+
+ # Check second fixation
+ fixation = ts_fixations.pop(0)
+
+ self.assertEqual(len(fixation), size)
+ self.assertGreaterEqual(fixation.duration, (size - 1) * min_time)
+ self.assertLessEqual(fixation.duration, (size - 1) * max_time)
+ self.assertLessEqual(fixation.is_finished(), True)
+
+ # Check that last position of a movement is equal to first position of next movement
+ 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."""
+
+ size = 10
+ move = 2
+ center_A = (0, 0)
+ out_A = (10, 10)
+ center_B = (50, 50)
+ deviation_max = 10
+ min_time = 0.05
+ max_time = 0.1
+ 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[-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 + 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)
+
+ # Check result size
+ self.assertEqual(len(ts_fixations), 2)
+ self.assertEqual(len(ts_saccades), 1)
+ self.assertEqual(len(ts_status), 2 * size + move - 1)
+
+ # Check first fixation
+ fixation = ts_fixations.pop(0)
+
+ 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.is_finished(), True)
+
+ # Check first saccade
+ saccade = ts_saccades.pop(0)
+
+ 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.is_finished(), True)
+
+ # Check that last position of a movement is equal to first position of next movement
+ self.assertEqual(fixation[-1].timestamp, saccade[0].timestamp)
+ self.assertEqual(fixation[-1].value, saccade[0].value)
+
+ # Check second fixation
+ fixation = ts_fixations.pop(0)
+
+ self.assertEqual(len(fixation), size)
+ self.assertGreaterEqual(fixation.duration, (size - 1) * min_time)
+ self.assertLessEqual(fixation.duration, (size - 1) * max_time)
+ self.assertLessEqual(fixation.is_finished(), True)
+
+ # Check that last position of a movement is equal to first position of next movement
+ 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."""
+
+ size = 15
+ center = (0, 0)
+ deviation_max = 10
+ min_time = 0.05
+ max_time = 0.1
+ velocity_max = deviation_max / min_time
+ validity = [True, True, True, True, True, True, True, False, False, False, True, True, True, True, True]
+
+ ts_gaze_positions = build_gaze_fixation(size, center, deviation_max, min_time, max_time, validity=validity)
+
+ 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)
+
+ # Check result size
+ self.assertEqual(len(ts_fixations), 2)
+ self.assertEqual(len(ts_saccades), 0)
+ self.assertEqual(len(ts_status), len(validity)-5)
+
+ # Check first fixation
+ fixation = ts_fixations.pop(0)
+
+ self.assertEqual(len(fixation), 6)
+ self.assertGreaterEqual(fixation.duration, 5 * min_time)
+ self.assertLessEqual(fixation.duration, 5 * max_time)
+ self.assertLessEqual(fixation.is_finished(), True)
- # Check second fixation
- fixation = ts_fixations.pop(0)
-
- self.assertEqual(len(fixation), 4)
- self.assertGreaterEqual(fixation.duration, 3 * min_time)
- self.assertLessEqual(fixation.duration, 3 * max_time)
- self.assertLessEqual(fixation.is_finished(), True)
+ # Check second fixation
+ fixation = ts_fixations.pop(0)
+
+ self.assertEqual(len(fixation), 4)
+ self.assertGreaterEqual(fixation.duration, 3 * min_time)
+ self.assertLessEqual(fixation.duration, 3 * max_time)
+ self.assertLessEqual(fixation.is_finished(), True)
- def test_identification_browsing(self):
- """Test VelocityThresholdIdentification identification browsing."""
-
- size = 10
- center_A = (0, 0)
- center_B = (50, 50)
- deviation_max = 10
- min_time = 0.01
- max_time = 0.1
- 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[-1].timestamp)
+ def test_identification_browsing(self):
+ """Test VelocityThresholdIdentification identification browsing."""
+
+ size = 10
+ center_A = (0, 0)
+ center_B = (50, 50)
+ deviation_max = 10
+ min_time = 0.01
+ max_time = 0.1
+ 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[-1].timestamp)
- ts_gaze_positions = ts_gaze_positions_A + 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)
+ gaze_movement_identifier = VelocityThresholdIdentification.GazeMovementIdentifier(velocity_max_threshold=velocity_max, duration_min_threshold=max_time*2)
- # Iterate on gaze positions
- for gaze_position in ts_gaze_positions:
+ # Iterate on gaze positions
+ for gaze_position in ts_gaze_positions:
- finished_gaze_movement = gaze_movement_identifier.identify(gaze_position, terminate=(gaze_position.timestamp == ts_gaze_positions[-1]))
+ finished_gaze_movement = gaze_movement_identifier.identify(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:
+ # Check that last gaze position date is not equal to given gaze position date
+ if finished_gaze_movement:
- self.assertNotEqual(finished_gaze_movement[-1].timestamp, gaze_position.timestamp)
+ 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:
+ # 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:
- self.assertEqual(current_gaze_movement[-1].timestamp, gaze_position.timestamp)
+ self.assertEqual(current_gaze_movement[-1].timestamp, gaze_position.timestamp)
if __name__ == '__main__':
- unittest.main() \ No newline at end of file
+ unittest.main() \ No newline at end of file
diff --git a/src/argaze/ArFeatures.py b/src/argaze/ArFeatures.py
index 2d9c281..aaac6ed 100644
--- a/src/argaze/ArFeatures.py
+++ b/src/argaze/ArFeatures.py
@@ -96,7 +96,7 @@ class ArLayer(DataFeatures.SharedObject, DataFeatures.PipelineStepObject):
Defines a space where to make matching of gaze movements and AOI and inside which those matching need to be analyzed.
!!! note
- Inherits from DataFeatures.SharedObject class to be shared by multiple threads.
+ Inherits from DataFeatures.SharedObject class to be shared by multiple threads.
"""
@DataFeatures.PipelineStepInit
@@ -320,7 +320,7 @@ class ArLayer(DataFeatures.SharedObject, DataFeatures.PipelineStepObject):
Project timestamped gaze movement into layer.
!!! warning
- Be aware that gaze movement positions are in the same range of value than aoi_scene size attribute.
+ Be aware that gaze movement positions are in the same range of value than aoi_scene size attribute.
Parameters:
gaze_movement: gaze movement to project
@@ -435,7 +435,7 @@ class ArFrame(DataFeatures.SharedObject, DataFeatures.PipelineStepObject):
Defines a rectangular area where to project in timestamped gaze positions and inside which they need to be analyzed.
!!! note
- Inherits from DataFeatures.SharedObject class to be shared by multiple threads
+ Inherits from DataFeatures.SharedObject class to be shared by multiple threads
"""
@DataFeatures.PipelineStepInit
@@ -703,7 +703,7 @@ class ArFrame(DataFeatures.SharedObject, DataFeatures.PipelineStepObject):
Project timestamped gaze position into frame.
!!! warning
- Be aware that gaze positions are in the same range of value than size attribute.
+ Be aware that gaze positions are in the same range of value than size attribute.
Parameters:
timestamped_gaze_position: gaze position to project
@@ -1305,10 +1305,10 @@ class ArCamera(ArFrame):
"""Copy camera frame background into scene frames background.
!!! warning
- This method have to be called once AOI have been projected into camera frame layers.
+ This method have to be called once AOI have been projected into camera frame layers.
!!! note
- This method makes each frame to send an 'on_copy_background_into_scenes_frames' signal to their observers.
+ This method makes each frame to send an 'on_copy_background_into_scenes_frames' signal to their observers.
"""
# Project camera frame background into each scene frame if possible
@@ -1445,7 +1445,7 @@ class ArCamera(ArFrame):
"""Project timestamped gaze position into each scene frames.
!!! warning
- watch method needs to be called first.
+ watch method needs to be called first.
Parameters:
timestamped_gaze_position: gaze position to project
@@ -1504,7 +1504,7 @@ DEFAULT_ARCONTEXT_IMAGE_PARAMETERS = {
class ArContext(DataFeatures.PipelineStepObject):
"""
- Defines abstract Python context manager to handle pipeline inputs.
+ Defines abstract Python context manager to handle incoming gaze data before passing them to a processing pipeline.
"""
# noinspection PyMissingConstructor
diff --git a/src/argaze/ArUcoMarker/ArUcoDetector.py b/src/argaze/ArUcoMarker/ArUcoDetector.py
index 32091a4..50da144 100644
--- a/src/argaze/ArUcoMarker/ArUcoDetector.py
+++ b/src/argaze/ArUcoMarker/ArUcoDetector.py
@@ -33,7 +33,7 @@ class DetectorParameters():
"""Wrapper class around ArUco marker detector parameters.
!!! note
- More details on [opencv page](https://docs.opencv.org/4.x/d1/dcd/structcv_1_1aruco_1_1DetectorParameters.html)
+ More details on [opencv page](https://docs.opencv.org/4.x/d1/dcd/structcv_1_1aruco_1_1DetectorParameters.html)
"""
__parameters = aruco.DetectorParameters()
@@ -188,13 +188,13 @@ class ArUcoDetector(DataFeatures.PipelineStepObject):
"""Detect all ArUco markers into an image.
!!! danger "DON'T MIRROR IMAGE"
- It makes the markers detection to fail.
+ It makes the markers detection to fail.
!!! danger "DON'T UNDISTORTED IMAGE"
- Camera intrinsic parameters and distortion coefficients are used later during pose estimation.
+ Camera intrinsic parameters and distortion coefficients are used later during pose estimation.
!!! note
- The pose of markers will be also estimated if the pose_size attribute is not None.
+ The pose of markers will be also estimated if the pose_size attribute is not None.
"""
# Reset detected markers data
@@ -262,7 +262,7 @@ class ArUcoDetector(DataFeatures.PipelineStepObject):
ids: markers id list to select detected markers.
!!! warning
- This method have to called after 'detect_markers'
+ This method have to called after 'detect_markers'
"""
# Is there detected markers ?
@@ -324,7 +324,7 @@ class ArUcoDetector(DataFeatures.PipelineStepObject):
"""Detect ArUco markers board in image setting up the number of detected markers needed to agree detection.
!!! danger "DON'T MIRROR IMAGE"
- It makes the markers detection to fail.
+ It makes the markers detection to fail.
"""
# detect markers from gray picture
diff --git a/src/argaze/ArUcoMarker/ArUcoMarker.py b/src/argaze/ArUcoMarker/ArUcoMarker.py
index bfd6350..fddc2aa 100644
--- a/src/argaze/ArUcoMarker/ArUcoMarker.py
+++ b/src/argaze/ArUcoMarker/ArUcoMarker.py
@@ -28,80 +28,80 @@ import cv2.aruco as aruco
@dataclass
class ArUcoMarker():
- """Define ArUco marker class."""
+ """Define ArUco marker class."""
- dictionary: ArUcoMarkerDictionary.ArUcoMarkerDictionary
- """Dictionary to which it belongs."""
+ dictionary: ArUcoMarkerDictionary.ArUcoMarkerDictionary
+ """Dictionary to which it belongs."""
- identifier: int
- """Index into dictionary"""
+ identifier: int
+ """Index into dictionary"""
- size: float = field(default=math.nan)
- """Size of marker in centimeters."""
+ size: float = field(default=math.nan)
+ """Size of marker in centimeters."""
- corners: numpy.array = field(init=False, repr=False)
- """Estimated 2D corners position in camera image referential."""
+ corners: numpy.array = field(init=False, repr=False)
+ """Estimated 2D corners position in camera image referential."""
- translation: numpy.array = field(init=False, repr=False)
- """Estimated 3D center position in camera world referential."""
+ translation: numpy.array = field(init=False, repr=False)
+ """Estimated 3D center position in camera world referential."""
- rotation: numpy.array = field(init=False, repr=False)
- """Estimated 3D marker rotation in camera world referential."""
+ rotation: numpy.array = field(init=False, repr=False)
+ """Estimated 3D marker rotation in camera world referential."""
- points: numpy.array = field(init=False, repr=False)
- """Estimated 3D corners positions in camera world referential."""
+ points: numpy.array = field(init=False, repr=False)
+ """Estimated 3D corners positions in camera world referential."""
- @property
- def center(self) -> numpy.array:
- """Get 2D center position in camera image referential."""
+ @property
+ def center(self) -> numpy.array:
+ """Get 2D center position in camera image referential."""
- return self.corners[0].mean(axis=0)
+ return self.corners[0].mean(axis=0)
- def image(self, dpi) -> numpy.array:
- """Create marker matrix image at a given resolution.
+ def image(self, dpi) -> numpy.array:
+ """Create marker matrix image at a given resolution.
- !!! warning
- Marker size have to be setup before.
- """
+ !!! warning
+ Marker size have to be setup before.
+ """
- assert(not math.isnan(self.size))
+ assert(not math.isnan(self.size))
- dimension = round(self.size * dpi / 2.54) # 1 cm = 2.54 inches
- matrix = numpy.zeros((dimension, dimension, 1), dtype="uint8")
+ dimension = round(self.size * dpi / 2.54) # 1 cm = 2.54 inches
+ matrix = numpy.zeros((dimension, dimension, 1), dtype="uint8")
- aruco.generateImageMarker(self.dictionary.markers, self.identifier, dimension, matrix, 1)
+ aruco.generateImageMarker(self.dictionary.markers, self.identifier, dimension, matrix, 1)
- return numpy.repeat(matrix, 3).reshape(dimension, dimension, 3)
+ return numpy.repeat(matrix, 3).reshape(dimension, dimension, 3)
- def draw(self, image: numpy.array, K: numpy.array, D: numpy.array, color: tuple = None, draw_axes: dict = None):
- """Draw marker in image.
+ def draw(self, image: numpy.array, K: numpy.array, D: numpy.array, color: tuple = None, draw_axes: dict = None):
+ """Draw marker in image.
- Parameters:
- image: image where to
- K:
- D:
- color: marker color (if None, no marker drawn)
- draw_axes: enable marker axes drawing
+ Parameters:
+ image: image where to
+ K:
+ D:
+ color: marker color (if None, no marker drawn)
+ draw_axes: enable marker axes drawing
- !!! warning
- draw_axes needs marker size and pose estimation.
- """
+ !!! warning
+ draw_axes needs marker size and pose estimation.
+ """
- # Draw marker if required
- if color is not None:
+ # Draw marker if required
+ if color is not None:
- aruco.drawDetectedMarkers(image, [numpy.array([list(self.corners)])], numpy.array([self.identifier]), color)
+ aruco.drawDetectedMarkers(image, [numpy.array([list(self.corners)])], numpy.array([self.identifier]), color)
- # Draw marker axes if pose has been estimated, marker have a size and if required
- if self.translation.size == 3 and self.rotation.size == 9 and not math.isnan(self.size) and draw_axes is not None:
+ # Draw marker axes if pose has been estimated, marker have a size and if required
+ if self.translation.size == 3 and self.rotation.size == 9 and not math.isnan(self.size) and draw_axes is not None:
- cv2.drawFrameAxes(image, numpy.array(K), numpy.array(D), self.rotation, self.translation, self.size, **draw_axes)
+ cv2.drawFrameAxes(image, numpy.array(K), numpy.array(D), self.rotation, self.translation, self.size, **draw_axes)
- def save(self, destination_folder, dpi):
- """Save marker image as .png file into a destination folder."""
+ def save(self, destination_folder, dpi):
+ """Save marker image as .png file into a destination folder."""
- filename = f'{self.dictionary.name}_{self.dictionary.format}_{self.identifier}.png'
- filepath = f'{destination_folder}/{filename}'
+ filename = f'{self.dictionary.name}_{self.dictionary.format}_{self.identifier}.png'
+ filepath = f'{destination_folder}/{filename}'
- cv2.imwrite(filepath, self.image(dpi))
+ cv2.imwrite(filepath, self.image(dpi))
diff --git a/src/argaze/ArUcoMarker/ArUcoMarkerGroup.py b/src/argaze/ArUcoMarker/ArUcoMarkerGroup.py
index 1cca6c4..5575cad 100644
--- a/src/argaze/ArUcoMarker/ArUcoMarkerGroup.py
+++ b/src/argaze/ArUcoMarker/ArUcoMarkerGroup.py
@@ -206,11 +206,10 @@ class ArUcoMarkerGroup(DataFeatures.PipelineStepObject):
"""Load ArUco markers group from .obj file.
!!! note
- Expected object (o) name format: <DICTIONARY>#<IDENTIFIER>_Marker
+ Expected object (o) name format: <DICTIONARY>#<IDENTIFIER>_Marker
!!! note
- All markers have to belong to the same dictionary.
-
+ All markers have to belong to the same dictionary.
"""
new_dictionary = None
diff --git a/src/argaze/AreaOfInterest/AOI2DScene.py b/src/argaze/AreaOfInterest/AOI2DScene.py
index 9c74637..b19c6e9 100644
--- a/src/argaze/AreaOfInterest/AOI2DScene.py
+++ b/src/argaze/AreaOfInterest/AOI2DScene.py
@@ -43,10 +43,10 @@ class AOI2DScene(AOIFeatures.AOIScene):
svg_filepath: path to svg file
!!! note
- Available SVG elements are: path, rect and circle.
+ Available SVG elements are: path, rect and circle.
!!! warning
- Available SVG path d-string commands are: MoveTo (M) LineTo (L) and ClosePath (Z) commands.
+ Available SVG path d-string commands are: MoveTo (M) LineTo (L) and ClosePath (Z) commands.
"""
with minidom.parse(svg_filepath) as description_file:
diff --git a/src/argaze/AreaOfInterest/AOI3DScene.py b/src/argaze/AreaOfInterest/AOI3DScene.py
index 13ea354..232329c 100644
--- a/src/argaze/AreaOfInterest/AOI3DScene.py
+++ b/src/argaze/AreaOfInterest/AOI3DScene.py
@@ -179,8 +179,8 @@ class AOI3DScene(AOIFeatures.AOIScene):
"""Get AOI which are inside and out a given cone field.
!!! note
- **By default**
- The cone have its tip at origin and its base oriented to positive Z axis.
+ **By default**
+ The cone have its tip at origin and its base oriented to positive Z axis.
Returns:
scene inside the cone
@@ -226,11 +226,11 @@ class AOI3DScene(AOIFeatures.AOIScene):
D: camera distortion coefficients vector
!!! danger
- Camera distortion coefficients could project points which are far from image frame into it.
+ Camera distortion coefficients could project points which are far from image frame into it.
!!! note
- As gaze is mainly focusing on frame center, where the distortion is low,
- it could be acceptable to not use camera distortion.
+ As gaze is mainly focusing on frame center, where the distortion is low,
+ it could be acceptable to not use camera distortion.
"""
aoi2D_scene = AOI2DScene.AOI2DScene()
diff --git a/src/argaze/AreaOfInterest/AOIFeatures.py b/src/argaze/AreaOfInterest/AOIFeatures.py
index 25046ff..fb61f61 100644
--- a/src/argaze/AreaOfInterest/AOIFeatures.py
+++ b/src/argaze/AreaOfInterest/AOIFeatures.py
@@ -143,7 +143,7 @@ class AreaOfInterest(numpy.ndarray):
def bounding_box(self) -> numpy.array:
"""Get area's bounding box.
!!! warning
- Available for 2D AOI only."""
+ Available for 2D AOI only."""
assert (self.points_number > 1)
assert (self.dimension == 2)
@@ -162,7 +162,7 @@ class AreaOfInterest(numpy.ndarray):
def clockwise(self) -> Self:
"""Get area points in clockwise order.
!!! warning
- Available for 2D AOI only."""
+ Available for 2D AOI only."""
assert (self.dimension == 2)
@@ -175,9 +175,9 @@ class AreaOfInterest(numpy.ndarray):
def contains_point(self, point: tuple) -> bool:
"""Is a point inside area?
!!! warning
- Available for 2D AOI only.
+ Available for 2D AOI only.
!!! danger
- The AOI points must be sorted in clockwise order."""
+ The AOI points must be sorted in clockwise order."""
assert (self.dimension == 2)
assert (len(point) == self.dimension)
@@ -187,9 +187,9 @@ class AreaOfInterest(numpy.ndarray):
def inner_axis(self, x: float, y: float) -> tuple:
"""Transform a point coordinates from global axis to AOI axis.
!!! warning
- Available for 2D AOI only.
+ Available for 2D AOI only.
!!! danger
- The AOI points must be sorted in clockwise order."""
+ The AOI points must be sorted in clockwise order."""
assert (self.dimension == 2)
@@ -210,9 +210,9 @@ class AreaOfInterest(numpy.ndarray):
def outter_axis(self, x: float, y: float) -> tuple:
"""Transform a point coordinates from AOI axis to global axis.
!!! danger
- The AOI points must be sorted in clockwise order.
+ The AOI points must be sorted in clockwise order.
!!! danger
- The AOI must be a rectangle.
+ The AOI must be a rectangle.
"""
# Origin point
@@ -230,7 +230,7 @@ class AreaOfInterest(numpy.ndarray):
def circle_intersection(self, center: tuple, radius: float) -> tuple[numpy.array, float, float]:
"""Get intersection shape with a circle, intersection area / AOI area ratio and intersection area / circle area ratio.
!!! warning
- Available for 2D AOI only.
+ Available for 2D AOI only.
Returns:
intersection shape
@@ -267,7 +267,7 @@ class AreaOfInterest(numpy.ndarray):
def draw(self, image: numpy.array, color, border_size=1):
"""Draw 2D AOI into image.
!!! warning
- Available for 2D AOI only."""
+ Available for 2D AOI only."""
assert (self.dimension == 2)
diff --git a/src/argaze/DataFeatures.py b/src/argaze/DataFeatures.py
index 60e382b..2629e8e 100644
--- a/src/argaze/DataFeatures.py
+++ b/src/argaze/DataFeatures.py
@@ -134,7 +134,7 @@ def from_json(filepath: str) -> any:
Load object from json file.
!!! note
- The directory where json file is will be used as global working directory.
+ The directory where json file is will be used as global working directory.
Parameters:
filepath: path to json file
@@ -354,8 +354,7 @@ class TimestampedObjectsList(list):
"""Handle timestamped object into a list.
!!! warning "Timestamped objects are not sorted internally"
-
- Timestamped objects are considered to be stored according to their coming time.
+ Timestamped objects are considered to be stored according to their coming time.
"""
# noinspection PyMissingConstructor
@@ -450,12 +449,10 @@ class TimestampedObjectsList(list):
For example: to convert {"point": (0, 0)} data as two separated "x" and "y" columns, use split={"point": ["x", "y"]}
!!! warning "Values must be dictionaries"
-
- Each key is stored as a column name.
+ Each key is stored as a column name.
!!! note
-
- Timestamps are stored as index column called 'timestamp'.
+ Timestamps are stored as index column called 'timestamp'.
"""
df = pandas.DataFrame(self.tuples(), columns=self.__object_properties_names)
diff --git a/src/argaze/GazeAnalysis/DeviationCircleCoverage.py b/src/argaze/GazeAnalysis/DeviationCircleCoverage.py
index 3d910c7..3bf8f46 100644
--- a/src/argaze/GazeAnalysis/DeviationCircleCoverage.py
+++ b/src/argaze/GazeAnalysis/DeviationCircleCoverage.py
@@ -26,195 +26,195 @@ from argaze.GazeAnalysis import DispersionThresholdIdentification, VelocityThres
class AOIMatcher(GazeFeatures.AOIMatcher):
- """Matching algorithm based on fixation's deviation circle coverage over AOI."""
+ """Matching algorithm based on fixation's deviation circle coverage over AOI."""
- @DataFeatures.PipelineStepInit
- def __init__(self, **kwargs):
+ @DataFeatures.PipelineStepInit
+ def __init__(self, **kwargs):
- # Init AOIMatcher class
- super().__init__()
+ # Init AOIMatcher class
+ super().__init__()
- self.__coverage_threshold = 0
+ self.__coverage_threshold = 0
- self.__reset()
+ self.__reset()
- @property
- def coverage_threshold(self) -> float:
- """Minimal coverage ratio to consider a fixation over an AOI (1 means that whole fixation's deviation circle have to be over the AOI)."""
- return self.__coverage_threshold
+ @property
+ def coverage_threshold(self) -> float:
+ """Minimal coverage ratio to consider a fixation over an AOI (1 means that whole fixation's deviation circle have to be over the AOI)."""
+ return self.__coverage_threshold
- @coverage_threshold.setter
- def coverage_threshold(self, coverage_threshold: float):
+ @coverage_threshold.setter
+ def coverage_threshold(self, coverage_threshold: float):
- self.__coverage_threshold = coverage_threshold
-
- def __reset(self):
+ self.__coverage_threshold = coverage_threshold
+
+ def __reset(self):
- self.__look_count = 0
- self.__looked_aoi_data = (None, None)
- self.__looked_probabilities = {}
- self.__circle_ratio_sum = {}
- self.__matched_gaze_movement = None
- self.__matched_region = None
+ self.__look_count = 0
+ self.__looked_aoi_data = (None, None)
+ self.__looked_probabilities = {}
+ self.__circle_ratio_sum = {}
+ self.__matched_gaze_movement = None
+ self.__matched_region = None
- @DataFeatures.PipelineStepMethod
- def match(self, gaze_movement: GazeFeatures.GazeMovement, aoi_scene) -> tuple[str, AOIFeatures.AreaOfInterest]:
- """Returns AOI with the maximal fixation's deviation circle coverage if above coverage threshold."""
+ @DataFeatures.PipelineStepMethod
+ def match(self, gaze_movement: GazeFeatures.GazeMovement, aoi_scene) -> tuple[str, AOIFeatures.AreaOfInterest]:
+ """Returns AOI with the maximal fixation's deviation circle coverage if above coverage threshold."""
- if GazeFeatures.is_fixation(gaze_movement):
+ if GazeFeatures.is_fixation(gaze_movement):
- self.__look_count += 1
+ self.__look_count += 1
- max_coverage = 0.
- most_likely_looked_aoi_data = (None, None)
- matched_region = None
+ max_coverage = 0.
+ most_likely_looked_aoi_data = (None, None)
+ matched_region = None
- for name, aoi in aoi_scene.items():
+ for name, aoi in aoi_scene.items():
- # DispersionThresholdIdentification.Fixation: use maximal deviation
- if issubclass(type(gaze_movement), DispersionThresholdIdentification.Fixation):
+ # DispersionThresholdIdentification.Fixation: use maximal deviation
+ if issubclass(type(gaze_movement), DispersionThresholdIdentification.Fixation):
- fixation_circle_radius = gaze_movement.deviation_max
+ fixation_circle_radius = gaze_movement.deviation_max
- # VelocityThresholdIdentification.Fixation: use amplitude
- elif issubclass(type(gaze_movement), VelocityThresholdIdentification.Fixation):
+ # VelocityThresholdIdentification.Fixation: use amplitude
+ elif issubclass(type(gaze_movement), VelocityThresholdIdentification.Fixation):
- fixation_circle_radius = gaze_movement.amplitude
+ fixation_circle_radius = gaze_movement.amplitude
- # Otherwise, compute maximal deviation
- else:
+ # Otherwise, compute maximal deviation
+ else:
- fixation_circle_radius = max(gaze_movement.distances(gaze_movement.focus))
+ fixation_circle_radius = max(gaze_movement.distances(gaze_movement.focus))
- # Intersect
- region, _, circle_ratio = aoi.circle_intersection(gaze_movement.focus, fixation_circle_radius)
+ # Intersect
+ region, _, circle_ratio = aoi.circle_intersection(gaze_movement.focus, fixation_circle_radius)
- if name not in self.exclude and circle_ratio > self.__coverage_threshold:
+ if name not in self.exclude and circle_ratio > self.__coverage_threshold:
- # Sum circle ratio to update aoi coverage
- try:
+ # Sum circle ratio to update aoi coverage
+ try:
- self.__circle_ratio_sum[name] += circle_ratio
+ self.__circle_ratio_sum[name] += circle_ratio
- except KeyError:
+ except KeyError:
- self.__circle_ratio_sum[name] = circle_ratio
+ self.__circle_ratio_sum[name] = circle_ratio
- # Update maximal coverage and most likely looked aoi
- if self.__circle_ratio_sum[name] > max_coverage:
+ # Update maximal coverage and most likely looked aoi
+ if self.__circle_ratio_sum[name] > max_coverage:
- max_coverage = self.__circle_ratio_sum[name]
- most_likely_looked_aoi_data = (name, aoi)
- matched_region = region
-
- # Check that aoi coverage happens
- if max_coverage > 0:
+ max_coverage = self.__circle_ratio_sum[name]
+ most_likely_looked_aoi_data = (name, aoi)
+ matched_region = region
+
+ # Check that aoi coverage happens
+ if max_coverage > 0:
- # Update looked aoi data
- # noinspection PyAttributeOutsideInit
- self.__looked_aoi_data = most_likely_looked_aoi_data
+ # Update looked aoi data
+ # noinspection PyAttributeOutsideInit
+ self.__looked_aoi_data = most_likely_looked_aoi_data
- # Calculate circle ratio means as looked probabilities
- # noinspection PyAttributeOutsideInit
- self.__looked_probabilities = {}
+ # Calculate circle ratio means as looked probabilities
+ # noinspection PyAttributeOutsideInit
+ self.__looked_probabilities = {}
- for aoi_name, circle_ratio_sum in self.__circle_ratio_sum.items():
+ for aoi_name, circle_ratio_sum in self.__circle_ratio_sum.items():
- circle_ratio_mean = circle_ratio_sum / self.__look_count
+ circle_ratio_mean = circle_ratio_sum / self.__look_count
- # Avoid probability greater than 1
- self.__looked_probabilities[aoi_name] = circle_ratio_mean if circle_ratio_mean < 1 else 1
+ # Avoid probability greater than 1
+ self.__looked_probabilities[aoi_name] = circle_ratio_mean if circle_ratio_mean < 1 else 1
- # Update matched gaze movement
- # noinspection PyAttributeOutsideInit
- self.__matched_gaze_movement = gaze_movement
+ # Update matched gaze movement
+ # noinspection PyAttributeOutsideInit
+ self.__matched_gaze_movement = gaze_movement
- # Update matched region
- # noinspection PyAttributeOutsideInit
- self.__matched_region = matched_region
+ # Update matched region
+ # noinspection PyAttributeOutsideInit
+ self.__matched_region = matched_region
- # Return
- return self.__looked_aoi_data
+ # Return
+ return self.__looked_aoi_data
- elif GazeFeatures.is_saccade(gaze_movement):
+ elif GazeFeatures.is_saccade(gaze_movement):
- self.__reset()
+ self.__reset()
- elif not gaze_movement:
+ elif not gaze_movement:
- self.__reset()
+ self.__reset()
- return (None, None)
+ return (None, None)
- def draw(self, image: numpy.array, aoi_scene: AOIFeatures.AOIScene, draw_matched_fixation: dict = None, draw_matched_region: dict = None, draw_looked_aoi: dict = None, update_looked_aoi: bool = False, looked_aoi_name_color: tuple = None, looked_aoi_name_offset: tuple = (0, 0)):
- """Draw matching into image.
-
- Parameters:
- image: where to draw
- aoi_scene: to refresh looked aoi if required
- draw_matched_fixation: Fixation.draw parameters (which depends on the loaded
- gaze movement identifier module, if None, no fixation is drawn)
- draw_matched_region: AOIFeatures.AOI.draw parameters (if None, no matched region is drawn)
- draw_looked_aoi: AOIFeatures.AOI.draw parameters (if None, no looked aoi is drawn)
- update_looked_aoi:
- looked_aoi_name_color: color of text (if None, no looked aoi name is drawn)
- looked_aoi_name_offset: offset of text from the upper left aoi bounding box corner
- """
+ def draw(self, image: numpy.array, aoi_scene: AOIFeatures.AOIScene, draw_matched_fixation: dict = None, draw_matched_region: dict = None, draw_looked_aoi: dict = None, update_looked_aoi: bool = False, looked_aoi_name_color: tuple = None, looked_aoi_name_offset: tuple = (0, 0)):
+ """Draw matching into image.
+
+ Parameters:
+ image: where to draw
+ aoi_scene: to refresh looked aoi if required
+ draw_matched_fixation: Fixation.draw parameters (which depends on the loaded
+ gaze movement identifier module, if None, no fixation is drawn)
+ draw_matched_region: AOIFeatures.AOI.draw parameters (if None, no matched region is drawn)
+ draw_looked_aoi: AOIFeatures.AOI.draw parameters (if None, no looked aoi is drawn)
+ update_looked_aoi:
+ looked_aoi_name_color: color of text (if None, no looked aoi name is drawn)
+ looked_aoi_name_offset: offset of text from the upper left aoi bounding box corner
+ """
- if self.__matched_gaze_movement is not None:
+ if self.__matched_gaze_movement is not None:
- if GazeFeatures.is_fixation(self.__matched_gaze_movement):
+ if GazeFeatures.is_fixation(self.__matched_gaze_movement):
- # Draw matched fixation if required
- if draw_matched_fixation is not None:
+ # Draw matched fixation if required
+ if draw_matched_fixation is not None:
- self.__matched_gaze_movement.draw(image, **draw_matched_fixation)
-
- # Draw matched aoi
- if self.looked_aoi().all() is not None:
+ self.__matched_gaze_movement.draw(image, **draw_matched_fixation)
+
+ # Draw matched aoi
+ if self.looked_aoi().all() is not None:
- if update_looked_aoi:
+ if update_looked_aoi:
- try:
+ try:
- # noinspection PyAttributeOutsideInit
- self.__looked_aoi_data = (self.looked_aoi_name(), aoi_scene[self.looked_aoi_name()])
+ # noinspection PyAttributeOutsideInit
+ self.__looked_aoi_data = (self.looked_aoi_name(), aoi_scene[self.looked_aoi_name()])
- except KeyError:
+ except KeyError:
- pass
+ pass
- # Draw looked aoi if required
- if draw_looked_aoi is not None:
+ # Draw looked aoi if required
+ if draw_looked_aoi is not None:
- self.looked_aoi().draw(image, **draw_looked_aoi)
+ self.looked_aoi().draw(image, **draw_looked_aoi)
- # Draw matched region if required
- if draw_matched_region is not None:
+ # Draw matched region if required
+ if draw_matched_region is not None:
- self.__matched_region.draw(image, **draw_matched_region)
+ self.__matched_region.draw(image, **draw_matched_region)
- # Draw looked aoi name if required
- if looked_aoi_name_color is not None:
+ # Draw looked aoi name if required
+ if looked_aoi_name_color is not None:
- top_left_corner_pixel = numpy.rint(self.looked_aoi().bounding_box[0]).astype(int) + looked_aoi_name_offset
- cv2.putText(image, self.looked_aoi_name(), top_left_corner_pixel, cv2.FONT_HERSHEY_SIMPLEX, 1, looked_aoi_name_color, 1, cv2.LINE_AA)
+ top_left_corner_pixel = numpy.rint(self.looked_aoi().bounding_box[0]).astype(int) + looked_aoi_name_offset
+ cv2.putText(image, self.looked_aoi_name(), top_left_corner_pixel, cv2.FONT_HERSHEY_SIMPLEX, 1, looked_aoi_name_color, 1, cv2.LINE_AA)
- def looked_aoi(self) -> AOIFeatures.AreaOfInterest:
- """Get most likely looked aoi for current fixation (e.g. the aoi with the highest coverage mean value)"""
+ def looked_aoi(self) -> AOIFeatures.AreaOfInterest:
+ """Get most likely looked aoi for current fixation (e.g. the aoi with the highest coverage mean value)"""
- return self.__looked_aoi_data[1]
+ return self.__looked_aoi_data[1]
- def looked_aoi_name(self) -> str:
- """Get most likely looked aoi name for current fixation (e.g. the aoi with the highest coverage mean value)"""
+ def looked_aoi_name(self) -> str:
+ """Get most likely looked aoi name for current fixation (e.g. the aoi with the highest coverage mean value)"""
- return self.__looked_aoi_data[0]
+ return self.__looked_aoi_data[0]
- def looked_probabilities(self) -> dict:
- """Get probabilities to be looked by current fixation for each aoi.
+ def looked_probabilities(self) -> dict:
+ """Get probabilities to be looked by current fixation for each aoi.
- !!! note
- aoi where fixation deviation circle never passed the coverage threshold will be missing.
- """
+ !!! note
+ aoi where fixation deviation circle never passed the coverage threshold will be missing.
+ """
- return self.__looked_probabilities \ No newline at end of file
+ return self.__looked_probabilities \ No newline at end of file
diff --git a/src/argaze/GazeAnalysis/Entropy.py b/src/argaze/GazeAnalysis/Entropy.py
index dfed82f..9d45d1d 100644
--- a/src/argaze/GazeAnalysis/Entropy.py
+++ b/src/argaze/GazeAnalysis/Entropy.py
@@ -24,79 +24,79 @@ from argaze.GazeAnalysis import TransitionMatrix
class AOIScanPathAnalyzer(GazeFeatures.AOIScanPathAnalyzer):
- """Implementation of entropy algorithm as described in:
+ """Implementation of entropy algorithm as described in:
- **Krejtz K., Szmidt T., Duchowski A.T. (2014).**
- *Entropy-based statistical analysis of eye movement transitions.*
- Proceedings of the Symposium on Eye Tracking Research and Applications (ETRA'14, 159-166).
- [https://doi.org/10.1145/2578153.2578176](https://doi.org/10.1145/2578153.2578176)
- """
+ **Krejtz K., Szmidt T., Duchowski A.T. (2014).**
+ *Entropy-based statistical analysis of eye movement transitions.*
+ Proceedings of the Symposium on Eye Tracking Research and Applications (ETRA'14, 159-166).
+ [https://doi.org/10.1145/2578153.2578176](https://doi.org/10.1145/2578153.2578176)
+ """
- @DataFeatures.PipelineStepInit
- def __init__(self, **kwargs):
+ @DataFeatures.PipelineStepInit
+ def __init__(self, **kwargs):
- # Init AOIScanPathAnalyzer class
- super().__init__()
+ # Init AOIScanPathAnalyzer class
+ super().__init__()
- self.__transition_matrix_analyzer = None
- self.__stationary_entropy = -1
- self.__transition_entropy = -1
+ self.__transition_matrix_analyzer = None
+ self.__stationary_entropy = -1
+ self.__transition_entropy = -1
- @property
- def transition_matrix_analyzer(self) -> TransitionMatrix.AOIScanPathAnalyzer:
- """Bind to TransitionMatrix analyzer to get its transition_matrix_probabilities.
+ @property
+ def transition_matrix_analyzer(self) -> TransitionMatrix.AOIScanPathAnalyzer:
+ """Bind to TransitionMatrix analyzer to get its transition_matrix_probabilities.
- !!! warning "Mandatory"
- TransitionMatrix analyzer have to be loaded before.
- """
+ !!! warning "Mandatory"
+ TransitionMatrix analyzer have to be loaded before.
+ """
- return self.__transition_matrix_analyzer
+ return self.__transition_matrix_analyzer
- @transition_matrix_analyzer.setter
- def transition_matrix_analyzer(self, transition_matrix_analyzer: TransitionMatrix.AOIScanPathAnalyzer):
+ @transition_matrix_analyzer.setter
+ def transition_matrix_analyzer(self, transition_matrix_analyzer: TransitionMatrix.AOIScanPathAnalyzer):
- self.__transition_matrix_analyzer = transition_matrix_analyzer
+ self.__transition_matrix_analyzer = transition_matrix_analyzer
- @DataFeatures.PipelineStepMethod
- def analyze(self, aoi_scan_path: GazeFeatures.AOIScanPath):
+ @DataFeatures.PipelineStepMethod
+ def analyze(self, aoi_scan_path: GazeFeatures.AOIScanPath):
- assert(len(aoi_scan_path) > 1)
+ assert(len(aoi_scan_path) > 1)
- # Count total number of fixations and how many fixations are there per aoi
- scan_fixations_count, aoi_fixations_count = aoi_scan_path.fixations_count()
+ # Count total number of fixations and how many fixations are there per aoi
+ scan_fixations_count, aoi_fixations_count = aoi_scan_path.fixations_count()
- # Probability to have a fixation onto each aoi
- stationary_probabilities = {aoi: count/scan_fixations_count for aoi, count in aoi_fixations_count.items()}
+ # Probability to have a fixation onto each aoi
+ stationary_probabilities = {aoi: count/scan_fixations_count for aoi, count in aoi_fixations_count.items()}
- # Stationary entropy
- self.__stationary_entropy = 0
+ # Stationary entropy
+ self.__stationary_entropy = 0
- for aoi, p in stationary_probabilities.items():
+ for aoi, p in stationary_probabilities.items():
- self.__stationary_entropy += p * numpy.log(p + 1e-9)
+ self.__stationary_entropy += p * numpy.log(p + 1e-9)
- self.__stationary_entropy *= -1
+ self.__stationary_entropy *= -1
- # Transition entropy
- self.__transition_entropy = 0
+ # Transition entropy
+ self.__transition_entropy = 0
- destination_p_log_sum = self.transition_matrix_analyzer.transition_matrix_probabilities.apply(lambda row: row.apply(lambda p: p * numpy.log(p + 1e-9)).sum(), axis=1)
+ destination_p_log_sum = self.transition_matrix_analyzer.transition_matrix_probabilities.apply(lambda row: row.apply(lambda p: p * numpy.log(p + 1e-9)).sum(), axis=1)
- for aoi, s in destination_p_log_sum.items():
+ for aoi, s in destination_p_log_sum.items():
- self.__transition_entropy += s * stationary_probabilities[aoi]
+ self.__transition_entropy += s * stationary_probabilities[aoi]
- self.__transition_entropy *= -1
+ self.__transition_entropy *= -1
- @property
- def stationary_entropy(self) -> float:
- """Stationary entropy."""
+ @property
+ def stationary_entropy(self) -> float:
+ """Stationary entropy."""
- return self.__stationary_entropy
+ return self.__stationary_entropy
- @property
- def transition_entropy(self) -> float:
- """Transition entropy."""
+ @property
+ def transition_entropy(self) -> float:
+ """Transition entropy."""
- return self.__transition_entropy
- \ No newline at end of file
+ return self.__transition_entropy
+ \ No newline at end of file
diff --git a/src/argaze/GazeFeatures.py b/src/argaze/GazeFeatures.py
index 5ef3c32..4aa65e7 100644
--- a/src/argaze/GazeFeatures.py
+++ b/src/argaze/GazeFeatures.py
@@ -86,10 +86,10 @@ class GazePosition(tuple):
"""Add position.
!!! note
- The returned position precision is the maximal precision.
+ The returned position precision is the maximal precision.
!!! note
- The returned position timestamp is the self object timestamp.
+ The returned position timestamp is the self object timestamp.
"""
if self.__precision is not None and position.precision is not None:
@@ -106,10 +106,10 @@ class GazePosition(tuple):
"""Subtract position.
!!! note
- The returned position precision is the maximal precision.
+ The returned position precision is the maximal precision.
!!! note
- The returned position timestamp is the self object timestamp.
+ The returned position timestamp is the self object timestamp.
"""
if self.__precision is not None and position.precision is not None:
@@ -124,10 +124,10 @@ class GazePosition(tuple):
"""Reversed subtract position.
!!! note
- The returned position precision is the maximal precision.
+ The returned position precision is the maximal precision.
!!! note
- The returned position timestamp is the self object timestamp.
+ The returned position timestamp is the self object timestamp.
"""
if self.__precision is not None and position.precision is not None:
@@ -142,10 +142,10 @@ class GazePosition(tuple):
"""Multiply position by a factor.
!!! note
- The returned position precision is also multiplied by the factor.
+ The returned position precision is also multiplied by the factor.
!!! note
- The returned position timestamp is the self object timestamp.
+ The returned position timestamp is the self object timestamp.
"""
return GazePosition(tuple(numpy.array(self) * factor), precision=self.__precision * factor if self.__precision is not None else None, timestamp=self.timestamp)
@@ -153,10 +153,10 @@ class GazePosition(tuple):
"""divide position by a factor.
!!! note
- The returned position precision is also divided by the factor.
+ The returned position precision is also divided by the factor.
!!! note
- The returned position timestamp is the self object timestamp.
+ The returned position timestamp is the self object timestamp.
"""
return GazePosition(tuple(numpy.array(self) / factor), precision=self.__precision / factor if self.__precision is not None else None, timestamp=self.timestamp)
@@ -164,10 +164,10 @@ class GazePosition(tuple):
"""Power position by a factor.
!!! note
- The returned position precision is also powered by the factor.
+ The returned position precision is also powered by the factor.
!!! note
- The returned position timestamp is the self object timestamp.
+ The returned position timestamp is the self object timestamp.
"""
return GazePosition(tuple(numpy.array(self) ** factor),
precision=self.__precision ** factor if self.__precision is not None else None,
@@ -394,7 +394,7 @@ class GazeMovement(TimeStampedGazePositions):
"""Define abstract gaze movement class as timestamped gaze positions list.
!!! note
- Gaze movement timestamp is always equal to its first position timestamp.
+ Gaze movement timestamp is always equal to its first position timestamp.
Parameters:
positions: timestamp gaze positions.
@@ -578,7 +578,7 @@ class GazeMovementIdentifier(DataFeatures.PipelineStepObject):
"""Identify gaze movement from successive timestamped gaze positions.
!!! warning "Mandatory"
- Each identified gaze movement have to share its first/last gaze position with previous/next gaze movement.
+ Each identified gaze movement have to share its first/last gaze position with previous/next gaze movement.
Parameters:
timestamped_gaze_position: new gaze position from where identification have to be done considering former gaze positions.
@@ -694,7 +694,7 @@ class ScanStep():
last_saccade: a saccade that comes after the previous fixation.
!!! warning
- Scan step have to start by a fixation and then end by a saccade.
+ Scan step have to start by a fixation and then end by a saccade.
"""
def __init__(self, first_fixation: Fixation, last_saccade: Saccade):
@@ -813,7 +813,7 @@ class ScanPath(list):
def append_fixation(self, fixation):
"""Append new fixation to scan path.
!!! warning
- Consecutive fixations are ignored keeping the last fixation"""
+ Consecutive fixations are ignored keeping the last fixation"""
self.__last_fixation = fixation
@@ -925,7 +925,7 @@ class AOIScanStep():
letter: AOI unique letter to ease sequence analysis.
!!! warning
- Aoi scan step have to start by a fixation and then end by a saccade.
+ Aoi scan step have to start by a fixation and then end by a saccade.
"""
def __init__(self, movements: TimeStampedGazeMovements, aoi: str = '', letter: str = ''):
@@ -1014,7 +1014,7 @@ class AOIScanPath(list):
"""Edit list of all expected aoi.
!!! warning
- This will clear the AOIScanPath
+ This will clear the AOIScanPath
"""
# Check expected aoi are not the same as previous ones
@@ -1134,7 +1134,7 @@ class AOIScanPath(list):
"""Append new fixation to aoi scan path and return last new aoi scan step if one have been created.
!!! warning
- It could raise AOIScanStepError
+ It could raise AOIScanStepError
"""
# Replace None aoi by generic OutsideAOI name
diff --git a/src/argaze/__init__.py b/src/argaze/__init__.py
index a07fa93..be4cbfc 100644
--- a/src/argaze/__init__.py
+++ b/src/argaze/__init__.py
@@ -8,7 +8,7 @@ def load(filepath: str) -> any:
Load object from json file.
!!! note
- The directory where json file is will be used as global working directory.
+ The directory where json file is will be used as global working directory.
Parameters:
filepath: path to json file
diff --git a/src/argaze/utils/UtilsFeatures.py b/src/argaze/utils/UtilsFeatures.py
index ff2bee6..23d6b24 100644
--- a/src/argaze/utils/UtilsFeatures.py
+++ b/src/argaze/utils/UtilsFeatures.py
@@ -250,7 +250,7 @@ class FileWriter(DataFeatures.PipelineStepObject):
"""Write data as a new line into file.
!!! note
- Tuple elements are converted into quoted strings separated by separator string.
+ Tuple elements are converted into quoted strings separated by separator string.
"""
# Format list or tuple element into quoted strings
@@ -307,7 +307,7 @@ class FileReader(DataFeatures.PipelineStepObject):
"""Read next data from file.
!!! note
- Quoted strings separated by separator string are converted into tuple elements.
+ Quoted strings separated by separator string are converted into tuple elements.
"""
try:
diff --git a/src/argaze/utils/contexts/TobiiProGlasses2.py b/src/argaze/utils/contexts/TobiiProGlasses2.py
index 80487f4..7f45f32 100644
--- a/src/argaze/utils/contexts/TobiiProGlasses2.py
+++ b/src/argaze/utils/contexts/TobiiProGlasses2.py
@@ -395,7 +395,7 @@ class LiveStream(ArFeatures.LiveProcessingContext):
@property
def configuration(self) -> dict:
- """Patch system configuration dictionary."""
+ """Edit system configuration dictionary."""
return self.__configuration
@configuration.setter
@@ -471,7 +471,7 @@ class LiveStream(ArFeatures.LiveProcessingContext):
"""Bind to a participant or create one if it doesn't exist.
!!! warning
- Bind to a project before.
+ Bind to a project before.
"""
if self.__participant_name is None: