pathviewR includes functions that estimate visual perceptions based on the distance between the subject/observer and visual stimuli on the walls of the experimental tunnel.

Loading packages

We’ll need to start by loading pathviewR as well as a couple tidyverse packages for visualizing the data.

Data preparation

Data objects must be prepared as described in the Data Import and Cleaning vignette pipeline prior to their use in these functions. For a detailed description of these functions, please see the linked vignette.

Let’s work with a few example datasets included in pathviewR. pathviewR_motive_example_data.csv is a .csv file exported from Motive. pathviewR_flydra_example_data.mat is a .mat file exported from Flydra. pathviewR contains an all-in-one cleaning function clean_viewr for more coarse-grained data cleaning tasks. We will use this function in the examples below.

## motive data set
motive_data <- # import
  read_motive_csv(
    system.file("extdata", "pathviewR_motive_example_data.csv",
                package = 'pathviewR')
  )

motive_full <-
  motive_data %>%
  clean_viewr(
    relabel_viewr_axes = TRUE,
    gather_tunnel_data = TRUE,
    trim_tunnel_outliers = TRUE,
    standardization_option = "rotate_tunnel",
    select_x_percent = TRUE,
    desired_percent = 50,
    rename_viewr_characters = FALSE,
    separate_trajectories = TRUE,
    max_frame_gap = "autodetect",
    get_full_trajectories = TRUE,
    span = 0.95
  )
#> autodetect is an experimental feature -- please report issues.
## flydra data set
flydra_data <- # import
  read_flydra_mat(
    system.file("extdata", "pathviewR_flydra_example_data.mat",
                package = 'pathviewR'),
    subject_name = "birdie_wooster")

flydra_full <- # clean
  flydra_data %>%
  clean_viewr(
    relabel_viewr_axes = FALSE,
    gather_tunnel_data = FALSE,
    trim_tunnel_outliers = FALSE,
    standardization_option = "redefine_tunnel_center",
    length_method = "middle",
    height_method = "user-defined",
    height_zero = 1.44,
    get_velocity = FALSE,
    select_x_percent = TRUE,
    rename_viewr_characters = FALSE,
    separate_trajectories = TRUE,
    get_full_trajectories = TRUE
  )

Add experiment information with insert_treatments()

Now that our objects have been cleaned, we will use insert_treatments() to add information about the experiments that are necessary for calculating visual perceptions.

Motive example

The data within motive_full were collected from birds flying through a V-shaped tunnel in which the origin (0,0,0) was set to the height of the perches that sit 0.3855m above the height of the vertex. The lateral walls were angled ±45˚ from the vertical axis. The visual stimulus on the positive side of the tunnel (where position_width values > 0) were horizontal sine wave gratings with a cycle length of 0.1m on the screen. The visual stimulus on the negative side of the tunnel (where position_width values < 0) were vertical sine wave gratings with a cycle length of 0.2m on the screen.

Therefore we will use the following code:

motive_V <- 
  motive_full %>% 
  insert_treatments(vertex_height = -0.3855,
                    vertex_angle = 45,
                    stim_param_pos = 0.1,
                    stim_param_neg = 0.2,
                    treatment = "latB")

Our object now has the variables, vertex_height, vertex_angle, stim_param_pos, and stim_param_neg which are needed to calculate visual perceptions. The variable treatment has also been included and this information has been stored in the object’s metadata.

motive_V
#> # A tibble: 449 x 29
#>    vertex_height vertex_angle stim_param_pos stim_param_neg treatment frame
#>            <dbl>        <dbl>          <dbl>          <dbl> <chr>     <int>
#>  1        -0.386        0.785            0.1            0.2 latB      72213
#>  2        -0.386        0.785            0.1            0.2 latB      72214
#>  3        -0.386        0.785            0.1            0.2 latB      72215
#>  4        -0.386        0.785            0.1            0.2 latB      72216
#>  5        -0.386        0.785            0.1            0.2 latB      72217
#>  6        -0.386        0.785            0.1            0.2 latB      72218
#>  7        -0.386        0.785            0.1            0.2 latB      72219
#>  8        -0.386        0.785            0.1            0.2 latB      72220
#>  9        -0.386        0.785            0.1            0.2 latB      72221
#> 10        -0.386        0.785            0.1            0.2 latB      72222
#> # … with 439 more rows, and 23 more variables: time_sec <dbl>, subject <chr>,
#> #   position_length <dbl>, position_width <dbl>, position_height <dbl>,
#> #   rotation_length <dbl>, rotation_width <dbl>, rotation_height <dbl>,
#> #   rotation_real <dbl>, mean_marker_error <dbl>, velocity <dbl>,
#> #   length_inst_vel <dbl>, width_inst_vel <dbl>, height_inst_vel <dbl>,
#> #   traj_id <int>, file_sub_traj <chr>, traj_length <int>, start_length <dbl>,
#> #   end_length <dbl>, length_diff <dbl>, start_length_sign <dbl>,
#> #   end_length_sign <dbl>, direction <chr>

Flydra example

The data within flydra_full were collected from birds flying in a rectangular tunnel i.e. a box where the positive, negative, and front walls were 0.5m from the origin. The visual stimuli were the same as in the motive example, though now with a stimulus on the front wall of 0.2m cycle length.

flydra_box <- # prep for calculations
  flydra_full %>%
  insert_treatments(pos_wall = 0.5,
                    neg_wall = 0.5,
                    front_wall = 0.5,
                    stim_param_pos = 0.1,
                    stim_param_neg = 0.2,
                    stim_param_front = 0.2,
                    treatment = "latB")

Calculating spatial frequency

To calculate the spatial frequency of the visual stimuli as perceived by the subject some distance from the stimuli, we will use calc_sf_V() and calc_sf_box depending on the configuration of the tunnel for each experiment.

motive_V_sf <- 
  motive_V %>%
  calc_sf_V(simplify_output = FALSE)

## The resulting object contains 10 new variables which are values involved in 
## the calculation of spatial frequency.
motive_V_sf
#> # A tibble: 449 x 39
#>    vertex_height vertex_angle stim_param_pos stim_param_neg treatment frame
#>            <dbl>        <dbl>          <dbl>          <dbl> <chr>     <int>
#>  1        -0.386        0.785            0.1            0.2 latB      72213
#>  2        -0.386        0.785            0.1            0.2 latB      72214
#>  3        -0.386        0.785            0.1            0.2 latB      72215
#>  4        -0.386        0.785            0.1            0.2 latB      72216
#>  5        -0.386        0.785            0.1            0.2 latB      72217
#>  6        -0.386        0.785            0.1            0.2 latB      72218
#>  7        -0.386        0.785            0.1            0.2 latB      72219
#>  8        -0.386        0.785            0.1            0.2 latB      72220
#>  9        -0.386        0.785            0.1            0.2 latB      72221
#> 10        -0.386        0.785            0.1            0.2 latB      72222
#> # … with 439 more rows, and 33 more variables: time_sec <dbl>, subject <chr>,
#> #   position_length <dbl>, position_width <dbl>, position_height <dbl>,
#> #   rotation_length <dbl>, rotation_width <dbl>, rotation_height <dbl>,
#> #   rotation_real <dbl>, mean_marker_error <dbl>, velocity <dbl>,
#> #   length_inst_vel <dbl>, width_inst_vel <dbl>, height_inst_vel <dbl>,
#> #   traj_id <int>, file_sub_traj <chr>, traj_length <int>, start_length <dbl>,
#> #   end_length <dbl>, length_diff <dbl>, start_length_sign <dbl>,
#> #   end_length_sign <dbl>, direction <chr>, height_2_vertex <dbl>,
#> #   height_2_screen <dbl>, width_2_screen_pos <dbl>, width_2_screen_neg <dbl>,
#> #   bound_pos <dbl>, bound_neg <dbl>, min_dist_pos <dbl>, min_dist_neg <dbl>,
#> #   sf_pos <dbl>, sf_neg <dbl>
flydra_box_sf <- 
  flydra_box %>% 
  calc_sf_box()
flydra_box_sf
#> # A tibble: 133 x 26
#>    pos_wall neg_wall front_wall stim_param_pos stim_param_neg stim_param_front
#>       <dbl>    <dbl>      <dbl>          <dbl>          <dbl>            <dbl>
#>  1      0.5      0.5        0.5            0.1            0.2              0.2
#>  2      0.5      0.5        0.5            0.1            0.2              0.2
#>  3      0.5      0.5        0.5            0.1            0.2              0.2
#>  4      0.5      0.5        0.5            0.1            0.2              0.2
#>  5      0.5      0.5        0.5            0.1            0.2              0.2
#>  6      0.5      0.5        0.5            0.1            0.2              0.2
#>  7      0.5      0.5        0.5            0.1            0.2              0.2
#>  8      0.5      0.5        0.5            0.1            0.2              0.2
#>  9      0.5      0.5        0.5            0.1            0.2              0.2
#> 10      0.5      0.5        0.5            0.1            0.2              0.2
#> # … with 123 more rows, and 20 more variables: treatment <chr>, frame <dbl>,
#> #   time_sec <dbl>, subject <chr>, position_length <dbl>, position_width <dbl>,
#> #   position_height <dbl>, traj_id <int>, file_sub_traj <chr>,
#> #   traj_length <int>, start_length <dbl>, end_length <dbl>, length_diff <dbl>,
#> #   start_length_sign <dbl>, end_length_sign <dbl>, direction <chr>,
#> #   min_dist_pos <dbl>, min_dist_neg <dbl>, sf_pos <dbl>, sf_neg <dbl>

simplify_output = TRUE returns an object containing the 4 new variables min_dist_pos, mind_dist_neg, sf_pos, sf_neg. Note for calc_sf_box, there is no need for a simplify_output argument.

Calculating visual angles

To calculate an estimation of the visual angles perceived by the subject, we will use calc_vis_angle_V and not calc_vis_angle_box because the data was collected in a V-shaped tunnel.

motive_V_angle <- 
  motive_V %>% 
  calc_vis_angle_V(simplify_output=FALSE)

## The resulting object contains 12 new variables which are values involved in the calculation of visual angles. 
motive_V_angle 
#> # A tibble: 449 x 41
#>    vertex_height vertex_angle stim_param_pos stim_param_neg treatment frame
#>            <dbl>        <dbl>          <dbl>          <dbl> <chr>     <int>
#>  1        -0.386        0.785            0.1            0.2 latB      72213
#>  2        -0.386        0.785            0.1            0.2 latB      72214
#>  3        -0.386        0.785            0.1            0.2 latB      72215
#>  4        -0.386        0.785            0.1            0.2 latB      72216
#>  5        -0.386        0.785            0.1            0.2 latB      72217
#>  6        -0.386        0.785            0.1            0.2 latB      72218
#>  7        -0.386        0.785            0.1            0.2 latB      72219
#>  8        -0.386        0.785            0.1            0.2 latB      72220
#>  9        -0.386        0.785            0.1            0.2 latB      72221
#> 10        -0.386        0.785            0.1            0.2 latB      72222
#> # … with 439 more rows, and 35 more variables: time_sec <dbl>, subject <chr>,
#> #   position_length <dbl>, position_width <dbl>, position_height <dbl>,
#> #   rotation_length <dbl>, rotation_width <dbl>, rotation_height <dbl>,
#> #   rotation_real <dbl>, mean_marker_error <dbl>, velocity <dbl>,
#> #   length_inst_vel <dbl>, width_inst_vel <dbl>, height_inst_vel <dbl>,
#> #   traj_id <int>, file_sub_traj <chr>, traj_length <int>, start_length <dbl>,
#> #   end_length <dbl>, length_diff <dbl>, start_length_sign <dbl>,
#> #   end_length_sign <dbl>, direction <chr>, height_2_vertex <dbl>,
#> #   height_2_screen <dbl>, width_2_screen_pos <dbl>, width_2_screen_neg <dbl>,
#> #   min_dist_pos <dbl>, min_dist_neg <dbl>, bound_pos <dbl>, bound_neg <dbl>,
#> #   vis_angle_pos_rad <dbl>, vis_angle_neg_rad <dbl>, vis_angle_pos_deg <dbl>,
#> #   vis_angle_neg_deg <dbl>
flydra_box_angle <- 
  flydra_box %>% 
  calc_vis_angle_box()

simplify_output=TRUE returns an object containing the 4 new variables min_dist_pos, min_dist_neg, vis_angle_pos_deg, and vis_angle_neg_deg.

Visualizing the calculations

Visualizing the calculations provides an more intuitive understanding of how these visual perceptions change as the subject moves throughout the tunnel. Note: The axes of the following examples differ between the motive and flydra data sets. These plots were generated to best visualize the perceptual calculations

Spatial frequency

Motive

ggplot(motive_V_sf, aes(x = position_width, y = position_height)) +
  geom_point(aes(color = sf_pos), shape=1, size=3) +
  coord_fixed() +
  geom_segment(aes(x = 0,         # dimensions of the positive wall
                  y = -0.3855,
                  xend = 0.5869,
                  yend = 0.2014)) +
  geom_segment(aes(x = 0,         # dimensions of the negative wall
                   y = -0.3855,
                   xend = -0.5869,
                   yend = 0.2014))

We can see that as the position of the subject is closer to the right (positive) wall, the perception of that stimulus is with smaller spatial frequency.

ggplot(motive_V_sf, aes(x = position_width, y = position_height)) +
  geom_point(aes(color = sf_neg), shape=1, size=3) +
  coord_fixed() +
  geom_segment(aes(x = 0,         # dimensions of the positive wall
                  y = -0.3855,
                  xend = 0.5869,
                  yend = 0.2014)) +
  geom_segment(aes(x = 0,         # dimensions of the negative wall
                   y = -0.3855,
                   xend = -0.5869,
                   yend = 0.2014))

A different pattern is found by visualizing the spatial frequencies observed from the left (negative) wall.

Flydra

ggplot(flydra_box_sf, aes(x = position_width, y = position_length)) +
  geom_point(aes(colour = sf_pos), size = 2) + 
  coord_fixed() +
  geom_segment(aes(x = -0.5,        # negative wall
                   y = -0.5,
                   xend = -0.5,
                   yend = 0.5)) +
  geom_segment(aes(x = 0.5,         # positive wall
                   y = -0.5,
                   xend = 0.5,
                   yend = 0.5))

ggplot(flydra_box_sf, aes(x = position_width, y = position_length)) +
  geom_point(aes(colour = sf_neg), size = 2) + 
  coord_fixed() +
  geom_segment(aes(x = -0.5,        # negative wall
                   y = -0.5,
                   xend = -0.5,
                   yend = 0.5)) +
  geom_segment(aes(x = 0.5,         # positive wall
                   y = -0.5,
                   xend = 0.5,
                   yend = 0.5))

Visual angles

Motive

ggplot(motive_V_angle, aes(x = position_width, y = position_height)) +
  geom_point(aes(color = vis_angle_pos_deg), shape=1, size=3) +
  coord_fixed() +
  geom_segment(aes(x = 0,         # dimensions of the positive wall
                  y = -0.3855,
                  xend = 0.5869,
                  yend = 0.2014)) +
  geom_segment(aes(x = 0,         # dimensions of the negative wall
                   y = -0.3855,
                   xend = -0.5869,
                   yend = 0.2014))

By displaying the visual angles (in degrees) created by the stimulus on the positive wall, we can see that the subject perceives larger visual angles the closer it gets to the positive wall.

Now displaying visual angles perceived on the negative wall.

ggplot(motive_V_angle, aes(x = position_width, y = position_height)) +
  geom_point(aes(color = vis_angle_neg_deg), shape=1, size=3) +
  coord_fixed() +
  geom_segment(aes(x = 0,         # dimensions of the positive wall
                  y = -0.3855,
                  xend = 0.5869,
                  yend = 0.2014)) +
  geom_segment(aes(x = 0,         # dimensions of the negative wall
                   y = -0.3855,
                   xend = -0.5869,
                   yend = 0.2014))

This shows that the subject perceives larger visual angles the closer it gets to the negative wall.

Flydra

ggplot(flydra_box_angle, aes(x = position_width, y = position_length)) +
  geom_point(aes(colour = vis_angle_pos_deg), size = 2) + 
  coord_fixed() +
  geom_segment(aes(x = -0.5,        # negative wall
                   y = -0.5,
                   xend = -0.5,
                   yend = 0.5)) +
  geom_segment(aes(x = 0.5,         # positive wall
                   y = -0.5,
                   xend = 0.5,
                   yend = 0.5))

ggplot(flydra_box_angle, aes(x = position_width, y = position_length)) +
  geom_point(aes(colour = vis_angle_neg_deg), size = 2) + 
  coord_fixed() +
  geom_segment(aes(x = -0.5,        # negative wall
                   y = -0.5,
                   xend = -0.5,
                   yend = 0.5)) +
  geom_segment(aes(x = 0.5,         # positive wall
                   y = -0.5,
                   xend = 0.5,
                   yend = 0.5))