jcm/utils.py (62 lines of code) (raw):

# coding=utf-8 # Copyright 2020 The Google Research Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # pylint: skip-file """Utility code for generating and saving image grids and checkpointing. The `save_image` code is copied from https://github.com/google/flax/blob/master/examples/vae/utils.py, which is a JAX equivalent to the same function in TorchVision (https://github.com/pytorch/vision/blob/master/torchvision/utils.py) """ import math from typing import Any, Dict, Optional, TypeVar import flax import jax import jax.numpy as jnp import numpy as np from PIL import Image import blobfile T = TypeVar("T") def batch_add(a, b): return jax.vmap(lambda a, b: a + b)(a, b) def batch_mul(a, b): return jax.vmap(lambda a, b: a * b)(a, b) def load_training_state(filepath, state): with blobfile.open(filepath, "rb") as f: state = flax.serialization.from_bytes(state, f.read()) return state def save_image(ndarray, fp, nrow=8, padding=2, pad_value=0.0, format=None): """Make a grid of images and save it into an image file. Pixel values are assumed to be within [0, 1]. Args: ndarray (array_like): 4D mini-batch images of shape (B x H x W x C). fp: A filename(string) or file object. nrow (int, optional): Number of images displayed in each row of the grid. The final grid size is ``(B / nrow, nrow)``. Default: ``8``. padding (int, optional): amount of padding. Default: ``2``. pad_value (float, optional): Value for the padded pixels. Default: ``0``. format(Optional): If omitted, the format to use is determined from the filename extension. If a file object was used instead of a filename, this parameter should always be used. """ if not ( isinstance(ndarray, jnp.ndarray) or ( isinstance(ndarray, list) and all(isinstance(t, jnp.ndarray) for t in ndarray) ) ): raise TypeError("array_like of tensors expected, got {}".format(type(ndarray))) ndarray = jnp.asarray(ndarray) if ndarray.ndim == 4 and ndarray.shape[-1] == 1: # single-channel images ndarray = jnp.concatenate((ndarray, ndarray, ndarray), -1) # make the mini-batch of images into a grid nmaps = ndarray.shape[0] xmaps = min(nrow, nmaps) ymaps = int(math.ceil(float(nmaps) / xmaps)) height, width = int(ndarray.shape[1] + padding), int(ndarray.shape[2] + padding) num_channels = ndarray.shape[3] grid = jnp.full( (height * ymaps + padding, width * xmaps + padding, num_channels), pad_value ).astype(jnp.float32) k = 0 for y in range(ymaps): for x in range(xmaps): if k >= nmaps: break grid = grid.at[ y * height + padding : (y + 1) * height, x * width + padding : (x + 1) * width, ].set(ndarray[k]) k = k + 1 # Add 0.5 after unnormalizing to [0, 255] to round to nearest integer ndarr = np.asarray(jnp.clip(grid * 255.0 + 0.5, 0, 255).astype(jnp.uint8)) im = Image.fromarray(ndarr.copy()) im.save(fp, format=format) def flatten_dict(config): """Flatten a hierarchical dict to a simple dict.""" new_dict = {} for key, value in config.items(): if isinstance(value, dict): sub_dict = flatten_dict(value) for subkey, subvalue in sub_dict.items(): new_dict[key + "/" + subkey] = subvalue elif isinstance(value, tuple): new_dict[key] = str(value) else: new_dict[key] = value return new_dict