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.
First, you need to create an export button in EyeSuite.
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"))
You can now export data from a patient in the Patient/Worklist Management.
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.
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)
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
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
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:
# 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
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
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")
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.