# 3 Guides

## 3.1 Importing trial data

All sensor data from an `.exp`

file can be imported with a single call to `ImportMotionData()`

from the package `motionImport`

, which automatically scrapes the sensor names from the file header, and assembles the position and rotation data into a nested list.

The function requires a path leading to the `.exp`

file, a numeric vector `timestamps`

giving the start and end times of the recording (in this case, .5 seconds before stimulus onset to 1.5 seconds after), and an optional argument `na.symbol`

(defaulting to `0`

) indicating the symbol used by the motion monitor output to denote missing values, which will be replaced with `NA`

in the resulting R object.

An example:

```
filename <- 'data/MDim1_s1_b1_0000.exp'
data <- ImportMotionData(filename, timestamps = c(-.5, 1.5), na.symbol = 0)
```

The file `data/MDim1_s1_b1_0000.exp`

contains data from a single grasping trial recorded at 60 Hz over a duration of 2 seconds, for a total of 120 samples. Position and rotation data (encoded by a position vector and a unit quaternion, respectively) are recorded at each sample from four sensors (thumb, index, middle finger, and back of hand) on both the right and left hands. The resulting object `data`

is a nested list with the following structure:

```
data
+-- RtmbS2
| +-- Timestamps
| +-- Position
| +-- Rotation
+-- RindexS3
| +-- Timestamps
| +-- Position
| +-- Rotation
+-- RmiddleS4
| +-- Timestamps
| +-- Position
| +-- Rotation
.
.
. etc
```

where `timestamps`

is a numeric vector of length `120`

with timestamps for each sample, `Position`

is a `120 x 3`

matrix of Cartesian coordinates denoting the position of the sensor, and `Rotation`

is a `120 x 4`

matrix of unit quaternions, denoting it’s orientation.

## 3.2 `Seacurve`

trajectories

The next step is to represent the position and rotation of the sensor as a single trajectory in SE(3), represented by a sequence of dual quaternions. The object is represented as an object of S3 class `Seacurve`

, though this is mostly just for type checking, and no S3 methods are currently implementd for the class. Creating a Seacurve object requires a `time x 3`

matrix giving the Cartesian coordinates of the object at each time step, and a `time x 4`

matrix of unit quaternions. Optionally, the user can also provide a vector of time stamps.

We can create a Seacurve object for the data from the right thumb as follows:

```
x <- sc.posrot2dq(v = data$RtmbS2$Position,
q = data$RtmbS2$Rotation,
timestamps = data$RtmbS2$Timestamps)
```

We can easily recover the original position and rotation data using `sc.dq2position()`

and `sc.dq2rotation()`

`head(sc.dq2position(x))`

```
## Time x y z
## [1,] -0.5000000 0.019186 0.09589 0.041145
## [2,] -0.4831933 0.019242 0.09589 0.041145
## [3,] -0.4663866 NA NA NA
## [4,] -0.4495798 0.019242 0.09589 0.041145
## [5,] -0.4327731 NA NA NA
## [6,] -0.4159664 0.019242 0.09589 0.041145
```

`head(sc.dq2rotation(x))`

```
## Time q0 q1 q2 q3
## [1,] -0.5000000 0.6652548 0.4429709 -0.3889559 0.4581769
## [2,] -0.4831933 0.6651109 0.4431029 -0.3889339 0.4582769
## [3,] -0.4663866 NA NA NA NA
## [4,] -0.4495798 0.6651000 0.4430970 -0.3889980 0.4582440
## [5,] -0.4327731 NA NA NA NA
## [6,] -0.4159664 0.6651000 0.4430970 -0.3889980 0.4582440
```

`Seacurve`

also implements some basic plotting functions. We can view position and rotation data separately using

`sc.plot(x, 'Position')`

`## Warning: Removed 24 rows containing missing values (geom_point).`

`sc.plot(x, 'Rotation')`

`## Warning: Removed 32 rows containing missing values (geom_point).`

or the full dual quaternion data

`sc.plot(x, 'DQ')`

`## Warning: Removed 64 rows containing missing values (geom_point).`

## 3.3 Orienting the trajectory

Notice the discontinuities in the rotation and dual quaternion plots. Because antipodal quaternions (and dual quaternions) encode identical transformations, we must choose a consistent orientation for the data in order to guarantee smooth trajectories. The `sc.orient()`

method accomplishes this by orienting the initial observation to have positive first coordinate, and then sequentially reorienting each observation to maximize smoothness. This is easy to do:

`x <- sc.orient(x)`

After which we can plot the rotation component again and see that the discontinuities have been removed:

`sc.plot(x, 'Rotation')`

`## Warning: Removed 32 rows containing missing values (geom_point).`

Note that it is essential to orient the trajectory BEFORE performing subsequent preprocessing, as discontinuities in the trajectory can severely impact procedures like interpolation.

## 3.4 Outlier removal

Because the sensor recordings are generally highly accurate (relative to the movement), trajectories don’t often have “outliers” of the kind often seen in noisy data. Instead, we’re mostly concerned with isolated sensor errors in which a single time point may be recorded incorrectly. This most often takes the form of a sensor appearing to teleport to another location for a single sample. To emphasize this goal (removing sensor errors, specifically), we’ll call this procedure “deteleporting”. These “teleports” are generally very apparent on visual inspection, but we would still like an automated method to remove them.

The approach taken by `seacurve`

(which will likely change in the future) uses a local, iterative cubic regression to identify and remove outlying datapoints. The model is applied not to the dual quaternion trajectory directly, but to the speed of the sensor (the norm of it’s positional velocity vector, normalized by the elapsed time). We start by choosing a window `win`

and a threshold `thresh`

in the interval `(0,1)`

. The trajectory is then partitioned in `win`

blocks of equal size, and a model

`y = f(time) + e`

is fit to each, where `f`

is a cubic polynomial. For each observation, we then compute the Cook’s distance (the effect of removing that observation from the model), and reject those observations with a quantile greater than `1 - thresh`

. Effectively, we perform a one-tailed z-test with threshold `thresh`

.

To see this at work, we can create a new trajectory where the position of the sensor has been perturbed at a single time point.

```
# Perturb single observation
v <- data$RtmbS2$Position
v[55,] <- v[55,] + c(-.2,.21,.2)
# Convert to seacurve and reorient
x <- sc.posrot2dq(v = v, q = data$RtmbS2$Rotation,
timestamps = data$RtmbS2$Timestamps)
x <- sc.orient(x)
sc.plot(x, 'Position')
```

`## Warning: Removed 24 rows containing missing values (geom_point).`

Now, we perform the above outlier removal procedure using `win = 5`

windows and a threshold of `thresh = .05`

:

```
x <- sc.deteleport(x, win = 5, thresh = .05)
sc.plot(x, 'Position')
```

`## Warning: Removed 48 rows containing missing values (geom_point).`

## 3.5 Interpolation

`Seathree`

performs interpolation directly on the dual quaternion trajectory using dual linear blending (DLB). Effectively, it linearly interpolates between adjacent dual quaternions, and then normalizes the result to have norm equal to 1. Although more accurate forms of interpolation exist, motion trajectories are generally sampled at very high temporal resolution, and so the distortions introduced by this form of interpolation are minimal.

The function `sc.interpolate()`

works in three stages: First, any missing observations at the beginning of the trajectory are set to the earliest available observation. Second, any missing values as the end of the trajectory are set to the latest available observations. Finally, any intermediate missing values are interpolated using DLB. To see this at work:

```
x <- sc.interpolate(x)
sc.plot(x, 'Position')
```

If the trajectory does not contain any observations, the function will return an error.

## 3.6 Preprocessing

For convenience, `Seacurve`

contains a wrapper function `sc.process()`

which accepts an argument `sensor`

, containing a list with elements `(v, q, timestamps)`

to be passed to `sc.posrot2dq()`

, and arguments `win`

and `thresh`

to be passed to `sc.deteleport()`

. The function then applies

`sc.posrot2dq(v,q,timestamps) -> sc.orient() -> sc.deteleport(..., win, thresh) -> sc.interpolate()`

before finally renormalizing each observation (in case of numerical error), and returning a processed `Seacurve`

object. For example:

```
filename <- 'data/MDim1_s1_b1_0000.exp'
data <- ImportMotionData(filename, timestamps = c(-.5, 1.5), na.symbol = 0)
x <- sc.process(data$RtmbS2, win = 5, thresh = .05)
sc.plot(x, 'DQ')
```

The `sc.process()`

function is fairly inflexible, since it’s designed to work with the way my own data are generally formated, and my own preprocessing pipeline, so it may be worthwhile to design your own wrapper if your needs are substantially different. In the event of an error during preprocessing, the function will return a `Seacurve`

object containing all missing (NA) values, which may also be undesirable depending on your workflow.