kaiko-eva 0.0.0.dev8__py3-none-any.whl → 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of kaiko-eva might be problematic. Click here for more details.
- eva/core/callbacks/__init__.py +2 -1
- eva/core/callbacks/config.py +143 -0
- eva/core/data/datasets/__init__.py +10 -2
- eva/core/data/datasets/embeddings/__init__.py +13 -0
- eva/core/data/datasets/{classification/embeddings.py → embeddings/base.py} +41 -43
- eva/core/data/datasets/embeddings/classification/__init__.py +10 -0
- eva/core/data/datasets/embeddings/classification/embeddings.py +66 -0
- eva/core/data/datasets/embeddings/classification/multi_embeddings.py +106 -0
- eva/core/data/transforms/__init__.py +3 -1
- eva/core/data/transforms/padding/__init__.py +5 -0
- eva/core/data/transforms/padding/pad_2d_tensor.py +38 -0
- eva/core/data/transforms/sampling/__init__.py +5 -0
- eva/core/data/transforms/sampling/sample_from_axis.py +40 -0
- eva/core/loggers/__init__.py +7 -0
- eva/core/loggers/dummy.py +38 -0
- eva/core/loggers/experimental_loggers.py +8 -0
- eva/core/loggers/log/__init__.py +5 -0
- eva/core/loggers/log/parameters.py +64 -0
- eva/core/loggers/log/utils.py +13 -0
- eva/core/models/modules/head.py +6 -11
- eva/core/models/modules/module.py +25 -1
- eva/core/trainers/_recorder.py +69 -7
- eva/core/trainers/functional.py +22 -5
- eva/core/trainers/trainer.py +20 -6
- eva/vision/__init__.py +1 -1
- eva/vision/data/datasets/__init__.py +1 -8
- eva/vision/data/datasets/_utils.py +3 -3
- eva/vision/data/datasets/classification/__init__.py +1 -8
- eva/vision/data/datasets/segmentation/base.py +20 -35
- eva/vision/data/datasets/segmentation/total_segmentator.py +88 -69
- eva/vision/models/.DS_Store +0 -0
- eva/vision/models/networks/.DS_Store +0 -0
- eva/vision/utils/convert.py +24 -0
- eva/vision/utils/io/nifti.py +10 -6
- {kaiko_eva-0.0.0.dev8.dist-info → kaiko_eva-0.0.2.dist-info}/METADATA +71 -27
- {kaiko_eva-0.0.0.dev8.dist-info → kaiko_eva-0.0.2.dist-info}/RECORD +39 -23
- {kaiko_eva-0.0.0.dev8.dist-info → kaiko_eva-0.0.2.dist-info}/WHEEL +1 -1
- eva/core/data/datasets/classification/__init__.py +0 -5
- eva/vision/data/datasets/classification/total_segmentator.py +0 -213
- {kaiko_eva-0.0.0.dev8.dist-info → kaiko_eva-0.0.2.dist-info}/entry_points.txt +0 -0
- {kaiko_eva-0.0.0.dev8.dist-info → kaiko_eva-0.0.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -6,25 +6,26 @@ from glob import glob
|
|
|
6
6
|
from typing import Callable, Dict, List, Literal, Tuple
|
|
7
7
|
|
|
8
8
|
import numpy as np
|
|
9
|
+
from torchvision import tv_tensors
|
|
9
10
|
from torchvision.datasets import utils
|
|
10
11
|
from typing_extensions import override
|
|
11
12
|
|
|
12
13
|
from eva.vision.data.datasets import _utils, _validators, structs
|
|
13
14
|
from eva.vision.data.datasets.segmentation import base
|
|
14
|
-
from eva.vision.utils import io
|
|
15
|
+
from eva.vision.utils import convert, io
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class TotalSegmentator2D(base.ImageSegmentation):
|
|
18
19
|
"""TotalSegmentator 2D segmentation dataset."""
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
_expected_dataset_lengths: Dict[str, int] = {
|
|
22
|
+
"train_small": 29892,
|
|
23
|
+
"val_small": 6480,
|
|
24
|
+
}
|
|
25
|
+
"""Dataset version and split to the expected size."""
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
_n_slices_per_image: int = 20
|
|
27
|
-
"""The amount of slices to sample per 3D CT scan image."""
|
|
27
|
+
_sample_every_n_slices: int | None = None
|
|
28
|
+
"""The amount of slices to sub-sample per 3D CT scan image."""
|
|
28
29
|
|
|
29
30
|
_resources_full: List[structs.DownloadResource] = [
|
|
30
31
|
structs.DownloadResource(
|
|
@@ -48,11 +49,10 @@ class TotalSegmentator2D(base.ImageSegmentation):
|
|
|
48
49
|
self,
|
|
49
50
|
root: str,
|
|
50
51
|
split: Literal["train", "val"] | None,
|
|
51
|
-
version: Literal["small", "full"] = "small",
|
|
52
|
+
version: Literal["small", "full"] | None = "small",
|
|
52
53
|
download: bool = False,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
image_target_transforms: Callable | None = None,
|
|
54
|
+
as_uint8: bool = True,
|
|
55
|
+
transforms: Callable | None = None,
|
|
56
56
|
) -> None:
|
|
57
57
|
"""Initialize dataset.
|
|
58
58
|
|
|
@@ -60,33 +60,26 @@ class TotalSegmentator2D(base.ImageSegmentation):
|
|
|
60
60
|
root: Path to the root directory of the dataset. The dataset will
|
|
61
61
|
be downloaded and extracted here, if it does not already exist.
|
|
62
62
|
split: Dataset split to use. If `None`, the entire dataset is used.
|
|
63
|
-
version: The version of the dataset to initialize.
|
|
63
|
+
version: The version of the dataset to initialize. If `None`, it will
|
|
64
|
+
use the files located at root as is and wont perform any checks.
|
|
64
65
|
download: Whether to download the data for the specified split.
|
|
65
66
|
Note that the download will be executed only by additionally
|
|
66
67
|
calling the :meth:`prepare_data` method and if the data does not
|
|
67
68
|
exist yet on disk.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
and transforms it.
|
|
72
|
-
image_target_transforms: A function/transforms that takes in an
|
|
73
|
-
image and a label and returns the transformed versions of both.
|
|
74
|
-
This transform happens after the `image_transforms` and
|
|
75
|
-
`target_transforms`.
|
|
69
|
+
as_uint8: Whether to convert and return the images as a 8-bit.
|
|
70
|
+
transforms: A function/transforms that takes in an image and a target
|
|
71
|
+
mask and returns the transformed versions of both.
|
|
76
72
|
"""
|
|
77
|
-
super().__init__(
|
|
78
|
-
image_transforms=image_transforms,
|
|
79
|
-
target_transforms=target_transforms,
|
|
80
|
-
image_target_transforms=image_target_transforms,
|
|
81
|
-
)
|
|
73
|
+
super().__init__(transforms=transforms)
|
|
82
74
|
|
|
83
75
|
self._root = root
|
|
84
76
|
self._split = split
|
|
85
77
|
self._version = version
|
|
86
78
|
self._download = download
|
|
79
|
+
self._as_uint8 = as_uint8
|
|
87
80
|
|
|
88
81
|
self._samples_dirs: List[str] = []
|
|
89
|
-
self._indices: List[int] = []
|
|
82
|
+
self._indices: List[Tuple[int, int]] = []
|
|
90
83
|
|
|
91
84
|
@functools.cached_property
|
|
92
85
|
@override
|
|
@@ -107,7 +100,8 @@ class TotalSegmentator2D(base.ImageSegmentation):
|
|
|
107
100
|
|
|
108
101
|
@override
|
|
109
102
|
def filename(self, index: int) -> str:
|
|
110
|
-
|
|
103
|
+
sample_idx, _ = self._indices[index]
|
|
104
|
+
sample_dir = self._samples_dirs[sample_idx]
|
|
111
105
|
return os.path.join(sample_dir, "ct.nii.gz")
|
|
112
106
|
|
|
113
107
|
@override
|
|
@@ -122,53 +116,58 @@ class TotalSegmentator2D(base.ImageSegmentation):
|
|
|
122
116
|
|
|
123
117
|
@override
|
|
124
118
|
def validate(self) -> None:
|
|
119
|
+
if self._version is None:
|
|
120
|
+
return
|
|
121
|
+
|
|
125
122
|
_validators.check_dataset_integrity(
|
|
126
123
|
self,
|
|
127
|
-
length=
|
|
124
|
+
length=self._expected_dataset_lengths.get(f"{self._split}_{self._version}", 0),
|
|
128
125
|
n_classes=117,
|
|
129
126
|
first_and_last_labels=("adrenal_gland_left", "vertebrae_T9"),
|
|
130
127
|
)
|
|
131
128
|
|
|
132
129
|
@override
|
|
133
130
|
def __len__(self) -> int:
|
|
134
|
-
return len(self._indices)
|
|
131
|
+
return len(self._indices)
|
|
135
132
|
|
|
136
133
|
@override
|
|
137
|
-
def load_image(self, index: int) ->
|
|
138
|
-
|
|
139
|
-
|
|
134
|
+
def load_image(self, index: int) -> tv_tensors.Image:
|
|
135
|
+
sample_index, slice_index = self._indices[index]
|
|
136
|
+
image_path = self._get_image_path(sample_index)
|
|
140
137
|
image_array = io.read_nifti_slice(image_path, slice_index)
|
|
141
|
-
|
|
138
|
+
if self._as_uint8:
|
|
139
|
+
image_array = convert.to_8bit(image_array)
|
|
140
|
+
image_rgb_array = image_array.repeat(3, axis=2)
|
|
141
|
+
return tv_tensors.Image(image_rgb_array.transpose(2, 0, 1))
|
|
142
142
|
|
|
143
143
|
@override
|
|
144
|
-
def load_mask(self, index: int) ->
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
def load_mask(self, index: int) -> tv_tensors.Mask:
|
|
145
|
+
sample_index, slice_index = self._indices[index]
|
|
146
|
+
masks_dir = self._get_masks_dir(sample_index)
|
|
147
147
|
mask_paths = (os.path.join(masks_dir, label + ".nii.gz") for label in self.classes)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
148
|
+
one_hot_encoded = np.concatenate(
|
|
149
|
+
[io.read_nifti_slice(path, slice_index) for path in mask_paths],
|
|
150
|
+
axis=2,
|
|
151
|
+
)
|
|
152
|
+
background_mask = one_hot_encoded.sum(axis=2, keepdims=True) == 0
|
|
153
|
+
one_hot_encoded_with_bg = np.concatenate([background_mask, one_hot_encoded], axis=2)
|
|
154
|
+
segmentation_label = np.argmax(one_hot_encoded_with_bg, axis=2)
|
|
155
|
+
return tv_tensors.Mask(segmentation_label)
|
|
155
156
|
|
|
156
|
-
def _get_image_path(self,
|
|
157
|
+
def _get_image_path(self, sample_index: int) -> str:
|
|
157
158
|
"""Returns the corresponding image path."""
|
|
158
|
-
sample_dir = self.
|
|
159
|
+
sample_dir = self._samples_dirs[sample_index]
|
|
159
160
|
return os.path.join(self._root, sample_dir, "ct.nii.gz")
|
|
160
161
|
|
|
161
|
-
def
|
|
162
|
-
"""Returns the corresponding
|
|
163
|
-
|
|
164
|
-
return self.
|
|
162
|
+
def _get_masks_dir(self, sample_index: int) -> str:
|
|
163
|
+
"""Returns the directory of the corresponding masks."""
|
|
164
|
+
sample_dir = self._samples_dirs[sample_index]
|
|
165
|
+
return os.path.join(self._root, sample_dir, "segmentations")
|
|
165
166
|
|
|
166
|
-
def
|
|
167
|
-
"""Returns the
|
|
168
|
-
image_path = self._get_image_path(
|
|
169
|
-
|
|
170
|
-
slice_indices = np.linspace(0, total_slices - 1, num=self._n_slices_per_image, dtype=int)
|
|
171
|
-
return slice_indices[index % self._n_slices_per_image]
|
|
167
|
+
def _get_number_of_slices_per_sample(self, sample_index: int) -> int:
|
|
168
|
+
"""Returns the total amount of slices of a sample."""
|
|
169
|
+
image_path = self._get_image_path(sample_index)
|
|
170
|
+
return io.fetch_total_nifti_slices(image_path)
|
|
172
171
|
|
|
173
172
|
def _fetch_samples_dirs(self) -> List[str]:
|
|
174
173
|
"""Returns the name of all the samples of all the splits of the dataset."""
|
|
@@ -179,31 +178,51 @@ class TotalSegmentator2D(base.ImageSegmentation):
|
|
|
179
178
|
]
|
|
180
179
|
return sorted(sample_filenames)
|
|
181
180
|
|
|
182
|
-
def
|
|
183
|
-
"""
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
"
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
181
|
+
def _get_split_indices(self) -> List[int]:
|
|
182
|
+
"""Returns the samples indices that corresponding the dataset split and version."""
|
|
183
|
+
key = f"{self._split}_{self._version}"
|
|
184
|
+
match key:
|
|
185
|
+
case "train_small":
|
|
186
|
+
index_ranges = [(0, 83)]
|
|
187
|
+
case "val_small":
|
|
188
|
+
index_ranges = [(83, 102)]
|
|
189
|
+
case _:
|
|
190
|
+
index_ranges = [(0, len(self._samples_dirs))]
|
|
192
191
|
|
|
193
192
|
return _utils.ranges_to_indices(index_ranges)
|
|
194
193
|
|
|
194
|
+
def _create_indices(self) -> List[Tuple[int, int]]:
|
|
195
|
+
"""Builds the dataset indices for the specified split.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
A list of tuples, where the first value indicates the
|
|
199
|
+
sample index which the second its corresponding slice
|
|
200
|
+
index.
|
|
201
|
+
"""
|
|
202
|
+
indices = [
|
|
203
|
+
(sample_idx, slide_idx)
|
|
204
|
+
for sample_idx in self._get_split_indices()
|
|
205
|
+
for slide_idx in range(self._get_number_of_slices_per_sample(sample_idx))
|
|
206
|
+
if slide_idx % (self._sample_every_n_slices or 1) == 0
|
|
207
|
+
]
|
|
208
|
+
return indices
|
|
209
|
+
|
|
195
210
|
def _download_dataset(self) -> None:
|
|
196
211
|
"""Downloads the dataset."""
|
|
197
212
|
dataset_resources = {
|
|
198
213
|
"small": self._resources_small,
|
|
199
214
|
"full": self._resources_full,
|
|
200
|
-
None: (0, 103),
|
|
201
215
|
}
|
|
202
|
-
resources = dataset_resources.get(self._version)
|
|
216
|
+
resources = dataset_resources.get(self._version or "")
|
|
203
217
|
if resources is None:
|
|
204
|
-
raise ValueError(
|
|
218
|
+
raise ValueError(
|
|
219
|
+
f"Can't download data version '{self._version}'. Use 'small' or 'full'."
|
|
220
|
+
)
|
|
205
221
|
|
|
206
222
|
for resource in resources:
|
|
223
|
+
if os.path.isdir(self._root):
|
|
224
|
+
continue
|
|
225
|
+
|
|
207
226
|
utils.download_and_extract_archive(
|
|
208
227
|
resource.url,
|
|
209
228
|
download_root=self._root,
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Image conversion related functionalities."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import numpy.typing as npt
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def to_8bit(image_array: npt.NDArray[Any]) -> npt.NDArray[np.uint8]:
|
|
10
|
+
"""Casts an image of higher bit image (i.e. 16bit) to 8bit.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
image_array: The image array to convert.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
The image as normalized as a 8-bit format.
|
|
17
|
+
"""
|
|
18
|
+
if np.issubdtype(image_array.dtype, np.integer):
|
|
19
|
+
image_array = image_array.astype(np.float64)
|
|
20
|
+
|
|
21
|
+
image_scaled_array = image_array - image_array.min()
|
|
22
|
+
image_scaled_array /= image_scaled_array.max()
|
|
23
|
+
image_scaled_array *= 255
|
|
24
|
+
return image_scaled_array.astype(np.uint8)
|
eva/vision/utils/io/nifti.py
CHANGED
|
@@ -8,16 +8,19 @@ import numpy.typing as npt
|
|
|
8
8
|
from eva.vision.utils.io import _utils
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
def read_nifti_slice(
|
|
11
|
+
def read_nifti_slice(
|
|
12
|
+
path: str, slice_index: int, *, use_storage_dtype: bool = True
|
|
13
|
+
) -> npt.NDArray[Any]:
|
|
12
14
|
"""Reads and loads a NIfTI image from a file path as `uint8`.
|
|
13
15
|
|
|
14
16
|
Args:
|
|
15
17
|
path: The path to the NIfTI file.
|
|
16
|
-
slice_index: The image slice index to return.
|
|
17
|
-
|
|
18
|
+
slice_index: The image slice index to return.
|
|
19
|
+
use_storage_dtype: Whether to cast the raw image
|
|
20
|
+
array to the inferred type.
|
|
18
21
|
|
|
19
22
|
Returns:
|
|
20
|
-
The image as a numpy array.
|
|
23
|
+
The image as a numpy array (height, width, channels).
|
|
21
24
|
|
|
22
25
|
Raises:
|
|
23
26
|
FileExistsError: If the path does not exist or it is unreachable.
|
|
@@ -25,10 +28,11 @@ def read_nifti_slice(path: str, slice_index: int) -> npt.NDArray[Any]:
|
|
|
25
28
|
"""
|
|
26
29
|
_utils.check_file(path)
|
|
27
30
|
image_data = nib.load(path) # type: ignore
|
|
28
|
-
dtype = image_data.get_data_dtype() # type: ignore
|
|
29
31
|
image_slice = image_data.slicer[:, :, slice_index : slice_index + 1] # type: ignore
|
|
30
32
|
image_array = image_slice.get_fdata()
|
|
31
|
-
|
|
33
|
+
if use_storage_dtype:
|
|
34
|
+
image_array = image_array.astype(image_data.get_data_dtype()) # type: ignore
|
|
35
|
+
return image_array
|
|
32
36
|
|
|
33
37
|
|
|
34
38
|
def fetch_total_nifti_slices(path: str) -> int:
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: kaiko-eva
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.2
|
|
4
4
|
Summary: Evaluation Framework for oncology foundation models.
|
|
5
|
-
Keywords: machine-learning
|
|
6
|
-
Author-Email: Ioannis Gatopoulos <ioannis@kaiko.ai>,
|
|
7
|
-
Maintainer-Email: Ioannis Gatopoulos <ioannis@kaiko.ai>,
|
|
5
|
+
Keywords: machine-learning,evaluation-framework,oncology,foundation-models
|
|
6
|
+
Author-Email: Ioannis Gatopoulos <ioannis@kaiko.ai>, =?utf-8?q?Nicolas_K=C3=A4nzig?= <nicolas@kaiko.ai>, Roman Moser <roman@kaiko.ai>
|
|
7
|
+
Maintainer-Email: Ioannis Gatopoulos <ioannis@kaiko.ai>, =?utf-8?q?Nicolas_K=C3=A4nzig?= <nicolas@kaiko.ai>, Roman Moser <roman@kaiko.ai>
|
|
8
8
|
License: Apache License
|
|
9
9
|
Version 2.0, January 2004
|
|
10
10
|
http://www.apache.org/licenses/
|
|
@@ -215,15 +215,17 @@ Project-URL: Homepage, https://kaiko-ai.github.io/eva/dev/
|
|
|
215
215
|
Project-URL: Repository, https://github.com/kaiko-ai/eva
|
|
216
216
|
Project-URL: Documentation, https://kaiko-ai.github.io/eva/dev/
|
|
217
217
|
Requires-Python: >=3.10
|
|
218
|
-
Requires-Dist:
|
|
219
|
-
Requires-Dist:
|
|
218
|
+
Requires-Dist: torch==2.3.0
|
|
219
|
+
Requires-Dist: lightning>=2.2.2
|
|
220
|
+
Requires-Dist: jsonargparse[omegaconf]==4.28
|
|
220
221
|
Requires-Dist: tensorboard>=2.16.2
|
|
221
222
|
Requires-Dist: loguru>=0.7.2
|
|
222
223
|
Requires-Dist: pandas>=2.2.0
|
|
223
224
|
Requires-Dist: transformers>=4.38.2
|
|
224
225
|
Requires-Dist: onnxruntime>=1.17.1
|
|
225
|
-
Requires-Dist: onnx>=1.
|
|
226
|
+
Requires-Dist: onnx>=1.16.0
|
|
226
227
|
Requires-Dist: toolz>=0.12.1
|
|
228
|
+
Requires-Dist: rich>=13.7.1
|
|
227
229
|
Requires-Dist: h5py>=3.10.0; extra == "vision"
|
|
228
230
|
Requires-Dist: nibabel>=5.2.0; extra == "vision"
|
|
229
231
|
Requires-Dist: opencv-python-headless>=4.9.0.80; extra == "vision"
|
|
@@ -240,15 +242,19 @@ Description-Content-Type: text/markdown
|
|
|
240
242
|
|
|
241
243
|
<div align="center">
|
|
242
244
|
|
|
243
|
-
<
|
|
245
|
+
<br />
|
|
246
|
+
|
|
247
|
+
<img src="https://github.com/kaiko-ai/eva/blob/main/docs/images/eva-logo.png?raw=true" width="340">
|
|
244
248
|
|
|
249
|
+
<br />
|
|
245
250
|
<br />
|
|
246
251
|
|
|
247
252
|
_Oncology FM Evaluation Framework by kaiko.ai_
|
|
248
253
|
|
|
249
254
|
[](https://pypi.python.org/pypi/kaiko-eva)
|
|
250
|
-
[](https://kaiko-ai.github.io/eva/latest)
|
|
256
|
+
[](https://github.com/kaiko-ai/eva#license)<br>
|
|
257
|
+
[](https://openreview.net/forum?id=FNBQOPj18N¬eId=FNBQOPj18N)
|
|
252
258
|
|
|
253
259
|
<p align="center">
|
|
254
260
|
<a href="https://github.com/kaiko-ai/eva#installation">Installation</a> •
|
|
@@ -264,7 +270,8 @@ _Oncology FM Evaluation Framework by kaiko.ai_
|
|
|
264
270
|
|
|
265
271
|
<br />
|
|
266
272
|
|
|
267
|
-
_`eva`_ is an evaluation framework for oncology foundation models (FMs) by [kaiko.ai](https://kaiko.ai/).
|
|
273
|
+
_`eva`_ is an evaluation framework for oncology foundation models (FMs) by [kaiko.ai](https://kaiko.ai/).
|
|
274
|
+
Check out the [documentation](https://kaiko-ai.github.io/eva/) for more information.
|
|
268
275
|
|
|
269
276
|
### Highlights:
|
|
270
277
|
- Easy and reliable benchmark of Oncology FMs
|
|
@@ -298,18 +305,33 @@ eva --version
|
|
|
298
305
|
|
|
299
306
|
## How To Use
|
|
300
307
|
|
|
301
|
-
|
|
308
|
+
_`eva`_ can be used directly from the terminal as a CLI tool as follows:
|
|
302
309
|
```sh
|
|
303
310
|
eva {fit,predict,predict_fit} --config url/or/path/to/the/config.yaml
|
|
304
311
|
```
|
|
305
312
|
|
|
306
|
-
|
|
313
|
+
When used as a CLI tool, _`eva`_ supports configuration files (`.yaml`) as an argument to define its functionality.
|
|
314
|
+
Native supported configs can be found at the [configs](https://github.com/kaiko-ai/eva/tree/main/configs) directory
|
|
315
|
+
of the repo. Apart from cloning the repo, you can download the latest config folder as `.zip` from your browser from
|
|
316
|
+
[here](https://download-directory.github.io/?url=https://github.com/kaiko-ai/eva/tree/main/configs). Alternatively,
|
|
317
|
+
from a specific release the configs can be downloaded from the terminal as follows:
|
|
318
|
+
```sh
|
|
319
|
+
curl -LO https://github.com/kaiko-ai/eva/releases/download/0.0.1/configs.zip | unzip configs
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
For example, to perform a downstream evaluation of DINO ViT-S/16 on the BACH dataset with
|
|
323
|
+
linear probing by first inferring the embeddings and performing 5 sequential fits, execute:
|
|
307
324
|
```sh
|
|
325
|
+
# from a locally stored config file
|
|
326
|
+
eva predict_fit --config ./configs/vision/dino_vit/offline/bach.yaml
|
|
327
|
+
|
|
328
|
+
# from a remote stored config file
|
|
308
329
|
eva predict_fit --config https://raw.githubusercontent.com/kaiko-ai/eva/main/configs/vision/dino_vit/offline/bach.yaml
|
|
309
330
|
```
|
|
310
331
|
|
|
311
332
|
> [!NOTE]
|
|
312
|
-
> All the datasets that support automatic download in the repo have by default the option to automatically download set to false.
|
|
333
|
+
> All the datasets that support automatic download in the repo have by default the option to automatically download set to false.
|
|
334
|
+
> For automatic download you have to manually set download=true.
|
|
313
335
|
|
|
314
336
|
|
|
315
337
|
To view all the possibles, execute:
|
|
@@ -317,11 +339,12 @@ To view all the possibles, execute:
|
|
|
317
339
|
eva --help
|
|
318
340
|
```
|
|
319
341
|
|
|
320
|
-
For more information, please refer to the [documentation](https://kaiko-ai.github.io/eva/dev/user-guide/tutorials/offline_vs_online/)
|
|
342
|
+
For more information, please refer to the [documentation](https://kaiko-ai.github.io/eva/dev/user-guide/tutorials/offline_vs_online/)
|
|
343
|
+
and [tutorials](https://kaiko-ai.github.io/eva/dev/user-guide/advanced/replicate_evaluations/).
|
|
321
344
|
|
|
322
345
|
## Benchmarks
|
|
323
346
|
|
|
324
|
-
In this section you will find model benchmarks which were generated with
|
|
347
|
+
In this section you will find model benchmarks which were generated with _`eva`_.
|
|
325
348
|
|
|
326
349
|
### Table I: WSI patch-level benchmark
|
|
327
350
|
|
|
@@ -334,13 +357,15 @@ In this section you will find model benchmarks which were generated with _eva_.
|
|
|
334
357
|
| ViT-S/16 _(random)_ <sup>[1]</sup> | 0.410 | 0.617 | 0.501 | 0.753 | 0.728 |
|
|
335
358
|
| ViT-S/16 _(ImageNet)_ <sup>[1]</sup> | 0.695 | 0.935 | 0.831 | 0.864 | 0.849 |
|
|
336
359
|
| ViT-B/8 _(ImageNet)_ <sup>[1]</sup> | 0.710 | 0.939 | 0.814 | 0.870 | 0.856 |
|
|
360
|
+
| ViT-L/14 _(ImageNet)_ <sup>[1]</sup> | 0.707 | 0.916 | 0.832 | 0.873 | 0.888 |
|
|
337
361
|
| DINO<sub>(p=16)</sub> <sup>[2]</sup> | 0.801 | 0.934 | 0.768 | 0.889 | 0.895 |
|
|
338
362
|
| Phikon <sup>[3]</sup> | 0.725 | 0.935 | 0.777 | 0.912 | 0.915 |
|
|
339
|
-
|
|
|
340
|
-
| ViT-S/
|
|
341
|
-
| ViT-
|
|
342
|
-
| ViT-B/
|
|
343
|
-
| ViT-
|
|
363
|
+
| UNI <sup>[4]</sup> | 0.814 | 0.950 | 0.837 | 0.936 | 0.938 |
|
|
364
|
+
| ViT-S/16 _(kaiko.ai)_ <sup>[5]</sup> | 0.797 | 0.943 | 0.828 | 0.903 | 0.893 |
|
|
365
|
+
| ViT-S/8 _(kaiko.ai)_ <sup>[5]</sup> | 0.834 | 0.946 | 0.832 | 0.897 | 0.887 |
|
|
366
|
+
| ViT-B/16 _(kaiko.ai)_ <sup>[5]</sup> | 0.810 | 0.960 | 0.826 | 0.900 | 0.898 |
|
|
367
|
+
| ViT-B/8 _(kaiko.ai)_ <sup>[5]</sup> | 0.865 | 0.956 | 0.809 | 0.913 | 0.921 |
|
|
368
|
+
| ViT-L/14 _(kaiko.ai)_ <sup>[5]</sup> | 0.870 | 0.930 | 0.809 | 0.908 | 0.898 |
|
|
344
369
|
|
|
345
370
|
_Table I: Linear probing evaluation of FMs on patch-level downstream datasets.<br> We report averaged balanced accuracy
|
|
346
371
|
over 5 runs, with an average standard deviation of ±0.003._
|
|
@@ -350,14 +375,16 @@ over 5 runs, with an average standard deviation of ±0.003._
|
|
|
350
375
|
<br />
|
|
351
376
|
|
|
352
377
|
_References_:
|
|
353
|
-
1. _"Emerging properties in self-supervised vision transformers”_
|
|
354
|
-
2. _"Benchmarking self-supervised learning on diverse pathology datasets”_
|
|
355
|
-
3. _"Scaling self-supervised learning for histopathology with masked image modeling”_
|
|
356
|
-
4. _"
|
|
378
|
+
1. _"Emerging properties in self-supervised vision transformers”_, [arXiv](https://arxiv.org/abs/2104.14294)
|
|
379
|
+
2. _"Benchmarking self-supervised learning on diverse pathology datasets”_, [arXiv](https://arxiv.org/abs/2212.04690)
|
|
380
|
+
3. _"Scaling self-supervised learning for histopathology with masked image modeling”_, [medRxiv](https://www.medrxiv.org/content/10.1101/2023.07.21.23292757v1)
|
|
381
|
+
4. _"A General-Purpose Self-Supervised Model for Computational Pathology”_, [arXiv](https://arxiv.org/abs/2308.15474)
|
|
382
|
+
5. _"Towards Training Large-Scale Pathology Foundation Models: from TCGA to Hospital Scale”_, [arXiv](https://arxiv.org/pdf/2404.15217)
|
|
357
383
|
|
|
358
384
|
## Contributing
|
|
359
385
|
|
|
360
|
-
|
|
386
|
+
_`eva`_ is an open source project and welcomes contributions of all kinds. Please checkout the [developer](./docs/DEVELOPER_GUIDE.md)
|
|
387
|
+
and [contributing guide](./docs/CONTRIBUTING.md) for help on how to do so.
|
|
361
388
|
|
|
362
389
|
All contributors must follow the [code of conduct](./docs/CODE_OF_CONDUCT.md).
|
|
363
390
|
|
|
@@ -381,7 +408,24 @@ Our codebase is built using multiple opensource contributions
|
|
|
381
408
|
|
|
382
409
|
</div>
|
|
383
410
|
|
|
384
|
-
|
|
411
|
+
|
|
412
|
+
## Citation
|
|
413
|
+
|
|
414
|
+
If you find this repository useful, please consider giving a star ⭐ and adding the following citation:
|
|
415
|
+
|
|
416
|
+
```
|
|
417
|
+
@inproceedings{
|
|
418
|
+
kaiko.ai2024eva,
|
|
419
|
+
title={eva: Evaluation framework for pathology foundation models},
|
|
420
|
+
author={kaiko.ai and Ioannis Gatopoulos and Nicolas K{\"a}nzig and Roman Moser and Sebastian Ot{\'a}lora},
|
|
421
|
+
booktitle={Medical Imaging with Deep Learning},
|
|
422
|
+
year={2024},
|
|
423
|
+
url={https://openreview.net/forum?id=FNBQOPj18N}
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
<br />
|
|
428
|
+
|
|
385
429
|
<div align="center">
|
|
386
430
|
<img src="https://github.com/kaiko-ai/eva/blob/main/docs/images/kaiko-logo.png?raw=true" width="200">
|
|
387
431
|
</div>
|