aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorThéo de la Hogue2022-12-10 23:37:44 +0100
committerThéo de la Hogue2022-12-10 23:37:44 +0100
commit47836903ccd6dbb91baf340777f1ddf9d73e8cb6 (patch)
treeeab761ec2a678bb225c985e33cf67105d611fc4a /src
parent93816a53267993a89163a5bf6985d33fb1faa93d (diff)
downloadargaze-47836903ccd6dbb91baf340777f1ddf9d73e8cb6.zip
argaze-47836903ccd6dbb91baf340777f1ddf9d73e8cb6.tar.gz
argaze-47836903ccd6dbb91baf340777f1ddf9d73e8cb6.tar.bz2
argaze-47836903ccd6dbb91baf340777f1ddf9d73e8cb6.tar.xz
Refactoring movement identification.
Diffstat (limited to 'src')
-rw-r--r--src/argaze/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py207
1 files changed, 114 insertions, 93 deletions
diff --git a/src/argaze/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py b/src/argaze/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py
index 62e8723..9373c4f 100644
--- a/src/argaze/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py
+++ b/src/argaze/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py
@@ -105,95 +105,91 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier):
# While there are 2 gaze positions at least
while len(self.__ts_gaze_positions) >= 2:
- # Copy remaining gaze positions
- remaining_gaze_positions = self.__ts_gaze_positions.copy()
+ # Remove all unvalid gaze positions until to find a valid one
+ ts_current, gaze_position_current = self.__ts_gaze_positions.pop_first()
- # Select gaze position until a duration threshold
- ts_start, gaze_position_start = remaining_gaze_positions.pop_first()
-
- # Ignore and remove non valid start positions
- while not gaze_position_start.valid:
-
- ts_start, gaze_position_start = remaining_gaze_positions.pop_first()
- self.__ts_gaze_positions.pop_first()
+ while not gaze_position_current.valid and len(self.__ts_gaze_positions) > 0:
+ ts_current, gaze_position_current = self.__ts_gaze_positions.pop_first()
+ # Select current and next valid gaze positions until a duration threshold
valid_gaze_positions = GazeFeatures.TimeStampedGazePositions()
- valid_gaze_positions[ts_start] = gaze_position_start
+ valid_gaze_positions[ts_current] = gaze_position_current
+ # Store unvalid gaze positions to count them
unvalid_gaze_positions = GazeFeatures.TimeStampedGazePositions()
- # Select next position
- ts_next, gaze_position_next = remaining_gaze_positions.first
+ for ts_next, gaze_position_next in self.__ts_gaze_positions.items():
- while (ts_next - ts_start) < self.duration_threshold:
+ if (ts_next - ts_current) < self.duration_threshold:
- # Store valid position
- if gaze_position_next.valid:
+ # Store valid position
+ if gaze_position_next.valid:
- ts, valid_gaze_position = remaining_gaze_positions.pop_first()
- valid_gaze_positions[ts] = valid_gaze_position
+ valid_gaze_positions[ts_next] = gaze_position_next
- # Store non valid position
- else:
+ # Store non valid position
+ else:
- ts, unvalid_gaze_position = remaining_gaze_positions.pop_first()
- unvalid_gaze_positions[ts] = unvalid_gaze_position
+ unvalid_gaze_positions[ts_next] = gaze_position_next
- try:
- # Read next position
- ts_next, gaze_position_next = remaining_gaze_positions.first
+ else:
- except:
break
- # Consider the last valid gaze positions as a new fixation
- new_fixation = Fixation(valid_gaze_positions)
+ # If there is at least 2 valid gaze positions selected
+ if len(valid_gaze_positions) >= 2:
- # Dispersion is small : extending fixation
- if new_fixation.dispersion <= self.dispersion_threshold:
+ # Consider selected valid gaze positions as part of a maybe new fixation
+ new_fixation = Fixation(valid_gaze_positions)
- # Remove valid gaze positions
- for ts, gp in valid_gaze_positions.items():
- self.__ts_gaze_positions.pop(ts)
+ # Dispersion small enough: it is a fixation ! Try to extend it
+ if new_fixation.dispersion <= self.dispersion_threshold:
- # Remove unvalid gaze positions
- for ts, gp in unvalid_gaze_positions.items():
- self.__ts_gaze_positions.pop(ts)
+ # Remove valid and unvalid gaze positions as there as now stored in new fixation
+ # -1 as current gaze position have already been poped
+ for _ in range(len(valid_gaze_positions) + len(unvalid_gaze_positions) - 1):
+ self.__ts_gaze_positions.pop_first()
- # Copy new fixation positions before to try to extend them
- extended_gaze_positions = new_fixation.positions.copy()
+ # Copy new fixation positions before to try to extend them
+ extended_gaze_positions = new_fixation.positions.copy()
- # Are next gaze positions not too dispersed ?
- while len(remaining_gaze_positions) > 0:
+ # Are next gaze positions not too dispersed ?
+ while len(self.__ts_gaze_positions) > 0:
- # Select next gaze position
- ts_next, gaze_position_next = remaining_gaze_positions.first
+ # Select next gaze position
+ ts_next, gaze_position_next = self.__ts_gaze_positions.first
- # Ignore and remove non valid next positions
- if not gaze_position_next.valid:
+ # Consider only valid next position
+ if gaze_position_next.valid:
- ts_next, gaze_position_next = remaining_gaze_positions.pop_first()
- self.__ts_gaze_positions.pop_first()
- continue
+ extended_gaze_positions[ts_next] = gaze_position_next
- extended_gaze_positions[ts_next] = gaze_position_next
+ # How much extended fixation is dispersed ?
+ extended_fixation = Fixation(extended_gaze_positions)
- # how much gaze is dispersed ?
- extended_fixation = Fixation(extended_gaze_positions)
+ # Dispersion is still small enough : continue
+ if extended_fixation.dispersion < self.dispersion_threshold:
+
+ # Remove selected gaze position
+ self.__ts_gaze_positions.pop_first()
+
+ continue
+
+ # Dispersion is too wide : break
+ else:
- # dispersion becomes too wide : ignore extended fixation
- if extended_fixation.dispersion > self.dispersion_threshold:
- break
+ # Remove last extended gaze position
+ extended_gaze_positions.pop_last()
- # update new fixation
- new_fixation = Fixation(extended_gaze_positions.copy())
+ break
- # remove selected gaze position
- remaining_gaze_positions.pop_first()
- self.__ts_gaze_positions.pop_first()
+ # Remove non valid positions and continue
+ else:
+
+ self.__ts_gaze_positions.pop_first()
- # Is the new fixation have a duration ?
- if new_fixation.duration > 0:
+ # Update new fixation
+ new_fixation = Fixation(extended_gaze_positions)
# Does a former fixation have been identified ?
if self.__last_fixation != None:
@@ -207,48 +203,69 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier):
# Else output last fixation and consider moving gaze positions buffer
else:
- yield self.__last_fixation
+ # Saccade happens between last and new fixations
+ last_ts, _ = self.__last_fixation.positions.last
+ new_ts, _ = new_fixation.positions.first
- # Is there a new movement ?
- if len(moving_gaze_positions) > 0:
+ # Saccade shouldn't be longer than fixation
+ # TODO : add a saccade duration threshold attribute ?
+ if new_ts - last_ts <= self.duration_threshold:
- # Compare first and last gaze position of the moving positions buffer to last and new fixation
- start_position_ts, start_position = moving_gaze_positions.first
- end_position_ts, end_position = moving_gaze_positions.last
+ # Edit saccade gaze positions
+ saccade_gaze_positions = GazeFeatures.TimeStampedGazePositions()
+
+ # Edit first saccade gaze position
+ last_ts, last_position = self.__last_fixation.positions.pop_last()
+ saccade_gaze_positions[last_ts] = last_position
+
+ # Is there unmatched gaze positions that belong to the saccade?
+ if len(moving_gaze_positions) > 0:
+
+ start_position_ts, start_position = moving_gaze_positions.first
+ end_position_ts, end_position = moving_gaze_positions.last
- last_ts, last_position = self.__last_fixation.positions.last
- new_ts, new_position = new_fixation.positions.first
+ if start_position_ts > last_ts and end_position_ts < new_ts:
- # Saccade conditions:
- # - should be between two fixations
- # - shouldn't be longer than fixation
- # TODO : add a saccade duration threshold attribute ?
- if start_position_ts > last_ts and end_position_ts < new_ts and last_ts - new_ts <= self.duration_threshold:
+ # Append unmatched gaze positions to saccade
+ saccade_gaze_positions.append(moving_gaze_positions)
- saccade_gaze_positions = GazeFeatures.TimeStampedGazePositions()
- saccade_gaze_positions[last_ts] = last_position
- saccade_gaze_positions.append(moving_gaze_positions)
- saccade_gaze_positions[new_ts] = new_position
+ # Edit last saccade gaze psoition
+ new_ts, new_position = new_fixation.positions.pop_first()
+ saccade_gaze_positions[new_ts] = new_position
+
+ # Output last fixation
+ yield self.__last_fixation
- yield Saccade(saccade_gaze_positions)
+ # Output saccade
+ yield Saccade(saccade_gaze_positions)
- # Otherwise, this movement is unknown
- else:
+ # Too much time between fixations: no saccade
+ # But if there are unmatched gaze positions, this movement is unknown
+ elif len(moving_gaze_positions) > 0:
- # Does this unknown movement an unmatched fixation ?
- unmatched_fixation = Fixation(moving_gaze_positions)
+ # Is this unknown movement happened between the last and the new fixation ?
+ start_position_ts, start_position = moving_gaze_positions.first
+ end_position_ts, end_position = moving_gaze_positions.last
+
+ if start_position_ts > last_ts and end_position_ts < new_ts:
- if unmatched_fixation.dispersion < self.dispersion_threshold:
+ # Output last fixation
+ yield self.__last_fixation
- yield unmatched_fixation
+ # Output unknown movement
+ yield UnknownGazeMovement(moving_gaze_positions)
- else:
+ # The unknown movement happened before last fixation
+ else:
- yield UnknownGazeMovement(moving_gaze_positions)
+ # QUESTION: What to do in this case?
+ # GazeMovementIdentifier have to output movements according their time apparition
+ pass
- # Forget former moving gaze positions
- moving_gaze_positions = GazeFeatures.TimeStampedGazePositions()
+ # Forget former moving gaze positions
+ moving_gaze_positions = GazeFeatures.TimeStampedGazePositions()
+ # New fixation becomes the last fixation to allow further merging
self.__last_fixation = new_fixation
else:
@@ -259,13 +276,17 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier):
# Forget former moving gaze positions
moving_gaze_positions = GazeFeatures.TimeStampedGazePositions()
- # Dispersion too wide :
- # - This gaze position is part of a movement but not a fixation
- # - Consider next gaze position
+ # Dispersion too wide:
+ # Current gaze position is not part of a fixation
+ else:
+
+ moving_gaze_positions[ts_current] = gaze_position_current
+
+ # Only one valid gaze position selected:
+ # Current gaze position is not part of a fixation
else:
- ts_moving, moving_gaze_position = self.__ts_gaze_positions.pop_first()
- moving_gaze_positions[ts_moving] = moving_gaze_position
+ moving_gaze_positions[ts_current] = gaze_position_current
# Output last fixation
if self.__last_fixation != None: