dsa-helpers 1.dev0__tar.gz → 1.0.1.dev0__tar.gz
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.
- {dsa_helpers-1.dev0/dsa_helpers.egg-info → dsa_helpers-1.0.1.dev0}/PKG-INFO +1 -1
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/ml/__init__.py +2 -0
- dsa_helpers-1.0.1.dev0/dsa_helpers/ml/evaluate.py +136 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/tile_utils.py +14 -2
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/utils.py +6 -3
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0/dsa_helpers.egg-info}/PKG-INFO +1 -1
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers.egg-info/SOURCES.txt +1 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/pyproject.toml +1 -1
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/.gitignore +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/.readthedocs.yaml +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/LICENSE +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/README.md +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/app.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/docs/requirements.txt +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/docs/source/conf.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/docs/source/index.rst +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/__init__.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/dash/__init__.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/dash/dash_paperdragon_utils.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/dash/header.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/dash/header_callbacks.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/girder_utils.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/imread.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/imwrite.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/ml/callbacks.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/ml/datasets/SegFormerSegmentationDataset.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/ml/datasets/__init__.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/ml/datasets/utils.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/ml/metrics.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/ml/object_detection/__init__.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/ml/object_detection/get_pearce_roi_images.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/ml/object_detection/tile_image.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/ml/object_detection/utils.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/ml/segformer_semantic_segmentation/__init__.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/ml/segformer_semantic_segmentation/inference.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/ml/transforms/__init__.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/ml/transforms/segformer_transforms.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/mongo_utils.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/tiling.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers.egg-info/dependency_links.txt +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers.egg-info/requires.txt +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers.egg-info/top_level.txt +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/setup.cfg +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/tests/app/Dockerfile +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/tests/app/app.py +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/tests/docker-compose.yaml +0 -0
- {dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/tests/test.ipynb +0 -0
|
@@ -5,6 +5,7 @@ from . import (
|
|
|
5
5
|
transforms,
|
|
6
6
|
segformer_semantic_segmentation,
|
|
7
7
|
callbacks,
|
|
8
|
+
evaluate,
|
|
8
9
|
)
|
|
9
10
|
|
|
10
11
|
# Modules that are imported from * notation.
|
|
@@ -15,4 +16,5 @@ __all__ = [
|
|
|
15
16
|
"transforms",
|
|
16
17
|
"segformer_semantic_segmentation",
|
|
17
18
|
"callbacks",
|
|
19
|
+
"evaluate",
|
|
18
20
|
]
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""This module contains functions for evaluating model performance on
|
|
2
|
+
datasets."""
|
|
3
|
+
|
|
4
|
+
import torch
|
|
5
|
+
from torch import nn
|
|
6
|
+
from tqdm import tqdm
|
|
7
|
+
from transformers.trainer import Trainer
|
|
8
|
+
import pandas as pd
|
|
9
|
+
|
|
10
|
+
from .datasets.utils import create_segformer_segmentation_dataset
|
|
11
|
+
from .transforms.segformer_transforms import val_transforms
|
|
12
|
+
from ..tile_utils import merge_tile_masks
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def evaluate_semantic_segmentation_segformer_group(
|
|
16
|
+
trainer: Trainer,
|
|
17
|
+
df: pd.DataFrame,
|
|
18
|
+
group_col: str,
|
|
19
|
+
batch_size: int = 16,
|
|
20
|
+
tile_size: int = 512,
|
|
21
|
+
background_label: int | None = None,
|
|
22
|
+
) -> float:
|
|
23
|
+
"""Evaluate a segformer trainer model on a specific dataset provided
|
|
24
|
+
as a dataframe, grouped by a specific column. For a unique value of
|
|
25
|
+
the column it will predict on the images for that group and then
|
|
26
|
+
merge the predictions into a single multipolygon. The mean IoU will
|
|
27
|
+
be calculated agains the ground truth masks for that group. The
|
|
28
|
+
reported mean IoU will be the average of all the mean IoUs for each
|
|
29
|
+
group.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
trainer (Trainer): The trainer object with the model to evaluate.
|
|
33
|
+
df (pandas.DataFrame): The dataframe with the tile metadata.
|
|
34
|
+
Must have the fp column (file path), x column (x coordinate),
|
|
35
|
+
y column (y coordinate), and group_col.
|
|
36
|
+
group_col (str): The column to group the tiles by.
|
|
37
|
+
batch_size (int): The batch size to use for prediction. Note that
|
|
38
|
+
this is not the batch size use for predictions, rather it is
|
|
39
|
+
how many masks will be stored in memory. Batch size used
|
|
40
|
+
for predictions is specified on the trainer input. Default is
|
|
41
|
+
16.
|
|
42
|
+
tile_size (int): The size of the tiles. Default is 512.
|
|
43
|
+
background_label (int | None): The label to use for the background
|
|
44
|
+
class, which will be ignored. If None then no label will be
|
|
45
|
+
ignored. Default is None.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
float: The mean IoU across all groups.
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
ious = []
|
|
52
|
+
|
|
53
|
+
# Predict on the images for each group separately.
|
|
54
|
+
groups = list(df[group_col].unique())
|
|
55
|
+
for group in tqdm(groups, desc="Evaluating groups"):
|
|
56
|
+
# Get the tiles in this group.
|
|
57
|
+
group_df = df[df[group_col] == group]
|
|
58
|
+
|
|
59
|
+
pred_tile_list = []
|
|
60
|
+
true_tile_list = []
|
|
61
|
+
|
|
62
|
+
# Predict on the group in batches.
|
|
63
|
+
for i in range(0, len(group_df), batch_size):
|
|
64
|
+
# Create dataset for this batch.
|
|
65
|
+
batch_df = group_df.iloc[i : i + batch_size]
|
|
66
|
+
x_list = batch_df["x"].tolist()
|
|
67
|
+
y_list = batch_df["y"].tolist()
|
|
68
|
+
|
|
69
|
+
# Create dataset for this batch.
|
|
70
|
+
group_dataset = create_segformer_segmentation_dataset(
|
|
71
|
+
batch_df, transforms=val_transforms
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Predict on the batch.
|
|
75
|
+
out = trainer.predict(group_dataset)
|
|
76
|
+
preds = out[0]
|
|
77
|
+
true = out[1]
|
|
78
|
+
|
|
79
|
+
# Reshape the prediction logits to tile size.
|
|
80
|
+
preds = torch.from_numpy(preds).cpu()
|
|
81
|
+
|
|
82
|
+
preds = nn.functional.interpolate(
|
|
83
|
+
preds,
|
|
84
|
+
size=(tile_size, tile_size),
|
|
85
|
+
mode="bilinear",
|
|
86
|
+
align_corners=False,
|
|
87
|
+
).argmax(
|
|
88
|
+
dim=1
|
|
89
|
+
) # logits -> class predictions
|
|
90
|
+
|
|
91
|
+
preds = preds.detach().numpy()
|
|
92
|
+
|
|
93
|
+
# Append to the tile list.
|
|
94
|
+
pred_tile_list.extend(
|
|
95
|
+
[(pred, x, y) for x, y, pred in zip(x_list, y_list, preds)]
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
true_tile_list.extend(
|
|
99
|
+
[(mask, x, y) for x, y, mask in zip(x_list, y_list, true)]
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Merge the tile masks.
|
|
103
|
+
pred_gdf = merge_tile_masks(
|
|
104
|
+
pred_tile_list, background_label=background_label
|
|
105
|
+
)
|
|
106
|
+
true_gdf = merge_tile_masks(
|
|
107
|
+
true_tile_list, background_label=background_label
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Get the unique labels in either the prediction or the ground truth.
|
|
111
|
+
labels = set(pred_gdf["label"].unique()) | set(
|
|
112
|
+
true_gdf["label"].unique()
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Calculate the IoU for each label.
|
|
116
|
+
ious = []
|
|
117
|
+
|
|
118
|
+
for label in labels:
|
|
119
|
+
pred_label_gdf = pred_gdf[pred_gdf["label"] == label]
|
|
120
|
+
true_label_gdf = true_gdf[true_gdf["label"] == label]
|
|
121
|
+
|
|
122
|
+
# If either the prediction or the ground truth is empty, the IoU is 0.
|
|
123
|
+
if pred_label_gdf.empty or true_label_gdf.empty:
|
|
124
|
+
ious.append(0)
|
|
125
|
+
else:
|
|
126
|
+
# Calculate the IoU.
|
|
127
|
+
geom1 = pred_label_gdf.iloc[0]["geometry"]
|
|
128
|
+
geom2 = true_label_gdf.iloc[0]["geometry"]
|
|
129
|
+
|
|
130
|
+
intersection_area = geom1.intersection(geom2).area
|
|
131
|
+
union = geom1.union(geom2).area
|
|
132
|
+
|
|
133
|
+
ious.append(intersection_area / union)
|
|
134
|
+
|
|
135
|
+
# Return the average of the ious.
|
|
136
|
+
return float(sum(ious) / len(ious))
|
|
@@ -8,7 +8,9 @@ from geopandas import GeoDataFrame
|
|
|
8
8
|
from .utils import mask_to_shapely
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
def merge_tile_masks(
|
|
11
|
+
def merge_tile_masks(
|
|
12
|
+
tile_list: list, buffer: int = 1, background_label: int | None = 0
|
|
13
|
+
) -> GeoDataFrame:
|
|
12
14
|
"""
|
|
13
15
|
Merge the tile masks into a single mask for a large image.
|
|
14
16
|
|
|
@@ -17,6 +19,9 @@ def merge_tile_masks(tile_list: list, buffer: int = 1) -> GeoDataFrame:
|
|
|
17
19
|
(1) fp or array for mask, (2) x-coordinate for tile, (3)
|
|
18
20
|
y-coordinate for tile.
|
|
19
21
|
buffer (int): Buffer to apply to the polygons before dissolving.
|
|
22
|
+
background_label (int | None): Label value of the background class,
|
|
23
|
+
which is ignored. Default is 0. If None then all labels are
|
|
24
|
+
considered.
|
|
20
25
|
|
|
21
26
|
Returns:
|
|
22
27
|
GeoDataFrame: GeoDataFrame with the merged mask.
|
|
@@ -30,7 +35,9 @@ def merge_tile_masks(tile_list: list, buffer: int = 1) -> GeoDataFrame:
|
|
|
30
35
|
|
|
31
36
|
# Process the mask by converting it to polygons.
|
|
32
37
|
polygons_and_labels.extend(
|
|
33
|
-
mask_to_shapely(
|
|
38
|
+
mask_to_shapely(
|
|
39
|
+
tile, x_offset=x, y_offset=y, background_label=background_label
|
|
40
|
+
)
|
|
34
41
|
)
|
|
35
42
|
|
|
36
43
|
# Convert polygons and labels into a GeoDataFrame.
|
|
@@ -45,4 +52,9 @@ def merge_tile_masks(tile_list: list, buffer: int = 1) -> GeoDataFrame:
|
|
|
45
52
|
# Remove the buffer.
|
|
46
53
|
gdf["geometry"] = gdf["geometry"].buffer(-buffer)
|
|
47
54
|
|
|
55
|
+
# Check that all polygons are valid.
|
|
56
|
+
for i, row in gdf.iterrows():
|
|
57
|
+
if not row["geometry"].is_valid:
|
|
58
|
+
gdf.loc[i, "geometry"] = row["geometry"].buffer(0)
|
|
59
|
+
|
|
48
60
|
return gdf
|
|
@@ -10,7 +10,7 @@ def mask_to_shapely(
|
|
|
10
10
|
mask: str | np.ndarray,
|
|
11
11
|
x_offset: int = 0,
|
|
12
12
|
y_offset: int = 0,
|
|
13
|
-
background_label: int = 0,
|
|
13
|
+
background_label: int | None = 0,
|
|
14
14
|
min_area: int = 0,
|
|
15
15
|
) -> list[Polygon, int]:
|
|
16
16
|
"""
|
|
@@ -20,9 +20,12 @@ def mask_to_shapely(
|
|
|
20
20
|
Args:
|
|
21
21
|
mask (str | np.ndarray): Path to the mask image or the mask.
|
|
22
22
|
x_offset (int): Offset to add to x coordinates of polygons.
|
|
23
|
+
Default is 0.
|
|
23
24
|
y_offset (int): Offset to add to y coordinates of polygons.
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
Default is 0.
|
|
26
|
+
background_label (int | None): Label value of the background class,
|
|
27
|
+
which is ignored. Default is 0. If None then all labels are
|
|
28
|
+
considered.
|
|
26
29
|
|
|
27
30
|
Returns:
|
|
28
31
|
list[Polygon, int]: List of polygons and their corresponding
|
|
@@ -26,6 +26,7 @@ dsa_helpers/dash/header.py
|
|
|
26
26
|
dsa_helpers/dash/header_callbacks.py
|
|
27
27
|
dsa_helpers/ml/__init__.py
|
|
28
28
|
dsa_helpers/ml/callbacks.py
|
|
29
|
+
dsa_helpers/ml/evaluate.py
|
|
29
30
|
dsa_helpers/ml/metrics.py
|
|
30
31
|
dsa_helpers/ml/datasets/SegFormerSegmentationDataset.py
|
|
31
32
|
dsa_helpers/ml/datasets/__init__.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dsa_helpers-1.dev0 → dsa_helpers-1.0.1.dev0}/dsa_helpers/ml/transforms/segformer_transforms.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|