Updating image converter and aading simple script to visulaize segmentation
This commit is contained in:
@@ -14,8 +14,9 @@ class UT:
|
|||||||
|
|
||||||
def __init__(self, roifile_fn: Path):
|
def __init__(self, roifile_fn: Path):
|
||||||
self.roifile_fn = roifile_fn
|
self.roifile_fn = roifile_fn
|
||||||
|
print("is file", self.roifile_fn.is_file())
|
||||||
self.rois = ImagejRoi.fromfile(self.roifile_fn)
|
self.rois = ImagejRoi.fromfile(self.roifile_fn)
|
||||||
self.stem = self.roifile_fn.stem.strip("-RoiSet")
|
self.stem = self.roifile_fn.stem.split("Roi-")[1]
|
||||||
self.image, self.image_props = self._load_images()
|
self.image, self.image_props = self._load_images()
|
||||||
|
|
||||||
def _load_images(self):
|
def _load_images(self):
|
||||||
@@ -23,12 +24,11 @@ class UT:
|
|||||||
array sequence is CZYX
|
array sequence is CZYX
|
||||||
"""
|
"""
|
||||||
print(self.roifile_fn.parent, self.stem)
|
print(self.roifile_fn.parent, self.stem)
|
||||||
fns = list(self.roifile_fn.parent.glob(f"{self.stem}*.tif*"))
|
fns = list(self.roifile_fn.parent.glob(f"{self.stem.lower()}*.tif*"))
|
||||||
stems = [fn.stem.split(self.stem)[-1] for fn in fns]
|
stems = [fn.stem.split(self.stem)[-1] for fn in fns]
|
||||||
n_ch = len(set([stem.split("-ch")[-1].split("t")[0] for stem in stems]))
|
n_ch = len(set([stem.split("-ch")[-1].split("t")[0] for stem in stems]))
|
||||||
n_p = len(set([stem.split("-")[0] for stem in stems]))
|
n_p = len(set([stem.split("-")[0] for stem in stems]))
|
||||||
n_t = len(set([stem.split("t")[1] for stem in stems]))
|
n_t = len(set([stem.split("t")[1] for stem in stems]))
|
||||||
print(n_ch, n_p, n_t)
|
|
||||||
|
|
||||||
with TiffFile(fns[0]) as tif:
|
with TiffFile(fns[0]) as tif:
|
||||||
img = tif.asarray()
|
img = tif.asarray()
|
||||||
@@ -42,6 +42,7 @@ class UT:
|
|||||||
"height": h,
|
"height": h,
|
||||||
"dtype": dtype,
|
"dtype": dtype,
|
||||||
}
|
}
|
||||||
|
print("Image props", self.image_props)
|
||||||
|
|
||||||
image_stack = np.zeros((n_ch, n_p, w, h), dtype=dtype)
|
image_stack = np.zeros((n_ch, n_p, w, h), dtype=dtype)
|
||||||
for fn in fns:
|
for fn in fns:
|
||||||
@@ -49,7 +50,7 @@ class UT:
|
|||||||
img = tif.asarray()
|
img = tif.asarray()
|
||||||
stem = fn.stem.split(self.stem)[-1]
|
stem = fn.stem.split(self.stem)[-1]
|
||||||
ch = int(stem.split("-ch")[-1].split("t")[0])
|
ch = int(stem.split("-ch")[-1].split("t")[0])
|
||||||
p = int(stem.split("-")[0].lstrip("p"))
|
p = int(stem.split("-")[0].split("p")[1])
|
||||||
t = int(stem.split("t")[1])
|
t = int(stem.split("t")[1])
|
||||||
print(fn.stem, "ch", ch, "p", p, "t", t)
|
print(fn.stem, "ch", ch, "p", p, "t", t)
|
||||||
image_stack[ch - 1, p - 1] = img
|
image_stack[ch - 1, p - 1] = img
|
||||||
@@ -82,11 +83,22 @@ class UT:
|
|||||||
):
|
):
|
||||||
"""Export rois to a file"""
|
"""Export rois to a file"""
|
||||||
with open(path / subfolder / f"{self.stem}.txt", "w") as f:
|
with open(path / subfolder / f"{self.stem}.txt", "w") as f:
|
||||||
for roi in self.rois:
|
for i, roi in enumerate(self.rois):
|
||||||
# TODO add image coordinates normalization
|
rc = roi.subpixel_coordinates
|
||||||
coords = ""
|
if rc is None:
|
||||||
for x, y in roi.subpixel_coordinates:
|
print(
|
||||||
coords += f"{x/self.width} {y/self.height}"
|
f"No coordinates: {self.roifile_fn}, element {i}, out of {len(self.rois)}"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
xmn, ymn = rc.min(axis=0)
|
||||||
|
xmx, ymx = rc.max(axis=0)
|
||||||
|
xc = (xmn + xmx) / 2
|
||||||
|
yc = (ymn + ymx) / 2
|
||||||
|
bw = xmx - xmn
|
||||||
|
bh = ymx - ymn
|
||||||
|
coords = f"{xc/self.width} {yc/self.height} {bw/self.width} {bh/self.height} "
|
||||||
|
for x, y in rc:
|
||||||
|
coords += f"{x/self.width} {y/self.height} "
|
||||||
f.write(f"{class_index} {coords}\n")
|
f.write(f"{class_index} {coords}\n")
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -112,11 +124,16 @@ if __name__ == "__main__":
|
|||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("input", type=Path)
|
parser.add_argument("-i", "--input", nargs="*", type=Path)
|
||||||
parser.add_argument("output", type=Path)
|
parser.add_argument("-o", "--output", type=Path)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
for rfn in args.input.glob("*.zip"):
|
for path in args.input:
|
||||||
|
print("Path:", path)
|
||||||
|
for rfn in Path(path).glob("*.zip"):
|
||||||
|
print("Roi FN:", rfn)
|
||||||
ut = UT(rfn)
|
ut = UT(rfn)
|
||||||
ut.export_rois(args.output, class_index=0)
|
ut.export_rois(args.output, class_index=0)
|
||||||
ut.export_image(args.output, plane_mode="max projection", channel=0)
|
ut.export_image(args.output, plane_mode="max projection", channel=0)
|
||||||
|
|
||||||
|
print()
|
||||||
|
|||||||
184
tests/show_yolo_seg.py
Normal file
184
tests/show_yolo_seg.py
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
show_yolo_seg.py
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python show_yolo_seg.py /path/to/image.jpg /path/to/labels.txt
|
||||||
|
|
||||||
|
Supports:
|
||||||
|
- Segmentation polygons: "class x1 y1 x2 y2 ... xn yn"
|
||||||
|
- YOLO bbox lines as fallback: "class x_center y_center width height"
|
||||||
|
Coordinates can be normalized [0..1] or absolute pixels (auto-detected).
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
import random
|
||||||
|
|
||||||
|
|
||||||
|
def parse_label_line(line):
|
||||||
|
parts = line.strip().split()
|
||||||
|
if not parts:
|
||||||
|
return None
|
||||||
|
cls = int(float(parts[0]))
|
||||||
|
coords = [float(x) for x in parts[1:]]
|
||||||
|
return cls, coords
|
||||||
|
|
||||||
|
|
||||||
|
def coords_are_normalized(coords):
|
||||||
|
# If every coordinate is between 0 and 1 (inclusive-ish), assume normalized
|
||||||
|
if not coords:
|
||||||
|
return False
|
||||||
|
return max(coords) <= 1.001
|
||||||
|
|
||||||
|
|
||||||
|
def yolo_bbox_to_xyxy(coords, img_w, img_h):
|
||||||
|
# coords: [xc, yc, w, h] normalized or absolute
|
||||||
|
xc, yc, w, h = coords[:4]
|
||||||
|
if max(coords) <= 1.001:
|
||||||
|
xc *= img_w
|
||||||
|
yc *= img_h
|
||||||
|
w *= img_w
|
||||||
|
h *= img_h
|
||||||
|
x1 = int(round(xc - w / 2))
|
||||||
|
y1 = int(round(yc - h / 2))
|
||||||
|
x2 = int(round(xc + w / 2))
|
||||||
|
y2 = int(round(yc + h / 2))
|
||||||
|
return x1, y1, x2, y2
|
||||||
|
|
||||||
|
|
||||||
|
def poly_to_pts(coords, img_w, img_h):
|
||||||
|
# coords: [x1 y1 x2 y2 ...] either normalized or absolute
|
||||||
|
if coords_are_normalized(coords[4:]):
|
||||||
|
coords = [
|
||||||
|
coords[i] * (img_w if i % 2 == 0 else img_h) for i in range(len(coords))
|
||||||
|
]
|
||||||
|
pts = np.array(coords, dtype=np.int32).reshape(-1, 2)
|
||||||
|
return pts
|
||||||
|
|
||||||
|
|
||||||
|
def random_color_for_class(cls):
|
||||||
|
random.seed(cls) # deterministic per class
|
||||||
|
return tuple(int(x) for x in np.array([random.randint(0, 255) for _ in range(3)]))
|
||||||
|
|
||||||
|
|
||||||
|
def draw_annotations(img, labels, alpha=0.4, draw_bbox_for_poly=True):
|
||||||
|
# img: BGR numpy array
|
||||||
|
overlay = img.copy()
|
||||||
|
h, w = img.shape[:2]
|
||||||
|
for cls, coords in labels:
|
||||||
|
if not coords:
|
||||||
|
continue
|
||||||
|
# polygon case (>=6 coordinates)
|
||||||
|
if len(coords) >= 6:
|
||||||
|
color = random_color_for_class(cls)
|
||||||
|
|
||||||
|
x1, y1, x2, y2 = yolo_bbox_to_xyxy(coords[:4], w, h)
|
||||||
|
cv2.rectangle(img, (x1, y1), (x2, y2), color, 2)
|
||||||
|
|
||||||
|
pts = poly_to_pts(coords[4:], w, h)
|
||||||
|
# fill on overlay
|
||||||
|
cv2.fillPoly(overlay, [pts], color)
|
||||||
|
# outline on base image
|
||||||
|
cv2.polylines(img, [pts], isClosed=True, color=color, thickness=2)
|
||||||
|
# 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,
|
||||||
|
)
|
||||||
|
|
||||||
|
# YOLO bbox case (4 coords)
|
||||||
|
elif len(coords) == 4:
|
||||||
|
x1, y1, x2, y2 = yolo_bbox_to_xyxy(coords, w, h)
|
||||||
|
color = random_color_for_class(cls)
|
||||||
|
cv2.rectangle(img, (x1, y1), (x2, y2), color, 2)
|
||||||
|
cv2.putText(
|
||||||
|
img,
|
||||||
|
str(cls),
|
||||||
|
(x1, max(6, y1 - 4)),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX,
|
||||||
|
0.6,
|
||||||
|
(255, 255, 255),
|
||||||
|
2,
|
||||||
|
cv2.LINE_AA,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Unknown / invalid format, skip
|
||||||
|
continue
|
||||||
|
|
||||||
|
# blend overlay for filled polygons
|
||||||
|
cv2.addWeighted(overlay, alpha, img, 1 - alpha, 0, img)
|
||||||
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
def load_labels_file(label_path):
|
||||||
|
labels = []
|
||||||
|
with open(label_path, "r") as f:
|
||||||
|
for raw in f:
|
||||||
|
line = raw.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
parsed = parse_label_line(line)
|
||||||
|
if parsed:
|
||||||
|
labels.append(parsed)
|
||||||
|
return labels
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Show YOLO segmentation / polygon annotations"
|
||||||
|
)
|
||||||
|
parser.add_argument("image", type=str, help="Path to image file")
|
||||||
|
parser.add_argument("labels", type=str, help="Path to YOLO label file (polygons)")
|
||||||
|
parser.add_argument(
|
||||||
|
"--alpha", type=float, default=0.4, help="Polygon fill alpha (0..1)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-bbox", action="store_true", help="Don't draw bounding boxes for polygons"
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
img_path = Path(args.image)
|
||||||
|
lbl_path = Path(args.labels)
|
||||||
|
|
||||||
|
if not img_path.exists():
|
||||||
|
print("Image not found:", img_path)
|
||||||
|
sys.exit(1)
|
||||||
|
if not lbl_path.exists():
|
||||||
|
print("Label file not found:", lbl_path)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
img = cv2.imread(str(img_path), cv2.IMREAD_COLOR)
|
||||||
|
if img is None:
|
||||||
|
print("Could not load image:", img_path)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
labels = load_labels_file(str(lbl_path))
|
||||||
|
if not labels:
|
||||||
|
print("No labels parsed from", lbl_path)
|
||||||
|
# continue and just show image
|
||||||
|
out = draw_annotations(
|
||||||
|
img.copy(), labels, alpha=args.alpha, draw_bbox_for_poly=(not args.no_bbox)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Convert BGR -> RGB for matplotlib display
|
||||||
|
out_rgb = cv2.cvtColor(out, cv2.COLOR_BGR2RGB)
|
||||||
|
plt.figure(figsize=(10, 10 * out.shape[0] / out.shape[1]))
|
||||||
|
plt.imshow(out_rgb)
|
||||||
|
plt.axis("off")
|
||||||
|
plt.title(f"{img_path.name} ({lbl_path.name})")
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user