aboutsummaryrefslogtreecommitdiff
path: root/docs/user_guide/gaze_analysis/gaze_movement.md
blob: 83f67e1ab8ed88695cac56af62b8cd19b1379f24 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
Gaze movement
=============

## Definition

!!! note

        *"The act of classifying eye movements into distinct events is, on a general level, driven by a desire to isolate different intervals of the data stream strongly correlated with certain oculomotor or cognitive properties."* 

        Citation from ["One algorithm to rule them all? An evaluation and discussion of ten eye movement event-detection algorithms"](https://link.springer.com/article/10.3758/s13428-016-0738-9) article.

[GazeFeatures](../../argaze.md/#argaze.GazeFeatures) defines abstract [GazeMovement](../../argaze.md/#argaze.GazeFeatures.GazeMovement) class, then abstract [Fixation](../../argaze.md/#argaze.GazeFeatures.Fixation) and [Saccade](../../argaze.md/#argaze.GazeFeatures.Saccade) classes which inherit from [GazeMovement](../../argaze.md/#argaze.GazeFeatures.GazeMovement).

The **positions** [GazeMovement](../../argaze.md/#argaze.GazeFeatures.GazeMovement) attribute contain all [GazePositions](../../argaze.md/#argaze.GazeFeatures.GazePosition) belonging to itself.

![Fixation and Saccade](../../img/fixation_and_saccade.png)

## Identification

[GazeFeatures](../../argaze.md/#argaze.GazeFeatures) defines abstract [GazeMovementIdentifier](../../argaze.md/#argaze.GazeFeatures.GazeMovementIdentifier) classe to let add various identification algorithms.

Some gaze movement identification algorithms are available thanks to [GazeAnalysis](../../argaze.md/#argaze.GazeAnalysis) submodule:

* [Dispersion threshold identification (I-DT)](../../argaze.md/#argaze.GazeAnalysis.DispersionThresholdIdentification)
* [Velocity threshold identification (I-VT)](../../argaze.md/#argaze.GazeAnalysis.VelocityThresholdIdentification)

### Identify method

[GazeMovementIdentifier.identify](../../argaze.md/#argaze.GazeFeatures.GazeMovementIdentifier.identify) method allows to fed its identification algorithm with successive gaze positions to output Fixation, Saccade or any kind of GazeMovement instances.

Here is a sample of code based on [I-DT](../../argaze.md/#argaze.GazeAnalysis.DispersionThresholdIdentification) algorithm to illustrate how to use it:

``` python
from argaze import GazeFeatures
from argaze.GazeAnalysis import DispersionThresholdIdentification

# Create a gaze movement identifier based on dispersion algorithm with 50px max deviation 200 ms max duration thresholds
gaze_movement_identifier = DispersionThresholdIdentification.GazeMovementIdentifier(50, 200)

# Assuming that timestamped gaze positions are provided through live stream or later data reading
...:

        gaze_movement = gaze_movement_identifier.identify(timestamp, gaze_position)

        # Fixation identified
        if GazeFeatures.is_fixation(gaze_movement):

                # Access to first gaze position of identified fixation
                start_ts, start_position = gaze_movement.positions.first

                # Access to fixation duration
                print('duration: {gaze_movement.duration}')

                # Iterate over all gaze positions of identified fixation
                for ts, position in gaze_movement.positions.items():

                        # Do something with each fixation position
                        ...

        # Saccade identified
        elif GazeFeatures.is_saccade(gaze_movement):

                # Access to first gaze position of identified saccade
                start_ts, start_position = gaze_movement.positions.first

                # Access to saccade amplitude
                print('amplitude: {gaze_movement.amplitude}')

                # Iterate over all gaze positions of identified saccade
                for ts, position in gaze_movement.positions.items():

                        # Do something with each saccade position
                        ...

        # No gaze movement identified
        else:

                continue

```

### Browse method

[GazeMovementIdentifier.browse](../../argaze.md/#argaze.GazeFeatures.GazeMovementIdentifier.browse) method allows to pass a [TimeStampedGazePositions](../../argaze.md/#argaze.GazeFeatures.TimeStampedGazePositions) buffer to apply identification algorithm on all gaze positions inside.

Identified gaze movements are returned through:

* [TimeStampedGazeMovements](../../argaze.md/#argaze.GazeFeatures.TimeStampedGazeMovements) instance where all fixations are stored by starting gaze position timestamp.
* [TimeStampedGazeMovements](../../argaze.md/#argaze.GazeFeatures.TimeStampedGazeMovements) instance where all saccades are stored by starting gaze position timestamp.
* [TimeStampedGazeStatus](../../argaze.md/#argaze.GazeFeatures.TimeStampedGazeStatus) instance where all gaze positions are linked to a fixation or saccade index.

``` python
# Assuming that timestamped gaze positions are provided through data reading

ts_fixations, ts_saccades, ts_status = gaze_movement_identifier.browse(ts_gaze_positions)

```

* ts_fixations would look like:

|timestamp|positions                                                     |duration|dispersion|focus    |
|:--------|:-------------------------------------------------------------|:-------|:---------|:--------|
|60034    |{"60034":[846,620], "60044":[837,641], "60054":[835,649], ...}|450     |40        |(840,660)|
|60504    |{"60504":[838,667], "60514":[838,667], "60524":[837,669], ...}|100     |38        |(834,651)|
|...      |...                                                           |...     |..        |...      |

* ts_saccades would look like:

|timestamp|positions                               |duration|
|:--------|:---------------------------------------|:-------|
|60484    |{"60484":[836, 669], "60494":[837, 669]}|10      |
|60594    |{"60594":[833, 613], "60614":[927, 601]}|20      |
|...      |...                                     |...     |

* ts_status would look like:

|timestamp|position  |type    |index|
|:--------|:---------|:-------|:----|
|60034    |(846, 620)|Fixation|1    |
|60044    |(837, 641)|Fixation|1    |
|...      |...       |...     |.    |
|60464    |(836, 668)|Fixation|1    |
|60474    |(836, 668)|Fixation|1    |
|60484    |(836, 669)|Saccade |1    |
|60494    |(837, 669)|Saccade |1    |
|60504    |(838, 667)|Fixation|2    |
|60514    |(838, 667)|Fixation|2    |
|...      |...       |...     |.    |
|60574    |(825, 629)|Fixation|2    |
|60584    |(829, 615)|Fixation|2    |
|60594    |(833, 613)|Saccade |2    |
|60614    |(927, 601)|Saccade |2    |
|60624    |(933, 599)|Fixation|3    |
|60634    |(934, 603)|Fixation|3    |
|...      |...       |...     |.    |


!!! note
        [TimeStampedGazeMovements](../../argaze.md/#argaze.GazeFeatures.TimeStampedGazeMovements), [TimeStampedGazeMovements](../../argaze.md/#argaze.GazeFeatures.TimeStampedGazeMovements) and [TimeStampedGazeStatus](../../argaze.md/#argaze.GazeFeatures.TimeStampedGazeStatus) classes inherit from [TimeStampedBuffer](../../argaze.md/#argaze.DataStructures.TimeStampedBuffer) class. 

        Read [Timestamped data](../timestamped_data/introduction.md) section to understand all features it provides.

### Generator method

[GazeMovementIdentifier](../../argaze.md/#argaze.GazeFeatures.GazeMovementIdentifier) can be called with a [TimeStampedGazePositions](../../argaze.md/#argaze.GazeFeatures.TimeStampedGazePositions) buffer in argument to generate gaze movement each time one is identified.

``` python
# Assuming that timestamped gaze positions are provided through data reading

for ts, gaze_movement in gaze_movement_identifier(ts_gaze_positions):

        # Fixation identified
        if GazeFeatures.is_fixation(gaze_movement):

                # Do something with each fixation
                ...

        # Saccade identified
        elif GazeFeatures.is_saccade(gaze_movement):

                # Do something with each saccade
                ...
```