The eyesuite software has an export function that creates a csv of visual fields that can be selected according to several criteria.
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.
NB: 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.
The csv-file is quite extensive. Besides the sensitivities, it also contains the coordinates and the normal values for the given age for all locations. Often, it contains different types of visual fields.
Therefore, I have decided to implement the loadoctopus function as a function that parses the csv-file into a list. Here, I show how information can be extracted from this list.
NB: This is the first, preliminary version.
# library(devtools)
# install_github(...)
library(visualFields)
octopus_list <- loadoctopus("EyesuiteTestfile.csv")
str(octopus_list)
## List of 9
## $ patients :'data.frame': 4 obs. of 4 variables:
## ..$ id : int [1:4] 2 2 3 1
## ..$ firstname: chr [1:4] "Peter" "Peter" "Norrin" "Hüsker"
## ..$ lastname : chr [1:4] "Parker" "Parker" "Radd" "Dü"
## ..$ dob : POSIXlt[1:4], format: "1977-12-18" "1977-12-18" ...
## $ 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] -26 -26 -20 -20 -20 -20 -20 -20 -14 -14 ...
## .. ..$ yod : num [1:59] -4 4 -20 -12 -4 4 12 20 -4 4 ...
## ..$ :'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] -60 -60 -45 -45 -45 -45 -45 -30 -30 -30 ...
## .. ..$ yod : num [1:76] 0 15 -30 -15 0 15 30 -45 -30 -15 ...
## ..$ :'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] -9.5 -9.5 -7.5 -7.5 -7.5 -7.5 -5.5 -5.5 -5.5 -5.5 ...
## .. ..$ yod : num [1:81] -3.5 3.5 -7.5 -2.5 2.5 7.5 -5.5 -1.5 1.5 5.5 ...
## $ sensitivities :List of 3
## ..$ : num [1:2, 1:59] 23 31 25 26 25.5 23 26 30 27 27 ...
## .. ..- attr(*, "dimnames")=List of 2
## .. .. ..$ : chr [1:2] "1" "2"
## .. .. ..$ : chr [1:59] "l1" "l2" "l3" "l4" ...
## ..$ : num [1, 1:76] 0 0 0 20 2 0 0 10 26 20 ...
## .. ..- attr(*, "dimnames")=List of 2
## .. .. ..$ : chr "3"
## .. .. ..$ : chr [1:76] "l1" "l2" "l3" "l4" ...
## ..$ : num [1, 1:81] 31 31 30 31 31 30 27 32 31 32 ...
## .. ..- attr(*, "dimnames")=List of 2
## .. .. ..$ : chr "4"
## .. .. ..$ : chr [1:81] "l1" "l2" "l3" "l4" ...
## $ defects :List of 3
## ..$ : num [1:2, 1:59] -3.9 4.1 -1.8 -0.8 -0.6 ...
## .. ..- attr(*, "dimnames")=List of 2
## .. .. ..$ : chr [1:2] "1" "2"
## .. .. ..$ : chr [1:59] "l1" "l2" "l3" "l4" ...
## ..$ : num [1, 1:76] -2.1 -3.3 -0.8 7.4 -12.8 ...
## .. ..- attr(*, "dimnames")=List of 2
## .. .. ..$ : chr "3"
## .. .. ..$ : chr [1:76] "l1" "l2" "l3" "l4" ...
## ..$ : num [1, 1:81] 0.6 0.7 -0.3 0.2 0.3 ...
## .. ..- 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] 2 2 3 1
## ..$ 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)
## ..- attr(*, "srcref")= 'srcref' int [1:8] 367 5 376 5 5 5 1924 1933
## .. ..- attr(*, "srcfile")=Classes 'srcfilealias', 'srcfile' <environment: 0x55c831fd9a50>
## $ get_sensitivities:function (vf_id)
## ..- attr(*, "srcref")= 'srcref' int [1:8] 379 5 382 5 5 5 1936 1939
## .. ..- attr(*, "srcfile")=Classes 'srcfilealias', 'srcfile' <environment: 0x55c831fd9a50>
## $ get_defects :function (vf_id)
## ..- attr(*, "srcref")= 'srcref' int [1:8] 385 5 388 5 5 5 1942 1945
## .. ..- attr(*, "srcfile")=Classes 'srcfilealias', 'srcfile' <environment: 0x55c831fd9a50>
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 2 Peter Parker 1977-12-18
## 2 2 Peter Parker 1977-12-18
## 3 3 Norrin Radd 1934-09-27
## 4 1 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 2 OD 2020-01-02 09:19:13 42 NA 0 0.000 0 1
## 2 2 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 l20
## 1 23 25 25.5 26 27 25 23 25 26 27 25 23 29 25.0 18 29 26 26 28.5 25.5
## 2 31 26 23.0 30 27 25 27 25 30 29 28 27 29 25.5 27 31 29 32 29.5 24.0
## l21 l22 l23 l24 l25 l26 l27 l28 l29 l30 l31 l32 l33 l34 l35 l36 l37 l38 l39
## 1 25 28 32 23 28 25.5 31 27 30 26 29 32 30 27 23 23 28 30 30
## 2 28 26 34 29 27 21.0 28 31 30 28 30 32 28 26 27 27 30 30 28
## l40 l41 l42 l43 l44 l45 l46 l47 l48 l49 l50 l51 l52 l53 l54 l55 l56 l57
## 1 27.0 21 22 28.5 21 29 30 18 23 26 26 21 25 27 21 22.0 26 23.0
## 2 26.5 22 26 28.5 27 28 25 25 21 27 27 26 26 32 25 26.5 26 25.5
## l58 l59
## 1 25 23
## 2 27 23
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 l7
## 1 2 OD 2020-01-02 09:19:13 42 NA 0 0.000 0 1 23 25 25.5 26 27 25 23
## 2 2 OS 2020-01-02 09:26:24 42 NA 0 0.125 0 1 31 26 23.0 30 27 25 27
## l8 l9 l10 l11 l12 l13 l14 l15 l16 l17 l18 l19 l20 l21 l22 l23 l24 l25 l26
## 1 25 26 27 25 23 29 25.0 18 29 26 26 28.5 25.5 25 28 32 23 28 25.5
## 2 25 30 29 28 27 29 25.5 27 31 29 32 29.5 24.0 28 26 34 29 27 21.0
## l27 l28 l29 l30 l31 l32 l33 l34 l35 l36 l37 l38 l39 l40 l41 l42 l43 l44 l45
## 1 31 27 30 26 29 32 30 27 23 23 28 30 30 27.0 21 22 28.5 21 29
## 2 28 31 30 28 30 32 28 26 27 27 30 30 28 26.5 22 26 28.5 27 28
## l46 l47 l48 l49 l50 l51 l52 l53 l54 l55 l56 l57 l58 l59
## 1 30 18 23 26 26 21 25 27 21 22.0 26 23.0 25 23
## 2 25 25 21 27 27 26 26 32 25 26.5 26 25.5 27 23
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 1 OD 2020-01-08 12:50:20 32 NA 0 0 2 3 0.6 0.7 -0.3 0.2 0.3 0
## l7 l8 l9 l10 l11 l12 l13 l14 l15 l16 l17 l18 l19 l20 l21 l22 l23 l24
## 4 -3.8 0.8 -0.1 1.5 -0.3 0.7 1.4 3.4 -0.2 0.1 -0.7 0.8 -2.8 1.2 3.2 1.3 1 0.6
## l25 l26 l27 l28 l29 l30 l31 l32 l33 l34 l35 l36 l37 l38 l39 l40 l41
## 4 -1.2 -0.8 -1 1.9 1 1.2 -0.6 1.1 1.4 -0.8 -1 1.7 -5.2 1.1 3.4 3.6 -1.4
## l42 l43 l44 l45 l46 l47 l48 l49 l50 l51 l52 l53 l54 l55 l56 l57 l58
## 4 -0.7 -0.9 -0.1 -2.3 2.8 2 3.3 2.6 -1.2 0.2 -1 1.9 2 3.2 0.4 1.1 -0.8
## l59 l60 l61 l62 l63 l64 l65 l66 l67 l68 l69 l70 l71 l72 l73 l74 l75
## 4 -1.1 0.3 -0.8 -2.7 0.4 3.1 -0.4 0.7 -1.3 0.4 -0.5 0.9 -0.8 -0.7 -1 0.1 0.6
## l76 l77 l78 l79 l80 l81
## 4 1.9 -0.5 0.6 0.2 0.9 2
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. NB: The locations are not numbered according to the standard!"
##
## $coord
## x y
## 1 -9.5 -3.5
## 2 -9.5 3.5
## 3 -7.5 -7.5
## 4 -7.5 -2.5
## 5 -7.5 2.5
## 6 -7.5 7.5
## 7 -5.5 -5.5
## 8 -5.5 -1.5
## 9 -5.5 1.5
## 10 -5.5 5.5
## 11 -3.5 -9.5
## 12 -3.5 -3.5
## 13 -3.5 -0.5
## 14 -3.5 0.5
## 15 -3.5 3.5
## 16 -3.5 9.5
## 17 -2.5 -7.5
## 18 -2.5 -2.5
## 19 -2.5 -1.5
## 20 -2.5 -0.5
## 21 -2.5 0.5
## 22 -2.5 1.5
## 23 -2.5 2.5
## 24 -2.5 7.5
## 25 -1.5 -5.5
## 26 -1.5 -2.5
## 27 -1.5 -1.5
## 28 -1.5 -0.5
## 29 -1.5 0.5
## 30 -1.5 1.5
## 31 -1.5 2.5
## 32 -1.5 5.5
## 33 -0.5 -3.5
## 34 -0.5 -2.5
## 35 -0.5 -1.5
## 36 -0.5 -0.5
## 37 -0.5 0.5
## 38 -0.5 1.5
## 39 -0.5 2.5
## 40 -0.5 3.5
## 41 0.0 0.0
## 42 0.5 -3.5
## 43 0.5 -2.5
## 44 0.5 -1.5
## 45 0.5 -0.5
## 46 0.5 0.5
## 47 0.5 1.5
## 48 0.5 2.5
## 49 0.5 3.5
## 50 1.5 -5.5
## 51 1.5 -2.5
## 52 1.5 -1.5
## 53 1.5 -0.5
## 54 1.5 0.5
## 55 1.5 1.5
## 56 1.5 2.5
## 57 1.5 5.5
## 58 2.5 -7.5
## 59 2.5 -2.5
## 60 2.5 -1.5
## 61 2.5 -0.5
## 62 2.5 0.5
## 63 2.5 1.5
## 64 2.5 2.5
## 65 2.5 7.5
## 66 3.5 -9.5
## 67 3.5 -3.5
## 68 3.5 -0.5
## 69 3.5 0.5
## 70 3.5 3.5
## 71 3.5 9.5
## 72 5.5 -5.5
## 73 5.5 -1.5
## 74 5.5 1.5
## 75 5.5 5.5
## 76 7.5 -7.5
## 77 7.5 -2.5
## 78 7.5 2.5
## 79 7.5 7.5
## 80 9.5 -3.5
## 81 9.5 3.5
##
## $bs
## [1] NA
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)
##
## PLEASE NOTE: The components "delsgs" and "summary" of the
## object returned by deldir() are now DATA FRAMES rather than
## matrices (as they were prior to release 0.0-18).
## See help("deldir").
##
## PLEASE NOTE: The process that deldir() uses for determining
## duplicated points has changed from that used in version
## 0.0-9 of this package (and previously). See help("deldir").
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)
# I'm not sure why this step is necessary? Otherwise, validity check fails.
vfM$date <- as.Date(vfM$date)
vfplot(vfM[1, ], type = "s")
Many functions can still not be used. Especially, an explicit model for age related normal values is necessary to plot defects. I will implement more functions as soon as I find the time.