-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path02-froc-data-format.Rmd
225 lines (126 loc) · 12.1 KB
/
02-froc-data-format.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# FROC data format {#quick-start-froc-data-format}
```{r setup, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>"
)
library(RJafroc)
```
## How much finished 90% {#quick-start-froc-how-much-finished}
## Introduction {#quick-start-froc-data-intro}
```{r echo=FALSE}
frocCr <- "R/quick-start/frocCr.xlsx"
ds <- DfReadDataFile(frocCr, newExcelFileFormat = TRUE)
```
The purpose of this chapter is to explain the format of the FROC Excel file and how to read this file into a dataset object suitable for analysis using the `RJafroc` package.
In the FROC paradigm the observer assigns a rating and a location to suspicious regions in images that exceed the reporting threshold. As an example a CAD algorithm may find tens of suspicious regions in each image but the algorithm designer only shows those regions (typically one or two) whose confidence levels exceed the chosen threshold.
The chapter is illustrated with a toy data file, `R/quick-start/frocCr.xlsx` in which readers '0', '1' and '2' interpret 8 cases in two modalities, '0' and '1'. The design is 'factorial', abbreviated to `FCTRL` in the software; this is also termed a 'fully-crossed' design. The Excel file has three worksheets named `Truth`, `NL` (or `FP`) and `LL` (or `TP`). These names are case-insensitive.
## The `Truth` worksheet {#quick-start-froc-data-truth}
![](images/quick-start/frocCrTruth.png){width=100%}
The `Truth` worksheet contains 6 columns: `CaseID`, `LesionID`, `Weight`, `ReaderID`, `ModalityID` and `Paradigm`. Since a diseased case may have more than one lesion, the first five columns contain **at least** as many rows as there are cases in the dataset. There are 8 cases ('1','2','3','70','71','72','73' and '74') in the dataset and 12 rows in the `Truth` worksheet, because some of the diseased cases contain more than one lesion.
1. `CaseID`: unique **integers** representing the individual cases in the dataset: e.g., '1', '2', '3', the 3 non-diseased cases and '70', '71', '72', '73', '74', the 5 diseased cases. The ordering of the numbers is inconsequential. ^[`CaseID` should not be so large that it cannot be represented in Excel by an integer; to be safe use unsigned short 8-bit integers.]
1. `LesionID`: non-negative integers 0, 1, 2, ..., where:
+ Each 0 represents a non-diseased case, e.g., this field is zero for non-diseased cases '1', '2' and '3'.
+ Each 1 represents the *first* lesion in a diseased case, 2 represents the *second* lesion, if present, and so on.
1. `Weight` or clinical importance associated with lesion:
+ It is 0 for each non-diseased case,
+ For each diseased case the values must sum to unity.
+ A shortcut to assigning equal weights to all lesions in a case is to fill the `Weight` column with zeroes.
1. `ReaderID`: see Section \@ref(quick-start-roc-truth).
1. `ModalityID`: see Section \@ref(quick-start-roc-truth).
1. `Paradigm`: see Section \@ref(quick-start-roc-truth).
### Comments on the `Truth` worksheet {#quick-start-froc-truth-comments}
There are 3 non-diseased cases in the dataset (the number of 0's in the `LesionID` column). There are 5 diseased cases in the dataset (the number of 1's in the `LesionID` column). There are 3 readers in the dataset labeled '0, 1, 2'. There are 2 modalities in the dataset labeled '0, 1'. Diseased case `70` has two lesions, with `LesionID`s '1' and '2' and weights 0.3 and 0.7, respectively. Diseased case `71` has one lesion with `LesionID` = 1 and `Weight` = 1. Diseased case `72` has three lesions with `LesionID`s 1, 2 and 3 and weights 1/3 each. Diseased case `73` has two lesions, with `LesionID`s 1, and 2 and weights 0.1 and 0.9, respectively. Diseased case `74` has one lesion, with `LesionID` = 1 and `Weight` = 1. Note that `LesionID`s *identify* the lesions - for example, a lesion with high morbidity may be labeled `LesionID` = 1 and assigned weight 0.9 while a second lower morbidity lesion on the same case may be assigned `LesionID` = 2 and weight 0.1. In this example reversing the lesion IDs would lead to incorrect weight assignments.
## The FP ratings {#quick-start-froc-data-fp}
These are found in the `FP` or `NL` worksheet.
![](images/quick-start/frocCrNL.png){width=100%}
It consists of 4 columns of equal length. The common length is an integer random variable $\ge 0$. It could be zero if the dataset has no NL marks (a possibility if the lesions are easy to find or the observer has perfect performance). In this example the common length is `r sum(ds$ratings$NL != -Inf)`, which is a-priori unpredictable: for example, if the dataset has many FPs it could be large.
1. `ReaderID`: the reader labels: these must be one of `0`, `1`, or `2` as declared in the `Truth` worksheet.
1. `ModalityID`: the modality labels: must be one of `0` or `1` as declared in the `Truth` worksheet.
1. `CaseID`: the labels of cases with `NL` marks. These must be one of `1`, `2`, `3`, `70`, `71`, `72`, `73`, `74` as declared in the `Truth` worksheet. In the FROC paradigm `NL` events can occur on non-diseased **and** diseased cases.
1. `FP_Rating`: the floating point ratings of `NL` marks. Each cell contains the rating corresponding to the values of `ReaderID`, `ModalityID` and `CaseID` for that row.
### Comments on the `FP` worksheet {#quick-start-roc-fp-comments}
* For `ModalityID` 0, `ReaderID` 0 and `CaseID` 1 (the first non-diseased case declared in the `Truth` worksheet), there is a single `NL` mark that was rated `r ds$ratings$NL[1,1,1,1]`, corresponding to row 2 of the `FP` worksheet.
* Diseased cases with `NL` marks are also recorded in the `FP` worksheet. Some examples are seen at rows 15, 16 and 21, 22, 23. Rows 21 and 22 show that `caseID` = 71 got two `NL` marks, rated `r ds$ratings$NL[2,3,5,1:2]`.
* Since this is the *only* case with two NL marks, it determines the length of the fourth dimension of the `ds$ratings$NL`, which is `r length(ds$ratings$NL[1,1,1,])` in this example. Absent this case, the length would have been one. The case with the most `NL` marks determines the length of the fourth dimension of `ds$ratings$NL`. The reader should confirm that the ratings in `ds$ratings$NL` reflect the contents of the `FP` worksheet.
## The TP ratings {#quick-start-froc-data-tp}
These are found in the `TP` or `LL` worksheet, see below.
![](images/quick-start/frocCrLL.png){width=100%}
This worksheet can only have diseased cases. The presence of a non-diseased case will generate an error. The common vertical length, 31 in this example, is a-priori unpredictable (as some lesions may not be marked). The maximum possible length, assuming every lesion is marked for each modality, reader and diseased case, is 9 X 2 X 3 = 54. The 9 comes from the total number of non-zero entries in the `LesionID` column of the `Truth` worksheet, the 2 from the number of modalities and 3 from the number of readers.
The fact that the actual length (31) is smaller than the maximum length (54) means that there are combinations of modality, reader and diseased cases on which some lesions were not marked.
As examples, line 2 in the worksheet, the first lesion in `CaseID` equal to `70` was marked (and rated `r ds$ratings$LL[1,1,1,1]`) in `ModalityID` `0` and `ReaderID` `0`. Line 3 in the worksheet, the second lesion in `CaseID` equal to `70` was also marked (and rated `r ds$ratings$LL[1,1,1,2]`) in `ModalityID` `0` and `ReaderID` `0`. However, lesions 2 and 3 in `CaseID` = 72 were not marked (line 5 in the worksheet indicates that for this modality-reader-case combination only the first lesion was marked). The reader should confirm that the ratings in `ds$ratings$LL` reflect the contents of the `TP` worksheet.
## Reading the FROC dataset {#quick-start-froc-data-structure}
The example shown above corresponds to file `R/quick-start/frocCr.xlsx` in the project directory. The next code reads this file into an `R` object `ds`.
```{r}
frocCr <- "R/quick-start/frocCr.xlsx"
ds <- DfReadDataFile(frocCr, newExcelFileFormat = TRUE)
str(ds)
```
This follows the general description in Chapter \@ref(quick-start-roc). The differences are described below.
* The `ds$descriptions$type` member indicates that this is an `FROC` dataset.
* The `ds$lesions$perCase` member is a vector containing the number of lesions in each diseased case, i.e., `r ds$lesions$perCase` in the current example.
* The `ds$lesions$IDs` member indicates the labeling of the lesions in each diseased case.
```{r}
ds$lesions$IDs
```
* This shows that the lesions on the first diseased case are labeled '1' and '2'. The `-Inf` is a filler denoting a missing value. The second diseased case has one lesion labeled '1'. The third diseased case has three lesions labeled '1', '2' and '3', etc.
* The `lesionWeight` member is the clinical importance of each lesion. Lacking specific clinical reasons, the lesions should be equally weighted; this is *not* true for this toy dataset (except for the third diseased case).
```{r}
ds$lesions$weights
```
* The first diseased case has two lesions, the first has weight 0.3 and the second has weight 0.7.
* The second diseased case has one lesion with weight 1.
* The third diseased case has three equally weighted lesions, each with weight 1/3. Etc.
## The distribution of lesions in diseased cases {#quick-start-froc-data-distribution-diseased-cases}
Consider a much larger real dataset, `dataset11`, with structure as shown below (for descriptions of all embedded datasets see Chapter \@ref(datasets)):
```{r}
ds <- dataset11
str(ds)
```
The large number of lesions is explained by the fact that this is a volumetric CT image for lung nodule detection (each nodule was verified by 3 radiologists).
Focus on the 115 diseased cases: the numbers of lesions in individual cases is contained in `ds$lesions$perCase`.
```{r}
ds$lesions$perCase
```
For example, the first diseased case contains 6 lesions, the second contains 4 lesions, the third contains 7 lesions, etc., and the last diseased case contains 1 lesion. To get the distribution of the numbers of lesions per diseased cases one could use the `which()` function:
```{r}
for (el in 1:max(ds$lesions$perCase)) cat(
"number of diseased cases with", el, "lesions = ",
length(which(ds$lesions$perCase == el)), "\n")
```
This tells us that 25 cases contain 1 lesion. Likewise, 23 cases contain 2 lesions, etc. Note that there are no cases with 13, 14, 15, 17, 18, and 19 lesions.
### Definition of `lesID` array {#quick-start-froc-data-lesion-distribution}
The fraction of diseased cases with 1 lesion, 2 lesions etc, can be calculated as follows:
```{r}
for (el in 1:max(ds$lesions$perCase))
cat("fraction of diseased cases with", el, "lesions = ",
length(which(ds$lesions$perCase == el))/length(ds$ratings$LL[1,1,,1]), "\n")
```
Fraction 0.217 of diseased cases contain 1 lesion, fraction 0.2 of (diseased) cases contain 2 lesions, etc.
This information is more readily obtained using the `RJafroc` function `UtilLesDistr()` as shown next (be sure to view both screens):
```{r}
UtilLesDistr(ds)
```
* The `UtilLesDistr()` function returns a dataframe with two columns.
* The first column (`lesID`) contains the number of lesions per case.
* The second column (`Freq`) contains the fraction of diseased cases with the number of lesions indicated in the first column.
* The second column sums to unity:
```{r}
sum(UtilLesDistr(ds)$Freq)
```
## Lesion weights {#quick-start-froc-data-lesion-weights}
* This `dataframe` is returned by `UtilLesWghtsDS()` or `UtilLesWghtsLD()`.
* This contains the same number of rows as `lesID`.
* The number of columns is one plus the number of rows.
* The first column contains the number of lesions per case.
* The second through the last column contain the weights of cases with number of lesions per case in column 1.
* Missing values are filled with `-Inf`.
```{r}
UtilLesWghtsDS(ds, relWeights = 0)
## or
## UtilLesWghtsLD(UtilLesDistr(ds), relWeights = 0)
##
```
* Row 3 corresponds to 3 lesions per case and the weights are 1/3, 1/3 and 1/3.
* Row 13 corresponds to 13 lesions per case and the weights are 0.06250000, 0.06250000, ..., repeated 13 times.
* Note that the number of rows equals the maximum number of lesions per case (20).