From d998c656658e42dbf2c236a3d69255d7c36f6d44 Mon Sep 17 00:00:00 2001 From: Martin Laasmaa Date: Mon, 12 Jan 2026 13:28:00 +0200 Subject: [PATCH] Updating image splitter --- src/utils/image.py | 23 ++++++++------- src/utils/image_splitter.py | 57 ++++++++++++++++++++++++------------- tests/show_yolo_seg.py | 45 +++++++++++++++++++++-------- 3 files changed, 83 insertions(+), 42 deletions(-) diff --git a/src/utils/image.py b/src/utils/image.py index d0f0070..c0fdeeb 100644 --- a/src/utils/image.py +++ b/src/utils/image.py @@ -17,7 +17,7 @@ from tifffile import imread, imwrite logger = get_logger(__name__) -def get_pseudo_rgb(arr: np.ndarray, gamma: float = 0.3) -> np.ndarray: +def get_pseudo_rgb(arr: np.ndarray, gamma: float = 0.5) -> np.ndarray: """ Convert a grayscale image to a pseudo-RGB image using a gamma correction. @@ -33,21 +33,22 @@ def get_pseudo_rgb(arr: np.ndarray, gamma: float = 0.3) -> np.ndarray: a1 = arr.copy().astype(np.float32) a1 -= np.percentile(a1, 2) a1[a1 < 0] = 0 - # p999 = np.percentile(a1, 99.9) - # a1[a1 > p999] = p999 + p999 = np.percentile(a1, 99.9) + a1[a1 > p999] = p999 a1 /= a1.max() - a2 = a1.copy() - a2 = a2**gamma - a2 /= a2.max() + if 0: + a2 = a1.copy() + a2 = a2**gamma + a2 /= a2.max() - a3 = a1.copy() - p9999 = np.percentile(a3, 99.99) - a3[a3 > p9999] = p9999 - a3 /= a3.max() + a3 = a1.copy() + p9999 = np.percentile(a3, 99.99) + a3[a3 > p9999] = p9999 + a3 /= a3.max() return np.stack([a1, np.zeros(a1.shape), np.zeros(a1.shape)], axis=0) - return np.stack([a2, np.zeros(a1.shape), np.zeros(a1.shape)], axis=0) + # return np.stack([a2, np.zeros(a1.shape), np.zeros(a1.shape)], axis=0) # return np.stack([a1, a2, a3], axis=0) diff --git a/src/utils/image_splitter.py b/src/utils/image_splitter.py index 3dacf6e..5f01d6e 100644 --- a/src/utils/image_splitter.py +++ b/src/utils/image_splitter.py @@ -4,10 +4,13 @@ from pathlib import Path from tifffile import imread, imwrite from shapely.geometry import LineString from copy import deepcopy +from scipy.ndimage import zoom + # debug from src.utils.image import Image from show_yolo_seg import draw_annotations + import pylab as plt import cv2 @@ -24,13 +27,15 @@ class Label: class_id = int(class_id) bbox = np.array(coords[:4], dtype=np.float32) polygon = np.array(coords[4:], dtype=np.float32).reshape(-1, 2) if len(coords) > 4 else None + if not any(np.isclose(polygon[0], polygon[-1])): + polygon = np.vstack([polygon, polygon[0]]) return class_id, bbox, polygon def offset_label( self, img_w, img_h, - distance: float = 3.0, + distance: float = 1.0, cap_style: int = 2, join_style: int = 2, ): @@ -64,7 +69,7 @@ class Label: line = LineString(pts) # Buffer distance in pixels buffered = line.buffer(distance=distance, cap_style=cap_style, join_style=join_style) - self.polygon = np.array(buffered.exterior.coords, dtype=np.float32) + self.polygon = np.array(buffered.exterior.coords, dtype=np.float32) / (img_w, img_h) xmn, ymn = self.polygon.min(axis=0) xmx, ymx = self.polygon.max(axis=0) xc = (xmn + xmx) / 2 @@ -158,7 +163,11 @@ class ImageSplitter: self.image = imread(image_path) self.image_path = image_path self.label_path = label_path - self.labels = YoloLabelReader(label_path) + if not label_path.exists(): + print(f"Label file {label_path} not found") + self.labels = None + else: + self.labels = YoloLabelReader(label_path) def split_into_tiles(self, patch_size: tuple = (2, 2)): """Split image into patches of size patch_size""" @@ -173,18 +182,21 @@ class ImageSplitter: tile_reference = f"i{i}j{j}" hrange = (i * hstep / h, (i + 1) * hstep / h) wrange = (j * wstep / w, (j + 1) * wstep / w) - labels = deepcopy(self.labels.get_labels(hrange, wrange)) tile = self.image[i * hstep : (i + 1) * hstep, j * wstep : (j + 1) * wstep] - print(id(labels)) - if labels is not None: - print(hrange[0], wrange[0]) - for l in labels: - print(l.bbox) - [l.translate(wrange[0], hrange[0], 2, 2) for l in labels] - print("translated") - for l in labels: - print(l.bbox) + labels = None + if self.labels is not None: + labels = deepcopy(self.labels.get_labels(hrange, wrange)) + print(id(labels)) + + if labels is not None: + print(hrange[0], wrange[0]) + for l in labels: + print(l.bbox) + [l.translate(wrange[0], hrange[0], 2, 2) for l in labels] + print("translated") + for l in labels: + print(l.bbox) # print(labels) yield tile_reference, tile, labels @@ -234,13 +246,14 @@ class ImageSplitter: # print("tile shape:", tile.shape) - yolo_annotation = f"{label.class_id} {x_offset/nx} {y_offset/ny} {h/ny} {w/nx} " + " ".join( + yolo_annotation = f"{label.class_id} {x_offset/nx} {y_offset/ny} {h_norm} {w_norm} " + print(yolo_annotation) + yolo_annotation += " ".join( [ f"{(x*self.image.shape[1]-(xc - x_offset))/nx:.6f} {(y*self.image.shape[0]-(yc-y_offset))/ny:.6f}" for x, y in label.polygon ] ) - # print(yolo_annotation) new_label = Label(yolo_annotation=yolo_annotation) yield tile_reference, tile, [new_label] @@ -251,6 +264,7 @@ def main(args): if args.output: args.output.mkdir(exist_ok=True, parents=True) (args.output / "images").mkdir(exist_ok=True) + (args.output / "images-zoomed").mkdir(exist_ok=True) (args.output / "labels").mkdir(exist_ok=True) for image_path in (args.input / "images").glob("*.tif"): @@ -297,10 +311,15 @@ def main(args): if args.output: imwrite(args.output / "images" / f"{image_path.stem}_{tile_reference}.tif", tile) - with open(args.output / "labels" / f"{image_path.stem}_{tile_reference}.txt", "w") as f: - for label in labels: - label.offset_label(tile.shape[1], tile.shape[0]) - f.write(label.to_string() + "\n") + scale = 5 + tile_zoomed = zoom(tile, zoom=scale) + imwrite(args.output / "images-zoomed" / f"{image_path.stem}_{tile_reference}.tif", tile_zoomed) + + if labels is not None: + with open(args.output / "labels" / f"{image_path.stem}_{tile_reference}.txt", "w") as f: + for label in labels: + label.offset_label(tile.shape[1], tile.shape[0]) + f.write(label.to_string() + "\n") if __name__ == "__main__": diff --git a/tests/show_yolo_seg.py b/tests/show_yolo_seg.py index e1deb8c..9f7eb53 100644 --- a/tests/show_yolo_seg.py +++ b/tests/show_yolo_seg.py @@ -103,16 +103,17 @@ def draw_annotations(img, labels, alpha=0.4, draw_bbox_for_poly=True): cv2.polylines(img, [pts], isClosed=True, color=color, thickness=1) # put class text at first point x, y = int(pts[0, 0]), int(pts[0, 1]) - 6 - cv2.putText( - img, - str(cls), - (x, max(6, y)), - cv2.FONT_HERSHEY_SIMPLEX, - 0.6, - (255, 255, 255), - 2, - cv2.LINE_AA, - ) + if 0: + cv2.putText( + img, + str(cls), + (x, max(6, y)), + cv2.FONT_HERSHEY_SIMPLEX, + 0.6, + (255, 255, 255), + 2, + cv2.LINE_AA, + ) # YOLO bbox case (4 coords) elif len(coords) == 4: @@ -188,12 +189,32 @@ def main(): # continue and just show image out = draw_annotations(img.copy(), labels, alpha=args.alpha, draw_bbox_for_poly=(not args.no_bbox)) + lclass, coords = labels[0] + print(lclass, coords) + bbox = coords[:4] + print("bbox", bbox) + bbox = np.array(bbox) * np.array([img.shape[1], img.shape[0], img.shape[1], img.shape[0]]) + yc, xc, h, w = bbox + print("bbox", bbox) + polyline = np.array(coords[4:]).reshape(-1, 2) * np.array([img.shape[1], img.shape[0]]) + print("pl", coords[4:]) + print("pl", polyline) + # Convert BGR -> RGB for matplotlib display - out_rgb = cv2.cvtColor(out, cv2.COLOR_BGR2RGB) + # out_rgb = cv2.cvtColor(out, cv2.COLOR_BGR2RGB) + out_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # out_rgb = Image() plt.figure(figsize=(10, 10 * out.shape[0] / out.shape[1])) plt.imshow(out_rgb) - plt.axis("off") + plt.plot(polyline[:, 0], polyline[:, 1], "y", linewidth=2) + plt.plot( + [yc - h / 2, yc - h / 2, yc + h / 2, yc + h / 2, yc - h / 2], + [xc - w / 2, xc + w / 2, xc + w / 2, xc - w / 2, xc - w / 2], + "r", + linewidth=2, + ) + + # plt.axis("off") plt.title(f"{img_path.name} ({lbl_path.name})") plt.show()