Download LIVE/tvideo/logger.py from BonanDing/worldmem-baseline-evals: direct link, hf CLI and curl.
- Browser
- Download file 12.9 kB
-
https://huggingface.co/BonanDing/worldmem-baseline-evals/resolve/181f4402aeeef8bd63844fcf2146162a5cc5f1b3/LIVE/tvideo/logger.py
- Command line
-
hf download hf://BonanDing/worldmem-baseline-evals@181f4402aeeef8bd63844fcf2146162a5cc5f1b3/LIVE/tvideo/logger.py
-
curl -L -o logger.py https://huggingface.co/BonanDing/worldmem-baseline-evals/resolve/181f4402aeeef8bd63844fcf2146162a5cc5f1b3/LIVE/tvideo/logger.py
12.9 kB
| import os | |
| import numpy as np | |
| import einops | |
| import imageio | |
| from typing import Union | |
| from matplotlib import pyplot as plt | |
| from PIL import Image, ImageFile | |
| ImageFile.LOAD_TRUNCATED_IMAGES = True # UnidentifiedImageError: https://github.com/python-pillow/Pillow/issues/5631 | |
| from pathlib import Path | |
| import torch | |
| import torchvision | |
| import wandb | |
| import lightning.pytorch as pl | |
| from lightning.pytorch.callbacks import Callback | |
| from lightning.pytorch.loggers import WandbLogger | |
| from lightning.pytorch.utilities.rank_zero import rank_zero_only | |
| from einops import rearrange | |
| from torchvision.io import write_video | |
| from sgm.util import exists, isheatmap, print0 | |
| class ImageVideoLogger(Callback): | |
| def __init__( | |
| self, | |
| batch_frequency, | |
| max_samples, | |
| clamp=True, | |
| increase_log_steps=True, | |
| batch_frequency_val=None, | |
| video_fps=8, | |
| rescale=True, | |
| disabled=False, | |
| log_on_batch_idx=True, # log on batch_idx instead of global_step. global_step is fixed in validation. batch_idx restarts at each validation | |
| log_first_step=True, | |
| log_images_kwargs=None, | |
| log_videos_kwargs=None, | |
| log_before_first_step=True, | |
| enable_autocast=True, | |
| ): | |
| super().__init__() | |
| self.enable_autocast = enable_autocast | |
| self.rescale = rescale | |
| self.batch_freq = batch_frequency | |
| self.batch_freq_val = batch_frequency_val if batch_frequency_val is not None else batch_frequency | |
| self.video_fps = video_fps | |
| self.max_samples = max_samples | |
| self.log_steps = [2**n for n in range(int(np.log2(self.batch_freq)) + 1)] | |
| if not increase_log_steps: | |
| self.log_steps = [self.batch_freq] | |
| self.increase_log_steps = increase_log_steps | |
| self.clamp = clamp | |
| self.disabled = disabled | |
| self.log_on_batch_idx = log_on_batch_idx | |
| self.log_images_kwargs = log_images_kwargs if log_images_kwargs else {} | |
| self.log_videos_kwargs = log_videos_kwargs if log_videos_kwargs else {} | |
| self.log_first_step = log_first_step | |
| self.log_before_first_step = log_before_first_step | |
| def log_img_local( | |
| self, | |
| save_dir, | |
| split, | |
| images, | |
| global_step, | |
| current_epoch, | |
| batch_idx, | |
| pl_module: Union[None, pl.LightningModule] = None, | |
| ): | |
| root = os.path.join(save_dir, "images", split) | |
| for k in images: | |
| if isheatmap(images[k]): | |
| fig, ax = plt.subplots() | |
| ax = ax.matshow( | |
| images[k].cpu().numpy(), cmap="hot", interpolation="lanczos" | |
| ) | |
| plt.colorbar(ax) | |
| plt.axis("off") | |
| filename = "{}_gs-{:06}_e-{:06}_b-{:06}.png".format( | |
| k, global_step, current_epoch, batch_idx | |
| ) | |
| os.makedirs(root, exist_ok=True) | |
| path = os.path.join(root, filename) | |
| plt.savefig(path) | |
| plt.close() | |
| else: | |
| if images[k].ndim == 5: | |
| images[k] = einops.rearrange(images[k], "b c t h w -> (b t) c h w") | |
| nrow = self.log_images_kwargs.get("n_rows", 8) | |
| # the follows are original | |
| grid = torchvision.utils.make_grid(images[k], nrow=nrow) | |
| if self.rescale: | |
| grid = (grid + 1.0) / 2.0 # -1,1 -> 0,1; c,h,w | |
| grid = grid.transpose(0, 1).transpose(1, 2).squeeze(-1) | |
| grid = grid.numpy() | |
| grid = (grid * 255).astype(np.uint8) | |
| filename = "{}_gs-{:06}_e-{:06}_b-{:06}.png".format( | |
| k, global_step, current_epoch, batch_idx | |
| ) | |
| path = os.path.join(root, filename) | |
| os.makedirs(os.path.split(path)[0], exist_ok=True) | |
| img = Image.fromarray(grid) | |
| img.save(path) | |
| # if exists(pl_module): | |
| # assert isinstance( | |
| # pl_module.logger, WandbLogger | |
| # ), "logger_log_image only supports WandbLogger currently" | |
| # pl_module.logger.log_image( | |
| # key=f"{split}/{k}", | |
| # images=[ | |
| # img, | |
| # ], | |
| # step=pl_module.global_step, | |
| # ) | |
| def log_vid_local( | |
| self, | |
| save_dir, | |
| split, | |
| videos, | |
| global_step, | |
| current_epoch, | |
| batch_idx, | |
| pl_module: Union[None, pl.LightningModule] = None, | |
| ): | |
| root = os.path.join(save_dir, "videos", split) | |
| for k in videos: | |
| # if is video, we can add captions | |
| if isinstance(videos[k], torch.Tensor) and videos[k].ndim == 5: | |
| # (b, t, c, h, w) | |
| if self.rescale: | |
| videos[k] = (videos[k] + 1.0) / 2.0 # -1,1 -> 0,1; c,h,w | |
| frames = [videos[k][:, i] for i in range(videos[k].shape[1])] | |
| frames = [torchvision.utils.make_grid(each, nrow=4) for each in frames] | |
| frames = [einops.rearrange(each, "c h w -> 1 c h w") for each in frames] | |
| frames = torch.clamp(torch.cat(frames, dim=0), min=0.0, max=1.0) | |
| frames = (frames.numpy() * 255).astype(np.uint8) # frames: (t, c, h, w) | |
| os.makedirs(root, exist_ok=True) | |
| # filename_gif = "{}_gs-{:06}_e-{:06}_b-{:06}.gif".format( | |
| # k, global_step, current_epoch, batch_idx | |
| # ) | |
| # path_gif = os.path.join(root, filename_gif) | |
| # save_numpy_as_gif(frames, path_gif, duration=1 / self.video_fps) | |
| filename_mp4 = "{}_gs-{:06}_e-{:06}_b-{:06}.mp4".format( | |
| k, global_step, current_epoch, batch_idx | |
| ) | |
| path_mp4 = os.path.join(root, filename_mp4) | |
| save_numpy_as_mp4(frames, path_mp4, fps=self.video_fps) | |
| # if exists(pl_module): | |
| # assert isinstance( | |
| # pl_module.logger, WandbLogger | |
| # ), "log_videos only supports WandbLogger currently" | |
| # wandb.log({f"{split}/{k}": wandb.Video(frames, fps=self.video_fps)}) # k is str | |
| # # wandb.log({f"{split}/{k}": wandb.Video(frames, caption='caption', fps=self.video_fps)}, step=global_step) | |
| def log_img(self, pl_module, batch, batch_idx, split="train"): | |
| check_idx = batch_idx if self.log_on_batch_idx else pl_module.global_step | |
| if ( | |
| (self.check_frequency(check_idx) or self.check_frequency_val(batch_idx, split)) | |
| and hasattr(pl_module, "log_images") # batch_idx % self.batch_freq == 0 | |
| and callable(pl_module.log_images) | |
| and | |
| # batch_idx > 5 and | |
| self.max_samples > 0 | |
| ): | |
| logger = type(pl_module.logger) | |
| is_train = pl_module.training | |
| if is_train: | |
| pl_module.eval() | |
| with torch.no_grad(), torch.autocast(enabled=self.enable_autocast, device_type="cuda"): | |
| images = pl_module.log_images( | |
| batch, split=split, **self.log_images_kwargs | |
| ) | |
| if images is not None: | |
| for k in images: | |
| N = min(images[k].shape[0], self.max_samples) | |
| if not isheatmap(images[k]): | |
| images[k] = images[k][:N] | |
| if isinstance(images[k], torch.Tensor): | |
| images[k] = images[k].detach().float().cpu() | |
| if self.clamp and not isheatmap(images[k]): | |
| images[k] = torch.clamp(images[k], -1.0, 1.0) | |
| self.log_img_local( | |
| pl_module.logger.save_dir, | |
| split, | |
| images, | |
| pl_module.global_step, | |
| pl_module.current_epoch, | |
| batch_idx, | |
| pl_module=pl_module | |
| if isinstance(pl_module.logger, WandbLogger) | |
| else None, | |
| ) | |
| if is_train: | |
| pl_module.train() | |
| def log_vid(self, pl_module, batch, batch_idx, split="train"): | |
| check_idx = batch_idx if self.log_on_batch_idx else pl_module.global_step | |
| if ( | |
| (self.check_frequency(check_idx) or self.check_frequency_val(batch_idx, split)) | |
| and hasattr(pl_module, "log_videos") # batch_idx % self.batch_freq == 0 | |
| and callable(pl_module.log_videos) | |
| and | |
| # batch_idx > 5 and | |
| self.max_samples > 0 | |
| ): | |
| logger = type(pl_module.logger) | |
| is_train = pl_module.training | |
| if is_train: | |
| pl_module.eval() | |
| with torch.no_grad(), torch.autocast(enabled=self.enable_autocast, device_type="cuda"): | |
| videos = pl_module.log_videos( | |
| batch, split=split, **self.log_videos_kwargs | |
| ) | |
| if videos is not None: | |
| for k in videos: | |
| N = min(videos[k].shape[0], self.max_samples) | |
| videos[k] = videos[k][:N] | |
| if isinstance(videos[k], torch.Tensor): | |
| videos[k] = videos[k].detach().float().cpu() | |
| if self.clamp: | |
| videos[k] = torch.clamp(videos[k], -1.0, 1.0) | |
| self.log_vid_local( | |
| pl_module.logger.save_dir, | |
| split, | |
| videos, | |
| pl_module.global_step, | |
| pl_module.current_epoch, | |
| batch_idx, | |
| pl_module=pl_module | |
| if isinstance(pl_module.logger, WandbLogger) | |
| else None, | |
| ) | |
| if is_train: | |
| pl_module.train() | |
| def check_frequency(self, check_idx): | |
| if ((check_idx % self.batch_freq) == 0 or (check_idx in self.log_steps)) and ( | |
| check_idx > 0 or self.log_first_step # seems starting from 1 | |
| ): | |
| if self.increase_log_steps: | |
| try: | |
| self.log_steps.pop(0) | |
| except IndexError as e: | |
| # print0(e) | |
| pass | |
| return True | |
| return False | |
| def check_frequency_val(self, check_idx, split): | |
| if 'val' in split: | |
| if ((check_idx % self.batch_freq_val) == 0) and ( | |
| check_idx > 0 or self.log_first_step): | |
| return True | |
| return False | |
| def on_train_batch_end(self, trainer, pl_module, outputs, batch, batch_idx): | |
| # global_step starts from 1, batch_idx starts from 0 | |
| # not log after first step | |
| if not self.disabled and (pl_module.global_step > 2): | |
| self.log_img(pl_module, batch, batch_idx, split="train") | |
| self.log_vid(pl_module, batch, batch_idx, split="train") | |
| def on_train_batch_start(self, trainer, pl_module, batch, batch_idx): | |
| # global_step starts from 0 | |
| if not self.disabled and (pl_module.global_step == 0 and self.log_before_first_step): | |
| self.log_img(pl_module, batch, batch_idx, split="train") | |
| self.log_vid(pl_module, batch, batch_idx, split="train") | |
| def on_validation_batch_end( | |
| self, trainer, pl_module, outputs, batch, batch_idx, *args, **kwargs | |
| ): | |
| if not self.disabled: | |
| self.log_img(pl_module, batch, batch_idx, split="val") | |
| self.log_vid(pl_module, batch, batch_idx, split="val") | |
| def save_numpy_as_gif(frames, path, duration=None): | |
| """ | |
| save numpy array as gif file | |
| """ | |
| image_list = [] | |
| for frame in frames: | |
| image = frame.transpose(1, 2, 0) | |
| image_list.append(image) | |
| if duration: | |
| imageio.mimsave(path, image_list, format="GIF", duration=duration, loop=0) | |
| # imageio.mimsave(path, image_list, format="GIF", duration=duration, loop=0, quality=10) | |
| else: | |
| imageio.mimsave(path, image_list, format="GIF", loop=0) | |
| # imageio.mimsave(path, image_list, format="GIF", loop=0, quality=10) | |
| def save_numpy_as_mp4(frames, path, fps): | |
| """ | |
| save numpy array as mp4 file | |
| """ | |
| # image_list = [] | |
| # for frame in frames: | |
| # image = frame.transpose(1, 2, 0) | |
| # image_list.append(image) | |
| # imageio.mimsave(path, image_list, format="FFMPEG", fps=fps) | |
| frames = rearrange(frames, "t c h w -> t h w c") | |
| write_video(path, frames, fps=fps) |