Background

The visualFields package is used for statistical analysis of data from standard automated perimetry. This tutorial shows how to export visual field data from the EyeSuite software into a csv file and how to use the loadoctopus function of the visualFields package to import these data.

Our version of Eyesuite, installed in Germany, uses a latin1-encoding and the date format “%d.%m.%Y”. I am not sure, if this is handled differently in other installation and would be grateful for feedback in this regard.

Problems may arise when semicolons are used in the “note” field in Eyesuite. We have trained our technicians to avoid this, but it has to be kept in mind when problems arise.

A. Setting up export in EyeSuite

First, you need to create an export button in EyeSuite.

Identify the path to the export template in the visualFields package

For this, you need to find the path to the export template that is included in the visualFields package. Currently, the export template is not included in the CRAN version of visualFields, yet. Please install the package as follows. In R, enter the following command (visualFields package needs to be installed):

# Until the version on CRAN is updated, download the visualField package as follows.
library(devtools)
## Lade nötiges Paket: usethis
install_github("imarinfr/vf1/source")
## Skipping install of 'visualFields' from a github remote, the SHA1 (55e674f0) has not changed since last install.
##   Use `force = TRUE` to force installation
library(visualFields)

# Get the file
print(system.file("octopus_template.txt", package="visualFields"))

Create the export button in Eyesuite

In Eyesuite, go to “Preferences -> Perimetry -> Exports”. Use the “+” tab for creating a new export button.

  • Activation
    • Select “Manual” for Mode
    • Pick a “Name” that will be displayed as a tool tip if you hover over the button
    • Optionally: select an icon for the button (you may wish to download the R logo as a png from “https://www.r-project.org/logo”)
  • Export file
    • Format: “EyeSuite template exporter”
    • Template: Path to the template
    • Filename: Pick a filename for the export file.
    • Output Folder: Pick a path fo the export file.
    • Options: Select append, if you want to append output to an existing file.
  • Push the “OK” button.

Export data

You can now export data from a patient in the Patient/Worklist Management.

  • Open visual fields
    • The export button appears in the vertical bar just below the menu bar.
    • Open view.
    • If you want to export several visual fields of one patient at once, select these fields and click on “Show series view” or “Show trend analysis view.
    • You can now press the export button and the data will be written into the file.
  • Import the data with the loadoctopus function.

The button will be displayed as an icon in the toolbar when you are in a tab that contains fields (not the “Patient Administration” tab). In the “Trend analysis” view, all fields will be exported.

B. Working with the loadoctopus function

For this tutorial, example data can be downloaded here. From now on, I will use these example fields.

# load file: please download the file as stated above and modify the path if necessary
octopus_list <- loadoctopus("../extdata/EyesuiteTestfile.csv") 

str(octopus_list)
## List of 11
##  $ patients         :'data.frame':   3 obs. of  4 variables:
##   ..$ id       : int [1:3] 1 2 3
##   ..$ firstname: chr [1:3] "Peter" "Norrin" "Hüsker"
##   ..$ lastname : chr [1:3] "Parker" "Radd" "Dü"
##   ..$ dob      : POSIXlt[1:3], format: "1977-12-18" "1934-09-27" ...
##  $ vf_types         :'data.frame':   3 obs. of  5 variables:
##   ..$ vfID      : int [1:3] 1 2 3
##   ..$ tperimetry: Factor w/ 2 levels "sap","swap": 1 1 1
##   ..$ pattern   : Factor w/ 3 levels "G","LVP","M": 1 2 3
##   ..$ locnum    : int [1:3] 59 76 81
##   ..$ strategy  : Factor w/ 7 levels "normal","dynamic",..: 2 4 2
##  $ locmaps          :List of 3
##   ..$ :'data.frame': 59 obs. of  3 variables:
##   .. ..$ loc_ID: int [1:59] 1 2 3 4 5 6 7 8 9 10 ...
##   .. ..$ xod   : num [1:59] -8 8 -20 -12 -4 4 12 20 -4 4 ...
##   .. ..$ yod   : num [1:59] 26 26 20 20 20 20 20 20 14 14 ...
##   ..$ :'data.frame': 76 obs. of  3 variables:
##   .. ..$ loc_ID: int [1:76] 1 2 3 4 5 6 7 8 9 10 ...
##   .. ..$ xod   : num [1:76] 0 15 30 -30 -15 0 15 30 45 60 ...
##   .. ..$ yod   : num [1:76] 60 60 60 45 45 45 45 45 45 45 ...
##   ..$ :'data.frame': 81 obs. of  3 variables:
##   .. ..$ loc_ID: int [1:81] 1 2 3 4 5 6 7 8 9 10 ...
##   .. ..$ xod   : num [1:81] -3.5 3.5 -7.5 -2.5 2.5 7.5 -5.5 -1.5 1.5 5.5 ...
##   .. ..$ yod   : num [1:81] 9.5 9.5 7.5 7.5 7.5 7.5 5.5 5.5 5.5 5.5 ...
##  $ sensitivities    :List of 3
##   ..$ : num [1:2, 1:59] 25.5 24 18 25 25 25 25 25.5 25.5 21 ...
##   .. ..- attr(*, "dimnames")=List of 2
##   .. .. ..$ : chr [1:2] "1" "2"
##   .. .. ..$ : chr [1:59] "l1" "l2" "l3" "l4" ...
##   ..$ : num [1, 1:76] 0 0 0 0 0 0 0 0 0 0 ...
##   .. ..- attr(*, "dimnames")=List of 2
##   .. .. ..$ : chr "3"
##   .. .. ..$ : chr [1:76] "l1" "l2" "l3" "l4" ...
##   ..$ : num [1, 1:81] 30 29 30 31 30 30 32 32 32 31 ...
##   .. ..- attr(*, "dimnames")=List of 2
##   .. .. ..$ : chr "4"
##   .. .. ..$ : chr [1:81] "l1" "l2" "l3" "l4" ...
##  $ defects          :List of 3
##   ..$ : num [1:2, 1:59] 1 -0.5 -6.5 0.5 -0.2 ...
##   .. ..- attr(*, "dimnames")=List of 2
##   .. .. ..$ : chr [1:2] "1" "2"
##   .. .. ..$ : chr [1:59] "l1" "l2" "l3" "l4" ...
##   ..$ : num [1, 1:76] 0 -2.8 -0.8 -6.8 -12 -13.3 -14.3 -13.8 -11.4 -6.3 ...
##   .. ..- attr(*, "dimnames")=List of 2
##   .. .. ..$ : chr "3"
##   .. .. ..$ : chr [1:76] "l1" "l2" "l3" "l4" ...
##   ..$ : num [1, 1:81] 0.1 -0.8 0 0.6 -0.4 ...
##   .. ..- attr(*, "dimnames")=List of 2
##   .. .. ..$ : chr "4"
##   .. .. ..$ : chr [1:81] "l1" "l2" "l3" "l4" ...
##  $ norm_values      :List of 3
##   ..$ : num [1:2, 1:59] 24.5 24.5 24.5 24.5 25.2 25.2 26.2 26.2 26.6 26.6 ...
##   .. ..- attr(*, "dimnames")=List of 2
##   .. .. ..$ : chr [1:2] "1" "2"
##   .. .. ..$ : chr [1:59] "l1" "l2" "l3" "l4" ...
##   ..$ : num [1, 1:76] 0 2.8 0.8 6.8 12 13.3 14.3 13.8 11.4 6.3 ...
##   .. ..- attr(*, "dimnames")=List of 2
##   .. .. ..$ : chr "3"
##   .. .. ..$ : chr [1:76] "l1" "l2" "l3" "l4" ...
##   ..$ : num [1, 1:81] 29.9 29.8 30 30.4 30.4 29.8 30.5 30.9 30.9 30.4 ...
##   .. ..- attr(*, "dimnames")=List of 2
##   .. .. ..$ : chr "4"
##   .. .. ..$ : chr [1:81] "l1" "l2" "l3" "l4" ...
##  $ fields           :'data.frame':   4 obs. of  10 variables:
##   ..$ id  : int [1:4] 1 1 2 3
##   ..$ eye : chr [1:4] "OD" "OS" "OD" "OD"
##   ..$ date: Date[1:4], format: "2020-01-02" "2020-01-02" ...
##   ..$ time: chr [1:4] "09:19:13" "09:26:24" "14:26:06" "12:50:20"
##   ..$ age : num [1:4] 42 42 85 32
##   ..$ type: logi [1:4] NA NA NA NA
##   ..$ fpr : num [1:4] 0 0 0 0
##   ..$ fnr : num [1:4] 0 0.125 0.091 0
##   ..$ fl  : int [1:4] 0 0 0 2
##   ..$ vfID: int [1:4] 1 1 2 3
##  $ create_locmap    :function (vf_id)  
##  $ get_sensitivities:function (vf_id)  
##  $ get_defects      :function (vf_id)  
##  $ get_norm_values  :function (vf_id)

Patients

The visualFields packages identifies each patients with a unique id.

When the csv-file is parsed, an id is created and data.frame is created to save the first name, last name, and date of birth for each patient.

print(octopus_list$patients)
##   id firstname lastname        dob
## 1  1     Peter   Parker 1977-12-18
## 3  2    Norrin     Radd 1934-09-27
## 4  3    Hüsker       Dü 1987-12-26

Visual field types

Different visual field patterns result in different column numbers in the visual field objects. Therefore, different lists are created. These are identified by a visual field id (vfID). The data.frame vf_types keeps track of these field types.

print(octopus_list$vf_types)
##   vfID tperimetry pattern locnum   strategy
## 1    1        sap       G     59    dynamic
## 3    2        sap     LVP     76 low vision
## 4    3        sap       M     81    dynamic

Getting sensitivities

With this id, a visual field object can be created. In order to avoid redundancy, sensitivities and further information on the visual field are saved in different lists:

  • octopus_list$fields: general information
  • octopus_list$sensitivities: sensitivities
# G pattern, dynamic strategy: vfID = 1

print(octopus_list$fields[octopus_list$fields$vfID == 1, ])
##   id eye       date     time age type fpr   fnr fl vfID
## 1  1  OD 2020-01-02 09:19:13  42   NA   0 0.000  0    1
## 2  1  OS 2020-01-02 09:26:24  42   NA   0 0.125  0    1
print(octopus_list$sensitivities[[1]])
##     l1 l2 l3   l4   l5 l6 l7   l8 l9  l10 l11 l12 l13 l14  l15 l16 l17 l18 l19
## 1 25.5 18 25 25.0 25.5 21 21 22.0 28 27.0  23  29  26  21 28.5  26  27  30  23
## 2 24.0 25 25 25.5 21.0 22 26 26.5 27 26.5  27  29  27  25 29.5  28  26  25  23
##   l20 l21 l22 l23 l24  l25 l26 l27 l28 l29 l30 l31 l32 l33 l34 l35 l36 l37 l38
## 1  25  25  27  23  30 23.0  26  30  30  29  29  26  27  32  21  23  27  26  32
## 2  26  25  29  29  28 25.5  32  30  28  28  30  29  31  32  27  31  27  30  34
##   l39 l40 l41  l42 l43 l44 l45 l46 l47 l48 l49 l50 l51  l52 l53 l54 l55 l56 l57
## 1  30  26  29 28.5  25  31  23  26  23  26  27  28  28 25.5  25  25  23  23  25
## 2  30  26  31 28.5  27  28  27  30  27  27  32  26  30 23.0  28  28  27  21  26
##   l58 l59
## 1  18  22
## 2  27  26

In order to simplify getting this information, the list contains a functions that returns a visual field object.

# G pattern, dynamic strategy: vfID = 1

sens <- octopus_list$get_sensitivities(1)

print(sens)
##   id eye       date     time age type fpr   fnr fl vfID   l1 l2 l3   l4   l5 l6
## 1  1  OD 2020-01-02 09:19:13  42   NA   0 0.000  0    1 25.5 18 25 25.0 25.5 21
## 2  1  OS 2020-01-02 09:26:24  42   NA   0 0.125  0    1 24.0 25 25 25.5 21.0 22
##   l7   l8 l9  l10 l11 l12 l13 l14  l15 l16 l17 l18 l19 l20 l21 l22 l23 l24  l25
## 1 21 22.0 28 27.0  23  29  26  21 28.5  26  27  30  23  25  25  27  23  30 23.0
## 2 26 26.5 27 26.5  27  29  27  25 29.5  28  26  25  23  26  25  29  29  28 25.5
##   l26 l27 l28 l29 l30 l31 l32 l33 l34 l35 l36 l37 l38 l39 l40 l41  l42 l43 l44
## 1  26  30  30  29  29  26  27  32  21  23  27  26  32  30  26  29 28.5  25  31
## 2  32  30  28  28  30  29  31  32  27  31  27  30  34  30  26  31 28.5  27  28
##   l45 l46 l47 l48 l49 l50 l51  l52 l53 l54 l55 l56 l57 l58 l59
## 1  23  26  23  26  27  28  28 25.5  25  25  23  23  25  18  22
## 2  27  30  27  27  32  26  30 23.0  28  28  27  21  26  27  26

Getting defects

Since the csv file also contains age-related normal values for each subject and each location, we can also directly calculate a defect list, without an explicit model for normal values.

# M pattern (M = macula): vfID = 3

def <- octopus_list$get_defects(3)

print(def)
##   id eye       date     time age type fpr fnr fl vfID  l1   l2 l3  l4   l5  l6
## 4  3  OD 2020-01-08 12:50:20  32   NA   0   0  2    3 0.1 -0.8  0 0.6 -0.4 0.2
##    l7  l8  l9 l10 l11  l12 l13 l14 l15 l16 l17 l18  l19 l20 l21 l22 l23 l24
## 4 1.5 1.1 1.1 0.6 0.7 -0.2 3.6 2.6 0.9   2 0.3   1 -0.6 3.4 3.3 0.4 3.1 0.6
##    l25 l26 l27 l28 l29 l30 l31 l32 l33 l34 l35  l36 l37 l38  l39  l40  l41 l42
## 4 -0.1 1.3 1.2 1.1   2 3.2 0.4 0.1 3.4 3.2   1 -5.2 2.8   2 -2.7 -0.5 -1.4 1.4
##   l43 l44 l45  l46 l47  l48 l49 l50  l51 l52 l53  l54 l55 l56 l57 l58 l59  l60
## 4 1.2 1.9 1.7 -2.3 1.9 -0.8 0.4 0.8 -2.8  -1  -1 -0.1  -1 0.3  -1 0.2 0.8 -0.8
##    l61  l62 l63  l64  l65 l66 l67 l68  l69  l70 l71  l72  l73  l74  l75  l76
## 4 -0.8 -0.9 0.2 -1.1 -0.5 0.6 0.7 1.4 -0.7 -1.3 0.9 -3.8 -1.2 -1.2 -0.7 -0.3
##    l77  l78 l79  l80 l81
## 4 -0.7 -0.8 1.9 -0.3 0.7

Extracting locmaps and graphic parameters from the parsed file

For many functions of the visualFields package, explicit information on locations and graphical parameters are needed. The locmap functions helps to create such tables. These are then loaded in the environment with special set functions.

# M pattern (M = macula): vfID = 3

m_locmap <- octopus_list$create_locmap(3)

print(m_locmap)
## $name
## [1] "M 81"
## 
## $desc
## [1] "This locmap was automatically created from the csv exported by Eyesuite."
## 
## $coord
##       x    y
## 1  -3.5  9.5
## 2   3.5  9.5
## 3  -7.5  7.5
## 4  -2.5  7.5
## 5   2.5  7.5
## 6   7.5  7.5
## 7  -5.5  5.5
## 8  -1.5  5.5
## 9   1.5  5.5
## 10  5.5  5.5
## 11 -9.5  3.5
## 12 -3.5  3.5
## 13 -0.5  3.5
## 14  0.5  3.5
## 15  3.5  3.5
## 16  9.5  3.5
## 17 -7.5  2.5
## 18 -2.5  2.5
## 19 -1.5  2.5
## 20 -0.5  2.5
## 21  0.5  2.5
## 22  1.5  2.5
## 23  2.5  2.5
## 24  7.5  2.5
## 25 -5.5  1.5
## 26 -2.5  1.5
## 27 -1.5  1.5
## 28 -0.5  1.5
## 29  0.5  1.5
## 30  1.5  1.5
## 31  2.5  1.5
## 32  5.5  1.5
## 33 -3.5  0.5
## 34 -2.5  0.5
## 35 -1.5  0.5
## 36 -0.5  0.5
## 37  0.5  0.5
## 38  1.5  0.5
## 39  2.5  0.5
## 40  3.5  0.5
## 41  0.0  0.0
## 42 -3.5 -0.5
## 43 -2.5 -0.5
## 44 -1.5 -0.5
## 45 -0.5 -0.5
## 46  0.5 -0.5
## 47  1.5 -0.5
## 48  2.5 -0.5
## 49  3.5 -0.5
## 50 -5.5 -1.5
## 51 -2.5 -1.5
## 52 -1.5 -1.5
## 53 -0.5 -1.5
## 54  0.5 -1.5
## 55  1.5 -1.5
## 56  2.5 -1.5
## 57  5.5 -1.5
## 58 -7.5 -2.5
## 59 -2.5 -2.5
## 60 -1.5 -2.5
## 61 -0.5 -2.5
## 62  0.5 -2.5
## 63  1.5 -2.5
## 64  2.5 -2.5
## 65  7.5 -2.5
## 66 -9.5 -3.5
## 67 -3.5 -3.5
## 68 -0.5 -3.5
## 69  0.5 -3.5
## 70  3.5 -3.5
## 71  9.5 -3.5
## 72 -5.5 -5.5
## 73 -1.5 -5.5
## 74  1.5 -5.5
## 75  5.5 -5.5
## 76 -7.5 -7.5
## 77 -2.5 -7.5
## 78  2.5 -7.5
## 79  7.5 -7.5
## 80 -3.5 -9.5
## 81  3.5 -9.5
setlocmap(m_locmap)

For plotting fields, a gpar list is needed. This is not fully implemented, yet. However, a rudimentary list can be easily created.

test_gpar <- list()
test_gpar$coord <- getlocmap()$coord
test_gpar$tess <- vftess(getlocmap()$coord, floor = 0, delta = 3)
setgpar(test_gpar)

Now, we can plot sensitivities. Note that the blind spot is outside the field for the macula test pattern.

vfM <- octopus_list$get_sensitivities(3)

vfplot(vfM[1, ], type = "s")

Work in progress

Currently, you cannot use many functions provided by the visualFields package with data imported as described here, because no normal values for Octopus perimeters have been included in the package, yet.