dsa-helpers 3.1.8.dev1__tar.gz → 3.2.0__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-3.1.8.dev1 → dsa_helpers-3.2.0}/.gitignore +1 -0
- {dsa_helpers-3.1.8.dev1/dsa_helpers.egg-info → dsa_helpers-3.2.0}/PKG-INFO +1 -1
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/__init__.py +1 -1
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/gpd_utils.py +3 -2
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/yolo/inference.py +253 -18
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/utils.py +2 -2
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0/dsa_helpers.egg-info}/PKG-INFO +1 -1
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/pyproject.toml +1 -1
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/.readthedocs.yaml +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/LICENSE +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/README.md +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/docker/2.0.6-pytorch2.9.0-cuda13.0-cudnn9/Dockerfile +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/docker/README.md +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/docs/requirements.txt +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/docs/source/conf.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/docs/source/index.rst +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/cli_utils.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/dash/__init__.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/dash/dash_paperdragon_utils.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/dash/header.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/dash/header_callbacks.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/girder_utils.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/image_utils.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/imread.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/imwrite.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/__init__.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/callbacks.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/datasets/SegFormerSegmentationDataset.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/datasets/__init__.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/datasets/utils.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/evaluate.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/inference_results.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/metrics.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/object_detection/__init__.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/object_detection/get_pearce_roi_images.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/object_detection/tile_image.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/object_detection/utils.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/segformer_semantic_segmentation/__init__.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/segformer_semantic_segmentation/datasets.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/segformer_semantic_segmentation/evaluate.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/segformer_semantic_segmentation/inference.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/segformer_semantic_segmentation/train.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/segformer_semantic_segmentation/transforms.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/segformer_semantic_segmentation/utils.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/transforms/__init__.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/transforms/segformer_transforms.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/yolo/__init__.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/yolo/tiling.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/yolo/train.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/mongo_utils.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/tile_utils.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/tiling.py +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers.egg-info/SOURCES.txt +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers.egg-info/dependency_links.txt +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers.egg-info/requires.txt +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers.egg-info/top_level.txt +0 -0
- {dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/setup.cfg +0 -0
|
@@ -14,6 +14,7 @@ Functions:
|
|
|
14
14
|
|
|
15
15
|
import geopandas as gpd
|
|
16
16
|
import numpy as np
|
|
17
|
+
import pandas as pd
|
|
17
18
|
from rasterio.features import rasterize
|
|
18
19
|
from rdp import rdp
|
|
19
20
|
from tqdm import tqdm
|
|
@@ -27,12 +28,12 @@ import matplotlib.pyplot as plt
|
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
def remove_contained_boxes(
|
|
30
|
-
df: gpd.GeoDataFrame, thr: float
|
|
31
|
+
df: gpd.GeoDataFrame | pd.DataFrame, thr: float
|
|
31
32
|
) -> gpd.GeoDataFrame:
|
|
32
33
|
"""Remove boxes contained in other boxes, or mostly contained.
|
|
33
34
|
|
|
34
35
|
Args:
|
|
35
|
-
df (geopandas.GeoDataFrame): Data for each box, must contain the
|
|
36
|
+
df (geopandas.GeoDataFrame | pandas.DataFrame): Data for each box, must contain the
|
|
36
37
|
x1, y1, x2, y2, conf columns with point 1 being top left of
|
|
37
38
|
the box and point 2 and bottom right of box.
|
|
38
39
|
thr (float): The threshold of the box that must be contained by
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import large_image_source_openslide, ultralytics
|
|
1
|
+
import large_image_source_openslide, ultralytics
|
|
2
2
|
import geopandas as gpd
|
|
3
|
+
import pandas as pd
|
|
3
4
|
import cv2 as cv
|
|
4
5
|
import numpy as np
|
|
5
6
|
from shapely import Polygon
|
|
@@ -9,6 +10,237 @@ from ...utils import non_max_suppression, return_mag_and_resolution
|
|
|
9
10
|
from ...gpd_utils import remove_contained_boxes
|
|
10
11
|
|
|
11
12
|
|
|
13
|
+
def yolo_inference_no_iterator(
|
|
14
|
+
model: str | ultralytics.YOLO,
|
|
15
|
+
wsi_fp: str,
|
|
16
|
+
mag: float | None = 20,
|
|
17
|
+
mm_px: float | None = None,
|
|
18
|
+
tile_size: int = 640,
|
|
19
|
+
overlap: int = 160,
|
|
20
|
+
batch_size: int = 64,
|
|
21
|
+
agnostic_nms: bool = True,
|
|
22
|
+
conf_thr: float = 0.25,
|
|
23
|
+
iou: float = 0.7,
|
|
24
|
+
tile_area_threshold: float = 0.25,
|
|
25
|
+
pad_rgb: tuple[int, int, int] = (114, 114, 114),
|
|
26
|
+
device: str | None = None,
|
|
27
|
+
):
|
|
28
|
+
"""Perform YOLO inference on a whole slide image. This version differs
|
|
29
|
+
from yolo_inference in that it does not use the eager iterator.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
model (str | ultralytics.YOLO): YOLO model or path to the model
|
|
33
|
+
weights.
|
|
34
|
+
wsi_fp (str): File path to the whole slide image.
|
|
35
|
+
mag (float | None, optional): Desired magnification for tiling.
|
|
36
|
+
Note magnification meaning can vary among scanners. We
|
|
37
|
+
convert this to a corresponding resolution defined by a
|
|
38
|
+
standard magnification to resolution ratio (Emory scanner).
|
|
39
|
+
Default is None. Defined by mm_px, or if that is None than
|
|
40
|
+
the scan resolution along the x axis is used.
|
|
41
|
+
mm_px (float | None, optional): Micrometers per pixel for the
|
|
42
|
+
desired resolution. If not provided, will be inferred from
|
|
43
|
+
the mag parameter by standardizing to the Emory scanner. If
|
|
44
|
+
both are not provided, the scan resolution in the x
|
|
45
|
+
direction is used. Default is None.
|
|
46
|
+
tile_size (int, optional): Image is tiled into smaller images,
|
|
47
|
+
tiles, of this size at the desired resolution.
|
|
48
|
+
overlap (int, optional): The number of pixels to overlap between
|
|
49
|
+
tiles. Default is 160.
|
|
50
|
+
batch_size (int, optional): The number of tiles to process in a
|
|
51
|
+
single batch. Used both for the eager iterator and the YOLO
|
|
52
|
+
training. Default is 64.
|
|
53
|
+
agnostic_nms (bool, optional): Whether to use agnostic NMS.
|
|
54
|
+
Default is True.
|
|
55
|
+
conf_thr (float, optional): The confidence threshold for the
|
|
56
|
+
NMS. Default is 0.25.
|
|
57
|
+
iou (float, optional): The IoU threshold for the NMS. Default is
|
|
58
|
+
0.7.
|
|
59
|
+
tile_area_threshold (float, optional): The threshold for the tile
|
|
60
|
+
area in region. Default is 0.25.
|
|
61
|
+
pad_rgb (tuple[int, int, int], optional): The RGB values to pad
|
|
62
|
+
the tiles with. Default is (114, 114, 114).
|
|
63
|
+
device (str | None, optional): The device to use for inference.
|
|
64
|
+
Default is None, will use "cuda" if available, otherwise
|
|
65
|
+
"cpu". You can specify values like "cuda:0", "0", or
|
|
66
|
+
multiple devices separated by commas.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
dict: A dictionary containing the inference results.
|
|
70
|
+
- gdf (geopandas.GeoDataFrame): The inference results as a
|
|
71
|
+
GeoDataFrame.
|
|
72
|
+
- mag (float): The magnification used for inference.
|
|
73
|
+
- mm_px (float): The micrometers per pixel used for inference.
|
|
74
|
+
- time (dict): A dictionary containing the time taken for
|
|
75
|
+
inference.
|
|
76
|
+
- total (float): The total time taken for inference.
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
ValueError: If both mag and mm_px are provided.
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
print("This function has not been reviewed yet, use with caution.")
|
|
83
|
+
start_time = perf_counter()
|
|
84
|
+
|
|
85
|
+
if isinstance(model, str):
|
|
86
|
+
model = ultralytics.YOLO(model)
|
|
87
|
+
|
|
88
|
+
ts = large_image_source_openslide.open(wsi_fp)
|
|
89
|
+
ts_metadata = ts.getMetadata()
|
|
90
|
+
|
|
91
|
+
mm_x = ts_metadata["mm_x"]
|
|
92
|
+
mm_y = ts_metadata["mm_y"]
|
|
93
|
+
|
|
94
|
+
# Get the desired resolution.
|
|
95
|
+
if mag is not None and mm_px is not None:
|
|
96
|
+
raise ValueError("Only one of mag or mm_px can be provided.")
|
|
97
|
+
if mag is None and mm_px is None:
|
|
98
|
+
# Use the scan resolution, we use the x resolution.
|
|
99
|
+
mag, mm_px = return_mag_and_resolution(mm_px=mm_x)
|
|
100
|
+
else:
|
|
101
|
+
mag, mm_px = return_mag_and_resolution(mag=mag, mm_px=mm_px)
|
|
102
|
+
|
|
103
|
+
# Calculate the x and y size of the tile at scan resolution.
|
|
104
|
+
# desired resolution x sf_* -> scan resolution
|
|
105
|
+
sf_x = mm_px / mm_x
|
|
106
|
+
sf_y = mm_px / mm_y
|
|
107
|
+
|
|
108
|
+
scan_tile_x = int(tile_size * sf_x)
|
|
109
|
+
scan_tile_y = int(tile_size * sf_y)
|
|
110
|
+
|
|
111
|
+
stride = tile_size - overlap
|
|
112
|
+
scan_stride_x = int(stride * sf_x)
|
|
113
|
+
scan_stride_y = int(stride * sf_y)
|
|
114
|
+
|
|
115
|
+
# Calculate the x, y coordinates
|
|
116
|
+
xys = []
|
|
117
|
+
|
|
118
|
+
right = ts_metadata["sizeX"]
|
|
119
|
+
bottom = ts_metadata["sizeY"]
|
|
120
|
+
|
|
121
|
+
for x in range(0, right, scan_stride_x):
|
|
122
|
+
for y in range(0, bottom, scan_stride_y):
|
|
123
|
+
xys.append((x, y))
|
|
124
|
+
|
|
125
|
+
# Process in batches.
|
|
126
|
+
batch_indices = np.arange(0, len(xys), batch_size)
|
|
127
|
+
|
|
128
|
+
# Iterate through the WSI.
|
|
129
|
+
boxes = []
|
|
130
|
+
|
|
131
|
+
# Calculate the tile area to be above the threshold.
|
|
132
|
+
tile_area = tile_size * tile_size
|
|
133
|
+
minimum_tile_area = tile_area * tile_area_threshold
|
|
134
|
+
|
|
135
|
+
for batch_idx in batch_indices:
|
|
136
|
+
# Get the tiles.
|
|
137
|
+
batch_xys = xys[batch_idx : batch_idx + batch_size]
|
|
138
|
+
|
|
139
|
+
tiles = []
|
|
140
|
+
|
|
141
|
+
tile_x_coords = []
|
|
142
|
+
tile_y_coords = []
|
|
143
|
+
|
|
144
|
+
for xy in batch_xys:
|
|
145
|
+
x1, y1 = xy
|
|
146
|
+
|
|
147
|
+
x2 = x1 + scan_tile_x
|
|
148
|
+
|
|
149
|
+
if x2 > right:
|
|
150
|
+
x2 = right
|
|
151
|
+
|
|
152
|
+
y2 = y1 + scan_tile_y
|
|
153
|
+
|
|
154
|
+
if y2 > bottom:
|
|
155
|
+
y2 = bottom
|
|
156
|
+
|
|
157
|
+
# Get the tiles.
|
|
158
|
+
tile = ts.getRegion(
|
|
159
|
+
region={
|
|
160
|
+
"left": x1,
|
|
161
|
+
"top": y1,
|
|
162
|
+
"right": x2,
|
|
163
|
+
"bottom": y2,
|
|
164
|
+
},
|
|
165
|
+
format="numpy",
|
|
166
|
+
scale={"mm_x": mm_px, "mm_y": mm_px},
|
|
167
|
+
)[0][:, :, :3].copy()
|
|
168
|
+
|
|
169
|
+
# Pad the tile if it is not the right shape.
|
|
170
|
+
tile_h, tile_w, _ = tile.shape
|
|
171
|
+
|
|
172
|
+
tile_area = tile_h * tile_w
|
|
173
|
+
|
|
174
|
+
if tile_area < minimum_tile_area:
|
|
175
|
+
continue
|
|
176
|
+
|
|
177
|
+
tile_x_coords.append(x1)
|
|
178
|
+
tile_y_coords.append(y1)
|
|
179
|
+
|
|
180
|
+
if tile_h != tile_size or tile_w != tile_size:
|
|
181
|
+
tile = cv.copyMakeBorder(
|
|
182
|
+
tile,
|
|
183
|
+
0,
|
|
184
|
+
tile_size - tile_h,
|
|
185
|
+
0,
|
|
186
|
+
tile_size - tile_w,
|
|
187
|
+
cv.BORDER_CONSTANT,
|
|
188
|
+
value=pad_rgb,
|
|
189
|
+
)
|
|
190
|
+
# Convert to BGR.
|
|
191
|
+
tile = cv.cvtColor(tile, cv.COLOR_RGB2BGR)
|
|
192
|
+
|
|
193
|
+
tiles.append(tile)
|
|
194
|
+
|
|
195
|
+
if len(tiles) == 0:
|
|
196
|
+
continue
|
|
197
|
+
|
|
198
|
+
results = model(
|
|
199
|
+
tiles,
|
|
200
|
+
imgsz=tile_size,
|
|
201
|
+
batch=batch_size,
|
|
202
|
+
agnostic_nms=agnostic_nms,
|
|
203
|
+
verbose=False,
|
|
204
|
+
conf=conf_thr,
|
|
205
|
+
iou=iou,
|
|
206
|
+
device=device,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Loop through tile variables.
|
|
210
|
+
for result, x, y in zip(results, tile_x_coords, tile_y_coords):
|
|
211
|
+
xyxys = result.boxes.xyxy
|
|
212
|
+
cls_list = result.boxes.cls
|
|
213
|
+
conf_list = result.boxes.conf
|
|
214
|
+
|
|
215
|
+
for xyxy, cls, conf in zip(xyxys, cls_list, conf_list):
|
|
216
|
+
# cls and conf are tensorts in device, convert to to int and float
|
|
217
|
+
cls = int(cls)
|
|
218
|
+
conf = float(conf)
|
|
219
|
+
|
|
220
|
+
x1, y1, x2, y2 = xyxy
|
|
221
|
+
|
|
222
|
+
# (1) scale to scan resolution, (2) shift to tile location.
|
|
223
|
+
x1 = int(x1 * sf_x) + x
|
|
224
|
+
y1 = int(y1 * sf_y) + y
|
|
225
|
+
x2 = int(x2 * sf_x) + x
|
|
226
|
+
y2 = int(y2 * sf_y) + y
|
|
227
|
+
|
|
228
|
+
# Create the polygon.
|
|
229
|
+
geom = Polygon([(x1, y1), (x2, y1), (x2, y2), (x1, y2)])
|
|
230
|
+
boxes.append([cls, x1, y1, x2, y2, conf, geom])
|
|
231
|
+
|
|
232
|
+
gdf = gpd.GeoDataFrame(
|
|
233
|
+
boxes, columns=["label", "x1", "y1", "x2", "y2", "conf", "geometry"]
|
|
234
|
+
)
|
|
235
|
+
gdf["box_area"] = gdf.geometry.area
|
|
236
|
+
|
|
237
|
+
# Clean up predictions further.
|
|
238
|
+
gdf = non_max_suppression(gdf, conf_thr)
|
|
239
|
+
gdf = remove_contained_boxes(gdf, iou).reset_index(drop=True)
|
|
240
|
+
|
|
241
|
+
return gdf, mag, mm_px
|
|
242
|
+
|
|
243
|
+
|
|
12
244
|
def yolo_inference(
|
|
13
245
|
model: str | ultralytics.YOLO,
|
|
14
246
|
wsi_fp: str,
|
|
@@ -80,8 +312,10 @@ def yolo_inference(
|
|
|
80
312
|
|
|
81
313
|
Raises:
|
|
82
314
|
ValueError: If both mag and mm_px are provided.
|
|
315
|
+
|
|
83
316
|
"""
|
|
84
317
|
start_time = perf_counter()
|
|
318
|
+
|
|
85
319
|
if isinstance(model, str):
|
|
86
320
|
model = ultralytics.YOLO(model)
|
|
87
321
|
|
|
@@ -144,7 +378,7 @@ def yolo_inference(
|
|
|
144
378
|
|
|
145
379
|
# Loop through tile variables.
|
|
146
380
|
for result, x, y in zip(results, tile_x_coords, tile_y_coords):
|
|
147
|
-
xyxys = result.boxes.
|
|
381
|
+
xyxys = result.boxes.xyxy
|
|
148
382
|
cls_list = result.boxes.cls
|
|
149
383
|
conf_list = result.boxes.conf
|
|
150
384
|
|
|
@@ -156,10 +390,10 @@ def yolo_inference(
|
|
|
156
390
|
x1, y1, x2, y2 = xyxy
|
|
157
391
|
|
|
158
392
|
# (1) scale to scan resolution, (2) shift to tile location.
|
|
159
|
-
x1 = int(x1 *
|
|
160
|
-
y1 = int(y1 *
|
|
161
|
-
x2 = int(x2 *
|
|
162
|
-
y2 = int(y2 *
|
|
393
|
+
x1 = int(x1 * sf_x) + x
|
|
394
|
+
y1 = int(y1 * sf_y) + y
|
|
395
|
+
x2 = int(x2 * sf_x) + x
|
|
396
|
+
y2 = int(y2 * sf_y) + y
|
|
163
397
|
|
|
164
398
|
# Create the polygon.
|
|
165
399
|
geom = Polygon([(x1, y1), (x2, y1), (x2, y2), (x1, y2)])
|
|
@@ -201,7 +435,7 @@ def yolo_inference_on_region(
|
|
|
201
435
|
tile_area_threshold: float = 0.25,
|
|
202
436
|
nms_iou_thr: float = 0.25,
|
|
203
437
|
remove_contained_thr: float = 0.7,
|
|
204
|
-
) -> tuple[gpd.GeoDataFrame, float, float]:
|
|
438
|
+
) -> tuple[gpd.GeoDataFrame | pd.DataFrame, float, float]:
|
|
205
439
|
"""Inference on a region of a WSI using a YOLO model.
|
|
206
440
|
|
|
207
441
|
Args:
|
|
@@ -317,10 +551,8 @@ def yolo_inference_on_region(
|
|
|
317
551
|
minimum_tile_area = tile_area * tile_area_threshold
|
|
318
552
|
|
|
319
553
|
for batch_idx in batch_indices:
|
|
320
|
-
xy_index = batch_idx * batch_size
|
|
321
|
-
|
|
322
554
|
# Get the tiles.
|
|
323
|
-
batch_xys = xys[
|
|
555
|
+
batch_xys = xys[batch_idx : batch_idx + batch_size]
|
|
324
556
|
|
|
325
557
|
tiles = []
|
|
326
558
|
|
|
@@ -348,7 +580,7 @@ def yolo_inference_on_region(
|
|
|
348
580
|
"right": x2,
|
|
349
581
|
"bottom": y2,
|
|
350
582
|
},
|
|
351
|
-
format=
|
|
583
|
+
format="numpy",
|
|
352
584
|
scale={"mm_x": mm_px, "mm_y": mm_px},
|
|
353
585
|
)[0][:, :, :3].copy()
|
|
354
586
|
|
|
@@ -378,6 +610,9 @@ def yolo_inference_on_region(
|
|
|
378
610
|
|
|
379
611
|
tiles.append(tile)
|
|
380
612
|
|
|
613
|
+
if len(tiles) == 0:
|
|
614
|
+
continue
|
|
615
|
+
|
|
381
616
|
results = model(
|
|
382
617
|
tiles,
|
|
383
618
|
imgsz=tile_size,
|
|
@@ -391,7 +626,7 @@ def yolo_inference_on_region(
|
|
|
391
626
|
|
|
392
627
|
# Loop through tile variables.
|
|
393
628
|
for result, x, y in zip(results, tile_x_coords, tile_y_coords):
|
|
394
|
-
xyxys = result.boxes.
|
|
629
|
+
xyxys = result.boxes.xyxy
|
|
395
630
|
cls_list = result.boxes.cls
|
|
396
631
|
conf_list = result.boxes.conf
|
|
397
632
|
|
|
@@ -403,10 +638,10 @@ def yolo_inference_on_region(
|
|
|
403
638
|
x1, y1, x2, y2 = xyxy
|
|
404
639
|
|
|
405
640
|
# (1) scale to scan resolution, (2) shift to tile location.
|
|
406
|
-
x1 = int(x1 *
|
|
407
|
-
y1 = int(y1 *
|
|
408
|
-
x2 = int(x2 *
|
|
409
|
-
y2 = int(y2 *
|
|
641
|
+
x1 = int(x1 * sf_x) + x
|
|
642
|
+
y1 = int(y1 * sf_y) + y
|
|
643
|
+
x2 = int(x2 * sf_x) + x
|
|
644
|
+
y2 = int(y2 * sf_y) + y
|
|
410
645
|
|
|
411
646
|
# Create the polygon.
|
|
412
647
|
geom = Polygon([(x1, y1), (x2, y1), (x2, y2), (x1, y2)])
|
|
@@ -418,9 +653,9 @@ def yolo_inference_on_region(
|
|
|
418
653
|
gdf["box_area"] = gdf.geometry.area
|
|
419
654
|
|
|
420
655
|
# Clean up predictions further.
|
|
421
|
-
if nms_iou_thr:
|
|
656
|
+
if nms_iou_thr > 0:
|
|
422
657
|
gdf = non_max_suppression(gdf, nms_iou_thr)
|
|
423
|
-
if remove_contained_thr:
|
|
658
|
+
if remove_contained_thr > 0:
|
|
424
659
|
gdf = remove_contained_boxes(gdf, remove_contained_thr)
|
|
425
660
|
|
|
426
661
|
return gdf, mag, mm_px
|
|
@@ -62,7 +62,7 @@ def non_max_suppression(df: pd.DataFrame, thr: float) -> pd.DataFrame:
|
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
def return_mag_and_resolution(
|
|
65
|
-
mag: int | None = None,
|
|
65
|
+
mag: float | int | None = None,
|
|
66
66
|
mm_px: float | None = None,
|
|
67
67
|
standard_mag: int = 40,
|
|
68
68
|
standard_mm_px: float = 0.0002519,
|
|
@@ -72,7 +72,7 @@ def return_mag_and_resolution(
|
|
|
72
72
|
standard_mm_per_px.
|
|
73
73
|
|
|
74
74
|
Args:
|
|
75
|
-
mag (int | None, optional): Magnification to use. Defaults to
|
|
75
|
+
mag (float | int | None, optional): Magnification to use. Defaults to
|
|
76
76
|
None.
|
|
77
77
|
mm_px (float | None, optional): Resolution to use. Defaults to
|
|
78
78
|
None.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/docker/2.0.6-pytorch2.9.0-cuda13.0-cudnn9/Dockerfile
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/datasets/SegFormerSegmentationDataset.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/segformer_semantic_segmentation/train.py
RENAMED
|
File without changes
|
|
File without changes
|
{dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/dsa_helpers/ml/segformer_semantic_segmentation/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
{dsa_helpers-3.1.8.dev1 → dsa_helpers-3.2.0}/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
|
|
File without changes
|