Source code for noisemaker.generators

"""Noise generation interface for Noisemaker"""

from functools import partial

import tempfile

from noisemaker.constants import (
    ColorSpace,
    InterpolationType,
    OctaveBlending,
    ValueDistribution
)

import noisemaker.ai as ai
import noisemaker.effects as effects
import noisemaker.oklab as oklab
import noisemaker.simplex as simplex
import noisemaker.util as util
import noisemaker.value as value
import tensorflow as tf

[docs]def basic(freq, shape, ridges=False, sin=0.0, spline_order=InterpolationType.bicubic, distrib=ValueDistribution.uniform, corners=False, mask=None, mask_inverse=False, mask_static=False, lattice_drift=0.0, color_space=ColorSpace.hsv, hue_range=.125, hue_rotation=None, saturation=1.0, hue_distrib=None, brightness_distrib=None, brightness_freq=None, saturation_distrib=None, speed=1.0, time=0.0, octave_effects=None, octave=1): """ Generate a single layer of scaled noise. .. image:: images/gaussian.jpg :width: 1024 :height: 256 :alt: Noisemaker example output (CC0) :param int|list[int] freq: Base noise frequency. Int, or list of ints for each spatial dimension :param list[int]: Shape of noise. For 2D noise, this is [height, width, channels] :param bool ridges: "Crease" at midpoint values: (1 - abs(n * 2 - 1)) :param float sin: Apply sin function to noise basis :param int spline_order: Spline point count. 0=Constant, 1=Linear, 2=Cosine, 3=Bicubic :param int|str|ValueDistribution distrib: Type of noise distribution. See :class:`ValueDistribution` enum :param bool corners: If True, pin values to corners instead of image center :param None|ValueMask mask: :param bool mask_inverse: :param bool mask_static: If True, don't animate the mask :param float lattice_drift: Push away from underlying lattice :param ColorSpace color_space: :param float hue_range: HSV hue range :param float|None hue_rotation: HSV hue bias :param float saturation: HSV saturation :param None|int|str|ValueDistribution hue_distrib: Override ValueDistribution for hue :param None|int|str|ValueDistribution saturation_distrib: Override ValueDistribution for saturation :param None|int|str|ValueDistribution brightness_distrib: Override ValueDistribution for brightness :param None|int|list[int] brightness_freq: Override frequency for brightness :param float speed: Displacement range for Z/W axis (simplex and periodic only) :param float time: Time argument for Z/W axis (simplex and periodic only) :return: Tensor """ if isinstance(freq, int): freq = value.freq_for_shape(freq, shape) color_space = value.coerce_enum(color_space, ColorSpace) common_value_params = { "corners": corners, "mask": mask, "mask_inverse": mask_inverse, "mask_static": mask_static, "speed": speed, "spline_order": spline_order, "time": time, } tensor = value.values(freq=freq, shape=shape, distrib=distrib, **common_value_params) if lattice_drift: tensor = value.refract(tensor, shape, time=time, speed=speed, displacement=lattice_drift / min(freq[0], freq[1]), warp_freq=freq, spline_order=spline_order, signed_range=False) if octave_effects is not None: for effect_or_preset in octave_effects: tensor = _apply_octave_effect_or_preset(effect_or_preset, tensor, shape, time, speed, octave) # Preserve alpha channel for color space conversions alpha = None if shape[2] == 4: alpha = tensor[:, :, 3] tensor = tf.stack([tensor[:, :, 0], tensor[:, :, 1], tensor[:, :, 2]], 2) elif shape[2] == 2: alpha = tensor[:, :, 1] tensor = tf.stack([tensor[:, :, 0]], 2) original_color_space = color_space if color_space == ColorSpace.oklab: L = tensor[:, :, 0] a = tensor[:, :, 1] * -.509 + .276 b = tensor[:, :, 2] * -.509 + .198 tensor = value.clamp01(oklab.oklab_to_rgb(tf.stack([L, a, b], 2))) color_space = ColorSpace.rgb if color_space == ColorSpace.rgb: tensor = tf.image.rgb_to_hsv([tensor])[0] color_space = ColorSpace.hsv if color_space == ColorSpace.hsv: # Use 1 channel for per-channel noise generation, if any common_value_params["shape"] = [shape[0], shape[1], 1] # tweak hue if hue_distrib: h = tf.squeeze(value.values(freq=freq, distrib=hue_distrib, **common_value_params)) else: if original_color_space == ColorSpace.hsv: if hue_rotation is None: hue_rotation = simplex.random(time=time, speed=speed) else: # Avoid hard edges on color models that don't wrap hue from 1 to 0 naturally hue_range = 1.0 hue_rotation = 0.0 h = (tensor[:, :, 0] * hue_range + hue_rotation) % 1.0 # tweak saturation if saturation_distrib: s = tf.squeeze(value.values(freq=freq, distrib=saturation_distrib, **common_value_params)) else: s = tensor[:, :, 1] s *= saturation # tweak brightness if brightness_distrib or brightness_freq: if isinstance(brightness_freq, int): brightness_freq = value.freq_for_shape(brightness_freq, shape) v = tf.squeeze(value.values(freq=brightness_freq or freq, distrib=brightness_distrib or ValueDistribution.uniform, **common_value_params)) else: v = tensor[:, :, 2] if ridges and spline_order: # ridges don't work with spline_order == 0 v = value.ridge(v) if sin: v = value.normalize(tf.sin(sin * v)) tensor = tf.image.hsv_to_rgb([tf.stack([h, s, v], 2)])[0] if color_space == ColorSpace.grayscale: if ridges and spline_order: # ridges don't work with spline_order == 0 tensor = value.ridge(tensor) if sin: tensor = tf.sin(sin * tensor) # re-insert the alpha channel if shape[2] == 4: tensor = tf.stack([tensor[:, :, 0], tensor[:, :, 1], tensor[:, :, 2], alpha], 2) elif shape[2] == 2: tensor = tf.stack([tensor[:, :, 0], alpha], 2) return tensor
[docs]def multires(preset, seed, freq=3, shape=None, octaves=1, ridges=False, sin=0.0, spline_order=InterpolationType.bicubic, distrib=ValueDistribution.uniform, corners=False, mask=None, mask_inverse=False, mask_static=False, lattice_drift=0.0, with_supersample=False, color_space=ColorSpace.hsv, hue_range=.125, hue_rotation=None, saturation=1.0, hue_distrib=None, saturation_distrib=None, brightness_distrib=None, brightness_freq=None, octave_blending=OctaveBlending.falloff, octave_effects=None, post_effects=None, with_alpha=False, with_ai=False, final_effects=None, with_upscale=False, stability_model=None, time=0.0, speed=1.0, tensor=None): """ Generate multi-resolution value noise. For each octave: freq increases, amplitude decreases. .. image:: images/multires.jpg :width: 1024 :height: 256 :alt: Noisemaker example output (CC0) :param Preset preset: The Preset object being rendered :param int seed: The current seed (informational only, use value.set_seed to set seed) :param int|list[int] freq: Bottom layer frequency. Int, or list of ints for each spatial dimension :param list[int]: Shape of noise. For 2D noise, this is [height, width, channels] :param int octaves: Octave count. Number of multi-res layers. Typically 1-8 :param bool ridges: Per-octave "crease" at midpoint values: (1 - abs(n * 2 - 1)) :param bool post_ridges: Post-reduce "crease" at midpoint values: (1 - abs(n * 2 - 1)) :param float sin: Apply sin function to noise basis :param int spline_order: Spline point count. 0=Constant, 1=Linear, 2=Cosine, 3=Bicubic :param int|ValueDistribution distrib: Type of noise distribution. See :class:`ValueDistribution` enum :param bool corners: If True, pin values to corners instead of image center :param None|ValueMask mask: :param bool mask_inverse: :param bool mask_static: If True, don't animate the mask :param float lattice_drift: Push away from underlying lattice :param bool with_supersample: Use x2 supersampling :param ColorSpace color_space: :param float hue_range: HSV hue range :param float|None hue_rotation: HSV hue bias :param float saturation: HSV saturation :param None|ValueDistribution hue_distrib: Override ValueDistribution for HSV hue :param None|ValueDistribution saturation_distrib: Override ValueDistribution for HSV saturation :param None|ValueDistribution brightness_distrib: Override ValueDistribution for HSV brightness :param None|int|list[int] brightness_freq: Override frequency for HSV brightness :param OctaveBlendingMethod|int octave_blending: Method for flattening octave values :param list[callable] octave_effects: A list of composer lambdas to invoke per-octave :param list[callable] post_effects: A list of composer lambdas to invoke after flattening layers :param bool with_alpha: Include alpha channel :param bool with_ai: AI: Apply image-to-image before the final effects pass :param list[callable] final_effects: A list of composer lambdas to invoke after everything else :param bool with_upscale: AI: x2 upscale final results :param str stability_model: AI: Override the default stability.ai model :param float speed: Displacement range for Z/W axis (simplex and periodic only) :param float time: Time argument for Z/W axis (simplex and periodic only) :return: Tensor """ if with_ai and with_supersample: # Supersampling makes the input too large for current AI models raise Exception("--with-ai and --with-supersample may not be used together.") # Normalize input color_space = value.coerce_enum(color_space, ColorSpace) octave_blending = value.coerce_enum(octave_blending, OctaveBlending) if shape[-1] is None: shape = util.shape_from_params(shape[1], shape[0], color_space, with_alpha) if isinstance(freq, int): freq = value.freq_for_shape(freq, shape) original_shape = shape.copy() if with_supersample: shape[0] *= 2 shape[1] *= 2 if octave_blending == OctaveBlending.alpha and shape[2] in (1, 3): # Make sure there's an alpha channel shape[2] += 1 if tensor is None: tensor = tf.zeros(shape) for octave in range(1, octaves + 1): multiplier = 2 ** octave base_freq = [int(f * .5 * multiplier) for f in freq] if all(base_freq[i] > shape[i] for i in range(len(base_freq))): break layer = basic(base_freq, shape, ridges=ridges, sin=sin, spline_order=spline_order, corners=corners, distrib=distrib, mask=mask, mask_inverse=mask_inverse, mask_static=mask_static, lattice_drift=lattice_drift, color_space=color_space, hue_range=hue_range, hue_rotation=hue_rotation, saturation=saturation, hue_distrib=hue_distrib, brightness_distrib=brightness_distrib, brightness_freq=brightness_freq, saturation_distrib=saturation_distrib, octave_effects=octave_effects, octave=octave, time=time, speed=speed) if octave_blending == OctaveBlending.reduce_max: tensor = tf.maximum(tensor, layer) elif octave_blending == OctaveBlending.alpha: a = tf.expand_dims(layer[:, :, -1], -1) tensor = (tensor * (1.0 - a)) + layer * a else: # falloff tensor += layer / multiplier # If the original shape did not include an alpha channel, reduce masked values to 0 (black) if octave_blending == OctaveBlending.alpha and original_shape[2] in (1, 3): a = tensor[:, :, -1] if original_shape[2] == 1: tensor = tf.expand_dims(tensor[:, :, 0] * a, -1) elif original_shape[2] == 3: tensor = tf.stack([tensor[:, :, 0], tensor[:, :, 1], tensor[:, :, 2]], 2) * tf.expand_dims(a, -1) shape = original_shape else: for effect_or_preset in octave_effects: tensor = _apply_octave_effect_or_preset(effect_or_preset, tensor, shape, time, speed, 1) tensor = value.normalize(tensor) final = [] for effect_or_preset in post_effects: tensor, f = _apply_post_effect_or_preset(effect_or_preset, tensor, shape, time, speed) final += f if with_ai: tensor = value.normalize(tensor) with tempfile.TemporaryDirectory() as tmp: tmp_path = f"{tmp}/temp.png" util.save(tensor, tmp_path) try: tensor = ai.apply(preset.ai_settings, seed, input_filename=tmp_path, stability_model=stability_model) preset.ai_success = True except Exception as e: util.logger.error(f"ai.apply() failed: {e}\nSeed: {seed}") for effect_or_preset in final + final_effects: tensor = _apply_final_effect_or_preset(effect_or_preset, tensor, shape, time, speed) tensor = value.normalize(tensor) if with_supersample: tensor = value.proportional_downsample(tensor, shape, original_shape) if with_upscale: with tempfile.TemporaryDirectory() as tmp: tmp_path = f"{tmp}/temp.png" util.save(tensor, tmp_path) try: tensor = ai.x2_upscale(tmp_path) except Exception as e: util.logger.error(f"preset.upscale() failed: {e}\nSeed: {seed}") return tensor
def _apply_octave_effect_or_preset(effect_or_preset, tensor, shape, time, speed, octave): """Helper function to either invoke a octave effect or unroll a preset.""" if callable(effect_or_preset): if "displacement" in effect_or_preset.keywords: kwargs = dict(effect_or_preset.keywords) # Be sure to copy, otherwise it modifies the original kwargs["displacement"] /= 2 ** octave effect_or_preset = partial(effect_or_preset.func, **kwargs) return effect_or_preset(tensor=tensor, shape=shape, time=time, speed=speed) else: # Is a Preset. Unroll me. for e_or_p in effect_or_preset.octave_effects: tensor = _apply_octave_effect_or_preset(e_or_p, tensor, shape, time, speed, octave) return tensor def _apply_post_effect_or_preset(effect_or_preset, tensor, shape, time, speed): """Helper function to either invoke a post effect or unroll a preset.""" if callable(effect_or_preset): return effect_or_preset(tensor=tensor, shape=shape, time=time, speed=speed), [] else: # Is a Preset. Unroll me. final = [] # Post effects may also define "final" effects. Collect them and return them so we # can tack them on at the end after everything is said and done final += effect_or_preset.final_effects for e_or_p in effect_or_preset.post_effects: tensor, f = _apply_post_effect_or_preset(e_or_p, tensor, shape, time, speed) final += f return tensor, final def _apply_final_effect_or_preset(effect_or_preset, tensor, shape, time, speed): """Helper function to either invoke a final effect or unroll a preset.""" if callable(effect_or_preset): return effect_or_preset(tensor=tensor, shape=shape, time=time, speed=speed) else: # Is a Preset. Unroll me. for e_or_p in effect_or_preset.post_effects + effect_or_preset.final_effects: tensor = _apply_final_effect_or_preset(e_or_p, tensor, shape, time, speed) return tensor