|
from pathlib import Path |
|
from typing import Dict, Optional, Tuple |
|
|
|
import numpy as np |
|
from PIL import Image, ImageDraw |
|
|
|
|
|
def create_fundus_overlay( |
|
rgb_path: str, |
|
av_path: Optional[str] = None, |
|
disc_path: Optional[str] = None, |
|
fovea_location: Optional[Tuple[int, int]] = None, |
|
output_path: Optional[str] = None, |
|
) -> np.ndarray: |
|
""" |
|
Create a visualization of a fundus image with overlaid segmentations and markers. |
|
|
|
Args: |
|
rgb_path: Path to the RGB fundus image |
|
av_path: Optional path to artery-vein segmentation (1=artery, 2=vein, 3=intersection) |
|
disc_path: Optional path to binary disc segmentation |
|
fovea_location: Optional (x,y) tuple indicating the location of the fovea |
|
output_path: Optional path to save the visualization image |
|
|
|
Returns: |
|
Numpy array containing the visualization image |
|
""" |
|
print(rgb_path, av_path, disc_path, fovea_location, output_path) |
|
|
|
rgb_img = np.array(Image.open(rgb_path)) |
|
|
|
|
|
output_img = rgb_img.copy() |
|
|
|
|
|
if av_path: |
|
av_mask = np.array(Image.open(av_path)) |
|
|
|
|
|
artery_mask = av_mask == 1 |
|
vein_mask = av_mask == 2 |
|
intersection_mask = av_mask == 3 |
|
|
|
|
|
artery_combined = np.logical_or(artery_mask, intersection_mask) |
|
vein_combined = np.logical_or(vein_mask, intersection_mask) |
|
|
|
|
|
|
|
output_img[artery_combined, 0] = 255 |
|
output_img[artery_combined, 1] = 0 |
|
output_img[artery_combined, 2] = 0 |
|
|
|
|
|
output_img[vein_combined, 0] = 0 |
|
output_img[vein_combined, 1] = 0 |
|
output_img[vein_combined, 2] = 255 |
|
|
|
|
|
if disc_path: |
|
disc_mask = np.array(Image.open(disc_path)) > 0 |
|
|
|
|
|
output_img[disc_mask, :] = [255, 255, 255] |
|
|
|
|
|
pil_img = Image.fromarray(output_img) |
|
|
|
|
|
if fovea_location: |
|
draw = ImageDraw.Draw(pil_img) |
|
x, y = fovea_location |
|
marker_size = ( |
|
min(pil_img.width, pil_img.height) // 50 |
|
) |
|
|
|
|
|
draw.line( |
|
[(x - marker_size, y - marker_size), (x + marker_size, y + marker_size)], |
|
fill=(255, 255, 0), |
|
width=2, |
|
) |
|
draw.line( |
|
[(x - marker_size, y + marker_size), (x + marker_size, y - marker_size)], |
|
fill=(255, 255, 0), |
|
width=2, |
|
) |
|
|
|
|
|
output_img = np.array(pil_img) |
|
|
|
|
|
if output_path: |
|
Image.fromarray(output_img).save(output_path) |
|
|
|
return output_img |
|
|
|
|
|
def batch_create_overlays( |
|
rgb_dir: Path, |
|
output_dir: Path, |
|
av_dir: Optional[Path] = None, |
|
disc_dir: Optional[Path] = None, |
|
fovea_data: Optional[Dict[str, Tuple[int, int]]] = None, |
|
) -> None: |
|
""" |
|
Create visualization overlays for a batch of images. |
|
|
|
Args: |
|
rgb_dir: Directory containing RGB fundus images |
|
output_dir: Directory to save visualization images |
|
av_dir: Optional directory containing AV segmentations |
|
disc_dir: Optional directory containing disc segmentations |
|
fovea_data: Optional dictionary mapping image IDs to fovea coordinates |
|
|
|
Returns: |
|
List of paths to created visualization images |
|
""" |
|
|
|
output_dir.mkdir(exist_ok=True, parents=True) |
|
|
|
|
|
rgb_files = list(rgb_dir.glob("*.png")) |
|
if not rgb_files: |
|
return [] |
|
|
|
|
|
for rgb_file in rgb_files: |
|
image_id = rgb_file.stem |
|
|
|
|
|
av_file = None |
|
if av_dir: |
|
av_file_path = av_dir / f"{image_id}.png" |
|
if av_file_path.exists(): |
|
av_file = str(av_file_path) |
|
|
|
|
|
disc_file = None |
|
if disc_dir: |
|
disc_file_path = disc_dir / f"{image_id}.png" |
|
if disc_file_path.exists(): |
|
disc_file = str(disc_file_path) |
|
|
|
|
|
fovea_location = None |
|
if fovea_data and image_id in fovea_data: |
|
fovea_location = fovea_data[image_id] |
|
|
|
|
|
output_file = output_dir / f"{image_id}.png" |
|
|
|
|
|
create_fundus_overlay( |
|
rgb_path=str(rgb_file), |
|
av_path=av_file, |
|
disc_path=disc_file, |
|
fovea_location=fovea_location, |
|
output_path=str(output_file), |
|
) |
|
|