Skip to content

Commit 7ca713d

Browse files
authored
Merge pull request #39 from jnolan14/master
Created guide for labels
2 parents a86f2a5 + c76d4c9 commit 7ca713d

File tree

1 file changed

+151
-0
lines changed

1 file changed

+151
-0
lines changed

docs/guide/labels.md

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
Labels
2+
======
3+
4+
Surfa provides utilities for working with image label mappings. Label mappings
5+
store information to correlate integer values in the image with a label name and
6+
RGBA color representation in an image viewer. The classes that surfa provides
7+
for working with labels are primarily built on top of dictionaries.
8+
9+
**LabelLookup** objects hold the label information in an ordered dictionary,
10+
where each key is the int corresponding to the label value in the image, and each value is a `LabelElement`.
11+
12+
**LabelElement** objects are mutable objects with two fields, `name` and `color`
13+
- `name` is a string representing the label name
14+
- `color` is a 4 element list of integer values representing the RGBA values
15+
16+
## Label Basics
17+
18+
`LabelLookup` objects can be instantiated by loading a FreeSurfer color lookup
19+
table file or defined manually by the user. There are also some utilities for
20+
creating `LabelLookup` objects representing label mapping commonly used in
21+
FreeSurfer.
22+
23+
```python
24+
>>> import surfa as sf
25+
26+
# instantiate `LabelLookup` from a file
27+
>>> labels_from_file = sf.load_label_lookup('/usr/local/freesurfer/7.4.1/luts/FreeSurferColorLUT.txt')
28+
29+
# instantiate from a commonly used label mapping (Destrieux cortical atlas)
30+
>>> destrieux_labels = sf.freesurfer.destrieux()
31+
32+
# instantiate manually
33+
>>> manual_labels = sf.LabelLookup()
34+
>>> manual_labels[0] = ('Unknown', [0,0,0,0])
35+
>>> manual_labels[24] = ('CSF', [255,255,255,0])
36+
>>> manual_labels[100] = ('CSF-Ventricle', [128,128,128,0])
37+
38+
>>> manual_labels[24].name
39+
'CSF'
40+
>>> manual_labels[24].color
41+
array([255., 255., 255., 0.])
42+
```
43+
44+
Labels can be accessed by indexing the label value in the `LabelLookup`. The
45+
index of a label can be searched for by passing a string to the `search` method.
46+
By default the search is greedy and not case sensitive, this can be changed to
47+
an exact match via the `exact` arg.
48+
49+
```python
50+
# greedy search
51+
>>> manual_labels.search('CSF')
52+
[24, 100]
53+
54+
# exact search
55+
>>> manual_labels.search('CSF',exact=True)
56+
24
57+
```
58+
59+
Subsets of a `LabelLookup` can be extracted into `LabelLookup` objects if
60+
working with the full label mapping contains lots of unused labels.
61+
62+
```python
63+
>>> import numpy as np
64+
65+
# load an aseg, find unique labels
66+
>>> aseg = sf.load_volume('aseg.mgz')
67+
>>> aseg_label_indices = np.unique(aseg.data)
68+
>>> aseg_label_indices
69+
array([ 0, 2, 3, 4, 5, 7, 8, 10, 11, 12, 13, 14, 15,
70+
16, 17, 18, 24, 26, 28, 30, 31, 41, 42, 43, 44, 46,
71+
47, 49, 50, 51, 52, 53, 54, 58, 60, 62, 63, 72, 77,
72+
85, 251, 252, 253, 254, 255], dtype=int32)
73+
74+
>>> aseg_label_lookup = sf.freesurfer.labels().extract(aseg_label_indices)
75+
76+
# sanity check
77+
>>> np.array([*aseg_label_lookup.keys()], dtype='int32') == aseg_label_indices
78+
array([ True, True, True, True, True, True, True, True, True,
79+
True, True, True, True, True, True, True, True, True,
80+
True, True, True, True, True, True, True, True, True,
81+
True, True, True, True, True, True, True, True, True,
82+
True, True, True, True, True, True, True, True, True])
83+
84+
>>> aseg_label_lookup
85+
0 Unknown 0, 0, 0, 1.00
86+
2 Left-Cerebral-White-Matter 245, 245, 245, 1.00
87+
3 Left-Cerebral-Cortex 205, 62, 78, 1.00
88+
4 Left-Lateral-Ventricle 120, 18, 134, 1.00
89+
5 Left-Inf-Lat-Vent 196, 58, 250, 1.00
90+
...
91+
85 Optic-Chiasm 234, 169, 30, 1.00
92+
251 CC_Posterior 0, 0, 64, 1.00
93+
252 CC_Mid_Posterior 0, 0, 112, 1.00
94+
253 CC_Central 0, 0, 160, 1.00
95+
254 CC_Mid_Anterior 0, 0, 208, 1.00
96+
255 CC_Anterior 0, 0, 255, 1.00
97+
```
98+
99+
## Label Recoding
100+
101+
Often times, label indices do not match up between label lookups, especially in
102+
the case we are recoding labels to merge classes. The `LabelRecoder` class
103+
provides the functionality to do this.
104+
105+
The `LabelRecoder` class has two fields:
106+
107+
- `mapping` a dictionary where the keys are the indices of the labels in the
108+
current `LabelLookup`, and the values are the index to recode it to
109+
- `target_labels` a `LabelLookup` that defines the target label names/colors.
110+
Can be `None`
111+
112+
> [!NOTE]
113+
> The `target_labels` field will be assigned to the `labels` field of a label
114+
> volume that is recoded using a `LabelRecoder`. If this field is `None`, then
115+
> the recoded segmentation volume will have no label mapping, however, that can
116+
> be assigned to after the recoded segmentation volume is created
117+
118+
Some predefined `LabelLookup` and `LabelRecoder` objects can be found under
119+
`surfa.freesurfer`
120+
121+
```python
122+
# recode the aseg volume labels to the tissue type
123+
>>> recoder = sf.freesurfer.tissue_type_recoder()
124+
>>> seg_tissue_types = sf.labels.recode(aseg,recoder)
125+
>>> seg_tissue_types.labels
126+
0 Unknown 0, 0, 0, 1.00
127+
1 Cortex 205, 62, 78, 1.00
128+
2 Subcortical-Gray-Matter 230, 148, 34, 1.00
129+
3 White-Matter 245, 245, 245, 1.00
130+
4 CSF 120, 18, 134, 1.00
131+
5 Head 150, 150, 200, 1.00
132+
6 Lesion 255, 165, 0, 1.00
133+
```
134+
135+
## Label Metrics
136+
137+
There are also some utilities for calculating the dice score and jaccard
138+
coefficients for two sets of label volumes. By default both will compute the
139+
metric for all unique labels found in the label volumes, or the user can specify
140+
a list of label indices to compute the metrics for.
141+
142+
```python
143+
# load another segmentation to compare to the aseg
144+
>>> seg = sf.load_volume('aseg_2.mgz')
145+
146+
>>> target_labels = [2,3,4,255]
147+
>>> sf.labels.dice(aseg,seg, target_labels)
148+
{2: 0.5299304443534139, 3: 0.42780355492851396, 4: 0.29737467850730337, 255: 0.21085134539038378}
149+
>>> sf.labels.jaccard(aseg,seg, target_labels)
150+
{2: 0.3604798441801161, 3: 0.2721056622851365, 4: 0.1746565581713504, 255: 0.1178500986193294}
151+
```

0 commit comments

Comments
 (0)