kaiko-eva 0.1.7__py3-none-any.whl → 0.2.0__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.

@@ -12,7 +12,7 @@ class EmbeddingsClassificationDataset(embeddings_base.EmbeddingsDataset[torch.Te
12
12
  """Embeddings dataset class for classification tasks."""
13
13
 
14
14
  @override
15
- def _load_embeddings(self, index: int) -> torch.Tensor:
15
+ def load_embeddings(self, index: int) -> torch.Tensor:
16
16
  filename = self.filename(index)
17
17
  embeddings_path = os.path.join(self._root, filename)
18
18
  tensor = torch.load(embeddings_path, map_location="cpu")
@@ -25,7 +25,7 @@ class EmbeddingsClassificationDataset(embeddings_base.EmbeddingsDataset[torch.Te
25
25
  return tensor.squeeze(0)
26
26
 
27
27
  @override
28
- def _load_target(self, index: int) -> torch.Tensor:
28
+ def load_target(self, index: int) -> torch.Tensor:
29
29
  target = self._data.at[index, self._column_mapping["target"]]
30
30
  return torch.tensor(target, dtype=torch.int64)
31
31
 
@@ -66,7 +66,7 @@ class MultiEmbeddingsClassificationDataset(embeddings_base.EmbeddingsDataset[tor
66
66
  self._multi_ids = list(self._data[self._column_mapping["multi_id"]].unique())
67
67
 
68
68
  @override
69
- def _load_embeddings(self, index: int) -> torch.Tensor:
69
+ def load_embeddings(self, index: int) -> torch.Tensor:
70
70
  """Loads and stacks all embedding corresponding to the `index`'th multi_id."""
71
71
  # Get all embeddings for the given index (multi_id)
72
72
  multi_id = self._multi_ids[index]
@@ -89,7 +89,7 @@ class MultiEmbeddingsClassificationDataset(embeddings_base.EmbeddingsDataset[tor
89
89
  return embeddings
90
90
 
91
91
  @override
92
- def _load_target(self, index: int) -> np.ndarray:
92
+ def load_target(self, index: int) -> np.ndarray:
93
93
  """Returns the target corresponding to the `index`'th multi_id.
94
94
 
95
95
  This method assumes that all the embeddings corresponding to the same `multi_id`
@@ -98,12 +98,12 @@ class EmbeddingsDataset(base.Dataset, Generic[TargetType]):
98
98
  Returns:
99
99
  A data sample and its target.
100
100
  """
101
- embeddings = self._load_embeddings(index)
102
- target = self._load_target(index)
101
+ embeddings = self.load_embeddings(index)
102
+ target = self.load_target(index)
103
103
  return self._apply_transforms(embeddings, target)
104
104
 
105
105
  @abc.abstractmethod
106
- def _load_embeddings(self, index: int) -> torch.Tensor:
106
+ def load_embeddings(self, index: int) -> torch.Tensor:
107
107
  """Returns the `index`'th embedding sample.
108
108
 
109
109
  Args:
@@ -114,7 +114,7 @@ class EmbeddingsDataset(base.Dataset, Generic[TargetType]):
114
114
  """
115
115
 
116
116
  @abc.abstractmethod
117
- def _load_target(self, index: int) -> TargetType:
117
+ def load_target(self, index: int) -> TargetType:
118
118
  """Returns the `index`'th target sample.
119
119
 
120
120
  Args:
@@ -4,6 +4,7 @@ from collections import defaultdict
4
4
  from typing import Dict, Iterator, List
5
5
 
6
6
  import numpy as np
7
+ from loguru import logger
7
8
  from typing_extensions import override
8
9
 
9
10
  from eva.core.data import datasets
@@ -33,6 +34,7 @@ class BalancedSampler(SamplerWithDataSource[int]):
33
34
  self._replacement = replacement
34
35
  self._class_indices: Dict[int, List[int]] = defaultdict(list)
35
36
  self._random_generator = np.random.default_rng(seed)
37
+ self._indices: List[int] = []
36
38
 
37
39
  def __len__(self) -> int:
38
40
  """Returns the total number of samples."""
@@ -44,18 +46,7 @@ class BalancedSampler(SamplerWithDataSource[int]):
44
46
  Returns:
45
47
  Iterator yielding dataset indices.
46
48
  """
47
- indices = []
48
-
49
- for class_idx in self._class_indices:
50
- class_indices = self._class_indices[class_idx]
51
- sampled_indices = self._random_generator.choice(
52
- class_indices, size=self._num_samples, replace=self._replacement
53
- ).tolist()
54
- indices.extend(sampled_indices)
55
-
56
- self._random_generator.shuffle(indices)
57
-
58
- return iter(indices)
49
+ return iter(self._indices)
59
50
 
60
51
  @override
61
52
  def set_dataset(self, data_source: datasets.MapDataset):
@@ -72,13 +63,13 @@ class BalancedSampler(SamplerWithDataSource[int]):
72
63
  self._make_indices()
73
64
 
74
65
  def _make_indices(self):
75
- """Builds indices for each class in the dataset."""
66
+ """Samples the indices for each class in the dataset."""
76
67
  self._class_indices.clear()
77
-
78
- for idx in tqdm(
79
- range(len(self.data_source)), desc="Fetching class indices for balanced sampler"
80
- ):
81
- _, target, _ = DataSample(*self.data_source[idx])
68
+ for idx in tqdm(range(len(self.data_source)), desc="Fetching class indices for sampler"):
69
+ if hasattr(self.data_source, "load_target"):
70
+ target = self.data_source.load_target(idx) # type: ignore
71
+ else:
72
+ _, target, _ = DataSample(*self.data_source[idx])
82
73
  if target is None:
83
74
  raise ValueError("The dataset must return non-empty targets.")
84
75
  if target.numel() != 1:
@@ -94,3 +85,13 @@ class BalancedSampler(SamplerWithDataSource[int]):
94
85
  f"Class {class_idx} has only {len(indices)} samples, "
95
86
  f"which is less than the required {self._num_samples} samples."
96
87
  )
88
+
89
+ self._indices = []
90
+ for class_idx in self._class_indices:
91
+ class_indices = self._class_indices[class_idx]
92
+ sampled_indices = self._random_generator.choice(
93
+ class_indices, size=self._num_samples, replace=self._replacement
94
+ ).tolist()
95
+ self._indices.extend(sampled_indices)
96
+ self._random_generator.shuffle(self._indices)
97
+ logger.debug(f"Sampled indices: {self._indices}")
@@ -0,0 +1,33 @@
1
+ # type: ignore
2
+ """Utility functions for logging with Weights & Biases."""
3
+
4
+ from typing import Any, Dict
5
+
6
+ from loguru import logger
7
+
8
+
9
+ def rename_active_run(name: str) -> None:
10
+ """Renames the current run."""
11
+ import wandb
12
+
13
+ if wandb.run:
14
+ wandb.run.name = name
15
+ wandb.run.save()
16
+ else:
17
+ logger.warning("No active wandb run found that could be renamed.")
18
+
19
+
20
+ def init_run(name: str, init_kwargs: Dict[str, Any]) -> None:
21
+ """Initializes a new run. If there is an active run, it will be renamed and reused."""
22
+ import wandb
23
+
24
+ init_kwargs["name"] = name
25
+ rename_active_run(name)
26
+ wandb.init(**init_kwargs)
27
+
28
+
29
+ def finish_run() -> None:
30
+ """Finish the current run."""
31
+ import wandb
32
+
33
+ wandb.finish()
@@ -1,4 +1,4 @@
1
- """"Neural Network Head Module."""
1
+ """Neural Network Head Module."""
2
2
 
3
3
  from typing import Any, Callable, Dict
4
4
 
@@ -39,7 +39,7 @@ def run_evaluation_session(
39
39
  base_trainer,
40
40
  base_model,
41
41
  datamodule,
42
- run_id=f"run_{run_index}",
42
+ run_id=run_index,
43
43
  verbose=not verbose,
44
44
  )
45
45
  recorder.update(validation_scores, test_scores)
@@ -51,7 +51,7 @@ def run_evaluation(
51
51
  base_model: modules.ModelModule,
52
52
  datamodule: datamodules.DataModule,
53
53
  *,
54
- run_id: str | None = None,
54
+ run_id: int | None = None,
55
55
  verbose: bool = True,
56
56
  ) -> Tuple[_EVALUATE_OUTPUT, _EVALUATE_OUTPUT | None]:
57
57
  """Fits and evaluates a model out-of-place.
@@ -61,7 +61,6 @@ def run_evaluation(
61
61
  base_model: The model module to use but not modify.
62
62
  datamodule: The data module.
63
63
  run_id: The run id to be appended to the output log directory.
64
- If `None`, it will use the log directory of the trainer as is.
65
64
  verbose: Whether to print the validation and test metrics
66
65
  in the end of the training.
67
66
 
@@ -70,8 +69,12 @@ def run_evaluation(
70
69
  """
71
70
  trainer, model = _utils.clone(base_trainer, base_model)
72
71
  model.configure_model()
73
- trainer.setup_log_dirs(run_id or "")
74
- return fit_and_validate(trainer, model, datamodule, verbose=verbose)
72
+
73
+ trainer.init_logger_run(run_id)
74
+ results = fit_and_validate(trainer, model, datamodule, verbose=verbose)
75
+ trainer.finish_logger_run(run_id)
76
+
77
+ return results
75
78
 
76
79
 
77
80
  def fit_and_validate(
@@ -12,6 +12,7 @@ from typing_extensions import override
12
12
 
13
13
  from eva.core import loggers as eva_loggers
14
14
  from eva.core.data import datamodules
15
+ from eva.core.loggers.utils import wandb as wandb_utils
15
16
  from eva.core.models import modules
16
17
  from eva.core.trainers import _logging, functional
17
18
 
@@ -53,7 +54,7 @@ class Trainer(pl_trainer.Trainer):
53
54
  self._session_id: str = _logging.generate_session_id()
54
55
  self._log_dir: str = self.default_log_dir
55
56
 
56
- self.setup_log_dirs()
57
+ self.init_logger_run(0)
57
58
 
58
59
  @property
59
60
  def default_log_dir(self) -> str:
@@ -65,31 +66,45 @@ class Trainer(pl_trainer.Trainer):
65
66
  def log_dir(self) -> str | None:
66
67
  return self.strategy.broadcast(self._log_dir)
67
68
 
68
- def setup_log_dirs(self, subdirectory: str = "") -> None:
69
- """Setups the logging directory of the trainer and experimental loggers in-place.
69
+ def init_logger_run(self, run_id: int | None) -> None:
70
+ """Setup the loggers & log directories when starting a new run.
70
71
 
71
72
  Args:
72
- subdirectory: Whether to append a subdirectory to the output log.
73
+ run_id: The id of the current run.
73
74
  """
75
+ subdirectory = f"run_{run_id}" if run_id is not None else ""
74
76
  self._log_dir = os.path.join(self.default_root_dir, self._session_id, subdirectory)
75
77
 
76
78
  enabled_loggers = []
77
- if isinstance(self.loggers, list) and len(self.loggers) > 0:
78
- for logger in self.loggers:
79
- if isinstance(logger, (pl_loggers.CSVLogger, pl_loggers.TensorBoardLogger)):
80
- if not cloud_io._is_local_file_protocol(self.default_root_dir):
81
- loguru.logger.warning(
82
- f"Skipped {type(logger).__name__} as remote storage is not supported."
83
- )
84
- continue
85
- else:
86
- logger._root_dir = self.default_root_dir
87
- logger._name = self._session_id
88
- logger._version = subdirectory
89
- enabled_loggers.append(logger)
79
+ for logger in self.loggers or []:
80
+ if isinstance(logger, (pl_loggers.CSVLogger, pl_loggers.TensorBoardLogger)):
81
+ if not cloud_io._is_local_file_protocol(self.default_root_dir):
82
+ loguru.logger.warning(
83
+ f"Skipped {type(logger).__name__} as remote storage is not supported."
84
+ )
85
+ continue
86
+ else:
87
+ logger._root_dir = self.default_root_dir
88
+ logger._name = self._session_id
89
+ logger._version = subdirectory
90
+ elif isinstance(logger, pl_loggers.WandbLogger):
91
+ task_name = self.default_root_dir.split("/")[-1]
92
+ run_name = os.getenv("WANDB_RUN_NAME", f"{task_name}_{self._session_id}")
93
+ wandb_utils.init_run(f"{run_name}_{run_id}", logger._wandb_init)
94
+ enabled_loggers.append(logger)
90
95
 
91
96
  self._loggers = enabled_loggers or [eva_loggers.DummyLogger(self._log_dir)]
92
97
 
98
+ def finish_logger_run(self, run_id: int | None) -> None:
99
+ """Finish the current run in the enabled loggers.
100
+
101
+ Args:
102
+ run_id: The id of the current run.
103
+ """
104
+ for logger in self.loggers or []:
105
+ if isinstance(logger, pl_loggers.WandbLogger):
106
+ wandb_utils.finish_run()
107
+
93
108
  def run_evaluation_session(
94
109
  self,
95
110
  model: modules.ModelModule,
@@ -2,12 +2,16 @@
2
2
 
3
3
  from eva.vision.data.datasets.classification import (
4
4
  BACH,
5
+ BRACS,
5
6
  CRC,
6
7
  MHIST,
7
8
  PANDA,
9
+ BreaKHis,
8
10
  Camelyon16,
11
+ GleasonArvaniti,
9
12
  PANDASmall,
10
13
  PatchCamelyon,
14
+ UniToPatho,
11
15
  WsiClassificationDataset,
12
16
  )
13
17
  from eva.vision.data.datasets.segmentation import (
@@ -26,12 +30,16 @@ from eva.vision.data.datasets.wsi import MultiWsiDataset, WsiDataset
26
30
  __all__ = [
27
31
  "BACH",
28
32
  "BCSS",
33
+ "BreaKHis",
34
+ "BRACS",
29
35
  "CRC",
36
+ "GleasonArvaniti",
30
37
  "MHIST",
31
38
  "PANDA",
32
39
  "PANDASmall",
33
40
  "Camelyon16",
34
41
  "PatchCamelyon",
42
+ "UniToPatho",
35
43
  "WsiClassificationDataset",
36
44
  "CoNSeP",
37
45
  "EmbeddingsSegmentationDataset",
@@ -1,18 +1,27 @@
1
1
  """Image classification datasets API."""
2
2
 
3
3
  from eva.vision.data.datasets.classification.bach import BACH
4
+ from eva.vision.data.datasets.classification.bracs import BRACS
5
+ from eva.vision.data.datasets.classification.breakhis import BreaKHis
4
6
  from eva.vision.data.datasets.classification.camelyon16 import Camelyon16
5
7
  from eva.vision.data.datasets.classification.crc import CRC
8
+ from eva.vision.data.datasets.classification.gleason_arvaniti import GleasonArvaniti
6
9
  from eva.vision.data.datasets.classification.mhist import MHIST
7
10
  from eva.vision.data.datasets.classification.panda import PANDA, PANDASmall
8
11
  from eva.vision.data.datasets.classification.patch_camelyon import PatchCamelyon
12
+ from eva.vision.data.datasets.classification.unitopatho import UniToPatho
9
13
  from eva.vision.data.datasets.classification.wsi import WsiClassificationDataset
10
14
 
11
15
  __all__ = [
12
16
  "BACH",
17
+ "BreaKHis",
18
+ "BRACS",
19
+ "Camelyon16",
13
20
  "CRC",
21
+ "GleasonArvaniti",
14
22
  "MHIST",
15
23
  "PatchCamelyon",
24
+ "UniToPatho",
16
25
  "WsiClassificationDataset",
17
26
  "PANDA",
18
27
  "PANDASmall",
@@ -0,0 +1,112 @@
1
+ """BRACS dataset class."""
2
+
3
+ import os
4
+ from typing import Callable, Dict, List, Literal, Tuple
5
+
6
+ import torch
7
+ from torchvision import tv_tensors
8
+ from torchvision.datasets import folder
9
+ from typing_extensions import override
10
+
11
+ from eva.vision.data.datasets import _validators
12
+ from eva.vision.data.datasets.classification import base
13
+ from eva.vision.utils import io
14
+
15
+
16
+ class BRACS(base.ImageClassification):
17
+ """Dataset class for BRACS images and corresponding targets."""
18
+
19
+ _expected_dataset_lengths: Dict[str, int] = {
20
+ "train": 3657,
21
+ "val": 312,
22
+ "test": 570,
23
+ }
24
+ """Expected dataset lengths for the splits and complete dataset."""
25
+
26
+ _license: str = "CC BY-NC 4.0 (https://creativecommons.org/licenses/by-nc/4.0/)"
27
+ """Dataset license."""
28
+
29
+ def __init__(
30
+ self,
31
+ root: str,
32
+ split: Literal["train", "val", "test"],
33
+ transforms: Callable | None = None,
34
+ ) -> None:
35
+ """Initializes the dataset.
36
+
37
+ Args:
38
+ root: Path to the root directory of the dataset.
39
+ split: Dataset split to use.
40
+ transforms: A function/transform which returns a transformed
41
+ version of the raw data samples.
42
+ """
43
+ super().__init__(transforms=transforms)
44
+
45
+ self._root = root
46
+ self._split = split
47
+
48
+ self._samples: List[Tuple[str, int]] = []
49
+
50
+ @property
51
+ @override
52
+ def classes(self) -> List[str]:
53
+ return ["0_N", "1_PB", "2_UDH", "3_FEA", "4_ADH", "5_DCIS", "6_IC"]
54
+
55
+ @property
56
+ @override
57
+ def class_to_idx(self) -> Dict[str, int]:
58
+ return {name: index for index, name in enumerate(self.classes)}
59
+
60
+ @override
61
+ def filename(self, index: int) -> str:
62
+ image_path, *_ = self._samples[index]
63
+ return os.path.relpath(image_path, self._dataset_path)
64
+
65
+ @override
66
+ def prepare_data(self) -> None:
67
+ _validators.check_dataset_exists(self._root, True)
68
+
69
+ @override
70
+ def configure(self) -> None:
71
+ self._samples = self._make_dataset()
72
+
73
+ @override
74
+ def validate(self) -> None:
75
+ _validators.check_dataset_integrity(
76
+ self,
77
+ length=self._expected_dataset_lengths[self._split],
78
+ n_classes=7,
79
+ first_and_last_labels=("0_N", "6_IC"),
80
+ )
81
+
82
+ @override
83
+ def load_image(self, index: int) -> tv_tensors.Image:
84
+ image_path, _ = self._samples[index]
85
+ return io.read_image_as_tensor(image_path)
86
+
87
+ @override
88
+ def load_target(self, index: int) -> torch.Tensor:
89
+ _, target = self._samples[index]
90
+ return torch.tensor(target, dtype=torch.long)
91
+
92
+ @override
93
+ def __len__(self) -> int:
94
+ return len(self._samples)
95
+
96
+ @property
97
+ def _dataset_path(self) -> str:
98
+ """Returns the full path of dataset directory."""
99
+ return os.path.join(self._root, "BRACS_RoI/latest_version")
100
+
101
+ def _make_dataset(self) -> List[Tuple[str, int]]:
102
+ """Builds the dataset for the specified split."""
103
+ dataset = folder.make_dataset(
104
+ directory=os.path.join(self._dataset_path, self._split),
105
+ class_to_idx=self.class_to_idx,
106
+ extensions=(".png"),
107
+ )
108
+ return dataset
109
+
110
+ def _print_license(self) -> None:
111
+ """Prints the dataset license."""
112
+ print(f"Dataset license: {self._license}")
@@ -0,0 +1,210 @@
1
+ """BreaKHis dataset class."""
2
+
3
+ import functools
4
+ import glob
5
+ import os
6
+ from typing import Any, Callable, Dict, List, Literal, Set
7
+
8
+ import torch
9
+ from torchvision import tv_tensors
10
+ from torchvision.datasets import utils
11
+ from typing_extensions import override
12
+
13
+ from eva.vision.data.datasets import _validators, structs
14
+ from eva.vision.data.datasets.classification import base
15
+ from eva.vision.utils import io
16
+
17
+
18
+ class BreaKHis(base.ImageClassification):
19
+ """Dataset class for BreaKHis images and corresponding targets."""
20
+
21
+ _resources: List[structs.DownloadResource] = [
22
+ structs.DownloadResource(
23
+ filename="BreaKHis_v1.tar.gz",
24
+ url="http://www.inf.ufpr.br/vri/databases/BreaKHis_v1.tar.gz",
25
+ ),
26
+ ]
27
+ """Dataset resources."""
28
+
29
+ _val_patient_ids: Set[str] = {
30
+ "18842D",
31
+ "19979",
32
+ "15275",
33
+ "15792",
34
+ "16875",
35
+ "3909",
36
+ "5287",
37
+ "16716",
38
+ "2773",
39
+ "5695",
40
+ "16184CD",
41
+ "23060CD",
42
+ "21998CD",
43
+ "21998EF",
44
+ }
45
+ """Patient IDs to use for dataset splits."""
46
+
47
+ _expected_dataset_lengths: Dict[str | None, int] = {
48
+ "train": 1132,
49
+ "val": 339,
50
+ None: 1471,
51
+ }
52
+ """Expected dataset lengths for the splits and complete dataset."""
53
+
54
+ _default_magnifications = ["40X"]
55
+ """Default magnification to use for images in train/val datasets."""
56
+
57
+ _license: str = "CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)"
58
+ """Dataset license."""
59
+
60
+ def __init__(
61
+ self,
62
+ root: str,
63
+ split: Literal["train", "val"] | None = None,
64
+ magnifications: List[Literal["40X", "100X", "200X", "400X"]] | None = None,
65
+ download: bool = False,
66
+ transforms: Callable | None = None,
67
+ ) -> None:
68
+ """Initialize the dataset.
69
+
70
+ The dataset is split into train and validation by taking into account
71
+ the patient IDs to avoid any data leakage.
72
+
73
+ Args:
74
+ root: Path to the root directory of the dataset. The dataset will
75
+ be downloaded and extracted here, if it does not already exist.
76
+ split: Dataset split to use. If `None`, the entire dataset is used.
77
+ magnifications: A list of the WSI magnifications to select. By default
78
+ only 40X images are used.
79
+ download: Whether to download the data for the specified split.
80
+ Note that the download will be executed only by additionally
81
+ calling the :meth:`prepare_data` method and if the data does
82
+ not yet exist on disk.
83
+ transforms: A function/transform which returns a transformed
84
+ version of the raw data samples.
85
+ """
86
+ super().__init__(transforms=transforms)
87
+
88
+ self._root = root
89
+ self._split = split
90
+ self._download = download
91
+
92
+ self._magnifications = magnifications or self._default_magnifications
93
+ self._indices: List[int] = []
94
+
95
+ @property
96
+ @override
97
+ def classes(self) -> List[str]:
98
+ return ["TA", "MC", "F", "DC"]
99
+
100
+ @property
101
+ @override
102
+ def class_to_idx(self) -> Dict[str, int]:
103
+ return {label: index for index, label in enumerate(self.classes)}
104
+
105
+ @property
106
+ def _dataset_path(self) -> str:
107
+ """Returns the path of the image data of the dataset."""
108
+ return os.path.join(self._root, "BreaKHis_v1", "histology_slides")
109
+
110
+ @functools.cached_property
111
+ def _image_files(self) -> List[str]:
112
+ """Return the list of image files in the dataset.
113
+
114
+ Returns:
115
+ List of image file paths.
116
+ """
117
+ image_files = []
118
+ for magnification in self._magnifications:
119
+ files_pattern = os.path.join(self._dataset_path, f"**/{magnification}", "*.png")
120
+ image_files.extend(list(glob.glob(files_pattern, recursive=True)))
121
+ return sorted(image_files)
122
+
123
+ @override
124
+ def filename(self, index: int) -> str:
125
+ image_path = self._image_files[self._indices[index]]
126
+ return os.path.relpath(image_path, self._dataset_path)
127
+
128
+ @override
129
+ def prepare_data(self) -> None:
130
+ if self._download:
131
+ self._download_dataset()
132
+ _validators.check_dataset_exists(self._root, True)
133
+
134
+ @override
135
+ def configure(self) -> None:
136
+ self._indices = self._make_indices()
137
+
138
+ @override
139
+ def validate(self) -> None:
140
+ _validators.check_dataset_integrity(
141
+ self,
142
+ length=self._expected_dataset_lengths[self._split],
143
+ n_classes=4,
144
+ first_and_last_labels=("TA", "DC"),
145
+ )
146
+
147
+ @override
148
+ def load_image(self, index: int) -> tv_tensors.Image:
149
+ image_path = self._image_files[self._indices[index]]
150
+ return io.read_image_as_tensor(image_path)
151
+
152
+ @override
153
+ def load_target(self, index: int) -> torch.Tensor:
154
+ class_name = self._extract_class(self._image_files[self._indices[index]])
155
+ return torch.tensor(self.class_to_idx[class_name], dtype=torch.long)
156
+
157
+ @override
158
+ def load_metadata(self, index: int) -> Dict[str, Any]:
159
+ return {"patient_id": self._extract_patient_id(self._image_files[self._indices[index]])}
160
+
161
+ @override
162
+ def __len__(self) -> int:
163
+ return len(self._indices)
164
+
165
+ def _download_dataset(self) -> None:
166
+ """Downloads the dataset."""
167
+ for resource in self._resources:
168
+ if os.path.isdir(self._dataset_path):
169
+ continue
170
+
171
+ self._print_license()
172
+ utils.download_and_extract_archive(
173
+ resource.url,
174
+ download_root=self._root,
175
+ filename=resource.filename,
176
+ remove_finished=True,
177
+ )
178
+
179
+ def _print_license(self) -> None:
180
+ """Prints the dataset license."""
181
+ print(f"Dataset license: {self._license}")
182
+
183
+ def _extract_patient_id(self, image_file: str) -> str:
184
+ """Extracts the patient ID from the image file name."""
185
+ return os.path.basename(image_file).split("-")[2]
186
+
187
+ def _extract_class(self, file: str) -> str:
188
+ return os.path.basename(file).split("-")[0].split("_")[-1]
189
+
190
+ def _make_indices(self) -> List[int]:
191
+ """Builds the dataset indices for the specified split."""
192
+ train_indices = []
193
+ val_indices = []
194
+
195
+ for index, image_file in enumerate(self._image_files):
196
+ if self._extract_class(image_file) not in self.classes:
197
+ continue
198
+ patient_id = self._extract_patient_id(image_file)
199
+ if patient_id in self._val_patient_ids:
200
+ val_indices.append(index)
201
+ else:
202
+ train_indices.append(index)
203
+
204
+ split_indices = {
205
+ "train": train_indices,
206
+ "val": val_indices,
207
+ None: train_indices + val_indices,
208
+ }
209
+
210
+ return split_indices[self._split]
@@ -0,0 +1,172 @@
1
+ """GleasonArvaniti dataset class."""
2
+
3
+ import functools
4
+ import glob
5
+ import os
6
+ from pathlib import Path
7
+ from typing import Callable, Dict, List, Literal
8
+
9
+ import pandas as pd
10
+ import torch
11
+ from loguru import logger
12
+ from torchvision import tv_tensors
13
+ from typing_extensions import override
14
+
15
+ from eva.vision.data.datasets import _validators
16
+ from eva.vision.data.datasets.classification import base
17
+ from eva.vision.utils import io
18
+
19
+
20
+ class GleasonArvaniti(base.ImageClassification):
21
+ """Dataset class for GleasonArvaniti images and corresponding targets."""
22
+
23
+ _expected_dataset_lengths: Dict[str | None, int] = {
24
+ "train": 15303,
25
+ "val": 2482,
26
+ "test": 4967,
27
+ None: 22752,
28
+ }
29
+ """Expected dataset lengths for the splits and complete dataset."""
30
+
31
+ _license: str = "CC0 1.0 Universal (https://creativecommons.org/publicdomain/zero/1.0/)"
32
+ """Dataset license."""
33
+
34
+ def __init__(
35
+ self,
36
+ root: str,
37
+ split: Literal["train", "val", "test"] | None = None,
38
+ transforms: Callable | None = None,
39
+ ) -> None:
40
+ """Initialize the dataset.
41
+
42
+ Args:
43
+ root: Path to the root directory of the dataset.
44
+ split: Dataset split to use. If `None`, the entire dataset is used.
45
+ transforms: A function/transform which returns a transformed
46
+ version of the raw data samples.
47
+ """
48
+ super().__init__(transforms=transforms)
49
+
50
+ self._root = root
51
+ self._split = split
52
+
53
+ self._indices: List[int] = []
54
+
55
+ @property
56
+ @override
57
+ def classes(self) -> List[str]:
58
+ return ["benign", "gleason_3", "gleason_4", "gleason_5"]
59
+
60
+ @property
61
+ @override
62
+ def class_to_idx(self) -> Dict[str, int]:
63
+ return {name: index for index, name in enumerate(self.classes)}
64
+
65
+ @functools.cached_property
66
+ def _image_files(self) -> List[str]:
67
+ """Return the list of image files in the dataset.
68
+
69
+ Returns:
70
+ List of image file paths.
71
+ """
72
+ subdirs = ["train_validation_patches_750", "test_patches_750/patho_1"]
73
+
74
+ image_files = []
75
+ for subdir in subdirs:
76
+ files_pattern = os.path.join(self._root, subdir, "**/*.jpg")
77
+ image_files += list(glob.glob(files_pattern, recursive=True))
78
+
79
+ return sorted(image_files)
80
+
81
+ @functools.cached_property
82
+ def _manifest(self) -> pd.DataFrame:
83
+ """Returns the train.csv & test.csv files as dataframe."""
84
+ df_train = pd.read_csv(os.path.join(self._root, "train.csv"))
85
+ df_val = pd.read_csv(os.path.join(self._root, "test.csv"))
86
+ df_train["split"], df_val["split"] = "train", "val"
87
+ return pd.concat([df_train, df_val], axis=0).set_index("image_id")
88
+
89
+ @override
90
+ def filename(self, index: int) -> str:
91
+ image_path = self._image_files[self._indices[index]]
92
+ return os.path.relpath(image_path, self._root)
93
+
94
+ @override
95
+ def prepare_data(self) -> None:
96
+ _validators.check_dataset_exists(self._root, download_available=False)
97
+ if not os.path.isdir(os.path.join(self._root, "train_validation_patches_750")):
98
+ raise FileNotFoundError(
99
+ f"`train_validation_patches_750` directory not found in {self._root}"
100
+ )
101
+ if not os.path.isdir(os.path.join(self._root, "test_patches_750")):
102
+ raise FileNotFoundError(f"`test_patches_750` directory not found in {self._root}")
103
+
104
+ if self._split == "test":
105
+ logger.warning(
106
+ "The test split currently leads to unstable evaluation results. "
107
+ "We recommend using the validation split instead."
108
+ )
109
+
110
+ @override
111
+ def configure(self) -> None:
112
+ self._indices = self._make_indices()
113
+
114
+ @override
115
+ def validate(self) -> None:
116
+ _validators.check_dataset_integrity(
117
+ self,
118
+ length=self._expected_dataset_lengths[self._split],
119
+ n_classes=4,
120
+ first_and_last_labels=("benign", "gleason_5"),
121
+ )
122
+
123
+ @override
124
+ def load_image(self, index: int) -> tv_tensors.Image:
125
+ image_path = self._image_files[self._indices[index]]
126
+ return io.read_image_as_tensor(image_path)
127
+
128
+ @override
129
+ def load_target(self, index: int) -> torch.Tensor:
130
+ target = self._extract_class(self._image_files[self._indices[index]])
131
+ return torch.tensor(target, dtype=torch.long)
132
+
133
+ @override
134
+ def __len__(self) -> int:
135
+ return len(self._indices)
136
+
137
+ def _print_license(self) -> None:
138
+ """Prints the dataset license."""
139
+ print(f"Dataset license: {self._license}")
140
+
141
+ def _extract_micro_array_id(self, file: str) -> str:
142
+ """Extracts the ID of the tissue micro array from the file name."""
143
+ return Path(file).stem.split("_")[0]
144
+
145
+ def _extract_class(self, file: str) -> int:
146
+ """Extracts the class label from the file name."""
147
+ return int(Path(file).stem.split("_")[-1])
148
+
149
+ def _make_indices(self) -> List[int]:
150
+ """Builds the dataset indices for the specified split."""
151
+ train_indices, val_indices, test_indices = [], [], []
152
+
153
+ for index, image_file in enumerate(self._image_files):
154
+ array_id = self._extract_micro_array_id(image_file)
155
+
156
+ if array_id == "ZT76":
157
+ val_indices.append(index)
158
+ elif array_id in {"ZT111", "ZT199", "ZT204"}:
159
+ train_indices.append(index)
160
+ elif "test_patches_750" in image_file:
161
+ test_indices.append(index)
162
+ else:
163
+ raise ValueError(f"Invalid microarray value found for file {image_file}")
164
+
165
+ split_indices = {
166
+ "train": train_indices,
167
+ "val": val_indices,
168
+ "test": test_indices,
169
+ None: train_indices + val_indices + test_indices,
170
+ }
171
+
172
+ return split_indices[self._split]
@@ -0,0 +1,159 @@
1
+ """UniToPatho dataset class."""
2
+
3
+ import functools
4
+ import glob
5
+ import os
6
+ from typing import Callable, Dict, List, Literal
7
+
8
+ import pandas as pd
9
+ import torch
10
+ from torchvision import tv_tensors
11
+ from typing_extensions import override
12
+
13
+ from eva.vision.data.datasets import _validators
14
+ from eva.vision.data.datasets.classification import base
15
+ from eva.vision.utils import io
16
+
17
+
18
+ class UniToPatho(base.ImageClassification):
19
+ """Dataset class for UniToPatho images and corresponding targets."""
20
+
21
+ _expected_dataset_lengths: Dict[str | None, int] = {
22
+ "train": 6270,
23
+ "val": 2399,
24
+ None: 8669,
25
+ }
26
+ """Expected dataset lengths for the splits and complete dataset."""
27
+
28
+ _license: str = "CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)"
29
+ """Dataset license."""
30
+
31
+ def __init__(
32
+ self,
33
+ root: str,
34
+ split: Literal["train", "val"] | None = None,
35
+ transforms: Callable | None = None,
36
+ ) -> None:
37
+ """Initialize the dataset.
38
+
39
+ The dataset is split into train and validation by taking into account
40
+ the patient IDs to avoid any data leakage.
41
+
42
+ Args:
43
+ root: Path to the root directory of the dataset.
44
+ split: Dataset split to use. If `None`, the entire dataset is used.
45
+ transforms: A function/transform which returns a transformed
46
+ version of the raw data samples.
47
+ """
48
+ super().__init__(transforms=transforms)
49
+
50
+ self._root = root
51
+ self._split = split
52
+
53
+ self._indices: List[int] = []
54
+
55
+ @property
56
+ @override
57
+ def classes(self) -> List[str]:
58
+ return ["HP", "NORM", "TA.HG", "TA.LG", "TVA.HG", "TVA.LG"]
59
+
60
+ @property
61
+ @override
62
+ def class_to_idx(self) -> Dict[str, int]:
63
+ return {"HP": 0, "NORM": 1, "TA.HG": 2, "TA.LG": 3, "TVA.HG": 4, "TVA.LG": 5}
64
+
65
+ @property
66
+ def _dataset_path(self) -> str:
67
+ """Returns the path of the image data of the dataset."""
68
+ return os.path.join(self._root, "800")
69
+
70
+ @functools.cached_property
71
+ def _image_files(self) -> List[str]:
72
+ """Return the list of image files in the dataset.
73
+
74
+ Returns:
75
+ List of image file paths.
76
+ """
77
+ files_pattern = os.path.join(self._dataset_path, "**/*.png")
78
+ image_files = list(glob.glob(files_pattern, recursive=True))
79
+ return sorted(image_files)
80
+
81
+ @functools.cached_property
82
+ def _manifest(self) -> pd.DataFrame:
83
+ """Returns the train.csv & test.csv files as dataframe."""
84
+ df_train = pd.read_csv(os.path.join(self._dataset_path, "train.csv"))
85
+ df_val = pd.read_csv(os.path.join(self._dataset_path, "test.csv"))
86
+ df_train["split"], df_val["split"] = "train", "val"
87
+ return pd.concat([df_train, df_val], axis=0).set_index("image_id")
88
+
89
+ @override
90
+ def filename(self, index: int) -> str:
91
+ image_path = self._image_files[self._indices[index]]
92
+ return os.path.relpath(image_path, self._dataset_path)
93
+
94
+ @override
95
+ def prepare_data(self) -> None:
96
+ _validators.check_dataset_exists(self._root, True)
97
+
98
+ @override
99
+ def configure(self) -> None:
100
+ self._indices = self._make_indices()
101
+
102
+ @override
103
+ def validate(self) -> None:
104
+ _validators.check_dataset_integrity(
105
+ self,
106
+ length=self._expected_dataset_lengths[self._split],
107
+ n_classes=6,
108
+ first_and_last_labels=("HP", "TVA.LG"),
109
+ )
110
+
111
+ @override
112
+ def load_image(self, index: int) -> tv_tensors.Image:
113
+ image_path = self._image_files[self._indices[index]]
114
+ return io.read_image_as_tensor(image_path)
115
+
116
+ @override
117
+ def load_target(self, index: int) -> torch.Tensor:
118
+ target = self._extract_class(self._image_files[self._indices[index]])
119
+ return torch.tensor(target, dtype=torch.long)
120
+
121
+ @override
122
+ def __len__(self) -> int:
123
+ return len(self._indices)
124
+
125
+ def _print_license(self) -> None:
126
+ """Prints the dataset license."""
127
+ print(f"Dataset license: {self._license}")
128
+
129
+ def _extract_image_id(self, image_file: str) -> str:
130
+ """Extracts the image_id from the file name."""
131
+ return os.path.basename(image_file)
132
+
133
+ def _extract_class(self, file: str) -> int:
134
+ image_id = self._extract_image_id(file)
135
+ return int(self._manifest.at[image_id, "top_label"])
136
+
137
+ def _make_indices(self) -> List[int]:
138
+ """Builds the dataset indices for the specified split."""
139
+ train_indices = []
140
+ val_indices = []
141
+
142
+ for index, image_file in enumerate(self._image_files):
143
+ image_id = self._extract_image_id(image_file)
144
+ split = self._manifest.at[image_id, "split"]
145
+
146
+ if split == "train":
147
+ train_indices.append(index)
148
+ elif split == "val":
149
+ val_indices.append(index)
150
+ else:
151
+ raise ValueError(f"Invalid split value found: {split}")
152
+
153
+ split_indices = {
154
+ "train": train_indices,
155
+ "val": val_indices,
156
+ None: train_indices + val_indices,
157
+ }
158
+
159
+ return split_indices[self._split]
@@ -14,7 +14,7 @@ class EmbeddingsSegmentationDataset(embeddings_base.EmbeddingsDataset[tv_tensors
14
14
  """Embeddings segmentation dataset."""
15
15
 
16
16
  @override
17
- def _load_embeddings(self, index: int) -> List[torch.Tensor]:
17
+ def load_embeddings(self, index: int) -> List[torch.Tensor]:
18
18
  filename = self.filename(index)
19
19
  embeddings_path = os.path.join(self._root, filename)
20
20
  embeddings = torch.load(embeddings_path, map_location="cpu")
@@ -23,7 +23,7 @@ class EmbeddingsSegmentationDataset(embeddings_base.EmbeddingsDataset[tv_tensors
23
23
  return [tensor.squeeze(0) for tensor in embeddings]
24
24
 
25
25
  @override
26
- def _load_target(self, index: int) -> tv_tensors.Mask:
26
+ def load_target(self, index: int) -> tv_tensors.Mask:
27
27
  filename = self._data.at[index, self._column_mapping["target"]]
28
28
  mask_path = os.path.join(self._root, filename)
29
29
  semantic_labels = torch.load(mask_path, map_location="cpu")
@@ -1,11 +1,9 @@
1
1
  """Pathology FMs from MahmoodLab."""
2
2
 
3
- import os
4
- from pathlib import Path
5
3
  from typing import Tuple
6
4
 
7
- import huggingface_hub
8
- from loguru import logger
5
+ import timm
6
+ import torch
9
7
  from torch import nn
10
8
 
11
9
  from eva.vision.models import wrappers
@@ -18,7 +16,6 @@ def mahmood_uni(
18
16
  dynamic_img_size: bool = True,
19
17
  out_indices: int | Tuple[int, ...] | None = None,
20
18
  hf_token: str | None = None,
21
- download_dir: str = os.path.join(str(Path.home()), ".cache/eva"),
22
19
  ) -> nn.Module:
23
20
  """Initializes UNI model from MahmoodLab.
24
21
 
@@ -27,29 +24,59 @@ def mahmood_uni(
27
24
  the grid size (interpolate abs and/or ROPE pos) in the forward pass.
28
25
  out_indices: Whether and which multi-level patch embeddings to return.
29
26
  hf_token: HuggingFace token to download the model.
30
- download_dir: Directory to download the model checkpoint.
31
27
 
32
28
  Returns:
33
29
  The model instance.
34
30
  """
35
- checkpoint_path = os.path.join(download_dir, "pytorch_model.bin")
36
- if not os.path.exists(checkpoint_path):
37
- logger.info(f"Downloading the model checkpoint to {download_dir} ...")
38
- os.makedirs(download_dir, exist_ok=True)
39
- _utils.huggingface_login(hf_token)
40
- huggingface_hub.hf_hub_download(
41
- "MahmoodLab/UNI",
42
- filename="pytorch_model.bin",
43
- local_dir=download_dir,
44
- force_download=True,
45
- )
31
+ _utils.huggingface_login(hf_token)
46
32
 
47
33
  return wrappers.TimmModel(
48
- model_name="vit_large_patch16_224",
34
+ model_name="hf-hub:MahmoodLab/uni",
35
+ pretrained=True,
49
36
  out_indices=out_indices,
50
37
  model_kwargs={
51
38
  "init_values": 1e-5,
52
39
  "dynamic_img_size": dynamic_img_size,
53
40
  },
54
- checkpoint_path=checkpoint_path,
41
+ )
42
+
43
+
44
+ @register_model("pathology/mahmood_uni2_h")
45
+ def mahmood_uni2_h(
46
+ dynamic_img_size: bool = True,
47
+ out_indices: int | Tuple[int, ...] | None = None,
48
+ hf_token: str | None = None,
49
+ ) -> nn.Module:
50
+ """Initializes UNI model from MahmoodLab.
51
+
52
+ Args:
53
+ dynamic_img_size: Support different input image sizes by allowing to change
54
+ the grid size (interpolate abs and/or ROPE pos) in the forward pass.
55
+ out_indices: Whether and which multi-level patch embeddings to return.
56
+ hf_token: HuggingFace token to download the model.
57
+
58
+ Returns:
59
+ The model instance.
60
+ """
61
+ _utils.huggingface_login(hf_token)
62
+
63
+ return wrappers.TimmModel(
64
+ model_name="hf-hub:MahmoodLab/UNI2-h",
65
+ pretrained=True,
66
+ out_indices=out_indices,
67
+ model_kwargs={
68
+ "img_size": 224,
69
+ "patch_size": 14,
70
+ "depth": 24,
71
+ "num_heads": 24,
72
+ "init_values": 1e-5,
73
+ "embed_dim": 1536,
74
+ "mlp_ratio": 2.66667 * 2,
75
+ "num_classes": 0,
76
+ "no_embed_class": True,
77
+ "mlp_layer": timm.layers.SwiGLUPacked,
78
+ "act_layer": torch.nn.SiLU,
79
+ "reg_tokens": 8,
80
+ "dynamic_img_size": dynamic_img_size,
81
+ },
55
82
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kaiko-eva
3
- Version: 0.1.7
3
+ Version: 0.2.0
4
4
  Summary: Evaluation Framework for oncology foundation models.
5
5
  Keywords: machine-learning,evaluation-framework,oncology,foundation-models
6
6
  Author-Email: Ioannis Gatopoulos <ioannis@kaiko.ai>, =?utf-8?q?Nicolas_K=C3=A4nzig?= <nicolas@kaiko.ai>, Roman Moser <roman@kaiko.ai>
@@ -228,6 +228,7 @@ Requires-Dist: onnx>=1.16.0
228
228
  Requires-Dist: toolz>=0.12.1
229
229
  Requires-Dist: rich>=13.7.1
230
230
  Requires-Dist: torchmetrics>=1.6.0
231
+ Requires-Dist: nibabel>=3.2.2
231
232
  Provides-Extra: vision
232
233
  Requires-Dist: h5py>=3.10.0; extra == "vision"
233
234
  Requires-Dist: nibabel>=4.0.1; extra == "vision"
@@ -25,14 +25,14 @@ eva/core/data/datamodules/schemas.py,sha256=rzcf3uow6T6slVSwxEGDVmpi3QUvkiDoT_gC
25
25
  eva/core/data/datasets/__init__.py,sha256=jWPxT3gjQjwS6HqVZAb7KhMgzgklPgHeH51iPxDh_Tg,493
26
26
  eva/core/data/datasets/base.py,sha256=BLzlRFuByhrGmI7NFwn7-Tw0vpSYSRhl2Y65iX4KaMw,2526
27
27
  eva/core/data/datasets/classification/__init__.py,sha256=wJ2jD9YODftt-dMcMf0TbCjJt47qXYBKkD4-XXajvRQ,340
28
- eva/core/data/datasets/classification/embeddings.py,sha256=bgBVQyGxlxVCvGjmwNB52E360QwzrhGZQ44rPNFR4k8,1110
29
- eva/core/data/datasets/classification/multi_embeddings.py,sha256=j_o0MH2gwn_y3rNFXEUzNg6WErlG3Rq_vn5Og1Yk7J0,4603
28
+ eva/core/data/datasets/classification/embeddings.py,sha256=hBO6dIRHAhoCaYb3ANc9fgvdBjyQNKPTrIhjc9y8-Ys,1108
29
+ eva/core/data/datasets/classification/multi_embeddings.py,sha256=4hQy4741NDKqWCpm3kGq7aC28DF5gcwUuIpYhnbTyeM,4601
30
30
  eva/core/data/datasets/dataset.py,sha256=tA6Wd_7vqOE9GsukSWrgN9zaZKtKCHaE58SqIfWxWdg,124
31
- eva/core/data/datasets/embeddings.py,sha256=zNEO8KxqiOopcN_lTjwtEAm9xbnYDSjOE8X2-iZQIhU,5545
31
+ eva/core/data/datasets/embeddings.py,sha256=0y7Fa4zHr4Y0mcB9pyP26YaeTPtetwVf_n6fnkTcgp0,5541
32
32
  eva/core/data/datasets/typings.py,sha256=KSmckjsU64pGV-8uSLkD1HmvPKYlyypngiRx9yy4RDs,383
33
33
  eva/core/data/samplers/__init__.py,sha256=rRrKtg4l6YoziD3M0MkctQvX1NdRxaQa5sm6RHH_jXc,315
34
34
  eva/core/data/samplers/classification/__init__.py,sha256=gvv7BH4lG9JlkMaTOnaL0f4k1ghiVBgrH64bh1-rreQ,147
35
- eva/core/data/samplers/classification/balanced.py,sha256=YE6InKu12Jnu7AObi_gjKLzeHAFlQsbJVrggeA8X4DU,3517
35
+ eva/core/data/samplers/classification/balanced.py,sha256=MGTHt-WQaQKiJ5A1D_P6HJ6YzPTD-ERhc0R7rNMFqfg,3788
36
36
  eva/core/data/samplers/random.py,sha256=znl0Z9a-X-3attP-EH9jwwo83n40FXW_JzOLNZAml_c,1252
37
37
  eva/core/data/samplers/sampler.py,sha256=0DOLUzFoweqEubuO1A4bZBRU0AWFoWGWrO3pawRT-eI,877
38
38
  eva/core/data/splitting/__init__.py,sha256=VQJ8lfakbv6y2kAk3VDtITAvh7kcZo3H1JwJBc5jT08,198
@@ -55,6 +55,7 @@ eva/core/loggers/log/image.py,sha256=iUwntQCdRNLtkSdqu8CvV34l06zPYVo4NAW2gUeiJIM
55
55
  eva/core/loggers/log/parameters.py,sha256=7Xi-I5gQvEVv71d58bwdZ-Hb4287NXxaUyMfriq_KDU,1634
56
56
  eva/core/loggers/log/utils.py,sha256=k4Q7uKpAQctfDv0EEYPnPv6wt9LnckEeqGvbYSLfKO0,415
57
57
  eva/core/loggers/loggers.py,sha256=igHxdxJSotWSg6nEOKnfFuBszzblHgi8T7sBrE00FEs,166
58
+ eva/core/loggers/utils/wandb.py,sha256=GdwzEeFTAng5kl_kIVRxKL7rvwqyicQHSaZS8VSMXvU,747
58
59
  eva/core/losses/__init__.py,sha256=D-Mp9fUFFFoH9YYWntVH3B839zHS3GjFJzkbQThzj6Y,118
59
60
  eva/core/losses/cross_entropy.py,sha256=Sunz7ogDAJpGvZtuk9cAxKZJBO08CKIEvbCoewEvees,862
60
61
  eva/core/metrics/__init__.py,sha256=-9Qch4npEQpy3oF6NUhh9WinCmFBFe0D2eEYCR0S0xU,558
@@ -72,7 +73,7 @@ eva/core/metrics/structs/schemas.py,sha256=ZaSrx0j_NfIwT7joMUD1LyrKdAXTLaeSzWYTH
72
73
  eva/core/metrics/structs/typings.py,sha256=qJd-FiD2IhJgBeo8FyP0vpVUIH4RKb1k6zYvHtjUA04,388
73
74
  eva/core/models/__init__.py,sha256=T6Fo886LxMj-Y58_ylzkPkFSnFR2aISiMIbuO_weC4s,430
74
75
  eva/core/models/modules/__init__.py,sha256=QJWJ42BceXZBzDGgk5FHBcCaRrB9egTFKVF6gDsBYfM,255
75
- eva/core/models/modules/head.py,sha256=Wza8IFAXFl_DwVnNqYKproI06iS-oIuUlGjRE6jAKXw,5185
76
+ eva/core/models/modules/head.py,sha256=2rPlo2Osuq77gjrJmvQKCvNTaawvQRirK2CM2o24_xs,5184
76
77
  eva/core/models/modules/inference.py,sha256=ih-0Rr2oNf2N6maiXPOW7XH5KVwUT1_MOxnJKOhJ1uQ,978
77
78
  eva/core/models/modules/module.py,sha256=LtjYxTZb7UY0owonmt_yQ5EySw3sX-xD9HLN2io8EK4,6697
78
79
  eva/core/models/modules/typings.py,sha256=yFMJCE4Nrfd8VEXU1zk8p6Sz5M7UslwitYPVC2OPLSY,776
@@ -96,8 +97,8 @@ eva/core/trainers/__init__.py,sha256=jhsKJF7HAae7EOiG3gKIAHH_h3dZlTE2JRcCHJmOzJc
96
97
  eva/core/trainers/_logging.py,sha256=gi4FqPy2GuVmh0WZY6mYwF7zMPvnoFA050B0XdCP6PU,2571
97
98
  eva/core/trainers/_recorder.py,sha256=y6i5hfXftWjeV3eQHmMjUOkWumnZ2QNv_u275LLmvPA,7702
98
99
  eva/core/trainers/_utils.py,sha256=M3h8lVhUmkeSiEXpX9hRdMvThGFCnTP15gv-hd1CZkc,321
99
- eva/core/trainers/functional.py,sha256=7OK2BNfX4_amHsyucr1ZNQRG3RgVKoagzd1zNN4nU3U,4472
100
- eva/core/trainers/trainer.py,sha256=HJNSfTG0k4j2ShqZzuUUSxnSu8NrwJ4karhvAto2Zn0,4229
100
+ eva/core/trainers/functional.py,sha256=rLtQZw8TcAa4NYIf901TmoQiJDNm4RGVLN-64nku3Jo,4445
101
+ eva/core/trainers/trainer.py,sha256=a3OwLWOZKDqxayrd0ugUmxJKyQx6XDb4GHtdL8-AEV0,4826
101
102
  eva/core/utils/__init__.py,sha256=cndVBvtYxEW7hykH39GCNVI86zkXNn8Lw2A0sUJHS04,237
102
103
  eva/core/utils/clone.py,sha256=qcThZOuAs1cs0uV3BL5eKeM2VIBjuRPBe1t-NiUFM5Y,569
103
104
  eva/core/utils/io/__init__.py,sha256=Py03AmoxhmTHkro6CzNps27uXKkXPzdA18mG97xHhWI,172
@@ -116,17 +117,21 @@ eva/vision/callbacks/loggers/batch/__init__.py,sha256=DVYP7Aonbi4wg_ERHRj_8kb87E
116
117
  eva/vision/callbacks/loggers/batch/base.py,sha256=hcAd5iiHvjZ0DIf4Qt4ENT54D6ky_1OO4rKQZqeo-1k,3628
117
118
  eva/vision/callbacks/loggers/batch/segmentation.py,sha256=GYh2kfexW5pUZ0BdApYJI3e8xsuNkjIzkj5jnuKtHR4,6886
118
119
  eva/vision/data/__init__.py,sha256=aoKPmX8P2Q2k2W3nlq8vFU41FV6Sze-0SDuWtU-ETh4,111
119
- eva/vision/data/datasets/__init__.py,sha256=COhMRB9QJcjfbmfpRcYEztDwN9pl7IJNiH29pCZo4CA,908
120
+ eva/vision/data/datasets/__init__.py,sha256=wvbkhBv_yS7hHMdMR-QpNHMkGoGzSL0L33XaXUwXTpM,1040
120
121
  eva/vision/data/datasets/_utils.py,sha256=epPcaYE4w2_LtUKLLQJh6qQxUNVBe22JA06k4WUerYQ,1430
121
122
  eva/vision/data/datasets/_validators.py,sha256=77WZj8ewsuxUjW5WegJ-7zDuR6WdF5JbaOYdywhKIK4,2594
122
- eva/vision/data/datasets/classification/__init__.py,sha256=T2eg8k3xxd_Pdbrr7TGYICSo7BVOTMOs1bL-rLnMmro,693
123
+ eva/vision/data/datasets/classification/__init__.py,sha256=5fOGZxKGPeMCf3Jd9qAOYADPrkZnYg97_QE4DC79AMI,1074
123
124
  eva/vision/data/datasets/classification/bach.py,sha256=kZba1dQlJWZAmA03akJ4fVUU-y9W8ezOwlgs2zL-QrE,5432
124
125
  eva/vision/data/datasets/classification/base.py,sha256=Ci0HoOhOuHwICTi1TUGA1PwZe642RywolTVfMhKrFHk,2772
126
+ eva/vision/data/datasets/classification/bracs.py,sha256=e9SqnQ_HVm9ypQiwsFi5tbngqs0yEZsfVBk3pt91W80,3347
127
+ eva/vision/data/datasets/classification/breakhis.py,sha256=_rzGx5IgJSW73es7Gusr_oOzI1jHCPhRH8yRvqcmuqw,6905
125
128
  eva/vision/data/datasets/classification/camelyon16.py,sha256=sChvRo0jbOVUMJvfpsFxgFOsYgci3v9wjeMBEjUysJU,8287
126
129
  eva/vision/data/datasets/classification/crc.py,sha256=8qjz9OklLg1gAr46RKZdlClmlO9awwfp0dkTs8v5jTE,5670
130
+ eva/vision/data/datasets/classification/gleason_arvaniti.py,sha256=CCXeBA3dlic7ZRiarf4_f76qkct8PMNM_tCfz3IRUPA,5893
127
131
  eva/vision/data/datasets/classification/mhist.py,sha256=xzShPncSfAV6Q5ojfimeq748MfA0n77fGWa9EpdRzYU,3055
128
132
  eva/vision/data/datasets/classification/panda.py,sha256=BU_gDoX3ZSDUugwaO2n0XSZhzseK1rkPoHMRoJLGL84,7303
129
133
  eva/vision/data/datasets/classification/patch_camelyon.py,sha256=fElKteZKx4M6AjylnhhgNH1jewHegWc1K8h4FFKp0gE,7171
134
+ eva/vision/data/datasets/classification/unitopatho.py,sha256=vC-dFbhETfDD9paTeQ73Dg1vLPWsK12AfpiBFznESaM,5151
130
135
  eva/vision/data/datasets/classification/wsi.py,sha256=x3mQ8iwyiSdfQOjJuV7_cd8-LRjjhY9tjtzuD8O87Lg,4099
131
136
  eva/vision/data/datasets/segmentation/__init__.py,sha256=hGNr7BM_StxvmlOKWWfHp615qgsrB6BB3qMOiYhE0Og,791
132
137
  eva/vision/data/datasets/segmentation/_total_segmentator.py,sha256=DTaQaAisY7j1h0-zYk1_81Sr4b3D9PTMieYX0PMPtIc,3127
@@ -134,7 +139,7 @@ eva/vision/data/datasets/segmentation/_utils.py,sha256=ps1qpuEkPgvwUw6H-KKaLaYqD
134
139
  eva/vision/data/datasets/segmentation/base.py,sha256=11IMODMB7KJ8Bs5p7MyOsBXCyPFJXfYcDLAIMitUwEk,3023
135
140
  eva/vision/data/datasets/segmentation/bcss.py,sha256=NHjHd1tgIfIw6TxsZTGb63iMEwXFbWX_JAwRT5WVsj4,8274
136
141
  eva/vision/data/datasets/segmentation/consep.py,sha256=Pw3LvVIK2scj_ys7rVNRb9B8snP8HlDIAbaI3v6ObQk,6056
137
- eva/vision/data/datasets/segmentation/embeddings.py,sha256=0KaadzPxN6OrKNnFu3YsGBFkG6XqqvkOZYUhERPwL4A,1220
142
+ eva/vision/data/datasets/segmentation/embeddings.py,sha256=RsTuAwGEJPnWPY7q3pwcjmqtEj0wtRBNRBD4a0RcGtA,1218
138
143
  eva/vision/data/datasets/segmentation/lits.py,sha256=cBRU5lkiTMAi_ZwyDQUN3ODyXUlLtuMWFLPDajcZnOo,7194
139
144
  eva/vision/data/datasets/segmentation/lits_balanced.py,sha256=s5kPfqB41Vkcm5Jh34mLAO0NweMSIlV2fMXJsRjJsF8,3384
140
145
  eva/vision/data/datasets/segmentation/monusac.py,sha256=OTWHAD1b48WeT6phVf466w_nJUOGdBCGKWiWw68PAdw,8423
@@ -194,7 +199,7 @@ eva/vision/models/networks/backbones/pathology/gigapath.py,sha256=mfGXtKhY7XLpKQ
194
199
  eva/vision/models/networks/backbones/pathology/histai.py,sha256=X_we3U7GK91RrXyOX2PJB-YFDF2ozdL2fzZhNxm9SVU,1914
195
200
  eva/vision/models/networks/backbones/pathology/kaiko.py,sha256=GSdBG4WXrs1PWB2hr-sy_dFe2riwpPKwHx71esDoVfE,3952
196
201
  eva/vision/models/networks/backbones/pathology/lunit.py,sha256=ku4lr9pWeeHatHN4x4OVgwlve9sVqiRqIbgI0PXLiqg,2160
197
- eva/vision/models/networks/backbones/pathology/mahmood.py,sha256=me8DXf9nsEegDmltP8f7ZnG89xYVEKzZLKfVzMZjWDs,1832
202
+ eva/vision/models/networks/backbones/pathology/mahmood.py,sha256=VYoVWrMNkoaEqa0och-GbwGd0VISQmbtzk1dSBZ1M0I,2464
198
203
  eva/vision/models/networks/backbones/pathology/owkin.py,sha256=uWJV5fgY7UZX6ilgGzkPY9fnlOiF03W7E8rc9TmlHGg,1231
199
204
  eva/vision/models/networks/backbones/pathology/paige.py,sha256=MjOLgdEKk8tdAIpCiHelasGwPE7xgzaooW6EE7IsuEE,1642
200
205
  eva/vision/models/networks/backbones/registry.py,sha256=anjILtEHHB6Ltwiw22h1bsgWtIjh_l5_fkPh87K7-d0,1631
@@ -225,8 +230,8 @@ eva/vision/utils/io/image.py,sha256=IdOkr5MYqhYHz8U9drZ7wULTM3YHwCWSjZlu_Qdl4GQ,
225
230
  eva/vision/utils/io/mat.py,sha256=qpGifyjmpE0Xhv567Si7-zxKrgkgE0sywP70cHiLFGU,808
226
231
  eva/vision/utils/io/nifti.py,sha256=4YoKjKuoNdE0qY7tYB_WlnSsYAx2oBzZRZXczc_8HAU,2555
227
232
  eva/vision/utils/io/text.py,sha256=qYgfo_ZaDZWfG02NkVVYzo5QFySqdCCz5uLA9d-zXtI,701
228
- kaiko_eva-0.1.7.dist-info/METADATA,sha256=ToVlQrhgzB06Ptgus3EhUrMNaEBUFGm7YHTuZdy6JMM,24869
229
- kaiko_eva-0.1.7.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
230
- kaiko_eva-0.1.7.dist-info/entry_points.txt,sha256=6CSLu9bmQYJSXEg8gbOzRhxH0AGs75BB-vPm3VvfcNE,88
231
- kaiko_eva-0.1.7.dist-info/licenses/LICENSE,sha256=e6AEzr7j_R-PYr2qLO-JwLn8y70jbVD3U2mxbRmwcI4,11338
232
- kaiko_eva-0.1.7.dist-info/RECORD,,
233
+ kaiko_eva-0.2.0.dist-info/METADATA,sha256=CTFbtAErERl1SU0--56Y5-d1tXrr1vBNtNVkaV3orrA,24899
234
+ kaiko_eva-0.2.0.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
235
+ kaiko_eva-0.2.0.dist-info/entry_points.txt,sha256=6CSLu9bmQYJSXEg8gbOzRhxH0AGs75BB-vPm3VvfcNE,88
236
+ kaiko_eva-0.2.0.dist-info/licenses/LICENSE,sha256=e6AEzr7j_R-PYr2qLO-JwLn8y70jbVD3U2mxbRmwcI4,11338
237
+ kaiko_eva-0.2.0.dist-info/RECORD,,