Download scripts/visualize_aug.py from smerkd/affmae: direct link, hf CLI and curl.
- Browser
- Download file 8.93 kB
-
https://huggingface.co/spaces/smerkd/affmae/resolve/main/scripts/visualize_aug.py
- Command line
-
hf download hf://spaces/smerkd/affmae/scripts/visualize_aug.py
-
curl -L -o visualize_aug.py https://huggingface.co/spaces/smerkd/affmae/resolve/main/scripts/visualize_aug.py
8.93 kB
| import argparse | |
| import os | |
| import random | |
| import numpy as np | |
| import torch | |
| import cv2 | |
| import tifffile | |
| import matplotlib.pyplot as plt | |
| from typing import List, Tuple | |
| from torch.utils.data import Dataset | |
| from torchvision.transforms import v2 | |
| from torchvision import tv_tensors | |
| import sys | |
| # Allow running as `python scripts/<name>.py` from anywhere. | |
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
| from affmae.data.transforms import ElasticTransform | |
| IMAGE_MEAN = [0.0] | |
| IMAGE_STD = [1.0] | |
| THRESHOLD = 0.5 | |
| class EMDatasetMultiClass(Dataset): | |
| def __init__( | |
| self, | |
| base_path: str, | |
| test_dataset: bool = False, | |
| img_size: int = 512, | |
| apply_transforms: bool = False, | |
| indices: List[int] = [0, 1, 2], | |
| input_ext: str = ".tif", | |
| return_path: bool = True, | |
| ): | |
| self.test_dataset = test_dataset | |
| self.img_size = img_size | |
| self.apply_transforms = apply_transforms | |
| self.indices = indices | |
| self.return_path = return_path | |
| # Logic to determine folders | |
| train_place_in = os.path.join(base_path, "train") | |
| test_place_in = os.path.join(base_path, "test") | |
| if test_dataset: | |
| input_folder = os.path.join(test_place_in, "images") | |
| output_folder = os.path.join(test_place_in, "masks") | |
| else: | |
| input_folder = os.path.join(train_place_in, "images") | |
| output_folder = os.path.join(train_place_in, "masks") | |
| if not os.path.exists(input_folder): | |
| raise FileNotFoundError(f"Input folder not found: {input_folder}") | |
| self.image_paths, self.mask_paths = self._load_image_names(input_folder, output_folder, input_ext) | |
| self.img_mean = torch.tensor(IMAGE_MEAN, dtype=torch.float) | |
| self.img_std = torch.tensor(IMAGE_STD, dtype=torch.float) | |
| self._setup_transforms() | |
| def _load_image_names(input_folder: str, output_folder: str, input_ext: str = ".tif") -> Tuple[np.ndarray, np.ndarray]: | |
| all_img = np.array(os.listdir(input_folder)) | |
| all_tiff = np.array(os.listdir(output_folder)) | |
| split_func = np.vectorize(lambda x: x.split(".")[0]) | |
| # intersection of filenames | |
| img_paths = np.intersect1d(split_func(all_img), split_func(all_tiff)) | |
| append_input = np.vectorize(lambda x: os.path.join(input_folder, x + input_ext)) | |
| input_imgs = append_input(img_paths) | |
| append_output = np.vectorize(lambda x: os.path.join(output_folder, x + ".tiff")) | |
| output_imgs = append_output(img_paths) | |
| return input_imgs, output_imgs | |
| def _setup_transforms(self): | |
| base_transforms = [ | |
| v2.ToImage(), | |
| v2.ConvertImageDtype(torch.float32), | |
| v2.Resize(size=(self.img_size, self.img_size), antialias=True, interpolation=v2.InterpolationMode.BILINEAR), | |
| ] | |
| if self.test_dataset or not self.apply_transforms: | |
| self.transforms = v2.Compose(base_transforms) | |
| else: | |
| augmentation_transforms = [ | |
| v2.RandomHorizontalFlip(p=0.5), | |
| v2.RandomVerticalFlip(p=0.5), | |
| v2.RandomAdjustSharpness(sharpness_factor=0.8, p=0.25), | |
| v2.RandomAdjustSharpness(sharpness_factor=1.25, p=0.25), | |
| v2.RandomAffine( | |
| degrees=30, | |
| translate=(0.0, 0.1), | |
| scale=(0.9, 1.3), | |
| shear=20.0 | |
| ), | |
| ElasticTransform(alpha=(100, 100), sigma=(10, 10), p=0.6) | |
| ] | |
| self.transforms = v2.Compose(base_transforms + augmentation_transforms) | |
| def __len__(self) -> int: | |
| return len(self.image_paths) | |
| def _load_segm_image(self, id: int) -> np.ndarray: | |
| img = tifffile.imread(self.image_paths[id]) | |
| return img if len(img.shape) == 3 else img.reshape(img.shape + (1,)) | |
| def _load_segm_target(self, id: int) -> np.ndarray: | |
| img = tifffile.imread(self.mask_paths[id]) | |
| if len(img.shape) == 4: img = img[:, :, :, 0] | |
| return img if len(img.shape) == 2 else img[[self.indices] if isinstance(self.indices, int) else self.indices, :, :] | |
| def __getitem__(self, index: int): | |
| image_np = self._load_segm_image(index) | |
| target_np_multi_channel = self._load_segm_target(index) | |
| # CLAHE preprocessing | |
| clahe = cv2.createCLAHE(clipLimit=4.25, tileGridSize=(8, 8)) | |
| try: | |
| if image_np.dtype != np.uint8: | |
| norm_img = cv2.normalize(image_np, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U) | |
| image_np = clahe.apply(norm_img) | |
| else: | |
| image_np = clahe.apply(image_np) | |
| except: | |
| if len(image_np.shape) == 3 and image_np.shape[2] == 3: | |
| image_np = cv2.cvtColor(image_np, cv2.COLOR_BGR2GRAY) | |
| image_np = clahe.apply(image_np) | |
| if len(image_np.shape) == 2: | |
| image_np = image_np.reshape(image_np.shape + (1,)) | |
| if len(target_np_multi_channel.shape) == 3: | |
| C, H, W = target_np_multi_channel.shape | |
| target_np_multi_class = np.zeros((H, W), dtype=np.int64) | |
| for c in range(C): | |
| class_id = c + 1 | |
| is_active = target_np_multi_channel[c, :, :] > THRESHOLD | |
| target_np_multi_class[is_active] = class_id | |
| else: | |
| target_np_multi_class = target_np_multi_channel | |
| image = image_np | |
| target = tv_tensors.Mask(target_np_multi_class) | |
| image, target = self.transforms(image, target) | |
| image = image.float().sub_(self.img_mean).div_(self.img_std) | |
| if self.return_path: | |
| return image, target, (self.image_paths[index], self.mask_paths[index]) | |
| return image, target | |
| def parse_args(): | |
| parser = argparse.ArgumentParser( | |
| description="Render a grid of augmented training samples to eyeball the " | |
| "augmentation pipeline.") | |
| parser.add_argument("--base-path", required=True, | |
| help="Dataset root (the directory holding train/test).") | |
| parser.add_argument("--output", default="aug_viz.png", | |
| help="Output image path.") | |
| parser.add_argument("--img-size", type=int, default=512) | |
| parser.add_argument("--input-ext", default=".tiff", | |
| help="Image file extension.") | |
| parser.add_argument("--config", default=None, | |
| help="Optional YAML config to take base_path, img_size and " | |
| "input_ext from instead of the flags above.") | |
| return parser.parse_args() | |
| def visualize_grid(base_path, output_path, img_size, input_ext): | |
| print(f"Loading dataset from: {base_path}") | |
| try: | |
| dataset = EMDatasetMultiClass( | |
| base_path=base_path, | |
| test_dataset=False, | |
| img_size=img_size, | |
| apply_transforms=True, | |
| input_ext=input_ext, | |
| return_path=True | |
| ) | |
| except Exception as e: | |
| print(f"Error initializing dataset: {e}") | |
| return | |
| print(f"Found {len(dataset)} images.") | |
| if len(dataset) < 25: | |
| print("Dataset is too small for a 5x5 grid, using all images.") | |
| indices = list(range(len(dataset))) | |
| else: | |
| indices = random.sample(range(len(dataset)), 25) | |
| fig, axes = plt.subplots(5, 5, figsize=(15, 15)) | |
| fig.suptitle("5x5 Random Augmentation Grid", fontsize=16) | |
| print("Generating grid...") | |
| for idx, ax in zip(indices, axes.flat): | |
| img_tensor, mask_tensor, (img_path, mask_path) = dataset[idx] | |
| rel_path = os.path.relpath(img_path, base_path) | |
| # Un-normalize for display | |
| img = img_tensor.cpu().numpy() | |
| img = np.transpose(img, (1, 2, 0)) | |
| mean = np.array(IMAGE_MEAN) | |
| std = np.array(IMAGE_STD) | |
| img = (img * std) + mean | |
| img = np.clip(img, 0.0, 1.0) | |
| if img.shape[2] == 1: | |
| img = img.squeeze(2) | |
| ax.imshow(img, cmap='gray') | |
| else: | |
| ax.imshow(img) | |
| # Set Title with relative path | |
| ax.set_title(rel_path, fontsize=6) | |
| ax.axis('off') | |
| plt.tight_layout() | |
| out_dir = os.path.dirname(os.path.abspath(output_path)) | |
| os.makedirs(out_dir, exist_ok=True) | |
| plt.savefig(output_path, dpi=125) | |
| print(f"Saved visualization to: {output_path}") | |
| def main(): | |
| args = parse_args() | |
| base_path, img_size, input_ext = args.base_path, args.img_size, args.input_ext | |
| if args.config: | |
| from affmae.config import load_config | |
| cfg = load_config(args.config) | |
| base_path = cfg.base_path | |
| img_size = cfg.img_size | |
| input_ext = cfg.input_ext | |
| visualize_grid(base_path, args.output, img_size, input_ext) | |
| if __name__ == "__main__": | |
| main() |