diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__init__.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/__init__.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75a38dd65de41cbc677953b6c42fc2e29f8032eb Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/__init__.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/category_encoding.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/category_encoding.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..83967358fb2b0c577ac73e3cdb79bb0a1f6b08bd Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/category_encoding.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/discretization.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/discretization.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2e87e20c341d5c04696f3b57349293dcf5e3504e Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/discretization.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/feature_space.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/feature_space.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c646383575777225ccd8375d52f60ca58eb1720 Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/feature_space.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/hashed_crossing.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/hashed_crossing.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..44d6f6ec40bf714008377c23bbd7aa3c9c5eef89 Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/hashed_crossing.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/hashing.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/hashing.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..328f6c710c95548f78446f96fc08379433c96fb4 Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/hashing.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/index_lookup.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/index_lookup.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c43e8a3f64a2dc1cf4791ab7226f67bdced5e329 Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/index_lookup.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/integer_lookup.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/integer_lookup.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2145fa5fe75868e6ca8fe63009f33cb696bdf252 Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/integer_lookup.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/mel_spectrogram.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/mel_spectrogram.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..98bb99cd19242521acdf799c7018f64baa2b4f28 Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/mel_spectrogram.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/normalization.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/normalization.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0cf4b6d1fb6c068a7c64557e4c9cc0bde50de9a3 Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/normalization.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/pipeline.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/pipeline.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e675d924778636927e893f384b84c84f6e4d2e5 Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/pipeline.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/rescaling.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/rescaling.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fbeca272625be7d850a1d49be9e1e477f6d4c0df Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/rescaling.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/stft_spectrogram.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/stft_spectrogram.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b07cb7a35eae5c30cc696895c38555d75bfdd90b Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/stft_spectrogram.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/string_lookup.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/string_lookup.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea7e9acd6422e31cc4edb04c61894820f2078dc3 Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/string_lookup.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/text_vectorization.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/text_vectorization.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..93ed1f5657349d71b2663a7a3d0aa37eccfd91fd Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/text_vectorization.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/tf_data_layer.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/tf_data_layer.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cea0a71e22233d56ba093986b8d8946949431cd9 Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/__pycache__/tf_data_layer.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/category_encoding.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/category_encoding.py new file mode 100644 index 0000000000000000000000000000000000000000..183debf49908176265072952eeedcd512542d392 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/category_encoding.py @@ -0,0 +1,166 @@ +from keras.src.api_export import keras_export +from keras.src.backend import KerasTensor +from keras.src.layers.preprocessing.tf_data_layer import TFDataLayer +from keras.src.utils import backend_utils +from keras.src.utils import numerical_utils + + +@keras_export("keras.layers.CategoryEncoding") +class CategoryEncoding(TFDataLayer): + """A preprocessing layer which encodes integer features. + + This layer provides options for condensing data into a categorical encoding + when the total number of tokens are known in advance. It accepts integer + values as inputs, and it outputs a dense or sparse representation of those + inputs. For integer inputs where the total number of tokens is not known, + use `keras.layers.IntegerLookup` instead. + + **Note:** This layer is safe to use inside a `tf.data` pipeline + (independently of which backend you're using). + + Examples: + + **One-hot encoding data** + + >>> layer = keras.layers.CategoryEncoding( + ... num_tokens=4, output_mode="one_hot") + >>> layer([3, 2, 0, 1]) + array([[0., 0., 0., 1.], + [0., 0., 1., 0.], + [1., 0., 0., 0.], + [0., 1., 0., 0.]]> + + **Multi-hot encoding data** + + >>> layer = keras.layers.CategoryEncoding( + ... num_tokens=4, output_mode="multi_hot") + >>> layer([[0, 1], [0, 0], [1, 2], [3, 1]]) + array([[1., 1., 0., 0.], + [1., 0., 0., 0.], + [0., 1., 1., 0.], + [0., 1., 0., 1.]]> + + **Using weighted inputs in `"count"` mode** + + >>> layer = keras.layers.CategoryEncoding( + ... num_tokens=4, output_mode="count") + >>> count_weights = np.array([[.1, .2], [.1, .1], [.2, .3], [.4, .2]]) + >>> layer([[0, 1], [0, 0], [1, 2], [3, 1]], count_weights=count_weights) + array([[0.1, 0.2, 0. , 0. ], + [0.2, 0. , 0. , 0. ], + [0. , 0.2, 0.3, 0. ], + [0. , 0.2, 0. , 0.4]]> + + Args: + num_tokens: The total number of tokens the layer should support. All + inputs to the layer must integers in the range `0 <= value < + num_tokens`, or an error will be thrown. + output_mode: Specification for the output of the layer. + Values can be `"one_hot"`, `"multi_hot"` or `"count"`, + configuring the layer as follows: + - `"one_hot"`: Encodes each individual element in the input + into an array of `num_tokens` size, containing a 1 at the + element index. If the last dimension is size 1, will encode + on that dimension. If the last dimension is not size 1, + will append a new dimension for the encoded output. + - `"multi_hot"`: Encodes each sample in the input into a single + array of `num_tokens` size, containing a 1 for each + vocabulary term present in the sample. Treats the last + dimension as the sample dimension, if input shape is + `(..., sample_length)`, output shape will be + `(..., num_tokens)`. + - `"count"`: Like `"multi_hot"`, but the int array contains a + count of the number of times the token at that index + appeared in the sample. + For all output modes, currently only output up to rank 2 is + supported. + Defaults to `"multi_hot"`. + sparse: Whether to return a sparse tensor; for backends that support + sparse tensors. + + Call arguments: + inputs: A 1D or 2D tensor of integer inputs. + count_weights: A tensor in the same shape as `inputs` indicating the + weight for each sample value when summing up in `count` mode. + Not used in `"multi_hot"` or `"one_hot"` modes. + """ + + def __init__( + self, num_tokens=None, output_mode="multi_hot", sparse=False, **kwargs + ): + super().__init__(**kwargs) + + # Support deprecated names for output_modes. + if output_mode == "binary": + output_mode = "multi_hot" + + # 'output_mode' must be one of ("count", "one_hot", "multi_hot") + if output_mode not in ("count", "one_hot", "multi_hot"): + raise ValueError(f"Unknown arg for output_mode: {output_mode}") + + if num_tokens is None: + raise ValueError( + "num_tokens must be set to use this layer. If the " + "number of tokens is not known beforehand, use the " + "IntegerLookup layer instead." + ) + if num_tokens < 1: + raise ValueError( + f"`num_tokens` must be >= 1. Received: num_tokens={num_tokens}." + ) + self.num_tokens = num_tokens + self.output_mode = output_mode + self.sparse = sparse + self._allow_non_tensor_positional_args = True + self._convert_input_args = False + + def _encode(self, inputs, count_weights=None): + inputs = self.backend.core.convert_to_tensor(inputs) + return numerical_utils.encode_categorical_inputs( + inputs, + output_mode=self.output_mode, + depth=self.num_tokens, + dtype=self.dtype, + sparse=self.sparse, + count_weights=count_weights, + backend_module=self.backend, + ) + + def compute_output_shape(self, input_shape): + if (input_shape is not None) & (len(input_shape) == 0): + return (self.num_tokens,) + if self.output_mode == "one_hot": + if input_shape[-1] != 1: + return tuple(input_shape) + (self.num_tokens,) + elif len(input_shape) == 1: + return tuple(input_shape) + (self.num_tokens,) + else: + return tuple(input_shape[:-1]) + (self.num_tokens,) + return tuple(input_shape[:-1]) + (self.num_tokens,) + + def compute_output_spec(self, inputs, count_weights=None): + output_shape = self.compute_output_shape(inputs.shape) + return KerasTensor( + output_shape, dtype=self.compute_dtype, sparse=self.sparse + ) + + def get_config(self): + config = { + "num_tokens": self.num_tokens, + "output_mode": self.output_mode, + } + base_config = super().get_config() + return {**base_config, **config} + + def call(self, inputs, count_weights=None): + if count_weights is not None: + if self.output_mode != "count": + raise ValueError( + "`count_weights` is not used when `output_mode` is not " + "`'count'`. Received `count_weights={count_weights}`." + ) + count_weights = self.backend.convert_to_tensor( + count_weights, dtype=self.compute_dtype + ) + outputs = self._encode(inputs, count_weights) + return backend_utils.convert_tf_tensor(outputs) diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/discretization.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/discretization.py new file mode 100644 index 0000000000000000000000000000000000000000..3d00e6b35a7e1758eaf05e0ba7e7886d8a0faa77 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/discretization.py @@ -0,0 +1,337 @@ +import numpy as np + +from keras.src import backend +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.tf_data_layer import TFDataLayer +from keras.src.utils import argument_validation +from keras.src.utils import numerical_utils +from keras.src.utils.module_utils import tensorflow as tf + + +@keras_export("keras.layers.Discretization") +class Discretization(TFDataLayer): + """A preprocessing layer which buckets continuous features by ranges. + + This layer will place each element of its input data into one of several + contiguous ranges and output an integer index indicating which range each + element was placed in. + + **Note:** This layer is safe to use inside a `tf.data` pipeline + (independently of which backend you're using). + + Input shape: + Any array of dimension 2 or higher. + + Output shape: + Same as input shape. + + Arguments: + bin_boundaries: A list of bin boundaries. + The leftmost and rightmost bins + will always extend to `-inf` and `inf`, + so `bin_boundaries=[0., 1., 2.]` + generates bins `(-inf, 0.)`, `[0., 1.)`, `[1., 2.)`, + and `[2., +inf)`. + If this option is set, `adapt()` should not be called. + num_bins: The integer number of bins to compute. + If this option is set, + `adapt()` should be called to learn the bin boundaries. + epsilon: Error tolerance, typically a small fraction + close to zero (e.g. 0.01). Higher values of epsilon increase + the quantile approximation, and hence result in more + unequal buckets, but could improve performance + and resource consumption. + output_mode: Specification for the output of the layer. + Values can be `"int"`, `"one_hot"`, `"multi_hot"`, or + `"count"` configuring the layer as follows: + - `"int"`: Return the discretized bin indices directly. + - `"one_hot"`: Encodes each individual element in the + input into an array the same size as `num_bins`, + containing a 1 at the input's bin + index. If the last dimension is size 1, will encode on that + dimension. If the last dimension is not size 1, + will append a new dimension for the encoded output. + - `"multi_hot"`: Encodes each sample in the input into a + single array the same size as `num_bins`, + containing a 1 for each bin index + index present in the sample. + Treats the last dimension as the sample + dimension, if input shape is `(..., sample_length)`, + output shape will be `(..., num_tokens)`. + - `"count"`: As `"multi_hot"`, but the int array contains + a count of the number of times the bin index appeared + in the sample. + Defaults to `"int"`. + sparse: Boolean. Only applicable to `"one_hot"`, `"multi_hot"`, + and `"count"` output modes. Only supported with TensorFlow + backend. If `True`, returns a `SparseTensor` instead of + a dense `Tensor`. Defaults to `False`. + + Examples: + + Discretize float values based on provided buckets. + >>> input = np.array([[-1.5, 1.0, 3.4, .5], [0.0, 3.0, 1.3, 0.0]]) + >>> layer = Discretization(bin_boundaries=[0., 1., 2.]) + >>> layer(input) + array([[0, 2, 3, 1], + [1, 3, 2, 1]]) + + Discretize float values based on a number of buckets to compute. + >>> input = np.array([[-1.5, 1.0, 3.4, .5], [0.0, 3.0, 1.3, 0.0]]) + >>> layer = Discretization(num_bins=4, epsilon=0.01) + >>> layer.adapt(input) + >>> layer(input) + array([[0, 2, 3, 2], + [1, 3, 3, 1]]) + """ + + def __init__( + self, + bin_boundaries=None, + num_bins=None, + epsilon=0.01, + output_mode="int", + sparse=False, + dtype=None, + name=None, + ): + if dtype is None: + dtype = "int64" if output_mode == "int" else backend.floatx() + + super().__init__(name=name, dtype=dtype) + + if sparse and not backend.SUPPORTS_SPARSE_TENSORS: + raise ValueError( + f"`sparse=True` cannot be used with backend {backend.backend()}" + ) + if sparse and output_mode == "int": + raise ValueError( + "`sparse=True` may only be used if `output_mode` is " + "`'one_hot'`, `'multi_hot'`, or `'count'`. " + f"Received: sparse={sparse} and " + f"output_mode={output_mode}" + ) + + argument_validation.validate_string_arg( + output_mode, + allowable_strings=( + "int", + "one_hot", + "multi_hot", + "count", + ), + caller_name=self.__class__.__name__, + arg_name="output_mode", + ) + + if num_bins is not None and num_bins < 0: + raise ValueError( + "`num_bins` must be greater than or equal to 0. " + f"Received: `num_bins={num_bins}`" + ) + if num_bins is not None and bin_boundaries is not None: + if len(bin_boundaries) != num_bins - 1: + raise ValueError( + "Both `num_bins` and `bin_boundaries` should not be " + f"set. Received: `num_bins={num_bins}` and " + f"`bin_boundaries={bin_boundaries}`" + ) + + self.input_bin_boundaries = bin_boundaries + self.bin_boundaries = ( + bin_boundaries if bin_boundaries is not None else [] + ) + self.num_bins = num_bins + self.epsilon = epsilon + self.output_mode = output_mode + self.sparse = sparse + + if self.bin_boundaries: + self.summary = None + else: + self.summary = np.array([[], []], dtype="float32") + + def build(self, input_shape=None): + self.built = True + + @property + def input_dtype(self): + return backend.floatx() + + def adapt(self, data, steps=None): + """Computes bin boundaries from quantiles in a input dataset. + + Calling `adapt()` on a `Discretization` layer is an alternative to + passing in a `bin_boundaries` argument during construction. A + `Discretization` layer should always be either adapted over a dataset or + passed `bin_boundaries`. + + During `adapt()`, the layer will estimate the quantile boundaries of the + input dataset. The number of quantiles can be controlled via the + `num_bins` argument, and the error tolerance for quantile boundaries can + be controlled via the `epsilon` argument. + + Arguments: + data: The data to train on. It can be passed either as a + batched `tf.data.Dataset`, + or as a NumPy array. + steps: Integer or `None`. + Total number of steps (batches of samples) to process. + If `data` is a `tf.data.Dataset`, and `steps` is `None`, + `adapt()` will run until the input dataset is exhausted. + When passing an infinitely + repeating dataset, you must specify the `steps` argument. This + argument is not supported with array inputs or list inputs. + """ + if self.input_bin_boundaries is not None: + raise ValueError( + "Cannot adapt a Discretization layer that has been initialized " + "with `bin_boundaries`, use `num_bins` instead." + ) + self.reset_state() + if isinstance(data, tf.data.Dataset): + if steps is not None: + data = data.take(steps) + for batch in data: + self.update_state(batch) + else: + self.update_state(data) + self.finalize_state() + + def update_state(self, data): + data = np.array(data).astype("float32") + summary = summarize(data, self.epsilon) + self.summary = merge_summaries(summary, self.summary, self.epsilon) + + def finalize_state(self): + if self.input_bin_boundaries is not None: + return + self.bin_boundaries = get_bin_boundaries( + self.summary, self.num_bins + ).tolist() + + def reset_state(self): + if self.input_bin_boundaries is not None: + return + self.summary = np.array([[], []], dtype="float32") + + def compute_output_spec(self, inputs): + return backend.KerasTensor(shape=inputs.shape, dtype=self.compute_dtype) + + def load_own_variables(self, store): + if len(store) == 1: + # Legacy format case + self.summary = store["0"] + return + + def call(self, inputs): + indices = self.backend.numpy.digitize(inputs, self.bin_boundaries) + return numerical_utils.encode_categorical_inputs( + indices, + output_mode=self.output_mode, + depth=len(self.bin_boundaries) + 1, + dtype=self.compute_dtype, + sparse=self.sparse, + backend_module=self.backend, + ) + + def get_config(self): + return { + "bin_boundaries": self.bin_boundaries, + "num_bins": self.num_bins, + "epsilon": self.epsilon, + "output_mode": self.output_mode, + "sparse": self.sparse, + "name": self.name, + "dtype": self.dtype, + } + + +def summarize(values, epsilon): + """Reduce a 1D sequence of values to a summary. + + This algorithm is based on numpy.quantiles but modified to allow for + intermediate steps between multiple data sets. It first finds the target + number of bins as the reciprocal of epsilon and then takes the individual + values spaced at appropriate intervals to arrive at that target. + The final step is to return the corresponding counts between those values + If the target num_bins is larger than the size of values, the whole array is + returned (with weights of 1). + + Args: + values: 1D `np.ndarray` to be summarized. + epsilon: A `'float32'` that determines the approximate desired + precision. + + Returns: + A 2D `np.ndarray` that is a summary of the inputs. First column is the + interpolated partition values, the second is the weights (counts). + """ + values = np.reshape(values, [-1]) + values = np.sort(values) + elements = np.size(values) + num_buckets = 1.0 / epsilon + increment = elements / num_buckets + start = increment + step = max(increment, 1) + boundaries = values[int(start) :: int(step)] + weights = np.ones_like(boundaries) + weights = weights * step + return np.stack([boundaries, weights]) + + +def merge_summaries(prev_summary, next_summary, epsilon): + """Weighted merge sort of summaries. + + Given two summaries of distinct data, this function merges (and compresses) + them to stay within `epsilon` error tolerance. + + Args: + prev_summary: 2D `np.ndarray` summary to be merged with `next_summary`. + next_summary: 2D `np.ndarray` summary to be merged with `prev_summary`. + epsilon: A float that determines the approximate desired precision. + + Returns: + A 2-D `np.ndarray` that is a merged summary. First column is the + interpolated partition values, the second is the weights (counts). + """ + merged = np.concatenate((prev_summary, next_summary), axis=1) + merged = np.take(merged, np.argsort(merged[0]), axis=1) + return compress_summary(merged, epsilon) + + +def get_bin_boundaries(summary, num_bins): + return compress_summary(summary, 1.0 / num_bins)[0, :-1] + + +def compress_summary(summary, epsilon): + """Compress a summary to within `epsilon` accuracy. + + The compression step is needed to keep the summary sizes small after + merging, and also used to return the final target boundaries. It finds the + new bins based on interpolating cumulative weight percentages from the large + summary. Taking the difference of the cumulative weights from the previous + bin's cumulative weight will give the new weight for that bin. + + Args: + summary: 2D `np.ndarray` summary to be compressed. + epsilon: A `'float32'` that determines the approximate desired + precision. + + Returns: + A 2D `np.ndarray` that is a compressed summary. First column is the + interpolated partition values, the second is the weights (counts). + """ + if summary.shape[1] * epsilon < 1: + return summary + + percents = epsilon + np.arange(0.0, 1.0, epsilon) + cum_weights = summary[1].cumsum() + cum_weight_percents = cum_weights / cum_weights[-1] + new_bins = np.interp(percents, cum_weight_percents, summary[0]) + cum_weights = np.interp(percents, cum_weight_percents, cum_weights) + new_weights = cum_weights - np.concatenate( + (np.array([0]), cum_weights[:-1]) + ) + summary = np.stack((new_bins, new_weights)) + return summary.astype("float32") diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/feature_space.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/feature_space.py new file mode 100644 index 0000000000000000000000000000000000000000..5fc5e34afafa7a8e49fd62062c36828f1763f3bb --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/feature_space.py @@ -0,0 +1,815 @@ +from keras.src import backend +from keras.src import layers +from keras.src import tree +from keras.src.api_export import keras_export +from keras.src.layers.layer import Layer +from keras.src.layers.preprocessing.tf_data_layer import TFDataLayer +from keras.src.saving import saving_lib +from keras.src.saving import serialization_lib +from keras.src.utils import backend_utils +from keras.src.utils.module_utils import tensorflow as tf +from keras.src.utils.naming import auto_name + + +class Cross: + def __init__(self, feature_names, crossing_dim, output_mode="one_hot"): + if output_mode not in {"int", "one_hot"}: + raise ValueError( + "Invalid value for argument `output_mode`. " + "Expected one of {'int', 'one_hot'}. " + f"Received: output_mode={output_mode}" + ) + self.feature_names = tuple(feature_names) + self.crossing_dim = crossing_dim + self.output_mode = output_mode + + @property + def name(self): + return "_X_".join(self.feature_names) + + def get_config(self): + return { + "feature_names": self.feature_names, + "crossing_dim": self.crossing_dim, + "output_mode": self.output_mode, + } + + @classmethod + def from_config(cls, config): + return cls(**config) + + +class Feature: + def __init__(self, dtype, preprocessor, output_mode): + if output_mode not in {"int", "one_hot", "float"}: + raise ValueError( + "Invalid value for argument `output_mode`. " + "Expected one of {'int', 'one_hot', 'float'}. " + f"Received: output_mode={output_mode}" + ) + self.dtype = dtype + if isinstance(preprocessor, dict): + preprocessor = serialization_lib.deserialize_keras_object( + preprocessor + ) + self.preprocessor = preprocessor + self.output_mode = output_mode + + def get_config(self): + return { + "dtype": self.dtype, + "preprocessor": serialization_lib.serialize_keras_object( + self.preprocessor + ), + "output_mode": self.output_mode, + } + + @classmethod + def from_config(cls, config): + return cls(**config) + + +@keras_export("keras.utils.FeatureSpace") +class FeatureSpace(Layer): + """One-stop utility for preprocessing and encoding structured data. + + Arguments: + feature_names: Dict mapping the names of your features to their + type specification, e.g. `{"my_feature": "integer_categorical"}` + or `{"my_feature": FeatureSpace.integer_categorical()}`. + For a complete list of all supported types, see + "Available feature types" paragraph below. + output_mode: One of `"concat"` or `"dict"`. In concat mode, all + features get concatenated together into a single vector. + In dict mode, the FeatureSpace returns a dict of individually + encoded features (with the same keys as the input dict keys). + crosses: List of features to be crossed together, e.g. + `crosses=[("feature_1", "feature_2")]`. The features will be + "crossed" by hashing their combined value into + a fixed-length vector. + crossing_dim: Default vector size for hashing crossed features. + Defaults to `32`. + hashing_dim: Default vector size for hashing features of type + `"integer_hashed"` and `"string_hashed"`. Defaults to `32`. + num_discretization_bins: Default number of bins to be used for + discretizing features of type `"float_discretized"`. + Defaults to `32`. + + **Available feature types:** + + Note that all features can be referred to by their string name, + e.g. `"integer_categorical"`. When using the string name, the default + argument values are used. + + ```python + # Plain float values. + FeatureSpace.float(name=None) + + # Float values to be preprocessed via featurewise standardization + # (i.e. via a `keras.layers.Normalization` layer). + FeatureSpace.float_normalized(name=None) + + # Float values to be preprocessed via linear rescaling + # (i.e. via a `keras.layers.Rescaling` layer). + FeatureSpace.float_rescaled(scale=1., offset=0., name=None) + + # Float values to be discretized. By default, the discrete + # representation will then be one-hot encoded. + FeatureSpace.float_discretized( + num_bins, bin_boundaries=None, output_mode="one_hot", name=None) + + # Integer values to be indexed. By default, the discrete + # representation will then be one-hot encoded. + FeatureSpace.integer_categorical( + max_tokens=None, num_oov_indices=1, output_mode="one_hot", name=None) + + # String values to be indexed. By default, the discrete + # representation will then be one-hot encoded. + FeatureSpace.string_categorical( + max_tokens=None, num_oov_indices=1, output_mode="one_hot", name=None) + + # Integer values to be hashed into a fixed number of bins. + # By default, the discrete representation will then be one-hot encoded. + FeatureSpace.integer_hashed(num_bins, output_mode="one_hot", name=None) + + # String values to be hashed into a fixed number of bins. + # By default, the discrete representation will then be one-hot encoded. + FeatureSpace.string_hashed(num_bins, output_mode="one_hot", name=None) + ``` + + Examples: + + **Basic usage with a dict of input data:** + + ```python + raw_data = { + "float_values": [0.0, 0.1, 0.2, 0.3], + "string_values": ["zero", "one", "two", "three"], + "int_values": [0, 1, 2, 3], + } + dataset = tf.data.Dataset.from_tensor_slices(raw_data) + + feature_space = FeatureSpace( + features={ + "float_values": "float_normalized", + "string_values": "string_categorical", + "int_values": "integer_categorical", + }, + crosses=[("string_values", "int_values")], + output_mode="concat", + ) + # Before you start using the FeatureSpace, + # you must `adapt()` it on some data. + feature_space.adapt(dataset) + + # You can call the FeatureSpace on a dict of data (batched or unbatched). + output_vector = feature_space(raw_data) + ``` + + **Basic usage with `tf.data`:** + + ```python + # Unlabeled data + preprocessed_ds = unlabeled_dataset.map(feature_space) + + # Labeled data + preprocessed_ds = labeled_dataset.map(lambda x, y: (feature_space(x), y)) + ``` + + **Basic usage with the Keras Functional API:** + + ```python + # Retrieve a dict Keras Input objects + inputs = feature_space.get_inputs() + # Retrieve the corresponding encoded Keras tensors + encoded_features = feature_space.get_encoded_features() + # Build a Functional model + outputs = keras.layers.Dense(1, activation="sigmoid")(encoded_features) + model = keras.Model(inputs, outputs) + ``` + + **Customizing each feature or feature cross:** + + ```python + feature_space = FeatureSpace( + features={ + "float_values": FeatureSpace.float_normalized(), + "string_values": FeatureSpace.string_categorical(max_tokens=10), + "int_values": FeatureSpace.integer_categorical(max_tokens=10), + }, + crosses=[ + FeatureSpace.cross(("string_values", "int_values"), crossing_dim=32) + ], + output_mode="concat", + ) + ``` + + **Returning a dict of integer-encoded features:** + + ```python + feature_space = FeatureSpace( + features={ + "string_values": FeatureSpace.string_categorical(output_mode="int"), + "int_values": FeatureSpace.integer_categorical(output_mode="int"), + }, + crosses=[ + FeatureSpace.cross( + feature_names=("string_values", "int_values"), + crossing_dim=32, + output_mode="int", + ) + ], + output_mode="dict", + ) + ``` + + **Specifying your own Keras preprocessing layer:** + + ```python + # Let's say that one of the features is a short text paragraph that + # we want to encode as a vector (one vector per paragraph) via TF-IDF. + data = { + "text": ["1st string", "2nd string", "3rd string"], + } + + # There's a Keras layer for this: TextVectorization. + custom_layer = layers.TextVectorization(output_mode="tf_idf") + + # We can use FeatureSpace.feature to create a custom feature + # that will use our preprocessing layer. + feature_space = FeatureSpace( + features={ + "text": FeatureSpace.feature( + preprocessor=custom_layer, dtype="string", output_mode="float" + ), + }, + output_mode="concat", + ) + feature_space.adapt(tf.data.Dataset.from_tensor_slices(data)) + output_vector = feature_space(data) + ``` + + **Retrieving the underlying Keras preprocessing layers:** + + ```python + # The preprocessing layer of each feature is available in `.preprocessors`. + preprocessing_layer = feature_space.preprocessors["feature1"] + + # The crossing layer of each feature cross is available in `.crossers`. + # It's an instance of keras.layers.HashedCrossing. + crossing_layer = feature_space.crossers["feature1_X_feature2"] + ``` + + **Saving and reloading a FeatureSpace:** + + ```python + feature_space.save("featurespace.keras") + reloaded_feature_space = keras.models.load_model("featurespace.keras") + ``` + """ + + @classmethod + def cross(cls, feature_names, crossing_dim, output_mode="one_hot"): + return Cross(feature_names, crossing_dim, output_mode=output_mode) + + @classmethod + def feature(cls, dtype, preprocessor, output_mode): + return Feature(dtype, preprocessor, output_mode) + + @classmethod + def float(cls, name=None): + name = name or auto_name("float") + preprocessor = TFDIdentity(dtype="float32", name=f"{name}_preprocessor") + return Feature( + dtype="float32", preprocessor=preprocessor, output_mode="float" + ) + + @classmethod + def float_rescaled(cls, scale=1.0, offset=0.0, name=None): + name = name or auto_name("float_rescaled") + preprocessor = layers.Rescaling( + scale=scale, offset=offset, name=f"{name}_preprocessor" + ) + return Feature( + dtype="float32", preprocessor=preprocessor, output_mode="float" + ) + + @classmethod + def float_normalized(cls, name=None): + name = name or auto_name("float_normalized") + preprocessor = layers.Normalization( + axis=-1, name=f"{name}_preprocessor" + ) + return Feature( + dtype="float32", preprocessor=preprocessor, output_mode="float" + ) + + @classmethod + def float_discretized( + cls, num_bins, bin_boundaries=None, output_mode="one_hot", name=None + ): + name = name or auto_name("float_discretized") + preprocessor = layers.Discretization( + num_bins=num_bins, + bin_boundaries=bin_boundaries, + name=f"{name}_preprocessor", + ) + return Feature( + dtype="float32", preprocessor=preprocessor, output_mode=output_mode + ) + + @classmethod + def integer_categorical( + cls, + max_tokens=None, + num_oov_indices=1, + output_mode="one_hot", + name=None, + ): + name = name or auto_name("integer_categorical") + preprocessor = layers.IntegerLookup( + name=f"{name}_preprocessor", + max_tokens=max_tokens, + num_oov_indices=num_oov_indices, + ) + return Feature( + dtype="int32", preprocessor=preprocessor, output_mode=output_mode + ) + + @classmethod + def string_categorical( + cls, + max_tokens=None, + num_oov_indices=1, + output_mode="one_hot", + name=None, + ): + name = name or auto_name("string_categorical") + preprocessor = layers.StringLookup( + name=f"{name}_preprocessor", + max_tokens=max_tokens, + num_oov_indices=num_oov_indices, + ) + return Feature( + dtype="string", preprocessor=preprocessor, output_mode=output_mode + ) + + @classmethod + def string_hashed(cls, num_bins, output_mode="one_hot", name=None): + name = name or auto_name("string_hashed") + preprocessor = layers.Hashing( + name=f"{name}_preprocessor", num_bins=num_bins + ) + return Feature( + dtype="string", preprocessor=preprocessor, output_mode=output_mode + ) + + @classmethod + def integer_hashed(cls, num_bins, output_mode="one_hot", name=None): + name = name or auto_name("integer_hashed") + preprocessor = layers.Hashing( + name=f"{name}_preprocessor", num_bins=num_bins + ) + return Feature( + dtype="int32", preprocessor=preprocessor, output_mode=output_mode + ) + + def __init__( + self, + features, + output_mode="concat", + crosses=None, + crossing_dim=32, + hashing_dim=32, + num_discretization_bins=32, + name=None, + ): + super().__init__(name=name) + if not features: + raise ValueError("The `features` argument cannot be None or empty.") + self.crossing_dim = crossing_dim + self.hashing_dim = hashing_dim + self.num_discretization_bins = num_discretization_bins + self.features = { + name: self._standardize_feature(name, value) + for name, value in features.items() + } + self.crosses = [] + if crosses: + feature_set = set(features.keys()) + for cross in crosses: + if isinstance(cross, dict): + cross = serialization_lib.deserialize_keras_object(cross) + if isinstance(cross, Cross): + self.crosses.append(cross) + else: + if not crossing_dim: + raise ValueError( + "When specifying `crosses`, the argument " + "`crossing_dim` " + "(dimensionality of the crossing space) " + "should be specified as well." + ) + for key in cross: + if key not in feature_set: + raise ValueError( + "All features referenced " + "in the `crosses` argument " + "should be present in the `features` dict. " + f"Received unknown features: {cross}" + ) + self.crosses.append(Cross(cross, crossing_dim=crossing_dim)) + self.crosses_by_name = {cross.name: cross for cross in self.crosses} + + if output_mode not in {"dict", "concat"}: + raise ValueError( + "Invalid value for argument `output_mode`. " + "Expected one of {'dict', 'concat'}. " + f"Received: output_mode={output_mode}" + ) + self.output_mode = output_mode + + self.inputs = { + name: self._feature_to_input(name, value) + for name, value in self.features.items() + } + self.preprocessors = { + name: value.preprocessor for name, value in self.features.items() + } + self.encoded_features = None + self.crossers = { + cross.name: self._cross_to_crosser(cross) for cross in self.crosses + } + self.one_hot_encoders = {} + self._is_adapted = False + self.concat = None + self._preprocessed_features_names = None + self._crossed_features_names = None + self._sublayers_built = False + + def _feature_to_input(self, name, feature): + return layers.Input(shape=(1,), dtype=feature.dtype, name=name) + + def _standardize_feature(self, name, feature): + if isinstance(feature, Feature): + return feature + + if isinstance(feature, dict): + return serialization_lib.deserialize_keras_object(feature) + + if feature == "float": + return self.float(name=name) + elif feature == "float_normalized": + return self.float_normalized(name=name) + elif feature == "float_rescaled": + return self.float_rescaled(name=name) + elif feature == "float_discretized": + return self.float_discretized( + name=name, num_bins=self.num_discretization_bins + ) + elif feature == "integer_categorical": + return self.integer_categorical(name=name) + elif feature == "string_categorical": + return self.string_categorical(name=name) + elif feature == "integer_hashed": + return self.integer_hashed(self.hashing_dim, name=name) + elif feature == "string_hashed": + return self.string_hashed(self.hashing_dim, name=name) + else: + raise ValueError(f"Invalid feature type: {feature}") + + def _cross_to_crosser(self, cross): + return layers.HashedCrossing(cross.crossing_dim, name=cross.name) + + def _list_adaptable_preprocessors(self): + adaptable_preprocessors = [] + for name in self.features.keys(): + preprocessor = self.preprocessors[name] + # Special case: a Normalization layer with preset mean/variance. + # Not adaptable. + if isinstance(preprocessor, layers.Normalization): + if preprocessor.input_mean is not None: + continue + # Special case: a TextVectorization layer with provided vocabulary. + elif isinstance(preprocessor, layers.TextVectorization): + if preprocessor._has_input_vocabulary: + continue + if hasattr(preprocessor, "adapt"): + adaptable_preprocessors.append(name) + return adaptable_preprocessors + + def adapt(self, dataset): + if not isinstance(dataset, tf.data.Dataset): + raise ValueError( + "`adapt()` can only be called on a tf.data.Dataset. " + f"Received instead: {dataset} (of type {type(dataset)})" + ) + + for name in self._list_adaptable_preprocessors(): + # Call adapt() on each individual adaptable layer. + + # TODO: consider rewriting this to instead iterate on the + # dataset once, split each batch into individual features, + # and call the layer's `_adapt_function` on each batch + # to simulate the behavior of adapt() in a more performant fashion. + + feature_dataset = dataset.map(lambda x: x[name]) + preprocessor = self.preprocessors[name] + # TODO: consider adding an adapt progress bar. + # Sample 1 element to check the rank + x = next(iter(feature_dataset)) + if len(x.shape) == 0: + # The dataset yields unbatched scalars; batch it. + feature_dataset = feature_dataset.batch(32) + if len(x.shape) in {0, 1}: + # If the rank is 1, add a dimension + # so we can reduce on axis=-1. + # Note: if rank was previously 0, it is now 1. + feature_dataset = feature_dataset.map( + lambda x: tf.expand_dims(x, -1) + ) + preprocessor.adapt(feature_dataset) + self._is_adapted = True + self.get_encoded_features() # Finish building the layer + self.built = True + self._sublayers_built = True + + def get_inputs(self): + self._check_if_built() + return self.inputs + + def get_encoded_features(self): + self._check_if_adapted() + + if self.encoded_features is None: + preprocessed_features = self._preprocess_features(self.inputs) + crossed_features = self._cross_features(preprocessed_features) + merged_features = self._merge_features( + preprocessed_features, crossed_features + ) + self.encoded_features = merged_features + return self.encoded_features + + def _preprocess_features(self, features): + return { + name: self.preprocessors[name](features[name]) + for name in features.keys() + } + + def _cross_features(self, features): + all_outputs = {} + for cross in self.crosses: + inputs = [features[name] for name in cross.feature_names] + outputs = self.crossers[cross.name](inputs) + all_outputs[cross.name] = outputs + return all_outputs + + def _merge_features(self, preprocessed_features, crossed_features): + if not self._preprocessed_features_names: + self._preprocessed_features_names = sorted( + preprocessed_features.keys() + ) + self._crossed_features_names = sorted(crossed_features.keys()) + + all_names = ( + self._preprocessed_features_names + self._crossed_features_names + ) + all_features = [ + preprocessed_features[name] + for name in self._preprocessed_features_names + ] + [crossed_features[name] for name in self._crossed_features_names] + + if self.output_mode == "dict": + output_dict = {} + else: + features_to_concat = [] + + if self._sublayers_built: + # Fast mode. + for name, feature in zip(all_names, all_features): + encoder = self.one_hot_encoders.get(name, None) + if encoder: + feature = encoder(feature) + if self.output_mode == "dict": + output_dict[name] = feature + else: + features_to_concat.append(feature) + if self.output_mode == "dict": + return output_dict + else: + return self.concat(features_to_concat) + + # If the object isn't built, + # we create the encoder and concat layers below + all_specs = [ + self.features[name] for name in self._preprocessed_features_names + ] + [ + self.crosses_by_name[name] for name in self._crossed_features_names + ] + + for name, feature, spec in zip(all_names, all_features, all_specs): + if tree.is_nested(feature): + dtype = tree.flatten(feature)[0].dtype + else: + dtype = feature.dtype + dtype = backend.standardize_dtype(dtype) + + if spec.output_mode == "one_hot": + preprocessor = self.preprocessors.get( + name + ) or self.crossers.get(name) + + cardinality = None + if not dtype.startswith("int"): + raise ValueError( + f"Feature '{name}' has `output_mode='one_hot'`. " + "Thus its preprocessor should return an integer dtype. " + f"Instead it returns a {dtype} dtype." + ) + + if isinstance( + preprocessor, (layers.IntegerLookup, layers.StringLookup) + ): + cardinality = preprocessor.vocabulary_size() + elif isinstance(preprocessor, layers.CategoryEncoding): + cardinality = preprocessor.num_tokens + elif isinstance(preprocessor, layers.Discretization): + cardinality = preprocessor.num_bins + elif isinstance( + preprocessor, (layers.HashedCrossing, layers.Hashing) + ): + cardinality = preprocessor.num_bins + else: + raise ValueError( + f"Feature '{name}' has `output_mode='one_hot'`. " + "However it isn't a standard feature and the " + "dimensionality of its output space is not known, " + "thus it cannot be one-hot encoded. " + "Try using `output_mode='int'`." + ) + if cardinality is not None: + encoder = layers.CategoryEncoding( + num_tokens=cardinality, output_mode="multi_hot" + ) + self.one_hot_encoders[name] = encoder + feature = encoder(feature) + + if self.output_mode == "concat": + dtype = feature.dtype + if dtype.startswith("int") or dtype == "string": + raise ValueError( + f"Cannot concatenate features because feature '{name}' " + f"has not been encoded (it has dtype {dtype}). " + "Consider using `output_mode='dict'`." + ) + features_to_concat.append(feature) + else: + output_dict[name] = feature + + if self.output_mode == "concat": + self.concat = TFDConcat(axis=-1) + return self.concat(features_to_concat) + else: + return output_dict + + def _check_if_adapted(self): + if not self._is_adapted: + if not self._list_adaptable_preprocessors(): + self._is_adapted = True + else: + raise ValueError( + "You need to call `.adapt(dataset)` on the FeatureSpace " + "before you can start using it." + ) + + def _check_if_built(self): + if not self._sublayers_built: + self._check_if_adapted() + # Finishes building + self.get_encoded_features() + self._sublayers_built = True + + def _convert_input(self, x): + if not isinstance(x, (tf.Tensor, tf.SparseTensor, tf.RaggedTensor)): + if not isinstance(x, (list, tuple, int, float)): + x = backend.convert_to_numpy(x) + x = tf.convert_to_tensor(x) + return x + + def __call__(self, data): + self._check_if_built() + if not isinstance(data, dict): + raise ValueError( + "A FeatureSpace can only be called with a dict. " + f"Received: data={data} (of type {type(data)}" + ) + + # Many preprocessing layers support all backends but many do not. + # Switch to TF to make FeatureSpace work universally. + data = {key: self._convert_input(value) for key, value in data.items()} + rebatched = False + for name, x in data.items(): + if len(x.shape) == 0: + data[name] = tf.reshape(x, (1, 1)) + rebatched = True + elif len(x.shape) == 1: + data[name] = tf.expand_dims(x, -1) + + with backend_utils.TFGraphScope(): + # This scope is to make sure that inner TFDataLayers + # will not convert outputs back to backend-native -- + # they should be TF tensors throughout + preprocessed_data = self._preprocess_features(data) + preprocessed_data = tree.map_structure( + lambda x: self._convert_input(x), preprocessed_data + ) + + crossed_data = self._cross_features(preprocessed_data) + crossed_data = tree.map_structure( + lambda x: self._convert_input(x), crossed_data + ) + + merged_data = self._merge_features(preprocessed_data, crossed_data) + + if rebatched: + if self.output_mode == "concat": + assert merged_data.shape[0] == 1 + if ( + backend.backend() != "tensorflow" + and not backend_utils.in_tf_graph() + ): + merged_data = backend.convert_to_numpy(merged_data) + merged_data = tf.squeeze(merged_data, axis=0) + else: + for name, x in merged_data.items(): + if len(x.shape) == 2 and x.shape[0] == 1: + merged_data[name] = tf.squeeze(x, axis=0) + + if ( + backend.backend() != "tensorflow" + and not backend_utils.in_tf_graph() + ): + merged_data = tree.map_structure( + lambda x: backend.convert_to_tensor(x, dtype=x.dtype), + merged_data, + ) + return merged_data + + def get_config(self): + return { + "features": serialization_lib.serialize_keras_object(self.features), + "output_mode": self.output_mode, + "crosses": serialization_lib.serialize_keras_object(self.crosses), + "crossing_dim": self.crossing_dim, + "hashing_dim": self.hashing_dim, + "num_discretization_bins": self.num_discretization_bins, + } + + @classmethod + def from_config(cls, config): + return cls(**config) + + def get_build_config(self): + return { + name: feature.preprocessor.get_build_config() + for name, feature in self.features.items() + } + + def build_from_config(self, config): + for name in config.keys(): + preprocessor = self.features[name].preprocessor + if not preprocessor.built: + preprocessor.build_from_config(config[name]) + self._is_adapted = True + + def save(self, filepath): + """Save the `FeatureSpace` instance to a `.keras` file. + + You can reload it via `keras.models.load_model()`: + + ```python + feature_space.save("featurespace.keras") + reloaded_fs = keras.models.load_model("featurespace.keras") + ``` + """ + saving_lib.save_model(self, filepath) + + def save_own_variables(self, store): + return + + def load_own_variables(self, store): + return + + +class TFDConcat(TFDataLayer): + def __init__(self, axis, **kwargs): + super().__init__(**kwargs) + self.axis = axis + + def call(self, xs): + return self.backend.numpy.concatenate(xs, axis=self.axis) + + +class TFDIdentity(TFDataLayer): + def call(self, x): + return x diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/hashed_crossing.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/hashed_crossing.py new file mode 100644 index 0000000000000000000000000000000000000000..faf2f7bc9af2a1ccfb8e2f1e9408377e1cd141b3 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/hashed_crossing.py @@ -0,0 +1,227 @@ +from keras.src import backend +from keras.src.api_export import keras_export +from keras.src.layers.layer import Layer +from keras.src.utils import argument_validation +from keras.src.utils import backend_utils +from keras.src.utils import numerical_utils +from keras.src.utils import tf_utils +from keras.src.utils.module_utils import tensorflow as tf + + +@keras_export("keras.layers.HashedCrossing") +class HashedCrossing(Layer): + """A preprocessing layer which crosses features using the "hashing trick". + + This layer performs crosses of categorical features using the "hashing + trick". Conceptually, the transformation can be thought of as: + `hash(concatenate(features)) % num_bins`. + + This layer currently only performs crosses of scalar inputs and batches of + scalar inputs. Valid input shapes are `(batch_size, 1)`, `(batch_size,)` and + `()`. + + **Note:** This layer wraps `tf.keras.layers.HashedCrossing`. It cannot + be used as part of the compiled computation graph of a model with + any backend other than TensorFlow. + It can however be used with any backend when running eagerly. + It can also always be used as part of an input preprocessing pipeline + with any backend (outside the model itself), which is how we recommend + to use this layer. + + **Note:** This layer is safe to use inside a `tf.data` pipeline + (independently of which backend you're using). + + Args: + num_bins: Number of hash bins. + output_mode: Specification for the output of the layer. Values can be + `"int"`, or `"one_hot"` configuring the layer as follows: + - `"int"`: Return the integer bin indices directly. + - `"one_hot"`: Encodes each individual element in the input into an + array the same size as `num_bins`, containing a 1 at the input's + bin index. Defaults to `"int"`. + sparse: Boolean. Only applicable to `"one_hot"` mode and only valid + when using the TensorFlow backend. If `True`, returns + a `SparseTensor` instead of a dense `Tensor`. Defaults to `False`. + **kwargs: Keyword arguments to construct a layer. + + Examples: + + **Crossing two scalar features.** + + >>> layer = keras.layers.HashedCrossing( + ... num_bins=5) + >>> feat1 = np.array(['A', 'B', 'A', 'B', 'A']) + >>> feat2 = np.array([101, 101, 101, 102, 102]) + >>> layer((feat1, feat2)) + array([1, 4, 1, 1, 3]) + + **Crossing and one-hotting two scalar features.** + + >>> layer = keras.layers.HashedCrossing( + ... num_bins=5, output_mode='one_hot') + >>> feat1 = np.array(['A', 'B', 'A', 'B', 'A']) + >>> feat2 = np.array([101, 101, 101, 102, 102]) + >>> layer((feat1, feat2)) + array([[0., 1., 0., 0., 0.], + [0., 0., 0., 0., 1.], + [0., 1., 0., 0., 0.], + [0., 1., 0., 0., 0.], + [0., 0., 0., 1., 0.]], dtype=float32) + """ + + def __init__( + self, + num_bins, + output_mode="int", + sparse=False, + name=None, + dtype=None, + **kwargs, + ): + if not tf.available: + raise ImportError( + "Layer HashedCrossing requires TensorFlow. " + "Install it via `pip install tensorflow`." + ) + + if output_mode == "int" and dtype is None: + dtype = "int64" + + super().__init__(name=name, dtype=dtype) + if sparse and backend.backend() != "tensorflow": + raise ValueError( + "`sparse=True` can only be used with the " "TensorFlow backend." + ) + + argument_validation.validate_string_arg( + output_mode, + allowable_strings=("int", "one_hot"), + caller_name=self.__class__.__name__, + arg_name="output_mode", + ) + + self.num_bins = num_bins + self.output_mode = output_mode + self.sparse = sparse + self._allow_non_tensor_positional_args = True + self._convert_input_args = False + self.supports_jit = False + + def compute_output_shape(self, input_shape): + if ( + not len(input_shape) == 2 + or not isinstance(input_shape[0], tuple) + or not isinstance(input_shape[1], tuple) + ): + raise ValueError( + "Expected as input a list/tuple of 2 tensors. " + f"Received input_shape={input_shape}" + ) + if input_shape[0][-1] != input_shape[1][-1]: + raise ValueError( + "Expected the two input tensors to have identical shapes. " + f"Received input_shape={input_shape}" + ) + + if not input_shape: + if self.output_mode == "int": + return () + return (self.num_bins,) + if self.output_mode == "int": + return input_shape[0] + + if self.output_mode == "one_hot" and input_shape[0][-1] != 1: + return tuple(input_shape[0]) + (self.num_bins,) + + return tuple(input_shape[0])[:-1] + (self.num_bins,) + + def call(self, inputs): + from keras.src.backend import tensorflow as tf_backend + + self._check_at_least_two_inputs(inputs) + inputs = [tf_utils.ensure_tensor(x) for x in inputs] + self._check_input_shape_and_type(inputs) + + # Uprank to rank 2 for the cross_hashed op. + rank = len(inputs[0].shape) + if rank < 2: + inputs = [tf_backend.numpy.expand_dims(x, -1) for x in inputs] + if rank < 1: + inputs = [tf_backend.numpy.expand_dims(x, -1) for x in inputs] + + # Perform the cross and convert to dense + outputs = tf.sparse.cross_hashed(inputs, self.num_bins) + outputs = tf.sparse.to_dense(outputs) + + # Fix output shape and downrank to match input rank. + if rank == 2: + # tf.sparse.cross_hashed output shape will always be None on the + # last dimension. Given our input shape restrictions, we want to + # force shape 1 instead. + outputs = tf.reshape(outputs, [-1, 1]) + elif rank == 1: + outputs = tf.reshape(outputs, [-1]) + elif rank == 0: + outputs = tf.reshape(outputs, []) + + # Encode outputs. + outputs = numerical_utils.encode_categorical_inputs( + outputs, + output_mode=self.output_mode, + depth=self.num_bins, + sparse=self.sparse, + dtype=self.compute_dtype, + backend_module=tf_backend, + ) + return backend_utils.convert_tf_tensor(outputs, dtype=self.dtype) + + def get_config(self): + return { + "num_bins": self.num_bins, + "output_mode": self.output_mode, + "sparse": self.sparse, + "name": self.name, + "dtype": self.dtype, + } + + def _check_at_least_two_inputs(self, inputs): + if not isinstance(inputs, (list, tuple)): + raise ValueError( + "`HashedCrossing` should be called on a list or tuple of " + f"inputs. Received: inputs={inputs}" + ) + if len(inputs) < 2: + raise ValueError( + "`HashedCrossing` should be called on at least two inputs. " + f"Received: inputs={inputs}" + ) + + def _check_input_shape_and_type(self, inputs): + first_shape = tuple(inputs[0].shape) + rank = len(first_shape) + if rank > 2 or (rank == 2 and first_shape[-1] != 1): + raise ValueError( + "All `HashedCrossing` inputs should have shape `()`, " + "`(batch_size)` or `(batch_size, 1)`. " + f"Received: inputs={inputs}" + ) + if not all(tuple(x.shape) == first_shape for x in inputs[1:]): + raise ValueError( + "All `HashedCrossing` inputs should have equal shape. " + f"Received: inputs={inputs}" + ) + if any( + isinstance(x, (tf.RaggedTensor, tf.SparseTensor)) for x in inputs + ): + raise ValueError( + "All `HashedCrossing` inputs should be dense tensors. " + f"Received: inputs={inputs}" + ) + if not all( + tf.as_dtype(x.dtype).is_integer or x.dtype == tf.string + for x in inputs + ): + raise ValueError( + "All `HashedCrossing` inputs should have an integer or " + f"string dtype. Received: inputs={inputs}" + ) diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/hashing.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/hashing.py new file mode 100644 index 0000000000000000000000000000000000000000..2f2a33f7e90bc7eb26800e12ab62d79f00c68626 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/hashing.py @@ -0,0 +1,287 @@ +from keras.src import backend +from keras.src.api_export import keras_export +from keras.src.layers.layer import Layer +from keras.src.utils import backend_utils +from keras.src.utils import numerical_utils +from keras.src.utils import tf_utils +from keras.src.utils.module_utils import tensorflow as tf + + +@keras_export("keras.layers.Hashing") +class Hashing(Layer): + """A preprocessing layer which hashes and bins categorical features. + + This layer transforms categorical inputs to hashed output. It element-wise + converts a ints or strings to ints in a fixed range. The stable hash + function uses `tensorflow::ops::Fingerprint` to produce the same output + consistently across all platforms. + + This layer uses [FarmHash64](https://github.com/google/farmhash) by default, + which provides a consistent hashed output across different platforms and is + stable across invocations, regardless of device and context, by mixing the + input bits thoroughly. + + If you want to obfuscate the hashed output, you can also pass a random + `salt` argument in the constructor. In that case, the layer will use the + [SipHash64](https://github.com/google/highwayhash) hash function, with + the `salt` value serving as additional input to the hash function. + + **Note:** This layer internally uses TensorFlow. It cannot + be used as part of the compiled computation graph of a model with + any backend other than TensorFlow. + It can however be used with any backend when running eagerly. + It can also always be used as part of an input preprocessing pipeline + with any backend (outside the model itself), which is how we recommend + to use this layer. + + **Note:** This layer is safe to use inside a `tf.data` pipeline + (independently of which backend you're using). + + **Example (FarmHash64)** + + >>> layer = keras.layers.Hashing(num_bins=3) + >>> inp = [['A'], ['B'], ['C'], ['D'], ['E']] + >>> layer(inp) + array([[1], + [0], + [1], + [1], + [2]])> + + **Example (FarmHash64) with a mask value** + + >>> layer = keras.layers.Hashing(num_bins=3, mask_value='') + >>> inp = [['A'], ['B'], [''], ['C'], ['D']] + >>> layer(inp) + array([[1], + [1], + [0], + [2], + [2]]) + + **Example (SipHash64)** + + >>> layer = keras.layers.Hashing(num_bins=3, salt=[133, 137]) + >>> inp = [['A'], ['B'], ['C'], ['D'], ['E']] + >>> layer(inp) + array([[1], + [2], + [1], + [0], + [2]]) + + **Example (Siphash64 with a single integer, same as `salt=[133, 133]`)** + + >>> layer = keras.layers.Hashing(num_bins=3, salt=133) + >>> inp = [['A'], ['B'], ['C'], ['D'], ['E']] + >>> layer(inp) + array([[0], + [0], + [2], + [1], + [0]]) + + Args: + num_bins: Number of hash bins. Note that this includes the `mask_value` + bin, so the effective number of bins is `(num_bins - 1)` + if `mask_value` is set. + mask_value: A value that represents masked inputs, which are mapped to + index 0. `None` means no mask term will be added and the + hashing will start at index 0. Defaults to `None`. + salt: A single unsigned integer or None. + If passed, the hash function used will be SipHash64, + with these values used as an additional input + (known as a "salt" in cryptography). + These should be non-zero. If `None`, uses the FarmHash64 hash + function. It also supports tuple/list of 2 unsigned + integer numbers, see reference paper for details. + Defaults to `None`. + output_mode: Specification for the output of the layer. Values can be + `"int"`, `"one_hot"`, `"multi_hot"`, or + `"count"` configuring the layer as follows: + - `"int"`: Return the integer bin indices directly. + - `"one_hot"`: Encodes each individual element in the input into an + array the same size as `num_bins`, containing a 1 + at the input's bin index. If the last dimension is size 1, + will encode on that dimension. + If the last dimension is not size 1, will append a new + dimension for the encoded output. + - `"multi_hot"`: Encodes each sample in the input into a + single array the same size as `num_bins`, + containing a 1 for each bin index + index present in the sample. Treats the last dimension + as the sample dimension, if input shape is + `(..., sample_length)`, output shape will be + `(..., num_tokens)`. + - `"count"`: As `"multi_hot"`, but the int array contains a count of + the number of times the bin index appeared in the sample. + Defaults to `"int"`. + sparse: Boolean. Only applicable to `"one_hot"`, `"multi_hot"`, + and `"count"` output modes. Only supported with TensorFlow + backend. If `True`, returns a `SparseTensor` instead of + a dense `Tensor`. Defaults to `False`. + **kwargs: Keyword arguments to construct a layer. + + Input shape: + A single string, a list of strings, or an `int32` or `int64` tensor + of shape `(batch_size, ...,)`. + + Output shape: + An `int32` tensor of shape `(batch_size, ...)`. + + Reference: + + - [SipHash with salt](https://www.131002.net/siphash/siphash.pdf) + """ + + def __init__( + self, + num_bins, + mask_value=None, + salt=None, + output_mode="int", + sparse=False, + **kwargs, + ): + if not tf.available: + raise ImportError( + "Layer Hashing requires TensorFlow. " + "Install it via `pip install tensorflow`." + ) + + # By default, output int32 when output_mode='int' and floats otherwise. + if "dtype" not in kwargs or kwargs["dtype"] is None: + kwargs["dtype"] = ( + "int64" if output_mode == "int" else backend.floatx() + ) + + super().__init__(**kwargs) + + if num_bins is None or num_bins <= 0: + raise ValueError( + "The `num_bins` for `Hashing` cannot be `None` or " + f"non-positive values. Received: num_bins={num_bins}." + ) + + if output_mode == "int" and ( + self.dtype_policy.name not in ("int32", "int64") + ): + raise ValueError( + 'When `output_mode="int"`, `dtype` should be an integer ' + f"type, 'int32' or 'in64'. Received: dtype={kwargs['dtype']}" + ) + + # 'output_mode' must be one of (INT, ONE_HOT, MULTI_HOT, COUNT) + accepted_output_modes = ("int", "one_hot", "multi_hot", "count") + if output_mode not in accepted_output_modes: + raise ValueError( + "Invalid value for argument `output_mode`. " + f"Expected one of {accepted_output_modes}. " + f"Received: output_mode={output_mode}" + ) + + if sparse and output_mode == "int": + raise ValueError( + "`sparse` may only be true if `output_mode` is " + '`"one_hot"`, `"multi_hot"`, or `"count"`. ' + f"Received: sparse={sparse} and " + f"output_mode={output_mode}" + ) + + self.num_bins = num_bins + self.mask_value = mask_value + self.strong_hash = True if salt is not None else False + self.output_mode = output_mode + self.sparse = sparse + self.salt = None + if salt is not None: + if isinstance(salt, (tuple, list)) and len(salt) == 2: + self.salt = list(salt) + elif isinstance(salt, int): + self.salt = [salt, salt] + else: + raise ValueError( + "The `salt` argument for `Hashing` can only be a tuple of " + "size 2 integers, or a single integer. " + f"Received: salt={salt}." + ) + self._convert_input_args = False + self._allow_non_tensor_positional_args = True + self.supports_jit = False + + def call(self, inputs): + from keras.src.backend import tensorflow as tf_backend + + inputs = tf_utils.ensure_tensor(inputs) + if self.output_mode == "one_hot" and inputs.shape[-1] == 1: + # One hot only unpranks if the final dimension is not 1. + inputs = tf_backend.numpy.squeeze(inputs, axis=-1) + if isinstance(inputs, tf.SparseTensor): + indices = tf.SparseTensor( + indices=inputs.indices, + values=self._hash_values_to_bins(inputs.values), + dense_shape=inputs.dense_shape, + ) + else: + indices = self._hash_values_to_bins(inputs) + outputs = numerical_utils.encode_categorical_inputs( + indices, + output_mode=self.output_mode, + depth=self.num_bins, + sparse=self.sparse, + dtype=self.dtype, + backend_module=tf_backend, + ) + return backend_utils.convert_tf_tensor(outputs) + + def _hash_values_to_bins(self, values): + """Converts a non-sparse tensor of values to bin indices.""" + hash_bins = self.num_bins + mask = None + # If mask_value is set, the zeroth bin is reserved for it. + if self.mask_value is not None and hash_bins > 1: + hash_bins -= 1 + mask = tf.equal(values, self.mask_value) + # Convert all values to strings before hashing. + # Floats are first normalized to int64. + if values.dtype.is_floating: + values = tf.cast(values, dtype="int64") + if values.dtype != tf.string: + values = tf.as_string(values) + # Hash the strings. + if self.strong_hash: + values = tf.strings.to_hash_bucket_strong( + values, hash_bins, name="hash", key=self.salt + ) + else: + values = tf.strings.to_hash_bucket_fast( + values, hash_bins, name="hash" + ) + if mask is not None: + values = tf.add(values, tf.ones_like(values)) + values = tf.where(mask, tf.zeros_like(values), values) + return values + + def compute_output_spec(self, inputs): + if self.output_mode == "int": + return backend.KerasTensor(shape=inputs.shape, dtype=self.dtype) + if len(inputs.shape) >= 1: + base_shape = tuple(inputs.shape)[:-1] + else: + base_shape = () + return backend.KerasTensor( + shape=base_shape + (self.num_bins,), dtype=self.dtype + ) + + def get_config(self): + config = super().get_config() + config.update( + { + "num_bins": self.num_bins, + "salt": self.salt, + "mask_value": self.mask_value, + "output_mode": self.output_mode, + "sparse": self.sparse, + } + ) + return config diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__init__.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_contrast.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_contrast.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f15f408a480ddd5fb8f489df0f0fa2579822e1ef Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_contrast.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_crop.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_crop.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eea1129312f6f2df8a3298b91fe476867f86334c Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_crop.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_flip.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_flip.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..acaddd6d4fc9d20f7126cabe1ee19511bf1299f0 Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_flip.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_grayscale.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_grayscale.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d8f7e00ea17772b213d127e60e5759c84358a74 Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_grayscale.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_hue.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_hue.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e24eeb1cf09c76a7618c54f7807598df2cb7adb8 Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_hue.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_posterization.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_posterization.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..709051d545d0da28d2ff6113b1697a8a474480aa Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_posterization.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_rotation.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_rotation.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2625b415db70708a52a35d84fbf933f7679bbaf4 Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_rotation.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_saturation.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_saturation.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..25e5f5398f70bb282c0cc66e7a5d8e15c427f57d Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_saturation.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_sharpness.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_sharpness.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f5247064b225aca5dd5ec3300f38fd3019383eb Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_sharpness.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_shear.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_shear.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..40277a5f1b0bb2f00b64882ea98712bf634fcee2 Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_shear.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_translation.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_translation.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5326f1967b0c88442a02fcb3a05d37f20705cca1 Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_translation.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_zoom.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_zoom.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aaa90adf09a2bb85df6ae4551a1565427029a7aa Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/random_zoom.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/resizing.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/resizing.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..edc607f47e8d01165795f50e4b5047ed06d5cc9c Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/resizing.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/solarization.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/solarization.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7facf7b2692ebcdf99f1b4404a237b81afccbeb Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/__pycache__/solarization.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/auto_contrast.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/auto_contrast.py new file mode 100644 index 0000000000000000000000000000000000000000..4d98a88fb37905a1ffe4e16f102a7a6dbded6a1c --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/auto_contrast.py @@ -0,0 +1,109 @@ +from keras.src import backend +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.image_preprocessing.base_image_preprocessing_layer import ( # noqa: E501 + BaseImagePreprocessingLayer, +) +from keras.src.ops.core import _saturate_cast + + +@keras_export("keras.layers.AutoContrast") +class AutoContrast(BaseImagePreprocessingLayer): + """Performs the auto-contrast operation on an image. + + Auto contrast stretches the values of an image across the entire available + `value_range`. This makes differences between pixels more obvious. An + example of this is if an image only has values `[0, 1]` out of the range + `[0, 255]`, auto contrast will change the `1` values to be `255`. + + This layer is active at both training and inference time. + + Args: + value_range: Range of values the incoming images will have. + Represented as a two number tuple written `(low, high)`. + This is typically either `(0, 1)` or `(0, 255)` depending + on how your preprocessing pipeline is set up. + Defaults to `(0, 255)`. + """ + + _USE_BASE_FACTOR = False + _VALUE_RANGE_VALIDATION_ERROR = ( + "The `value_range` argument should be a list of two numbers. " + ) + + def __init__( + self, + value_range=(0, 255), + **kwargs, + ): + super().__init__(**kwargs) + self._set_value_range(value_range) + + def _set_value_range(self, value_range): + if not isinstance(value_range, (tuple, list)): + raise ValueError( + self._VALUE_RANGE_VALIDATION_ERROR + + f"Received: value_range={value_range}" + ) + if len(value_range) != 2: + raise ValueError( + self._VALUE_RANGE_VALIDATION_ERROR + + f"Received: value_range={value_range}" + ) + self.value_range = sorted(value_range) + + def transform_images(self, images, transformation=None, training=True): + original_images = images + images = self._transform_value_range( + images, + original_range=self.value_range, + target_range=(0, 255), + dtype=self.compute_dtype, + ) + + images = self.backend.cast(images, self.compute_dtype) + low = self.backend.numpy.min(images, axis=(1, 2), keepdims=True) + high = self.backend.numpy.max(images, axis=(1, 2), keepdims=True) + scale = 255.0 / (high - low) + offset = -low * scale + + images = images * scale + offset + results = self.backend.numpy.clip(images, 0.0, 255.0) + results = self._transform_value_range( + results, + original_range=(0, 255), + target_range=self.value_range, + dtype=self.compute_dtype, + ) + # don't process NaN channels + results = self.backend.numpy.where( + self.backend.numpy.isnan(results), original_images, results + ) + if results.dtype == images.dtype: + return results + if backend.is_int_dtype(images.dtype): + results = self.backend.numpy.round(results) + return _saturate_cast(results, images.dtype, self.backend) + + def transform_labels(self, labels, transformation, training=True): + return labels + + def transform_bounding_boxes( + self, + bounding_boxes, + transformation, + training=True, + ): + return bounding_boxes + + def transform_segmentation_masks( + self, segmentation_masks, transformation, training=True + ): + return segmentation_masks + + def get_config(self): + config = super().get_config() + config.update({"value_range": self.value_range}) + return config + + def compute_output_shape(self, input_shape): + return input_shape diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/base_image_preprocessing_layer.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/base_image_preprocessing_layer.py new file mode 100644 index 0000000000000000000000000000000000000000..ba1ba3e24b7e542653c078e7792c71b495ea19b6 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/base_image_preprocessing_layer.py @@ -0,0 +1,385 @@ +import math + +from keras.src.backend import config as backend_config +from keras.src.layers.preprocessing.image_preprocessing.bounding_boxes.validation import ( # noqa: E501 + densify_bounding_boxes, +) +from keras.src.layers.preprocessing.tf_data_layer import TFDataLayer + + +class BaseImagePreprocessingLayer(TFDataLayer): + _USE_BASE_FACTOR = True + _FACTOR_BOUNDS = (-1, 1) + + def __init__( + self, factor=None, bounding_box_format=None, data_format=None, **kwargs + ): + super().__init__(**kwargs) + self.bounding_box_format = bounding_box_format + self.data_format = backend_config.standardize_data_format(data_format) + if self._USE_BASE_FACTOR: + factor = factor or 0.0 + self._set_factor(factor) + elif factor is not None: + raise ValueError( + f"Layer {self.__class__.__name__} does not take " + f"a `factor` argument. Received: factor={factor}" + ) + + def _set_factor(self, factor): + error_msg = ( + "The `factor` argument should be a number " + "(or a list of two numbers) " + "in the range " + f"[{self._FACTOR_BOUNDS[0]}, {self._FACTOR_BOUNDS[1]}]. " + f"Received: factor={factor}" + ) + if isinstance(factor, (tuple, list)): + if len(factor) != 2: + raise ValueError(error_msg) + if ( + factor[0] > self._FACTOR_BOUNDS[1] + or factor[1] < self._FACTOR_BOUNDS[0] + ): + raise ValueError(error_msg) + lower, upper = sorted(factor) + elif isinstance(factor, (int, float)): + if ( + factor < self._FACTOR_BOUNDS[0] + or factor > self._FACTOR_BOUNDS[1] + ): + raise ValueError(error_msg) + factor = abs(factor) + lower, upper = [max(-factor, self._FACTOR_BOUNDS[0]), factor] + else: + raise ValueError(error_msg) + self.factor = lower, upper + + def get_random_transformation(self, data, training=True, seed=None): + return None + + def transform_images(self, images, transformation, training=True): + raise NotImplementedError() + + def transform_labels(self, labels, transformation, training=True): + raise NotImplementedError() + + def transform_bounding_boxes( + self, + bounding_boxes, + transformation, + training=True, + ): + raise NotImplementedError() + + def transform_segmentation_masks( + self, segmentation_masks, transformation, training=True + ): + raise NotImplementedError() + + def transform_single_image(self, image, transformation, training=True): + images = self.backend.numpy.expand_dims(image, axis=0) + outputs = self.transform_images( + images, transformation=transformation, training=training + ) + return self.backend.numpy.squeeze(outputs, axis=0) + + def transform_single_label(self, label, transformation, training=True): + labels = self.backend.numpy.expand_dims(label, axis=0) + outputs = self.transform_labels( + labels, transformation=transformation, training=training + ) + return self.backend.numpy.squeeze(outputs, axis=0) + + def transform_single_bounding_box( + self, + bounding_box, + transformation, + training=True, + ): + bounding_boxes = self._format_single_input_bounding_box(bounding_box) + outputs = self.transform_bounding_boxes( + bounding_boxes, + transformation=transformation, + training=training, + ) + bounding_box = self._format_single_output_bounding_box(outputs) + return bounding_box + + def transform_single_segmentation_mask( + self, segmentation_mask, transformation, training=True + ): + segmentation_masks = self.backend.numpy.expand_dims( + segmentation_mask, axis=0 + ) + outputs = self.transform_segmentation_masks( + segmentation_masks, transformation=transformation, training=training + ) + return self.backend.numpy.squeeze(outputs, axis=0) + + def _is_batched(self, maybe_image_batch): + shape = self.backend.core.shape(maybe_image_batch) + if len(shape) == 3: + return False + if len(shape) == 4: + return True + raise ValueError( + "Expected image tensor to have rank 3 (single image) " + f"or 4 (batch of images). Received: data.shape={shape}" + ) + + def call(self, data, training=True): + transformation = self.get_random_transformation(data, training=training) + if isinstance(data, dict): + is_batched = self._is_batched(data["images"]) + if is_batched: + data["images"] = self.transform_images( + self.backend.convert_to_tensor(data["images"]), + transformation=transformation, + training=training, + ) + else: + data["images"] = self.transform_single_image( + self.backend.convert_to_tensor(data["images"]), + transformation=transformation, + training=training, + ) + if "bounding_boxes" in data: + if not self.bounding_box_format: + raise ValueError( + "You passed an input with a 'bounding_boxes' key, " + "but you didn't specify a bounding box format. " + "Pass a `bounding_box_format` argument to your " + f"{self.__class__.__name__} layer, e.g. " + "`bounding_box_format='xyxy'`." + ) + bounding_boxes = densify_bounding_boxes( + data["bounding_boxes"], + is_batched=is_batched, + backend=self.backend, + ) + + if is_batched: + data["bounding_boxes"] = self.transform_bounding_boxes( + bounding_boxes, + transformation=transformation, + training=training, + ) + else: + data["bounding_boxes"] = self.transform_single_bounding_box( + bounding_boxes, + transformation=transformation, + training=training, + ) + if "labels" in data: + if is_batched: + data["labels"] = self.transform_labels( + self.backend.convert_to_tensor(data["labels"]), + transformation=transformation, + training=training, + ) + else: + data["labels"] = self.transform_single_label( + self.backend.convert_to_tensor(data["labels"]), + transformation=transformation, + training=training, + ) + if "segmentation_masks" in data: + if is_batched: + data["segmentation_masks"] = ( + self.transform_segmentation_masks( + data["segmentation_masks"], + transformation=transformation, + training=training, + ) + ) + else: + data["segmentation_masks"] = ( + self.transform_single_segmentation_mask( + data["segmentation_masks"], + transformation=transformation, + training=training, + ) + ) + return data + + # `data` is just images. + if self._is_batched(data): + return self.transform_images( + self.backend.convert_to_tensor(data), + transformation=transformation, + training=training, + ) + return self.transform_single_image( + self.backend.convert_to_tensor(data), + transformation=transformation, + training=training, + ) + + def _format_single_input_bounding_box(self, bounding_box): + for key in bounding_box: + if key == "labels": + bounding_box[key] = self.backend.numpy.expand_dims( + bounding_box[key], axis=0 + ) + if key == "boxes": + bounding_box[key] = self.backend.numpy.expand_dims( + bounding_box[key], axis=0 + ) + + return bounding_box + + def _format_single_output_bounding_box(self, bounding_boxes): + for key in bounding_boxes: + if key == "labels": + bounding_boxes[key] = self.backend.numpy.squeeze( + bounding_boxes[key], axis=0 + ) + if key == "boxes": + bounding_boxes[key] = self.backend.numpy.squeeze( + bounding_boxes[key], axis=0 + ) + + return bounding_boxes + + def get_config(self): + config = super().get_config() + if self.bounding_box_format is not None: + config.update( + { + "bounding_box_format": self.bounding_box_format, + } + ) + return config + + def _transform_value_range( + self, images, original_range, target_range, dtype="float32" + ): + """Convert input values from `original_range` to `target_range`. + + This function is intended to be used in preprocessing layers that + rely upon color values. This allows us to assume internally that + the input tensor is always in the range `(0, 255)`. + + Args: + images: the set of images to transform to the target range. + original_range: the value range to transform from. + target_range: the value range to transform to. + dtype: the dtype to compute the conversion with, + defaults to "float32". + + Returns: + a new Tensor with values in the target range. + + Example: + + ```python + original_range = [0, 1] + target_range = [0, 255] + images = layer.preprocessing.transform_value_range( + images, + original_range, + target_range + ) + images = ops.minimum(images + 10, 255) + images = layer.preprocessing.transform_value_range( + images, + target_range, + original_range + ) + ``` + """ + if ( + original_range[0] == target_range[0] + and original_range[1] == target_range[1] + ): + return images + + images = self.backend.cast(images, dtype=dtype) + original_min_value, original_max_value = self._unwrap_value_range( + original_range, dtype=dtype + ) + target_min_value, target_max_value = self._unwrap_value_range( + target_range, dtype=dtype + ) + + # images in the [0, 1] scale + images = (images - original_min_value) / ( + original_max_value - original_min_value + ) + + scale_factor = target_max_value - target_min_value + return (images * scale_factor) + target_min_value + + def _unwrap_value_range(self, value_range, dtype="float32"): + min_value, max_value = value_range + min_value = self.backend.cast(min_value, dtype=dtype) + max_value = self.backend.cast(max_value, dtype=dtype) + return min_value, max_value + + def _compute_affine_matrix( + self, + center_x, + center_y, + angle, + translate_x, + translate_y, + scale, + shear_x, + shear_y, + height, + width, + ): + """ + # Scaling Shear Rotation + # [sx 0 0] [1 shx 0] [cos(θ) -sin(θ) 0] + # M = [0 sy 0] * [shy 1 0] * [sin(θ) cos(θ) 0] + # [0 0 1] [0 0 1] [0 0 1] + + # a0 = sx * (cos(θ) + shx * sin(θ)) + # a1 = sx * (-sin(θ) + shx * cos(θ)) + # a2 = tx + cx - cx * a0 - cy * a1 + # b0 = sy * (shy * cos(θ) + sin(θ)) + # b1 = sy * (shy * -sin(θ) + cos(θ)) + # b2 = ty + cy - cx * b0 - cy * b1 + """ + ops = self.backend + + degree_to_radian_factor = ops.convert_to_tensor(math.pi / 180.0) + + angle = angle * degree_to_radian_factor + shear_x = shear_x * degree_to_radian_factor + shear_y = shear_y * degree_to_radian_factor + + batch_size = ops.shape(angle)[0] + dtype = angle.dtype + width = ops.cast(width, dtype) + height = ops.cast(height, dtype) + cx = center_x * (width - 1) + cy = center_y * (height - 1) + + cos_theta = ops.numpy.cos(angle) + sin_theta = ops.numpy.sin(angle) + shear_x = ops.numpy.tan(shear_x) + shear_y = ops.numpy.tan(shear_y) + + a0 = scale * (cos_theta + shear_x * sin_theta) + a1 = scale * (-sin_theta + shear_x * cos_theta) + a2 = translate_x + cx - cx * a0 - cy * a1 + b0 = scale * (shear_y * cos_theta + sin_theta) + b1 = scale * (shear_y * -sin_theta + cos_theta) + b2 = translate_y + cy - cx * b0 - cy * b1 + affine_matrix = ops.numpy.concatenate( + [ + a0[:, None], + a1[:, None], + a2[:, None], + b0[:, None], + b1[:, None], + b2[:, None], + ops.numpy.zeros((batch_size, 2)), + ], + axis=1, + ) + + return affine_matrix diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/center_crop.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/center_crop.py new file mode 100644 index 0000000000000000000000000000000000000000..1d39914c18e513f03fb9db43c6dd494d4cf747e0 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/center_crop.py @@ -0,0 +1,273 @@ +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.image_preprocessing.base_image_preprocessing_layer import ( # noqa: E501 + BaseImagePreprocessingLayer, +) +from keras.src.layers.preprocessing.image_preprocessing.bounding_boxes.converters import ( # noqa: E501 + clip_to_image_size, +) +from keras.src.layers.preprocessing.image_preprocessing.bounding_boxes.converters import ( # noqa: E501 + convert_format, +) +from keras.src.utils import image_utils + + +@keras_export("keras.layers.CenterCrop") +class CenterCrop(BaseImagePreprocessingLayer): + """A preprocessing layer which crops images. + + This layers crops the central portion of the images to a target size. If an + image is smaller than the target size, it will be resized and cropped + so as to return the largest possible window in the image that matches + the target aspect ratio. + + Input pixel values can be of any range (e.g. `[0., 1.)` or `[0, 255]`). + + Input shape: + 3D (unbatched) or 4D (batched) tensor with shape: + `(..., height, width, channels)`, in `"channels_last"` format, + or `(..., channels, height, width)`, in `"channels_first"` format. + + Output shape: + 3D (unbatched) or 4D (batched) tensor with shape: + `(..., target_height, target_width, channels)`, + or `(..., channels, target_height, target_width)`, + in `"channels_first"` format. + + If the input height/width is even and the target height/width is odd (or + inversely), the input image is left-padded by 1 pixel. + + **Note:** This layer is safe to use inside a `tf.data` pipeline + (independently of which backend you're using). + + Args: + height: Integer, the height of the output shape. + width: Integer, the width of the output shape. + data_format: string, either `"channels_last"` or `"channels_first"`. + The ordering of the dimensions in the inputs. `"channels_last"` + corresponds to inputs with shape `(batch, height, width, channels)` + while `"channels_first"` corresponds to inputs with shape + `(batch, channels, height, width)`. It defaults to the + `image_data_format` value found in your Keras config file at + `~/.keras/keras.json`. If you never set it, then it will be + `"channels_last"`. + """ + + _USE_BASE_FACTOR = False + + def __init__(self, height, width, data_format=None, **kwargs): + super().__init__(data_format=data_format, **kwargs) + self.height = height + self.width = width + + def get_random_transformation(self, data, training=True, seed=None): + if isinstance(data, dict): + images = data["images"] + else: + images = data + shape = self.backend.core.shape(images) + return {"input_shape": shape} + + def transform_labels(self, labels, transformation, training=True): + return labels + + def transform_bounding_boxes( + self, bounding_boxes, transformation, training=True + ): + def _get_height_width(input_shape): + if self.data_format == "channels_first": + input_height = input_shape[-2] + input_width = input_shape[-1] + else: + input_height = input_shape[-3] + input_width = input_shape[-2] + return input_height, input_width + + def _get_clipped_bbox(bounding_boxes, h_end, h_start, w_end, w_start): + bboxes = bounding_boxes["boxes"] + x1, y1, x2, y2 = self.backend.numpy.split(bboxes, 4, axis=-1) + x1 = self.backend.numpy.clip(x1, w_start, w_end) - w_start + y1 = self.backend.numpy.clip(y1, h_start, h_end) - h_start + x2 = self.backend.numpy.clip(x2, w_start, w_end) - w_start + y2 = self.backend.numpy.clip(y2, h_start, h_end) - h_start + bounding_boxes["boxes"] = self.backend.numpy.concatenate( + [x1, y1, x2, y2], axis=-1 + ) + return bounding_boxes + + input_shape = transformation["input_shape"] + + init_height, init_width = _get_height_width(input_shape) + + bounding_boxes = convert_format( + bounding_boxes, + source=self.bounding_box_format, + target="xyxy", + height=init_height, + width=init_width, + ) + + h_diff = init_height - self.height + w_diff = init_width - self.width + + if h_diff >= 0 and w_diff >= 0: + h_start = int(h_diff / 2) + w_start = int(w_diff / 2) + + h_end = h_start + self.height + w_end = w_start + self.width + + bounding_boxes = _get_clipped_bbox( + bounding_boxes, h_end, h_start, w_end, w_start + ) + else: + width = init_width + height = init_height + target_height = self.height + target_width = self.width + + crop_height = int(float(width * target_height) / target_width) + crop_height = max(min(height, crop_height), 1) + crop_width = int(float(height * target_width) / target_height) + crop_width = max(min(width, crop_width), 1) + crop_box_hstart = int(float(height - crop_height) / 2) + crop_box_wstart = int(float(width - crop_width) / 2) + + h_start = crop_box_hstart + w_start = crop_box_wstart + + h_end = crop_box_hstart + crop_height + w_end = crop_box_wstart + crop_width + bounding_boxes = _get_clipped_bbox( + bounding_boxes, h_end, h_start, w_end, w_start + ) + + bounding_boxes = convert_format( + bounding_boxes, + source="xyxy", + target="rel_xyxy", + height=crop_height, + width=crop_width, + ) + + bounding_boxes = convert_format( + bounding_boxes, + source="rel_xyxy", + target="xyxy", + height=self.height, + width=self.width, + ) + + bounding_boxes = clip_to_image_size( + bounding_boxes=bounding_boxes, + height=self.height, + width=self.width, + bounding_box_format="xyxy", + ) + + bounding_boxes = convert_format( + bounding_boxes, + source="xyxy", + target=self.bounding_box_format, + height=self.height, + width=self.width, + ) + + return bounding_boxes + + def transform_segmentation_masks( + self, segmentation_masks, transformation, training=True + ): + return self.transform_images( + segmentation_masks, transformation, training=training + ) + + def transform_images(self, images, transformation=None, training=True): + inputs = self.backend.cast(images, self.compute_dtype) + if self.data_format == "channels_first": + init_height = inputs.shape[-2] + init_width = inputs.shape[-1] + else: + init_height = inputs.shape[-3] + init_width = inputs.shape[-2] + + if init_height is None or init_width is None: + # Dynamic size case. TODO. + raise ValueError( + "At this time, CenterCrop can only " + "process images with a static spatial " + f"shape. Received: inputs.shape={inputs.shape}" + ) + + h_diff = init_height - self.height + w_diff = init_width - self.width + + h_start = int(h_diff / 2) + w_start = int(w_diff / 2) + + if h_diff >= 0 and w_diff >= 0: + if len(inputs.shape) == 4: + if self.data_format == "channels_first": + return inputs[ + :, + :, + h_start : h_start + self.height, + w_start : w_start + self.width, + ] + return inputs[ + :, + h_start : h_start + self.height, + w_start : w_start + self.width, + :, + ] + elif len(inputs.shape) == 3: + if self.data_format == "channels_first": + return inputs[ + :, + h_start : h_start + self.height, + w_start : w_start + self.width, + ] + return inputs[ + h_start : h_start + self.height, + w_start : w_start + self.width, + :, + ] + return image_utils.smart_resize( + inputs, + [self.height, self.width], + data_format=self.data_format, + backend_module=self.backend, + ) + + def compute_output_shape(self, input_shape): + input_shape = list(input_shape) + if isinstance(input_shape[0], (list, tuple)) or len( + input_shape + ) not in (3, 4): + raise ValueError( + "`input_shape` must be a non-nested tuple or list " + "of rank-1 with size 3 (unbatched) or 4 (batched). " + ) + if len(input_shape) == 4: + if self.data_format == "channels_last": + input_shape[1] = self.height + input_shape[2] = self.width + else: + input_shape[2] = self.height + input_shape[3] = self.width + else: + if self.data_format == "channels_last": + input_shape[0] = self.height + input_shape[1] = self.width + else: + input_shape[1] = self.height + input_shape[2] = self.width + return tuple(input_shape) + + def get_config(self): + base_config = super().get_config() + config = { + "height": self.height, + "width": self.width, + "data_format": self.data_format, + } + return {**base_config, **config} diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/equalization.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/equalization.py new file mode 100644 index 0000000000000000000000000000000000000000..555713bf8542268346ca852de48962e6ed4c8d7a --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/equalization.py @@ -0,0 +1,224 @@ +from keras.src import backend +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.image_preprocessing.base_image_preprocessing_layer import ( # noqa: E501 + BaseImagePreprocessingLayer, +) + + +@keras_export("keras.layers.Equalization") +class Equalization(BaseImagePreprocessingLayer): + """Preprocessing layer for histogram equalization on image channels. + + Histogram equalization is a technique to adjust image intensities to + enhance contrast by effectively spreading out the most frequent + intensity values. This layer applies equalization on a channel-wise + basis, which can improve the visibility of details in images. + + This layer works with both grayscale and color images, performing + equalization independently on each color channel. At inference time, + the equalization is consistently applied. + + **Note:** This layer is safe to use inside a `tf.data` pipeline + (independently of which backend you're using). + + Args: + value_range: Optional list/tuple of 2 floats specifying the lower + and upper limits of the input data values. Defaults to `[0, 255]`. + If the input image has been scaled, use the appropriate range + (e.g., `[0.0, 1.0]`). The equalization will be scaled to this + range, and output values will be clipped accordingly. + bins: Integer specifying the number of histogram bins to use for + equalization. Defaults to 256, which is suitable for 8-bit images. + Larger values can provide more granular intensity redistribution. + + Input shape: + 3D (unbatched) or 4D (batched) tensor with shape: + `(..., height, width, channels)`, in `"channels_last"` format, + or `(..., channels, height, width)`, in `"channels_first"` format. + + Output shape: + 3D (unbatched) or 4D (batched) tensor with shape: + `(..., target_height, target_width, channels)`, + or `(..., channels, target_height, target_width)`, + in `"channels_first"` format. + + Example: + + ```python + # Create an equalization layer for standard 8-bit images + equalizer = keras.layers.Equalization() + + # An image with uneven intensity distribution + image = [...] # your input image + + # Apply histogram equalization + equalized_image = equalizer(image) + + # For images with custom value range + custom_equalizer = keras.layers.Equalization( + value_range=[0.0, 1.0], # for normalized images + bins=128 # fewer bins for more subtle equalization + ) + custom_equalized = custom_equalizer(normalized_image) + ``` + """ + + def __init__( + self, value_range=(0, 255), bins=256, data_format=None, **kwargs + ): + super().__init__(**kwargs) + self.bins = bins + self._set_value_range(value_range) + self.data_format = backend.standardize_data_format(data_format) + + def _set_value_range(self, value_range): + if not isinstance(value_range, (tuple, list)): + raise ValueError( + self._VALUE_RANGE_VALIDATION_ERROR + + f"Received: value_range={value_range}" + ) + if len(value_range) != 2: + raise ValueError( + self._VALUE_RANGE_VALIDATION_ERROR + + f"Received: value_range={value_range}" + ) + self.value_range = sorted(value_range) + + def _custom_histogram_fixed_width(self, values, value_range, nbins): + values = self.backend.cast(values, "float32") + value_min, value_max = value_range + value_min = self.backend.cast(value_min, "float32") + value_max = self.backend.cast(value_max, "float32") + + scaled = (values - value_min) * (nbins - 1) / (value_max - value_min) + indices = self.backend.cast(scaled, "int32") + indices = self.backend.numpy.clip(indices, 0, nbins - 1) + flat_indices = self.backend.numpy.reshape(indices, [-1]) + + if backend.backend() == "jax": + # for JAX bincount is never jittable because of output shape + histogram = self.backend.numpy.zeros(nbins, dtype="int32") + for i in range(nbins): + matches = self.backend.cast( + self.backend.numpy.equal(flat_indices, i), "int32" + ) + bin_count = self.backend.numpy.sum(matches) + one_hot = self.backend.cast( + self.backend.numpy.arange(nbins) == i, "int32" + ) + histogram = histogram + (bin_count * one_hot) + return histogram + else: + # TensorFlow/PyTorch/NumPy implementation using bincount + return self.backend.numpy.bincount( + flat_indices, + minlength=nbins, + ) + + def _scale_values(self, values, source_range, target_range): + source_min, source_max = source_range + target_min, target_max = target_range + scale = (target_max - target_min) / (source_max - source_min) + offset = target_min - source_min * scale + return values * scale + offset + + def _equalize_channel(self, channel, value_range): + if value_range != (0, 255): + channel = self._scale_values(channel, value_range, (0, 255)) + + hist = self._custom_histogram_fixed_width( + channel, value_range=(0, 255), nbins=self.bins + ) + + nonzero_bins = self.backend.numpy.count_nonzero(hist) + equalized = self.backend.numpy.where( + nonzero_bins <= 1, channel, self._apply_equalization(channel, hist) + ) + + if value_range != (0, 255): + equalized = self._scale_values(equalized, (0, 255), value_range) + + return equalized + + def _apply_equalization(self, channel, hist): + cdf = self.backend.numpy.cumsum(hist) + + if self.backend.name == "jax": + mask = cdf > 0 + first_nonzero_idx = self.backend.numpy.argmax(mask) + cdf_min = self.backend.numpy.take(cdf, first_nonzero_idx) + else: + cdf_min = self.backend.numpy.take( + cdf, self.backend.numpy.nonzero(cdf)[0][0] + ) + + denominator = cdf[-1] - cdf_min + denominator = self.backend.numpy.where( + denominator == 0, + self.backend.numpy.ones_like(1, dtype=denominator.dtype), + denominator, + ) + + lookup_table = ((cdf - cdf_min) * 255) / denominator + lookup_table = self.backend.numpy.clip( + self.backend.numpy.round(lookup_table), 0, 255 + ) + + scaled_channel = (channel / 255.0) * (self.bins - 1) + indices = self.backend.cast( + self.backend.numpy.clip(scaled_channel, 0, self.bins - 1), "int32" + ) + return self.backend.numpy.take(lookup_table, indices) + + def transform_images(self, images, transformation, training=True): + if training: + images = self.backend.cast(images, self.compute_dtype) + + if self.data_format == "channels_first": + channels = [] + for i in range(self.backend.core.shape(images)[-3]): + channel = images[..., i, :, :] + equalized = self._equalize_channel( + channel, self.value_range + ) + channels.append(equalized) + equalized_images = self.backend.numpy.stack(channels, axis=-3) + else: + channels = [] + for i in range(self.backend.core.shape(images)[-1]): + channel = images[..., i] + equalized = self._equalize_channel( + channel, self.value_range + ) + channels.append(equalized) + equalized_images = self.backend.numpy.stack(channels, axis=-1) + + return self.backend.cast(equalized_images, self.compute_dtype) + return images + + def compute_output_shape(self, input_shape): + return input_shape + + def compute_output_spec(self, inputs, **kwargs): + return inputs + + def transform_bounding_boxes( + self, + bounding_boxes, + transformation, + training=True, + ): + return bounding_boxes + + def transform_labels(self, labels, transformation, training=True): + return labels + + def transform_segmentation_masks( + self, segmentation_masks, transformation, training=True + ): + return segmentation_masks + + def get_config(self): + config = super().get_config() + config.update({"bins": self.bins, "value_range": self.value_range}) + return config diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/max_num_bounding_box.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/max_num_bounding_box.py new file mode 100644 index 0000000000000000000000000000000000000000..dff68d0f3d01fc9faa9fd26bc0c9104033e33355 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/max_num_bounding_box.py @@ -0,0 +1,89 @@ +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.image_preprocessing.base_image_preprocessing_layer import ( # noqa: E501 + BaseImagePreprocessingLayer, +) + + +@keras_export("keras.layers.MaxNumBoundingBoxes") +class MaxNumBoundingBoxes(BaseImagePreprocessingLayer): + """Ensure the maximum number of bounding boxes. + + Args: + max_number: Desired output number of bounding boxes. + padding_value: The padding value of the `boxes` and `labels` in + `bounding_boxes`. Defaults to `-1`. + """ + + def __init__(self, max_number, fill_value=-1, **kwargs): + super().__init__(**kwargs) + self.max_number = int(max_number) + self.fill_value = int(fill_value) + + def transform_images(self, images, transformation=None, training=True): + return images + + def transform_labels(self, labels, transformation=None, training=True): + return labels + + def transform_bounding_boxes( + self, bounding_boxes, transformation, training=True + ): + ops = self.backend + boxes = bounding_boxes["boxes"] + labels = bounding_boxes["labels"] + boxes_shape = ops.shape(boxes) + batch_size = boxes_shape[0] + num_boxes = boxes_shape[1] + + # Get pad size + pad_size = ops.numpy.maximum( + ops.numpy.subtract(self.max_number, num_boxes), 0 + ) + boxes = boxes[:, : self.max_number, ...] + boxes = ops.numpy.pad( + boxes, + [[0, 0], [0, pad_size], [0, 0]], + constant_values=self.fill_value, + ) + labels = labels[:, : self.max_number] + labels = ops.numpy.pad( + labels, [[0, 0], [0, pad_size]], constant_values=self.fill_value + ) + + # Ensure shape + boxes = ops.numpy.reshape(boxes, [batch_size, self.max_number, 4]) + labels = ops.numpy.reshape(labels, [batch_size, self.max_number]) + + bounding_boxes = bounding_boxes.copy() + bounding_boxes["boxes"] = boxes + bounding_boxes["labels"] = labels + return bounding_boxes + + def transform_segmentation_masks( + self, segmentation_masks, transformation=None, training=True + ): + return self.transform_images(segmentation_masks) + + def compute_output_shape(self, input_shape): + if isinstance(input_shape, dict) and "bounding_boxes" in input_shape: + input_keys = set(input_shape["bounding_boxes"].keys()) + extra_keys = input_keys - set(("boxes", "labels")) + if extra_keys: + raise KeyError( + "There are unsupported keys in `bounding_boxes`: " + f"{list(extra_keys)}. " + "Only `boxes` and `labels` are supported." + ) + + boxes_shape = list(input_shape["bounding_boxes"]["boxes"]) + boxes_shape[1] = self.max_number + labels_shape = list(input_shape["bounding_boxes"]["labels"]) + labels_shape[1] = self.max_number + input_shape["bounding_boxes"]["boxes"] = boxes_shape + input_shape["bounding_boxes"]["labels"] = labels_shape + return input_shape + + def get_config(self): + config = super().get_config() + config.update({"max_number": self.max_number}) + return config diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/mix_up.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/mix_up.py new file mode 100644 index 0000000000000000000000000000000000000000..e5c49a4209f61c8657248de1226b21b33814a936 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/mix_up.py @@ -0,0 +1,180 @@ +from keras.src import ops +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.image_preprocessing.base_image_preprocessing_layer import ( # noqa: E501 + BaseImagePreprocessingLayer, +) +from keras.src.random import SeedGenerator +from keras.src.utils import backend_utils + + +@keras_export("keras.layers.MixUp") +class MixUp(BaseImagePreprocessingLayer): + """MixUp implements the MixUp data augmentation technique. + + Args: + alpha: Float between 0 and 1. Controls the blending strength. + Smaller values mean less mixing, while larger values allow + for more blending between images. Defaults to 0.2, + recommended for ImageNet1k classification. + seed: Integer. Used to create a random seed. + + References: + - [MixUp paper](https://arxiv.org/abs/1710.09412). + - [MixUp for Object Detection paper](https://arxiv.org/pdf/1902.04103). + + Example: + ```python + (images, labels), _ = keras.datasets.cifar10.load_data() + images, labels = images[:8], labels[:8] + labels = keras.ops.cast(keras.ops.one_hot(labels.flatten(), 10), "float32") + mix_up = keras.layers.MixUp(alpha=0.2) + output = mix_up({"images": images, "labels": labels}) + ``` + """ + + def __init__(self, alpha=0.2, data_format=None, seed=None, **kwargs): + super().__init__(data_format=data_format, **kwargs) + self.alpha = alpha + self.seed = seed + self.generator = SeedGenerator(seed) + + def get_random_transformation(self, data, training=True, seed=None): + if isinstance(data, dict): + images = data["images"] + else: + images = data + + images_shape = self.backend.shape(images) + + if len(images_shape) == 3: + batch_size = 1 + else: + batch_size = self.backend.shape(images)[0] + + if seed is None: + seed = self._get_seed_generator(self.backend._backend) + + permutation_order = self.backend.random.shuffle( + self.backend.numpy.arange(0, batch_size, dtype="int64"), + seed=seed, + ) + + mix_weight = self.backend.random.beta( + (batch_size,), self.alpha, self.alpha, seed=seed + ) + return { + "mix_weight": mix_weight, + "permutation_order": permutation_order, + } + + def transform_images(self, images, transformation=None, training=True): + def _mix_up_input(images, transformation): + images = self.backend.cast(images, self.compute_dtype) + mix_weight = transformation["mix_weight"] + permutation_order = transformation["permutation_order"] + mix_weight = self.backend.cast( + self.backend.numpy.reshape(mix_weight, [-1, 1, 1, 1]), + dtype=self.compute_dtype, + ) + mix_up_images = self.backend.cast( + self.backend.numpy.take(images, permutation_order, axis=0), + dtype=self.compute_dtype, + ) + images = mix_weight * images + (1.0 - mix_weight) * mix_up_images + return images + + if training: + images = _mix_up_input(images, transformation) + return images + + def transform_labels(self, labels, transformation, training=True): + def _mix_up_labels(labels, transformation): + mix_weight = transformation["mix_weight"] + permutation_order = transformation["permutation_order"] + labels_for_mix_up = self.backend.numpy.take( + labels, permutation_order, axis=0 + ) + mix_weight = self.backend.numpy.reshape(mix_weight, [-1, 1]) + labels = ( + mix_weight * labels + (1.0 - mix_weight) * labels_for_mix_up + ) + return labels + + if training: + labels = _mix_up_labels(labels, transformation) + return labels + + def transform_bounding_boxes( + self, + bounding_boxes, + transformation, + training=True, + ): + def _mix_up_bounding_boxes(bounding_boxes, transformation): + if backend_utils.in_tf_graph(): + self.backend.set_backend("tensorflow") + + permutation_order = transformation["permutation_order"] + # Make sure we are on cpu for torch tensors. + permutation_order = ops.convert_to_numpy(permutation_order) + + boxes, labels = bounding_boxes["boxes"], bounding_boxes["labels"] + boxes_for_mix_up = self.backend.numpy.take( + boxes, permutation_order, axis=0 + ) + + labels_for_mix_up = self.backend.numpy.take( + labels, permutation_order, axis=0 + ) + boxes = self.backend.numpy.concatenate( + [boxes, boxes_for_mix_up], axis=1 + ) + + labels = self.backend.numpy.concatenate( + [labels, labels_for_mix_up], axis=0 + ) + + self.backend.reset() + + return {"boxes": boxes, "labels": labels} + + if training: + bounding_boxes = _mix_up_bounding_boxes( + bounding_boxes, transformation + ) + return bounding_boxes + + def transform_segmentation_masks( + self, segmentation_masks, transformation, training=True + ): + def _mix_up_segmentation_masks(segmentation_masks, transformation): + mix_weight = transformation["mix_weight"] + # Make sure we are on cpu for torch tensors. + mix_weight = ops.convert_to_numpy(mix_weight) + permutation_order = transformation["permutation_order"] + mix_weight = self.backend.numpy.reshape(mix_weight, [-1, 1, 1, 1]) + segmentation_masks_for_mix_up = self.backend.numpy.take( + segmentation_masks, permutation_order + ) + segmentation_masks = ( + mix_weight * segmentation_masks + + (1.0 - mix_weight) * segmentation_masks_for_mix_up + ) + return segmentation_masks + + if training: + segmentation_masks = _mix_up_segmentation_masks( + segmentation_masks, transformation + ) + return segmentation_masks + + def compute_output_shape(self, input_shape): + return input_shape + + def get_config(self): + config = { + "alpha": self.alpha, + "seed": self.seed, + } + base_config = super().get_config() + return {**base_config, **config} diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/rand_augment.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/rand_augment.py new file mode 100644 index 0000000000000000000000000000000000000000..f7d2794ce26c9238cadfdca1d2d1124e905946b6 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/rand_augment.py @@ -0,0 +1,235 @@ +import random + +import keras.src.layers as layers +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.image_preprocessing.base_image_preprocessing_layer import ( # noqa: E501 + BaseImagePreprocessingLayer, +) +from keras.src.random import SeedGenerator +from keras.src.utils import backend_utils + + +@keras_export("keras.layers.RandAugment") +class RandAugment(BaseImagePreprocessingLayer): + """RandAugment performs the Rand Augment operation on input images. + + This layer can be thought of as an all-in-one image augmentation layer. The + policy implemented by this layer has been benchmarked extensively and is + effective on a wide variety of datasets. + + References: + - [RandAugment](https://arxiv.org/abs/1909.13719) + + Args: + value_range: The range of values the input image can take. + Default is `(0, 255)`. Typically, this would be `(0, 1)` + for normalized images or `(0, 255)` for raw images. + num_ops: The number of augmentation operations to apply sequentially + to each image. Default is 2. + factor: The strength of the augmentation as a normalized value + between 0 and 1. Default is 0.5. + interpolation: The interpolation method to use for resizing operations. + Options include `nearest`, `bilinear`. Default is `bilinear`. + seed: Integer. Used to create a random seed. + + """ + + _USE_BASE_FACTOR = False + _FACTOR_BOUNDS = (0, 1) + + _AUGMENT_LAYERS = [ + "random_shear", + "random_translation", + "random_rotation", + "random_brightness", + "random_color_degeneration", + "random_contrast", + "random_sharpness", + "random_posterization", + "solarization", + "auto_contrast", + "equalization", + ] + + def __init__( + self, + value_range=(0, 255), + num_ops=2, + factor=0.5, + interpolation="bilinear", + seed=None, + data_format=None, + **kwargs, + ): + super().__init__(data_format=data_format, **kwargs) + + self.value_range = value_range + self.num_ops = num_ops + self._set_factor(factor) + self.interpolation = interpolation + self.seed = seed + self.generator = SeedGenerator(seed) + + self.random_shear = layers.RandomShear( + x_factor=self.factor, + y_factor=self.factor, + interpolation=interpolation, + seed=self.seed, + data_format=data_format, + **kwargs, + ) + + self.random_translation = layers.RandomTranslation( + height_factor=self.factor, + width_factor=self.factor, + interpolation=interpolation, + seed=self.seed, + data_format=data_format, + **kwargs, + ) + + self.random_rotation = layers.RandomRotation( + factor=self.factor, + interpolation=interpolation, + seed=self.seed, + data_format=data_format, + **kwargs, + ) + + self.random_brightness = layers.RandomBrightness( + factor=self.factor, + value_range=self.value_range, + seed=self.seed, + data_format=data_format, + **kwargs, + ) + + self.random_color_degeneration = layers.RandomColorDegeneration( + factor=self.factor, + value_range=self.value_range, + seed=self.seed, + data_format=data_format, + **kwargs, + ) + + self.random_contrast = layers.RandomContrast( + factor=self.factor, + value_range=self.value_range, + seed=self.seed, + data_format=data_format, + **kwargs, + ) + + self.random_sharpness = layers.RandomSharpness( + factor=self.factor, + value_range=self.value_range, + seed=self.seed, + data_format=data_format, + **kwargs, + ) + + self.solarization = layers.Solarization( + addition_factor=self.factor, + threshold_factor=self.factor, + value_range=self.value_range, + seed=self.seed, + data_format=data_format, + **kwargs, + ) + + self.random_posterization = layers.RandomPosterization( + factor=max(1, int(8 * self.factor[1])), + value_range=self.value_range, + seed=self.seed, + data_format=data_format, + **kwargs, + ) + + self.auto_contrast = layers.AutoContrast( + value_range=self.value_range, data_format=data_format, **kwargs + ) + + self.equalization = layers.Equalization( + value_range=self.value_range, data_format=data_format, **kwargs + ) + + def build(self, input_shape): + for layer_name in self._AUGMENT_LAYERS: + augmentation_layer = getattr(self, layer_name) + augmentation_layer.build(input_shape) + + def get_random_transformation(self, data, training=True, seed=None): + if not training: + return None + + if backend_utils.in_tf_graph(): + self.backend.set_backend("tensorflow") + + for layer_name in self._AUGMENT_LAYERS: + augmentation_layer = getattr(self, layer_name) + augmentation_layer.backend.set_backend("tensorflow") + + transformation = {} + random.shuffle(self._AUGMENT_LAYERS) + for layer_name in self._AUGMENT_LAYERS[: self.num_ops]: + augmentation_layer = getattr(self, layer_name) + transformation[layer_name] = ( + augmentation_layer.get_random_transformation( + data, + training=training, + seed=self._get_seed_generator(self.backend._backend), + ) + ) + + return transformation + + def transform_images(self, images, transformation, training=True): + if training: + images = self.backend.cast(images, self.compute_dtype) + + for layer_name, transformation_value in transformation.items(): + augmentation_layer = getattr(self, layer_name) + images = augmentation_layer.transform_images( + images, transformation_value + ) + + images = self.backend.cast(images, self.compute_dtype) + return images + + def transform_labels(self, labels, transformation, training=True): + return labels + + def transform_bounding_boxes( + self, + bounding_boxes, + transformation, + training=True, + ): + if training: + for layer_name, transformation_value in transformation.items(): + augmentation_layer = getattr(self, layer_name) + bounding_boxes = augmentation_layer.transform_bounding_boxes( + bounding_boxes, transformation_value, training=training + ) + return bounding_boxes + + def transform_segmentation_masks( + self, segmentation_masks, transformation, training=True + ): + return self.transform_images( + segmentation_masks, transformation, training=training + ) + + def compute_output_shape(self, input_shape): + return input_shape + + def get_config(self): + config = { + "value_range": self.value_range, + "num_ops": self.num_ops, + "factor": self.factor, + "interpolation": self.interpolation, + "seed": self.seed, + } + base_config = super().get_config() + return {**base_config, **config} diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_brightness.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_brightness.py new file mode 100644 index 0000000000000000000000000000000000000000..74aa3106b4241075a87ba80691af744fe3f7bd2e --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_brightness.py @@ -0,0 +1,158 @@ +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.image_preprocessing.base_image_preprocessing_layer import ( # noqa: E501 + BaseImagePreprocessingLayer, +) +from keras.src.random.seed_generator import SeedGenerator + + +@keras_export("keras.layers.RandomBrightness") +class RandomBrightness(BaseImagePreprocessingLayer): + """A preprocessing layer which randomly adjusts brightness during training. + + This layer will randomly increase/reduce the brightness for the input RGB + images. At inference time, the output will be identical to the input. + Call the layer with `training=True` to adjust the brightness of the input. + + **Note:** This layer is safe to use inside a `tf.data` pipeline + (independently of which backend you're using). + + Args: + factor: Float or a list/tuple of 2 floats between -1.0 and 1.0. The + factor is used to determine the lower bound and upper bound of the + brightness adjustment. A float value will be chosen randomly between + the limits. When -1.0 is chosen, the output image will be black, and + when 1.0 is chosen, the image will be fully white. + When only one float is provided, eg, 0.2, + then -0.2 will be used for lower bound and 0.2 + will be used for upper bound. + value_range: Optional list/tuple of 2 floats + for the lower and upper limit + of the values of the input data. + To make no change, use `[0.0, 1.0]`, e.g., if the image input + has been scaled before this layer. Defaults to `[0.0, 255.0]`. + The brightness adjustment will be scaled to this range, and the + output values will be clipped to this range. + seed: optional integer, for fixed RNG behavior. + + Inputs: 3D (HWC) or 4D (NHWC) tensor, with float or int dtype. Input pixel + values can be of any range (e.g. `[0., 1.)` or `[0, 255]`) + + Output: 3D (HWC) or 4D (NHWC) tensor with brightness adjusted based on the + `factor`. By default, the layer will output floats. + The output value will be clipped to the range `[0, 255]`, + the valid range of RGB colors, and + rescaled based on the `value_range` if needed. + + Example: + + ```python + random_bright = keras.layers.RandomBrightness(factor=0.2) + + # An image with shape [2, 2, 3] + image = [[[1, 2, 3], [4 ,5 ,6]], [[7, 8, 9], [10, 11, 12]]] + + # Assume we randomly select the factor to be 0.1, then it will apply + # 0.1 * 255 to all the channel + output = random_bright(image, training=True) + + # output will be int64 with 25.5 added to each channel and round down. + >>> array([[[26.5, 27.5, 28.5] + [29.5, 30.5, 31.5]] + [[32.5, 33.5, 34.5] + [35.5, 36.5, 37.5]]], + shape=(2, 2, 3), dtype=int64) + ``` + """ + + _VALUE_RANGE_VALIDATION_ERROR = ( + "The `value_range` argument should be a list of two numbers. " + ) + + def __init__(self, factor, value_range=(0, 255), seed=None, **kwargs): + super().__init__(factor=factor, **kwargs) + self.seed = seed + self.generator = SeedGenerator(seed) + self._set_value_range(value_range) + + def _set_value_range(self, value_range): + if not isinstance(value_range, (tuple, list)): + raise ValueError( + self._VALUE_RANGE_VALIDATION_ERROR + + f"Received: value_range={value_range}" + ) + if len(value_range) != 2: + raise ValueError( + self._VALUE_RANGE_VALIDATION_ERROR + + f"Received: value_range={value_range}" + ) + self.value_range = sorted(value_range) + + def get_random_transformation(self, data, training=True, seed=None): + if isinstance(data, dict): + images = data["images"] + else: + images = data + images_shape = self.backend.shape(images) + rank = len(images_shape) + if rank == 3: + rgb_delta_shape = (1, 1, 1) + elif rank == 4: + # Keep only the batch dim. This will ensure to have same adjustment + # with in one image, but different across the images. + rgb_delta_shape = [images_shape[0], 1, 1, 1] + else: + raise ValueError( + "Expected the input image to be rank 3 or 4. Received " + f"inputs.shape={images_shape}" + ) + if not training: + return {"rgb_delta": self.backend.numpy.zeros(rgb_delta_shape)} + + if seed is None: + seed = self._get_seed_generator(self.backend._backend) + rgb_delta = self.backend.random.uniform( + minval=self.factor[0], + maxval=self.factor[1], + shape=rgb_delta_shape, + seed=seed, + ) + rgb_delta = rgb_delta * (self.value_range[1] - self.value_range[0]) + return {"rgb_delta": rgb_delta} + + def transform_images(self, images, transformation, training=True): + if training: + rgb_delta = transformation["rgb_delta"] + rgb_delta = self.backend.cast(rgb_delta, images.dtype) + images += rgb_delta + return self.backend.numpy.clip( + images, self.value_range[0], self.value_range[1] + ) + return images + + def transform_labels(self, labels, transformation, training=True): + return labels + + def transform_bounding_boxes( + self, + bounding_boxes, + transformation, + training=True, + ): + return bounding_boxes + + def transform_segmentation_masks( + self, segmentation_masks, transformation, training=True + ): + return segmentation_masks + + def compute_output_shape(self, input_shape): + return input_shape + + def get_config(self): + config = { + "factor": self.factor, + "value_range": self.value_range, + "seed": self.seed, + } + base_config = super().get_config() + return {**base_config, **config} diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_color_degeneration.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_color_degeneration.py new file mode 100644 index 0000000000000000000000000000000000000000..c3255c846ebf91d73da6f141564435d1be395a67 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_color_degeneration.py @@ -0,0 +1,132 @@ +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.image_preprocessing.base_image_preprocessing_layer import ( # noqa: E501 + BaseImagePreprocessingLayer, +) +from keras.src.random import SeedGenerator + + +@keras_export("keras.layers.RandomColorDegeneration") +class RandomColorDegeneration(BaseImagePreprocessingLayer): + """Randomly performs the color degeneration operation on given images. + + The sharpness operation first converts an image to gray scale, then back to + color. It then takes a weighted average between original image and the + degenerated image. This makes colors appear more dull. + + Args: + factor: A tuple of two floats or a single float. + `factor` controls the extent to which the + image sharpness is impacted. `factor=0.0` makes this layer perform a + no-op operation, while a value of 1.0 uses the degenerated result + entirely. Values between 0 and 1 result in linear interpolation + between the original image and the sharpened image. + Values should be between `0.0` and `1.0`. If a tuple is used, a + `factor` is sampled between the two values for every image + augmented. If a single float is used, a value between `0.0` and the + passed float is sampled. In order to ensure the value is always the + same, please pass a tuple with two identical floats: `(0.5, 0.5)`. + seed: Integer. Used to create a random seed. + """ + + _VALUE_RANGE_VALIDATION_ERROR = ( + "The `value_range` argument should be a list of two numbers. " + ) + + def __init__( + self, + factor, + value_range=(0, 255), + data_format=None, + seed=None, + **kwargs, + ): + super().__init__(data_format=data_format, **kwargs) + self._set_factor(factor) + self._set_value_range(value_range) + self.seed = seed + self.generator = SeedGenerator(seed) + + def _set_value_range(self, value_range): + if not isinstance(value_range, (tuple, list)): + raise ValueError( + self._VALUE_RANGE_VALIDATION_ERROR + + f"Received: value_range={value_range}" + ) + if len(value_range) != 2: + raise ValueError( + self._VALUE_RANGE_VALIDATION_ERROR + + f"Received: value_range={value_range}" + ) + self.value_range = sorted(value_range) + + def get_random_transformation(self, data, training=True, seed=None): + if isinstance(data, dict): + images = data["images"] + else: + images = data + images_shape = self.backend.shape(images) + rank = len(images_shape) + if rank == 3: + batch_size = 1 + elif rank == 4: + batch_size = images_shape[0] + else: + raise ValueError( + "Expected the input image to be rank 3 or 4. Received: " + f"inputs.shape={images_shape}" + ) + + if seed is None: + seed = self._get_seed_generator(self.backend._backend) + + factor = self.backend.random.uniform( + (batch_size, 1, 1, 1), + minval=self.factor[0], + maxval=self.factor[1], + seed=seed, + ) + factor = factor + return {"factor": factor} + + def transform_images(self, images, transformation=None, training=True): + if training: + images = self.backend.cast(images, self.compute_dtype) + factor = self.backend.cast( + transformation["factor"], self.compute_dtype + ) + degenerates = self.backend.image.rgb_to_grayscale( + images, data_format=self.data_format + ) + images = images + factor * (degenerates - images) + images = self.backend.numpy.clip( + images, self.value_range[0], self.value_range[1] + ) + images = self.backend.cast(images, self.compute_dtype) + return images + + def transform_labels(self, labels, transformation, training=True): + return labels + + def transform_segmentation_masks( + self, segmentation_masks, transformation, training=True + ): + return segmentation_masks + + def transform_bounding_boxes( + self, bounding_boxes, transformation, training=True + ): + return bounding_boxes + + def get_config(self): + config = super().get_config() + config.update( + { + "factor": self.factor, + "value_range": self.value_range, + "seed": self.seed, + } + ) + return config + + def compute_output_shape(self, input_shape): + return input_shape diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_color_jitter.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_color_jitter.py new file mode 100644 index 0000000000000000000000000000000000000000..e2f1962579d88a79e0e9d9643eca39f4865f0db4 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_color_jitter.py @@ -0,0 +1,210 @@ +import keras.src.layers.preprocessing.image_preprocessing.random_brightness as random_brightness # noqa: E501 +import keras.src.layers.preprocessing.image_preprocessing.random_contrast as random_contrast # noqa: E501 +import keras.src.layers.preprocessing.image_preprocessing.random_hue as random_hue # noqa: E501 +import keras.src.layers.preprocessing.image_preprocessing.random_saturation as random_saturation # noqa: E501 +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.image_preprocessing.base_image_preprocessing_layer import ( # noqa: E501 + BaseImagePreprocessingLayer, +) +from keras.src.random.seed_generator import SeedGenerator +from keras.src.utils import backend_utils + + +@keras_export("keras.layers.RandomColorJitter") +class RandomColorJitter(BaseImagePreprocessingLayer): + """RandomColorJitter class randomly apply brightness, contrast, saturation + and hue image processing operation sequentially and randomly on the + input. + + Args: + value_range: the range of values the incoming images will have. + Represented as a two number tuple written [low, high]. + This is typically either `[0, 1]` or `[0, 255]` depending + on how your preprocessing pipeline is set up. + brightness_factor: Float or a list/tuple of 2 floats between -1.0 + and 1.0. The factor is used to determine the lower bound and + upper bound of the brightness adjustment. A float value will + be chosen randomly between the limits. When -1.0 is chosen, + the output image will be black, and when 1.0 is chosen, the + image will be fully white. When only one float is provided, + eg, 0.2, then -0.2 will be used for lower bound and 0.2 will + be used for upper bound. + contrast_factor: a positive float represented as fraction of value, + or a tuple of size 2 representing lower and upper bound. When + represented as a single float, lower = upper. The contrast + factor will be randomly picked between `[1.0 - lower, 1.0 + + upper]`. For any pixel x in the channel, the output will be + `(x - mean) * factor + mean` where `mean` is the mean value + of the channel. + saturation_factor: A tuple of two floats or a single float. `factor` + controls the extent to which the image saturation is impacted. + `factor=0.5` makes this layer perform a no-op operation. + `factor=0.0` makes the image fully grayscale. `factor=1.0` + makes the image fully saturated. Values should be between + `0.0` and `1.0`. If a tuple is used, a `factor` is sampled + between the two values for every image augmented. If a single + float is used, a value between `0.0` and the passed float is + sampled. To ensure the value is always the same, pass a tuple + with two identical floats: `(0.5, 0.5)`. + hue_factor: A single float or a tuple of two floats. `factor` + controls the extent to which the image hue is impacted. + `factor=0.0` makes this layer perform a no-op operation, + while a value of `1.0` performs the most aggressive contrast + adjustment available. If a tuple is used, a `factor` is + sampled between the two values for every image augmented. + If a single float is used, a value between `0.0` and the + passed float is sampled. In order to ensure the value is + always the same, please pass a tuple with two identical + floats: `(0.5, 0.5)`. + seed: Integer. Used to create a random seed. + """ + + def __init__( + self, + value_range=(0, 255), + brightness_factor=None, + contrast_factor=None, + saturation_factor=None, + hue_factor=None, + seed=None, + data_format=None, + **kwargs, + ): + super().__init__(data_format=data_format, **kwargs) + self.value_range = value_range + self.brightness_factor = brightness_factor + self.contrast_factor = contrast_factor + self.saturation_factor = saturation_factor + self.hue_factor = hue_factor + self.seed = seed + self.generator = SeedGenerator(seed) + + self.random_brightness = None + self.random_contrast = None + self.random_saturation = None + self.random_hue = None + + if self.brightness_factor is not None: + self.random_brightness = random_brightness.RandomBrightness( + factor=self.brightness_factor, + value_range=self.value_range, + seed=self.seed, + ) + + if self.contrast_factor is not None: + self.random_contrast = random_contrast.RandomContrast( + factor=self.contrast_factor, + value_range=self.value_range, + seed=self.seed, + ) + + if self.saturation_factor is not None: + self.random_saturation = random_saturation.RandomSaturation( + factor=self.saturation_factor, + value_range=self.value_range, + seed=self.seed, + ) + + if self.hue_factor is not None: + self.random_hue = random_hue.RandomHue( + factor=self.hue_factor, + value_range=self.value_range, + seed=self.seed, + ) + + def build(self, input_shape): + if self.brightness_factor is not None: + self.random_brightness.build(input_shape) + + if self.contrast_factor is not None: + self.random_contrast.build(input_shape) + + if self.saturation_factor is not None: + self.random_saturation.build(input_shape) + + if self.hue_factor is not None: + self.random_hue.build(input_shape) + + def transform_images(self, images, transformation, training=True): + if training: + if backend_utils.in_tf_graph(): + self.backend.set_backend("tensorflow") + images = self.backend.cast(images, self.compute_dtype) + if self.brightness_factor is not None: + if backend_utils.in_tf_graph(): + self.random_brightness.backend.set_backend("tensorflow") + transformation = ( + self.random_brightness.get_random_transformation( + images, + seed=self._get_seed_generator(self.backend._backend), + ) + ) + images = self.random_brightness.transform_images( + images, transformation + ) + if self.contrast_factor is not None: + if backend_utils.in_tf_graph(): + self.random_contrast.backend.set_backend("tensorflow") + transformation = self.random_contrast.get_random_transformation( + images, seed=self._get_seed_generator(self.backend._backend) + ) + transformation["contrast_factor"] = self.backend.cast( + transformation["contrast_factor"], dtype=self.compute_dtype + ) + images = self.random_contrast.transform_images( + images, transformation + ) + if self.saturation_factor is not None: + if backend_utils.in_tf_graph(): + self.random_saturation.backend.set_backend("tensorflow") + transformation = ( + self.random_saturation.get_random_transformation( + images, + seed=self._get_seed_generator(self.backend._backend), + ) + ) + images = self.random_saturation.transform_images( + images, transformation + ) + if self.hue_factor is not None: + if backend_utils.in_tf_graph(): + self.random_hue.backend.set_backend("tensorflow") + transformation = self.random_hue.get_random_transformation( + images, seed=self._get_seed_generator(self.backend._backend) + ) + images = self.random_hue.transform_images( + images, transformation + ) + images = self.backend.cast(images, self.compute_dtype) + return images + + def transform_labels(self, labels, transformation, training=True): + return labels + + def transform_bounding_boxes( + self, + bounding_boxes, + transformation, + training=True, + ): + return bounding_boxes + + def transform_segmentation_masks( + self, segmentation_masks, transformation, training=True + ): + return segmentation_masks + + def compute_output_shape(self, input_shape): + return input_shape + + def get_config(self): + config = { + "value_range": self.value_range, + "brightness_factor": self.brightness_factor, + "contrast_factor": self.contrast_factor, + "saturation_factor": self.saturation_factor, + "hue_factor": self.hue_factor, + "seed": self.seed, + } + base_config = super().get_config() + return {**base_config, **config} diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_contrast.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_contrast.py new file mode 100644 index 0000000000000000000000000000000000000000..ab793b266e099c9faae3331c9fedf27fbcb7a670 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_contrast.py @@ -0,0 +1,149 @@ +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.image_preprocessing.base_image_preprocessing_layer import ( # noqa: E501 + BaseImagePreprocessingLayer, +) +from keras.src.random.seed_generator import SeedGenerator + + +@keras_export("keras.layers.RandomContrast") +class RandomContrast(BaseImagePreprocessingLayer): + """A preprocessing layer which randomly adjusts contrast during training. + + This layer will randomly adjust the contrast of an image or images + by a random factor. Contrast is adjusted independently + for each channel of each image during training. + + For each channel, this layer computes the mean of the image pixels in the + channel and then adjusts each component `x` of each pixel to + `(x - mean) * contrast_factor + mean`. + + Input pixel values can be of any range (e.g. `[0., 1.)` or `[0, 255]`) and + in integer or floating point dtype. + By default, the layer will output floats. + + **Note:** This layer is safe to use inside a `tf.data` pipeline + (independently of which backend you're using). + + Input shape: + 3D (unbatched) or 4D (batched) tensor with shape: + `(..., height, width, channels)`, in `"channels_last"` format. + + Output shape: + 3D (unbatched) or 4D (batched) tensor with shape: + `(..., height, width, channels)`, in `"channels_last"` format. + + Args: + factor: a positive float represented as fraction of value, or a tuple of + size 2 representing lower and upper bound. + When represented as a single float, lower = upper. + The contrast factor will be randomly picked between + `[1.0 - lower, 1.0 + upper]`. For any pixel x in the channel, + the output will be `(x - mean) * factor + mean` + where `mean` is the mean value of the channel. + value_range: the range of values the incoming images will have. + Represented as a two-number tuple written `[low, high]`. This is + typically either `[0, 1]` or `[0, 255]` depending on how your + preprocessing pipeline is set up. + seed: Integer. Used to create a random seed. + """ + + _FACTOR_BOUNDS = (0, 1) + + def __init__(self, factor, value_range=(0, 255), seed=None, **kwargs): + super().__init__(**kwargs) + self._set_factor(factor) + self.value_range = value_range + self.seed = seed + self.generator = SeedGenerator(seed) + + def get_random_transformation(self, data, training=True, seed=None): + if isinstance(data, dict): + images = data["images"] + else: + images = data + images_shape = self.backend.shape(images) + rank = len(images_shape) + if rank == 3: + factor_shape = (1, 1, 1) + elif rank == 4: + # Keep only the batch dim. This will ensure to have same adjustment + # with in one image, but different across the images. + factor_shape = [images_shape[0], 1, 1, 1] + else: + raise ValueError( + "Expected the input image to be rank 3 or 4. Received " + f"inputs.shape={images_shape}" + ) + + if not training: + return {"contrast_factor": self.backend.numpy.zeros(factor_shape)} + + if seed is None: + seed = self._get_seed_generator(self.backend._backend) + + factor = self.backend.random.uniform( + shape=factor_shape, + minval=1.0 - self.factor[0], + maxval=1.0 + self.factor[1], + seed=seed, + dtype=self.compute_dtype, + ) + return {"contrast_factor": factor} + + def transform_images(self, images, transformation, training=True): + if training: + constrast_factor = transformation["contrast_factor"] + outputs = self._adjust_constrast(images, constrast_factor) + outputs = self.backend.numpy.clip( + outputs, self.value_range[0], self.value_range[1] + ) + self.backend.numpy.reshape(outputs, self.backend.shape(images)) + return outputs + return images + + def transform_labels(self, labels, transformation, training=True): + return labels + + def transform_bounding_boxes( + self, + bounding_boxes, + transformation, + training=True, + ): + return bounding_boxes + + def transform_segmentation_masks( + self, segmentation_masks, transformation, training=True + ): + return segmentation_masks + + def _adjust_constrast(self, inputs, contrast_factor): + if self.data_format == "channels_first": + height_axis = -2 + width_axis = -1 + else: + height_axis = -3 + width_axis = -2 + # reduce mean on height + inp_mean = self.backend.numpy.mean( + inputs, axis=height_axis, keepdims=True + ) + # reduce mean on width + inp_mean = self.backend.numpy.mean( + inp_mean, axis=width_axis, keepdims=True + ) + + outputs = (inputs - inp_mean) * contrast_factor + inp_mean + return outputs + + def compute_output_shape(self, input_shape): + return input_shape + + def get_config(self): + config = { + "factor": self.factor, + "value_range": self.value_range, + "seed": self.seed, + } + base_config = super().get_config() + return {**base_config, **config} diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_crop.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_crop.py new file mode 100644 index 0000000000000000000000000000000000000000..f67469089f99f540f357b0fc09c441eeb3df118e --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_crop.py @@ -0,0 +1,276 @@ +from keras.src import backend +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.image_preprocessing.base_image_preprocessing_layer import ( # noqa: E501 + BaseImagePreprocessingLayer, +) +from keras.src.layers.preprocessing.image_preprocessing.bounding_boxes.converters import ( # noqa: E501 + convert_format, +) +from keras.src.layers.preprocessing.image_preprocessing.bounding_boxes.validation import ( # noqa: E501 + densify_bounding_boxes, +) +from keras.src.random.seed_generator import SeedGenerator + + +@keras_export("keras.layers.RandomCrop") +class RandomCrop(BaseImagePreprocessingLayer): + """A preprocessing layer which randomly crops images during training. + + During training, this layer will randomly choose a location to crop images + down to a target size. The layer will crop all the images in the same batch + to the same cropping location. + + At inference time, and during training if an input image is smaller than the + target size, the input will be resized and cropped so as to return the + largest possible window in the image that matches the target aspect ratio. + If you need to apply random cropping at inference time, set `training` to + True when calling the layer. + + Input pixel values can be of any range (e.g. `[0., 1.)` or `[0, 255]`) and + of integer or floating point dtype. By default, the layer will output + floats. + + **Note:** This layer is safe to use inside a `tf.data` pipeline + (independently of which backend you're using). + + Input shape: + 3D (unbatched) or 4D (batched) tensor with shape: + `(..., height, width, channels)`, in `"channels_last"` format. + + Output shape: + 3D (unbatched) or 4D (batched) tensor with shape: + `(..., target_height, target_width, channels)`. + + Args: + height: Integer, the height of the output shape. + width: Integer, the width of the output shape. + seed: Integer. Used to create a random seed. + **kwargs: Base layer keyword arguments, such as + `name` and `dtype`. + """ + + def __init__( + self, height, width, seed=None, data_format=None, name=None, **kwargs + ): + super().__init__(name=name, **kwargs) + self.height = height + self.width = width + self.seed = ( + seed if seed is not None else backend.random.make_default_seed() + ) + self.generator = SeedGenerator(seed) + self.data_format = backend.standardize_data_format(data_format) + + if self.data_format == "channels_first": + self.height_axis = -2 + self.width_axis = -1 + elif self.data_format == "channels_last": + self.height_axis = -3 + self.width_axis = -2 + + self.supports_masking = False + self.supports_jit = False + self._convert_input_args = False + self._allow_non_tensor_positional_args = True + + def get_random_transformation(self, data, training=True, seed=None): + if seed is None: + seed = self._get_seed_generator(self.backend._backend) + + if isinstance(data, dict): + input_shape = self.backend.shape(data["images"]) + else: + input_shape = self.backend.shape(data) + + input_height, input_width = ( + input_shape[self.height_axis], + input_shape[self.width_axis], + ) + if input_height is None or input_width is None: + raise ValueError( + "RandomCrop requires the input to have a fully defined " + f"height and width. Received: images.shape={input_shape}" + ) + + if training and input_height > self.height and input_width > self.width: + h_start = self.backend.cast( + self.backend.random.uniform( + (), + 0, + maxval=float(input_height - self.height + 1), + seed=seed, + ), + "int32", + ) + w_start = self.backend.cast( + self.backend.random.uniform( + (), + 0, + maxval=float(input_width - self.width + 1), + seed=seed, + ), + "int32", + ) + else: + crop_height = int(float(input_width * self.height) / self.width) + crop_height = max(min(input_height, crop_height), 1) + crop_width = int(float(input_height * self.width) / self.height) + crop_width = max(min(input_width, crop_width), 1) + h_start = int(float(input_height - crop_height) / 2) + w_start = int(float(input_width - crop_width) / 2) + + return h_start, w_start + + def transform_images(self, images, transformation, training=True): + if training: + images = self.backend.cast(images, self.compute_dtype) + crop_box_hstart, crop_box_wstart = transformation + crop_height = self.height + crop_width = self.width + + if self.data_format == "channels_last": + if len(images.shape) == 4: + images = images[ + :, + crop_box_hstart : crop_box_hstart + crop_height, + crop_box_wstart : crop_box_wstart + crop_width, + :, + ] + else: + images = images[ + crop_box_hstart : crop_box_hstart + crop_height, + crop_box_wstart : crop_box_wstart + crop_width, + :, + ] + else: + if len(images.shape) == 4: + images = images[ + :, + :, + crop_box_hstart : crop_box_hstart + crop_height, + crop_box_wstart : crop_box_wstart + crop_width, + ] + else: + images = images[ + :, + crop_box_hstart : crop_box_hstart + crop_height, + crop_box_wstart : crop_box_wstart + crop_width, + ] + + shape = self.backend.shape(images) + new_height = shape[self.height_axis] + new_width = shape[self.width_axis] + if ( + not isinstance(new_height, int) + or not isinstance(new_width, int) + or new_height != self.height + or new_width != self.width + ): + # Resize images if size mismatch or + # if size mismatch cannot be determined + # (in the case of a TF dynamic shape). + images = self.backend.image.resize( + images, + size=(self.height, self.width), + data_format=self.data_format, + ) + # Resize may have upcasted the outputs + images = self.backend.cast(images, self.compute_dtype) + return images + + def transform_labels(self, labels, transformation, training=True): + return labels + + def transform_bounding_boxes( + self, + bounding_boxes, + transformation, + training=True, + ): + """ + bounding_boxes = { + "boxes": (batch, num_boxes, 4), # left-top-right-bottom (xyxy) + "labels": (batch, num_boxes, num_classes), + } + or + bounding_boxes = { + "boxes": (num_boxes, 4), + "labels": (num_boxes, num_classes), + } + """ + + if training: + h_start, w_start = transformation + if not self.backend.is_tensor(bounding_boxes["boxes"]): + bounding_boxes = densify_bounding_boxes( + bounding_boxes, backend=self.backend + ) + boxes = bounding_boxes["boxes"] + # Convert to a standard xyxy as operations are done xyxy by default. + boxes = convert_format( + boxes=boxes, + source=self.bounding_box_format, + target="xyxy", + height=self.height, + width=self.width, + ) + h_start = self.backend.cast(h_start, boxes.dtype) + w_start = self.backend.cast(w_start, boxes.dtype) + if len(self.backend.shape(boxes)) == 3: + boxes = self.backend.numpy.stack( + [ + self.backend.numpy.maximum(boxes[:, :, 0] - h_start, 0), + self.backend.numpy.maximum(boxes[:, :, 1] - w_start, 0), + self.backend.numpy.maximum(boxes[:, :, 2] - h_start, 0), + self.backend.numpy.maximum(boxes[:, :, 3] - w_start, 0), + ], + axis=-1, + ) + else: + boxes = self.backend.numpy.stack( + [ + self.backend.numpy.maximum(boxes[:, 0] - h_start, 0), + self.backend.numpy.maximum(boxes[:, 1] - w_start, 0), + self.backend.numpy.maximum(boxes[:, 2] - h_start, 0), + self.backend.numpy.maximum(boxes[:, 3] - w_start, 0), + ], + axis=-1, + ) + + # Convert to user defined bounding box format + boxes = convert_format( + boxes=boxes, + source="xyxy", + target=self.bounding_box_format, + height=self.height, + width=self.width, + ) + + return { + "boxes": boxes, + "labels": bounding_boxes["labels"], + } + return bounding_boxes + + def transform_segmentation_masks( + self, segmentation_masks, transformation, training=True + ): + return self.transform_images(segmentation_masks, transformation) + + def compute_output_shape(self, input_shape, *args, **kwargs): + input_shape = list(input_shape) + input_shape[self.height_axis] = self.height + input_shape[self.width_axis] = self.width + return tuple(input_shape) + + def get_config(self): + config = super().get_config() + config.update( + { + "height": self.height, + "width": self.width, + "seed": self.seed, + "data_format": self.data_format, + } + ) + return config diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_flip.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_flip.py new file mode 100644 index 0000000000000000000000000000000000000000..83deff5fc05f0a7e6a997fd952f3535ccdd9b4ee --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_flip.py @@ -0,0 +1,236 @@ +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.image_preprocessing.base_image_preprocessing_layer import ( # noqa: E501 + BaseImagePreprocessingLayer, +) +from keras.src.layers.preprocessing.image_preprocessing.bounding_boxes.converters import ( # noqa: E501 + clip_to_image_size, +) +from keras.src.layers.preprocessing.image_preprocessing.bounding_boxes.converters import ( # noqa: E501 + convert_format, +) +from keras.src.random.seed_generator import SeedGenerator +from keras.src.utils import backend_utils + +HORIZONTAL = "horizontal" +VERTICAL = "vertical" +HORIZONTAL_AND_VERTICAL = "horizontal_and_vertical" + + +@keras_export("keras.layers.RandomFlip") +class RandomFlip(BaseImagePreprocessingLayer): + """A preprocessing layer which randomly flips images during training. + + This layer will flip the images horizontally and or vertically based on the + `mode` attribute. During inference time, the output will be identical to + input. Call the layer with `training=True` to flip the input. + Input pixel values can be of any range (e.g. `[0., 1.)` or `[0, 255]`) and + of integer or floating point dtype. + By default, the layer will output floats. + + **Note:** This layer is safe to use inside a `tf.data` pipeline + (independently of which backend you're using). + + Input shape: + 3D (unbatched) or 4D (batched) tensor with shape: + `(..., height, width, channels)`, in `"channels_last"` format. + + Output shape: + 3D (unbatched) or 4D (batched) tensor with shape: + `(..., height, width, channels)`, in `"channels_last"` format. + + Args: + mode: String indicating which flip mode to use. Can be `"horizontal"`, + `"vertical"`, or `"horizontal_and_vertical"`. `"horizontal"` is a + left-right flip and `"vertical"` is a top-bottom flip. Defaults to + `"horizontal_and_vertical"` + seed: Integer. Used to create a random seed. + **kwargs: Base layer keyword arguments, such as + `name` and `dtype`. + """ + + _USE_BASE_FACTOR = False + + def __init__( + self, + mode=HORIZONTAL_AND_VERTICAL, + seed=None, + data_format=None, + **kwargs, + ): + super().__init__(data_format=data_format, **kwargs) + self.seed = seed + self.generator = SeedGenerator(seed) + self.mode = mode + self._convert_input_args = False + self._allow_non_tensor_positional_args = True + + def get_random_transformation(self, data, training=True, seed=None): + if not training: + return None + + if isinstance(data, dict): + images = data["images"] + else: + images = data + shape = self.backend.core.shape(images) + if len(shape) == 3: + flips_shape = (1, 1, 1) + else: + flips_shape = (shape[0], 1, 1, 1) + + if seed is None: + seed = self._get_seed_generator(self.backend._backend) + + flips = self.backend.numpy.less_equal( + self.backend.random.uniform(shape=flips_shape, seed=seed), 0.5 + ) + return {"flips": flips, "input_shape": shape} + + def transform_images(self, images, transformation, training=True): + images = self.backend.cast(images, self.compute_dtype) + if training: + return self._flip_inputs(images, transformation) + return images + + def transform_labels(self, labels, transformation, training=True): + return labels + + def transform_bounding_boxes( + self, + bounding_boxes, + transformation, + training=True, + ): + def _flip_boxes_horizontal(boxes): + x1, x2, x3, x4 = self.backend.numpy.split(boxes, 4, axis=-1) + outputs = self.backend.numpy.concatenate( + [1 - x3, x2, 1 - x1, x4], axis=-1 + ) + return outputs + + def _flip_boxes_vertical(boxes): + x1, x2, x3, x4 = self.backend.numpy.split(boxes, 4, axis=-1) + outputs = self.backend.numpy.concatenate( + [x1, 1 - x4, x3, 1 - x2], axis=-1 + ) + return outputs + + def _transform_xyxy(boxes, box_flips): + bboxes = boxes["boxes"] + if self.mode in {HORIZONTAL, HORIZONTAL_AND_VERTICAL}: + bboxes = self.backend.numpy.where( + box_flips, + _flip_boxes_horizontal(bboxes), + bboxes, + ) + if self.mode in {VERTICAL, HORIZONTAL_AND_VERTICAL}: + bboxes = self.backend.numpy.where( + box_flips, + _flip_boxes_vertical(bboxes), + bboxes, + ) + return bboxes + + if training: + if backend_utils.in_tf_graph(): + self.backend.set_backend("tensorflow") + + flips = self.backend.numpy.squeeze(transformation["flips"], axis=-1) + + if self.data_format == "channels_first": + height_axis = -2 + width_axis = -1 + else: + height_axis = -3 + width_axis = -2 + + input_height, input_width = ( + transformation["input_shape"][height_axis], + transformation["input_shape"][width_axis], + ) + + bounding_boxes = convert_format( + bounding_boxes, + source=self.bounding_box_format, + target="rel_xyxy", + height=input_height, + width=input_width, + ) + + bounding_boxes["boxes"] = _transform_xyxy(bounding_boxes, flips) + + bounding_boxes = clip_to_image_size( + bounding_boxes=bounding_boxes, + height=input_height, + width=input_width, + bounding_box_format="xyxy", + ) + + bounding_boxes = convert_format( + bounding_boxes, + source="rel_xyxy", + target=self.bounding_box_format, + height=input_height, + width=input_width, + ) + + self.backend.reset() + + return bounding_boxes + + def transform_segmentation_masks( + self, segmentation_masks, transformation, training=True + ): + return self.transform_images( + segmentation_masks, transformation, training=training + ) + + def _flip_inputs(self, inputs, transformation): + if transformation is None: + return inputs + + flips = transformation["flips"] + inputs_shape = self.backend.shape(inputs) + unbatched = len(inputs_shape) == 3 + if unbatched: + inputs = self.backend.numpy.expand_dims(inputs, axis=0) + + flipped_outputs = inputs + if self.data_format == "channels_last": + horizontal_axis = -2 + vertical_axis = -3 + else: + horizontal_axis = -1 + vertical_axis = -2 + + if self.mode == HORIZONTAL or self.mode == HORIZONTAL_AND_VERTICAL: + flipped_outputs = self.backend.numpy.where( + flips, + self.backend.numpy.flip(flipped_outputs, axis=horizontal_axis), + flipped_outputs, + ) + if self.mode == VERTICAL or self.mode == HORIZONTAL_AND_VERTICAL: + flipped_outputs = self.backend.numpy.where( + flips, + self.backend.numpy.flip(flipped_outputs, axis=vertical_axis), + flipped_outputs, + ) + if unbatched: + flipped_outputs = self.backend.numpy.squeeze( + flipped_outputs, axis=0 + ) + return flipped_outputs + + def compute_output_shape(self, input_shape): + return input_shape + + def get_config(self): + config = super().get_config() + config.update( + { + "seed": self.seed, + "mode": self.mode, + "data_format": self.data_format, + } + ) + return config diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_grayscale.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_grayscale.py new file mode 100644 index 0000000000000000000000000000000000000000..e03a626852ed34e28bcdcaf508dd3238521cc370 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_grayscale.py @@ -0,0 +1,110 @@ +from keras.src import backend +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.image_preprocessing.base_image_preprocessing_layer import ( # noqa: E501 + BaseImagePreprocessingLayer, +) + + +@keras_export("keras.layers.RandomGrayscale") +class RandomGrayscale(BaseImagePreprocessingLayer): + """Preprocessing layer for random conversion of RGB images to grayscale. + + This layer randomly converts input images to grayscale with a specified + factor. When applied, it maintains the original number of channels + but sets all channels to the same grayscale value. This can be useful + for data augmentation and training models to be robust to color + variations. + + The conversion preserves the perceived luminance of the original color + image using standard RGB to grayscale conversion coefficients. Images + that are not selected for conversion remain unchanged. + + **Note:** This layer is safe to use inside a `tf.data` pipeline + (independently of which backend you're using). + + Args: + factor: Float between 0 and 1, specifying the factor of + converting each image to grayscale. Defaults to 0.5. A value of + 1.0 means all images will be converted, while 0.0 means no images + will be converted. + data_format: String, one of `"channels_last"` (default) or + `"channels_first"`. The ordering of the dimensions in the inputs. + `"channels_last"` corresponds to inputs with shape + `(batch, height, width, channels)` while `"channels_first"` + corresponds to inputs with shape + `(batch, channels, height, width)`. + + Input shape: + 3D (unbatched) or 4D (batched) tensor with shape: + `(..., height, width, channels)`, in `"channels_last"` format, + or `(..., channels, height, width)`, in `"channels_first"` format. + + Output shape: + Same as input shape. The output maintains the same number of channels + as the input, even for grayscale-converted images where all channels + will have the same value. + """ + + def __init__(self, factor=0.5, data_format=None, seed=None, **kwargs): + super().__init__(**kwargs) + if factor < 0 or factor > 1: + raise ValueError( + "`factor` should be between 0 and 1. " + f"Received: factor={factor}" + ) + self.factor = factor + self.data_format = backend.standardize_data_format(data_format) + self.seed = seed + self.generator = self.backend.random.SeedGenerator(seed) + + def get_random_transformation(self, images, training=True, seed=None): + if seed is None: + seed = self._get_seed_generator(self.backend._backend) + random_values = self.backend.random.uniform( + shape=(self.backend.core.shape(images)[0],), + minval=0, + maxval=1, + seed=seed, + ) + should_apply = self.backend.numpy.expand_dims( + random_values < self.factor, axis=[1, 2, 3] + ) + return should_apply + + def transform_images(self, images, transformation, training=True): + if training: + should_apply = ( + transformation + if transformation is not None + else self.get_random_transformation(images) + ) + + grayscale_images = self.backend.image.rgb_to_grayscale( + images, data_format=self.data_format + ) + return self.backend.numpy.where( + should_apply, grayscale_images, images + ) + return images + + def compute_output_shape(self, input_shape): + return input_shape + + def compute_output_spec(self, inputs, **kwargs): + return inputs + + def transform_bounding_boxes(self, bounding_boxes, **kwargs): + return bounding_boxes + + def transform_labels(self, labels, transformations=None, **kwargs): + return labels + + def transform_segmentation_masks( + self, segmentation_masks, transformations=None, **kwargs + ): + return segmentation_masks + + def get_config(self): + config = super().get_config() + config.update({"factor": self.factor}) + return config diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_hue.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_hue.py new file mode 100644 index 0000000000000000000000000000000000000000..43ee63ad62b8dd9d1574e7a95bb78fa7cbe2d3c2 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_hue.py @@ -0,0 +1,168 @@ +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.image_preprocessing.base_image_preprocessing_layer import ( # noqa: E501 + BaseImagePreprocessingLayer, +) + + +@keras_export("keras.layers.RandomHue") +class RandomHue(BaseImagePreprocessingLayer): + """Randomly adjusts the hue on given images. + + This layer will randomly increase/reduce the hue for the input RGB + images. + + The image hue is adjusted by converting the image(s) to HSV and rotating the + hue channel (H) by delta. The image is then converted back to RGB. + + Args: + factor: A single float or a tuple of two floats. + `factor` controls the extent to which the + image hue is impacted. `factor=0.0` makes this layer perform a + no-op operation, while a value of `1.0` performs the most aggressive + contrast adjustment available. If a tuple is used, a `factor` is + sampled between the two values for every image augmented. If a + single float is used, a value between `0.0` and the passed float is + sampled. In order to ensure the value is always the same, please + pass a tuple with two identical floats: `(0.5, 0.5)`. + value_range: the range of values the incoming images will have. + Represented as a two-number tuple written `[low, high]`. This is + typically either `[0, 1]` or `[0, 255]` depending on how your + preprocessing pipeline is set up. + seed: Integer. Used to create a random seed. + + Example: + + ```python + (images, labels), _ = keras.datasets.cifar10.load_data() + random_hue = keras.layers.RandomHue(factor=0.5, value_range=[0, 1]) + images = keras.ops.cast(images, "float32") + augmented_images_batch = random_hue(images[:8]) + ``` + """ + + _USE_BASE_FACTOR = True + _FACTOR_BOUNDS = (0, 1) + + def __init__( + self, + factor, + value_range=(0, 255), + data_format=None, + seed=None, + **kwargs, + ): + super().__init__(data_format=data_format, **kwargs) + self._set_factor(factor) + self.value_range = value_range + self.seed = seed + self.generator = self.backend.random.SeedGenerator(seed) + + def get_random_transformation(self, data, training=True, seed=None): + if isinstance(data, dict): + images = data["images"] + else: + images = data + images_shape = self.backend.shape(images) + rank = len(images_shape) + if rank == 3: + batch_size = 1 + elif rank == 4: + batch_size = images_shape[0] + else: + raise ValueError( + "Expected the input image to be rank 3 or 4. Received " + f"inputs.shape={images_shape}" + ) + + if seed is None: + seed = self._get_seed_generator(self.backend._backend) + invert = self.backend.random.uniform((batch_size,), seed=seed) + + invert = self.backend.numpy.where( + invert > 0.5, + -self.backend.numpy.ones_like(invert), + self.backend.numpy.ones_like(invert), + ) + factor = self.backend.random.uniform( + (batch_size,), + minval=self.factor[0], + maxval=self.factor[1], + seed=seed, + ) + return {"factor": invert * factor * 0.5} + + def transform_images(self, images, transformation=None, training=True): + def _apply_random_hue(images, transformation): + images = self.backend.cast(images, self.compute_dtype) + images = self._transform_value_range( + images, self.value_range, (0, 1) + ) + adjust_factors = transformation["factor"] + adjust_factors = self.backend.cast(adjust_factors, images.dtype) + adjust_factors = self.backend.numpy.expand_dims(adjust_factors, -1) + adjust_factors = self.backend.numpy.expand_dims(adjust_factors, -1) + images = self.backend.image.rgb_to_hsv( + images, data_format=self.data_format + ) + if self.data_format == "channels_first": + h_channel = images[:, 0, :, :] + adjust_factors + h_channel = self.backend.numpy.where( + h_channel > 1.0, h_channel - 1.0, h_channel + ) + h_channel = self.backend.numpy.where( + h_channel < 0.0, h_channel + 1.0, h_channel + ) + images = self.backend.numpy.stack( + [h_channel, images[:, 1, :, :], images[:, 2, :, :]], axis=1 + ) + else: + h_channel = images[..., 0] + adjust_factors + h_channel = self.backend.numpy.where( + h_channel > 1.0, h_channel - 1.0, h_channel + ) + h_channel = self.backend.numpy.where( + h_channel < 0.0, h_channel + 1.0, h_channel + ) + images = self.backend.numpy.stack( + [h_channel, images[..., 1], images[..., 2]], axis=-1 + ) + images = self.backend.image.hsv_to_rgb( + images, data_format=self.data_format + ) + images = self.backend.numpy.clip(images, 0, 1) + images = self._transform_value_range( + images, (0, 1), self.value_range + ) + images = self.backend.cast(images, self.compute_dtype) + return images + + if training: + images = _apply_random_hue(images, transformation) + return images + + def transform_labels(self, labels, transformation, training=True): + return labels + + def transform_segmentation_masks( + self, segmentation_masks, transformation, training=True + ): + return segmentation_masks + + def transform_bounding_boxes( + self, bounding_boxes, transformation, training=True + ): + return bounding_boxes + + def get_config(self): + config = super().get_config() + config.update( + { + "factor": self.factor, + "value_range": self.value_range, + "seed": self.seed, + } + ) + return config + + def compute_output_shape(self, input_shape): + return input_shape diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_posterization.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_posterization.py new file mode 100644 index 0000000000000000000000000000000000000000..55e7536724fddb80cdfeabd603cb22736d4c722e --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_posterization.py @@ -0,0 +1,151 @@ +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.image_preprocessing.base_image_preprocessing_layer import ( # noqa: E501 + BaseImagePreprocessingLayer, +) + + +@keras_export("keras.layers.RandomPosterization") +class RandomPosterization(BaseImagePreprocessingLayer): + """Reduces the number of bits for each color channel. + + References: + - [AutoAugment: Learning Augmentation Policies from Data](https://arxiv.org/abs/1805.09501) + - [RandAugment: Practical automated data augmentation with a reduced search space](https://arxiv.org/abs/1909.13719) + + Args: + value_range: a tuple or a list of two elements. The first value + represents the lower bound for values in passed images, the second + represents the upper bound. Images passed to the layer should have + values within `value_range`. Defaults to `(0, 255)`. + factor: integer, the number of bits to keep for each channel. Must be a + value between 1-8. + """ + + _USE_BASE_FACTOR = False + _FACTOR_BOUNDS = (1, 8) + _MAX_FACTOR = 8 + _VALUE_RANGE_VALIDATION_ERROR = ( + "The `value_range` argument should be a list of two numbers. " + ) + + def __init__( + self, + factor, + value_range=(0, 255), + data_format=None, + seed=None, + **kwargs, + ): + super().__init__(data_format=data_format, **kwargs) + self._set_factor(factor) + self._set_value_range(value_range) + self.seed = seed + self.generator = self.backend.random.SeedGenerator(seed) + + def _set_value_range(self, value_range): + if not isinstance(value_range, (tuple, list)): + raise ValueError( + self._VALUE_RANGE_VALIDATION_ERROR + + f"Received: value_range={value_range}" + ) + if len(value_range) != 2: + raise ValueError( + self._VALUE_RANGE_VALIDATION_ERROR + + f"Received: value_range={value_range}" + ) + self.value_range = sorted(value_range) + + def get_random_transformation(self, data, training=True, seed=None): + if isinstance(data, dict): + images = data["images"] + else: + images = data + images_shape = self.backend.shape(images) + rank = len(images_shape) + if rank == 3: + batch_size = 1 + elif rank == 4: + batch_size = images_shape[0] + else: + raise ValueError( + "Expected the input image to be rank 3 or 4. Received: " + f"inputs.shape={images_shape}" + ) + + if seed is None: + seed = self._get_seed_generator(self.backend._backend) + + if self.factor[0] != self.factor[1]: + factor = self.backend.random.randint( + (batch_size,), + minval=self.factor[0], + maxval=self.factor[1], + seed=seed, + dtype="uint8", + ) + else: + factor = ( + self.backend.numpy.ones((batch_size,), dtype="uint8") + * self.factor[0] + ) + + shift_factor = self._MAX_FACTOR - factor + return {"shift_factor": shift_factor} + + def transform_images(self, images, transformation=None, training=True): + if training: + shift_factor = transformation["shift_factor"] + + shift_factor = self.backend.numpy.reshape( + shift_factor, self.backend.shape(shift_factor) + (1, 1, 1) + ) + + images = self._transform_value_range( + images, + original_range=self.value_range, + target_range=(0, 255), + dtype=self.compute_dtype, + ) + + images = self.backend.cast(images, "uint8") + images = self.backend.numpy.bitwise_left_shift( + self.backend.numpy.bitwise_right_shift(images, shift_factor), + shift_factor, + ) + images = self.backend.cast(images, self.compute_dtype) + + images = self._transform_value_range( + images, + original_range=(0, 255), + target_range=self.value_range, + dtype=self.compute_dtype, + ) + + return images + + def transform_labels(self, labels, transformation, training=True): + return labels + + def transform_segmentation_masks( + self, segmentation_masks, transformation, training=True + ): + return segmentation_masks + + def transform_bounding_boxes( + self, bounding_boxes, transformation, training=True + ): + return bounding_boxes + + def get_config(self): + config = super().get_config() + config.update( + { + "factor": self.factor, + "value_range": self.value_range, + "seed": self.seed, + } + ) + return config + + def compute_output_shape(self, input_shape): + return input_shape diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_rotation.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_rotation.py new file mode 100644 index 0000000000000000000000000000000000000000..7ddc2b3eaf07d526e5bfa97a0949c9e0d286dfcb --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_rotation.py @@ -0,0 +1,249 @@ +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.image_preprocessing.base_image_preprocessing_layer import ( # noqa: E501 + BaseImagePreprocessingLayer, +) +from keras.src.layers.preprocessing.image_preprocessing.bounding_boxes import ( + converters, +) +from keras.src.random.seed_generator import SeedGenerator + + +@keras_export("keras.layers.RandomRotation") +class RandomRotation(BaseImagePreprocessingLayer): + """A preprocessing layer which randomly rotates images during training. + + This layer will apply random rotations to each image, filling empty space + according to `fill_mode`. + + By default, random rotations are only applied during training. + At inference time, the layer does nothing. If you need to apply random + rotations at inference time, pass `training=True` when calling the layer. + + Input pixel values can be of any range (e.g. `[0., 1.)` or `[0, 255]`) and + of integer or floating point dtype. + By default, the layer will output floats. + + **Note:** This layer is safe to use inside a `tf.data` pipeline + (independently of which backend you're using). + + Input shape: + 3D (unbatched) or 4D (batched) tensor with shape: + `(..., height, width, channels)`, in `"channels_last"` format + + Output shape: + 3D (unbatched) or 4D (batched) tensor with shape: + `(..., height, width, channels)`, in `"channels_last"` format + + Args: + factor: a float represented as fraction of 2 Pi, or a tuple of size 2 + representing lower and upper bound for rotating clockwise and + counter-clockwise. A positive values means rotating + counter clock-wise, + while a negative value means clock-wise. + When represented as a single + float, this value is used for both the upper and lower bound. + For instance, `factor=(-0.2, 0.3)` + results in an output rotation by a random + amount in the range `[-20% * 360, 30% * 360]`. + `factor=0.2` results in an + output rotating by a random amount + in the range `[-20% * 360, 20% * 360]`. + fill_mode: Points outside the boundaries of the input are filled + according to the given mode + (one of `{"constant", "reflect", "wrap", "nearest"}`). + - *reflect*: `(d c b a | a b c d | d c b a)` + The input is extended by reflecting about + the edge of the last pixel. + - *constant*: `(k k k k | a b c d | k k k k)` + The input is extended by + filling all values beyond the edge with + the same constant value k = 0. + - *wrap*: `(a b c d | a b c d | a b c d)` The input is extended by + wrapping around to the opposite edge. + - *nearest*: `(a a a a | a b c d | d d d d)` + The input is extended by the nearest pixel. + interpolation: Interpolation mode. Supported values: `"nearest"`, + `"bilinear"`. + seed: Integer. Used to create a random seed. + fill_value: a float represents the value to be filled outside + the boundaries when `fill_mode="constant"`. + data_format: string, either `"channels_last"` or `"channels_first"`. + The ordering of the dimensions in the inputs. `"channels_last"` + corresponds to inputs with shape `(batch, height, width, channels)` + while `"channels_first"` corresponds to inputs with shape + `(batch, channels, height, width)`. It defaults to the + `image_data_format` value found in your Keras config file at + `~/.keras/keras.json`. If you never set it, then it will be + `"channels_last"`. + """ + + _SUPPORTED_FILL_MODE = ("reflect", "wrap", "constant", "nearest") + _SUPPORTED_INTERPOLATION = ("nearest", "bilinear") + + def __init__( + self, + factor, + fill_mode="reflect", + interpolation="bilinear", + seed=None, + fill_value=0.0, + data_format=None, + **kwargs, + ): + super().__init__(factor=factor, data_format=data_format, **kwargs) + self.seed = seed + self.generator = SeedGenerator(seed) + self.fill_mode = fill_mode + self.interpolation = interpolation + self.fill_value = fill_value + self.supports_jit = False + + if self.fill_mode not in self._SUPPORTED_FILL_MODE: + raise NotImplementedError( + f"Unknown `fill_mode` {fill_mode}. Expected of one " + f"{self._SUPPORTED_FILL_MODE}." + ) + if self.interpolation not in self._SUPPORTED_INTERPOLATION: + raise NotImplementedError( + f"Unknown `interpolation` {interpolation}. Expected of one " + f"{self._SUPPORTED_INTERPOLATION}." + ) + + def transform_images(self, images, transformation, training=True): + images = self.backend.cast(images, self.compute_dtype) + if training: + return self.backend.image.affine_transform( + images=images, + transform=transformation["rotation_matrix"], + interpolation=self.interpolation, + fill_mode=self.fill_mode, + fill_value=self.fill_value, + data_format=self.data_format, + ) + return images + + def transform_labels(self, labels, transformation, training=True): + return labels + + def transform_bounding_boxes( + self, + bounding_boxes, + transformation, + training=True, + ): + if training: + ops = self.backend + boxes = bounding_boxes["boxes"] + height = transformation["image_height"] + width = transformation["image_width"] + batch_size = transformation["batch_size"] + boxes = converters.affine_transform( + boxes=boxes, + angle=transformation["angle"], + translate_x=ops.numpy.zeros([batch_size]), + translate_y=ops.numpy.zeros([batch_size]), + scale=ops.numpy.ones([batch_size]), + shear_x=ops.numpy.zeros([batch_size]), + shear_y=ops.numpy.zeros([batch_size]), + height=height, + width=width, + ) + + bounding_boxes["boxes"] = boxes + bounding_boxes = converters.clip_to_image_size( + bounding_boxes, + height=height, + width=width, + bounding_box_format="xyxy", + ) + bounding_boxes = converters.convert_format( + bounding_boxes, + source="xyxy", + target=self.bounding_box_format, + height=height, + width=width, + ) + return bounding_boxes + + def transform_segmentation_masks( + self, segmentation_masks, transformation, training=True + ): + return self.transform_images( + segmentation_masks, transformation, training=training + ) + + def get_random_transformation(self, data, training=True, seed=None): + ops = self.backend + if not training: + return None + if isinstance(data, dict): + images = data["images"] + else: + images = data + shape = ops.core.shape(images) + if len(shape) == 4: + batch_size = shape[0] + if self.data_format == "channels_last": + image_height = shape[1] + image_width = shape[2] + else: + image_height = shape[2] + image_width = shape[3] + else: + batch_size = 1 + if self.data_format == "channels_last": + image_height = shape[0] + image_width = shape[1] + else: + image_height = shape[1] + image_width = shape[2] + + if seed is None: + seed = self._get_seed_generator(ops._backend) + lower = self.factor[0] * 360.0 + upper = self.factor[1] * 360.0 + angle = ops.random.uniform( + shape=(batch_size,), + minval=lower, + maxval=upper, + seed=seed, + ) + center_x, center_y = 0.5, 0.5 + rotation_matrix = self._compute_affine_matrix( + center_x=center_x, + center_y=center_y, + angle=angle, + translate_x=ops.numpy.zeros([batch_size]), + translate_y=ops.numpy.zeros([batch_size]), + scale=ops.numpy.ones([batch_size]), + shear_x=ops.numpy.zeros([batch_size]), + shear_y=ops.numpy.zeros([batch_size]), + height=image_height, + width=image_width, + ) + if len(shape) == 3: + rotation_matrix = self.backend.numpy.squeeze( + rotation_matrix, axis=0 + ) + return { + "angle": angle, + "rotation_matrix": rotation_matrix, + "image_height": image_height, + "image_width": image_width, + "batch_size": batch_size, + } + + def compute_output_shape(self, input_shape): + return input_shape + + def get_config(self): + config = { + "factor": self.factor, + "data_format": self.data_format, + "fill_mode": self.fill_mode, + "fill_value": self.fill_value, + "interpolation": self.interpolation, + "seed": self.seed, + } + base_config = super().get_config() + return {**base_config, **config} diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_saturation.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_saturation.py new file mode 100644 index 0000000000000000000000000000000000000000..70d33730786d91a2bc81294a0bfe2db7cf2b786b --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_saturation.py @@ -0,0 +1,163 @@ +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.image_preprocessing.base_image_preprocessing_layer import ( # noqa: E501 + BaseImagePreprocessingLayer, +) +from keras.src.random import SeedGenerator + + +@keras_export("keras.layers.RandomSaturation") +class RandomSaturation(BaseImagePreprocessingLayer): + """Randomly adjusts the saturation on given images. + + This layer will randomly increase/reduce the saturation for the input RGB + images. + + Args: + factor: A tuple of two floats or a single float. + `factor` controls the extent to which the image saturation + is impacted. `factor=0.5` makes this layer perform a no-op + operation. `factor=0.0` makes the image fully grayscale. + `factor=1.0` makes the image fully saturated. Values should + be between `0.0` and `1.0`. If a tuple is used, a `factor` + is sampled between the two values for every image augmented. + If a single float is used, a value between `0.0` and the passed + float is sampled. To ensure the value is always the same, + pass a tuple with two identical floats: `(0.5, 0.5)`. + value_range: the range of values the incoming images will have. + Represented as a two-number tuple written `[low, high]`. This is + typically either `[0, 1]` or `[0, 255]` depending on how your + preprocessing pipeline is set up. + seed: Integer. Used to create a random seed. + + Example: + ```python + (images, labels), _ = keras.datasets.cifar10.load_data() + images = images.astype("float32") + random_saturation = keras.layers.RandomSaturation(factor=0.2) + augmented_images = random_saturation(images) + ``` + """ + + _VALUE_RANGE_VALIDATION_ERROR = ( + "The `value_range` argument should be a list of two numbers. " + ) + + def __init__( + self, + factor, + value_range=(0, 255), + data_format=None, + seed=None, + **kwargs, + ): + super().__init__(data_format=data_format, **kwargs) + self._set_factor(factor) + self._set_value_range(value_range) + self.seed = seed + self.generator = SeedGenerator(seed) + + def _set_value_range(self, value_range): + if not isinstance(value_range, (tuple, list)): + raise ValueError( + self._VALUE_RANGE_VALIDATION_ERROR + + f"Received: value_range={value_range}" + ) + if len(value_range) != 2: + raise ValueError( + self._VALUE_RANGE_VALIDATION_ERROR + + f"Received: value_range={value_range}" + ) + self.value_range = sorted(value_range) + + def get_random_transformation(self, data, training=True, seed=None): + if isinstance(data, dict): + images = data["images"] + else: + images = data + images_shape = self.backend.shape(images) + rank = len(images_shape) + if rank == 3: + batch_size = 1 + elif rank == 4: + batch_size = images_shape[0] + else: + raise ValueError( + "Expected the input image to be rank 3 or 4. Received: " + f"inputs.shape={images_shape}" + ) + + if seed is None: + seed = self._get_seed_generator(self.backend._backend) + + factor = self.backend.random.uniform( + (batch_size,), + minval=self.factor[0], + maxval=self.factor[1], + seed=seed, + ) + factor = factor / (1 - factor) + return {"factor": factor} + + def transform_images(self, images, transformation=None, training=True): + if training: + adjust_factors = transformation["factor"] + adjust_factors = self.backend.cast( + adjust_factors, self.compute_dtype + ) + adjust_factors = self.backend.numpy.reshape( + adjust_factors, self.backend.shape(adjust_factors) + (1, 1) + ) + images = self.backend.image.rgb_to_hsv( + images, data_format=self.data_format + ) + if self.data_format == "channels_first": + s_channel = self.backend.numpy.multiply( + images[:, 1, :, :], adjust_factors + ) + s_channel = self.backend.numpy.clip( + s_channel, self.value_range[0], self.value_range[1] + ) + images = self.backend.numpy.stack( + [images[:, 0, :, :], s_channel, images[:, 2, :, :]], axis=1 + ) + else: + s_channel = self.backend.numpy.multiply( + images[..., 1], adjust_factors + ) + s_channel = self.backend.numpy.clip( + s_channel, self.value_range[0], self.value_range[1] + ) + images = self.backend.numpy.stack( + [images[..., 0], s_channel, images[..., 2]], axis=-1 + ) + images = self.backend.image.hsv_to_rgb( + images, data_format=self.data_format + ) + return images + + def transform_labels(self, labels, transformation, training=True): + return labels + + def transform_segmentation_masks( + self, segmentation_masks, transformation, training=True + ): + return segmentation_masks + + def transform_bounding_boxes( + self, bounding_boxes, transformation, training=True + ): + return bounding_boxes + + def get_config(self): + config = super().get_config() + config.update( + { + "factor": self.factor, + "value_range": self.value_range, + "seed": self.seed, + } + ) + return config + + def compute_output_shape(self, input_shape): + return input_shape diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_sharpness.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_sharpness.py new file mode 100644 index 0000000000000000000000000000000000000000..f6f4edb3b813127c22e013e8bbaaab087d0759c2 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_sharpness.py @@ -0,0 +1,168 @@ +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.image_preprocessing.base_image_preprocessing_layer import ( # noqa: E501 + BaseImagePreprocessingLayer, +) +from keras.src.random import SeedGenerator + + +@keras_export("keras.layers.RandomSharpness") +class RandomSharpness(BaseImagePreprocessingLayer): + """Randomly performs the sharpness operation on given images. + + The sharpness operation first performs a blur, then blends between the + original image and the processed image. This operation adjusts the clarity + of the edges in an image, ranging from blurred to enhanced sharpness. + + Args: + factor: A tuple of two floats or a single float. + `factor` controls the extent to which the image sharpness + is impacted. `factor=0.0` results in a fully blurred image, + `factor=0.5` applies no operation (preserving the original image), + and `factor=1.0` enhances the sharpness beyond the original. Values + should be between `0.0` and `1.0`. If a tuple is used, a `factor` + is sampled between the two values for every image augmented. + If a single float is used, a value between `0.0` and the passed + float is sampled. To ensure the value is always the same, + pass a tuple with two identical floats: `(0.5, 0.5)`. + value_range: the range of values the incoming images will have. + Represented as a two-number tuple written `[low, high]`. This is + typically either `[0, 1]` or `[0, 255]` depending on how your + preprocessing pipeline is set up. + seed: Integer. Used to create a random seed. + """ + + _USE_BASE_FACTOR = False + _FACTOR_BOUNDS = (0, 1) + + _VALUE_RANGE_VALIDATION_ERROR = ( + "The `value_range` argument should be a list of two numbers. " + ) + + def __init__( + self, + factor, + value_range=(0, 255), + data_format=None, + seed=None, + **kwargs, + ): + super().__init__(data_format=data_format, **kwargs) + self._set_factor(factor) + self._set_value_range(value_range) + self.seed = seed + self.generator = SeedGenerator(seed) + + def _set_value_range(self, value_range): + if not isinstance(value_range, (tuple, list)): + raise ValueError( + self._VALUE_RANGE_VALIDATION_ERROR + + f"Received: value_range={value_range}" + ) + if len(value_range) != 2: + raise ValueError( + self._VALUE_RANGE_VALIDATION_ERROR + + f"Received: value_range={value_range}" + ) + self.value_range = sorted(value_range) + + def get_random_transformation(self, data, training=True, seed=None): + if isinstance(data, dict): + images = data["images"] + else: + images = data + images_shape = self.backend.shape(images) + rank = len(images_shape) + if rank == 3: + batch_size = 1 + elif rank == 4: + batch_size = images_shape[0] + else: + raise ValueError( + "Expected the input image to be rank 3 or 4. Received: " + f"inputs.shape={images_shape}" + ) + + if seed is None: + seed = self._get_seed_generator(self.backend._backend) + + factor = self.backend.random.uniform( + (batch_size,), + minval=self.factor[0], + maxval=self.factor[1], + seed=seed, + ) + return {"factor": factor} + + def transform_images(self, images, transformation=None, training=True): + images = self.backend.cast(images, self.compute_dtype) + if training: + if self.data_format == "channels_first": + images = self.backend.numpy.swapaxes(images, -3, -1) + + sharpness_factor = self.backend.cast( + transformation["factor"] * 2, dtype=self.compute_dtype + ) + sharpness_factor = self.backend.numpy.reshape( + sharpness_factor, (-1, 1, 1, 1) + ) + + num_channels = self.backend.shape(images)[-1] + + a, b = 1.0 / 13.0, 5.0 / 13.0 + kernel = self.backend.convert_to_tensor( + [[a, a, a], [a, b, a], [a, a, a]], dtype=self.compute_dtype + ) + kernel = self.backend.numpy.reshape(kernel, (3, 3, 1, 1)) + kernel = self.backend.numpy.tile(kernel, [1, 1, num_channels, 1]) + kernel = self.backend.cast(kernel, self.compute_dtype) + + smoothed_image = self.backend.nn.depthwise_conv( + images, + kernel, + strides=1, + padding="same", + data_format="channels_last", + ) + + smoothed_image = self.backend.cast( + smoothed_image, dtype=self.compute_dtype + ) + images = images + (1.0 - sharpness_factor) * ( + smoothed_image - images + ) + + images = self.backend.numpy.clip( + images, self.value_range[0], self.value_range[1] + ) + + if self.data_format == "channels_first": + images = self.backend.numpy.swapaxes(images, -3, -1) + + return images + + def transform_labels(self, labels, transformation, training=True): + return labels + + def transform_segmentation_masks( + self, segmentation_masks, transformation, training=True + ): + return segmentation_masks + + def transform_bounding_boxes( + self, bounding_boxes, transformation, training=True + ): + return bounding_boxes + + def get_config(self): + config = super().get_config() + config.update( + { + "factor": self.factor, + "value_range": self.value_range, + "seed": self.seed, + } + ) + return config + + def compute_output_shape(self, input_shape): + return input_shape diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_shear.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_shear.py new file mode 100644 index 0000000000000000000000000000000000000000..74390c77c772d5fe2c5b8c0ae0bf0bb8f5779607 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_shear.py @@ -0,0 +1,401 @@ +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.image_preprocessing.base_image_preprocessing_layer import ( # noqa: E501 + BaseImagePreprocessingLayer, +) +from keras.src.layers.preprocessing.image_preprocessing.bounding_boxes.converters import ( # noqa: E501 + clip_to_image_size, +) +from keras.src.layers.preprocessing.image_preprocessing.bounding_boxes.converters import ( # noqa: E501 + convert_format, +) +from keras.src.random.seed_generator import SeedGenerator +from keras.src.utils import backend_utils + + +@keras_export("keras.layers.RandomShear") +class RandomShear(BaseImagePreprocessingLayer): + """A preprocessing layer that randomly applies shear transformations to + images. + + This layer shears the input images along the x-axis and/or y-axis by a + randomly selected factor within the specified range. The shear + transformation is applied to each image independently in a batch. Empty + regions created during the transformation are filled according to the + `fill_mode` and `fill_value` parameters. + + Args: + x_factor: A tuple of two floats. For each augmented image, a value + is sampled from the provided range. If a float is passed, the + range is interpreted as `(0, x_factor)`. Values represent a + percentage of the image to shear over. For example, 0.3 shears + pixels up to 30% of the way across the image. All provided values + should be positive. + y_factor: A tuple of two floats. For each augmented image, a value + is sampled from the provided range. If a float is passed, the + range is interpreted as `(0, y_factor)`. Values represent a + percentage of the image to shear over. For example, 0.3 shears + pixels up to 30% of the way across the image. All provided values + should be positive. + interpolation: Interpolation mode. Supported values: `"nearest"`, + `"bilinear"`. + fill_mode: Points outside the boundaries of the input are filled + according to the given mode. Available methods are `"constant"`, + `"nearest"`, `"wrap"` and `"reflect"`. Defaults to `"constant"`. + - `"reflect"`: `(d c b a | a b c d | d c b a)` + The input is extended by reflecting about the edge of the + last pixel. + - `"constant"`: `(k k k k | a b c d | k k k k)` + The input is extended by filling all values beyond the edge + with the same constant value `k` specified by `fill_value`. + - `"wrap"`: `(a b c d | a b c d | a b c d)` + The input is extended by wrapping around to the opposite edge. + - `"nearest"`: `(a a a a | a b c d | d d d d)` + The input is extended by the nearest pixel. + Note that when using torch backend, `"reflect"` is redirected to + `"mirror"` `(c d c b | a b c d | c b a b)` because torch does + not support `"reflect"`. + Note that torch backend does not support `"wrap"`. + fill_value: A float representing the value to be filled outside the + boundaries when `fill_mode="constant"`. + seed: Integer. Used to create a random seed. + """ + + _USE_BASE_FACTOR = False + _FACTOR_BOUNDS = (0, 1) + _FACTOR_VALIDATION_ERROR = ( + "The `factor` argument should be a number (or a list of two numbers) " + "in the range [0, 1.0]. " + ) + _SUPPORTED_FILL_MODE = ("reflect", "wrap", "constant", "nearest") + _SUPPORTED_INTERPOLATION = ("nearest", "bilinear") + + def __init__( + self, + x_factor=0.0, + y_factor=0.0, + interpolation="bilinear", + fill_mode="reflect", + fill_value=0.0, + data_format=None, + seed=None, + **kwargs, + ): + super().__init__(data_format=data_format, **kwargs) + self.x_factor = self._set_factor_with_name(x_factor, "x_factor") + self.y_factor = self._set_factor_with_name(y_factor, "y_factor") + + if fill_mode not in self._SUPPORTED_FILL_MODE: + raise NotImplementedError( + f"Unknown `fill_mode` {fill_mode}. Expected of one " + f"{self._SUPPORTED_FILL_MODE}." + ) + if interpolation not in self._SUPPORTED_INTERPOLATION: + raise NotImplementedError( + f"Unknown `interpolation` {interpolation}. Expected of one " + f"{self._SUPPORTED_INTERPOLATION}." + ) + + self.fill_mode = fill_mode + self.fill_value = fill_value + self.interpolation = interpolation + self.seed = seed + self.generator = SeedGenerator(seed) + self.supports_jit = False + + def _set_factor_with_name(self, factor, factor_name): + if isinstance(factor, (tuple, list)): + if len(factor) != 2: + raise ValueError( + self._FACTOR_VALIDATION_ERROR + + f"Received: {factor_name}={factor}" + ) + self._check_factor_range(factor[0]) + self._check_factor_range(factor[1]) + lower, upper = sorted(factor) + elif isinstance(factor, (int, float)): + self._check_factor_range(factor) + factor = abs(factor) + lower, upper = [-factor, factor] + else: + raise ValueError( + self._FACTOR_VALIDATION_ERROR + + f"Received: {factor_name}={factor}" + ) + return lower, upper + + def _check_factor_range(self, input_number): + if input_number > 1.0 or input_number < 0.0: + raise ValueError( + self._FACTOR_VALIDATION_ERROR + + f"Received: input_number={input_number}" + ) + + def get_random_transformation(self, data, training=True, seed=None): + if not training: + return None + + if isinstance(data, dict): + images = data["images"] + else: + images = data + + images_shape = self.backend.shape(images) + if len(images_shape) == 3: + batch_size = 1 + else: + batch_size = images_shape[0] + + if seed is None: + seed = self._get_seed_generator(self.backend._backend) + + invert = self.backend.random.uniform( + minval=0, + maxval=1, + shape=[batch_size, 1], + seed=seed, + dtype=self.compute_dtype, + ) + invert = self.backend.numpy.where( + invert > 0.5, + -self.backend.numpy.ones_like(invert), + self.backend.numpy.ones_like(invert), + ) + + shear_y = self.backend.random.uniform( + minval=self.y_factor[0], + maxval=self.y_factor[1], + shape=[batch_size, 1], + seed=seed, + dtype=self.compute_dtype, + ) + shear_x = self.backend.random.uniform( + minval=self.x_factor[0], + maxval=self.x_factor[1], + shape=[batch_size, 1], + seed=seed, + dtype=self.compute_dtype, + ) + shear_factor = ( + self.backend.cast( + self.backend.numpy.concatenate([shear_x, shear_y], axis=1), + dtype=self.compute_dtype, + ) + * invert + ) + return {"shear_factor": shear_factor, "input_shape": images_shape} + + def transform_images(self, images, transformation, training=True): + images = self.backend.cast(images, self.compute_dtype) + if training: + return self._shear_inputs(images, transformation) + return images + + def _shear_inputs(self, inputs, transformation): + if transformation is None: + return inputs + + inputs_shape = self.backend.shape(inputs) + unbatched = len(inputs_shape) == 3 + if unbatched: + inputs = self.backend.numpy.expand_dims(inputs, axis=0) + + shear_factor = transformation["shear_factor"] + outputs = self.backend.image.affine_transform( + inputs, + transform=self._get_shear_matrix(shear_factor), + interpolation=self.interpolation, + fill_mode=self.fill_mode, + fill_value=self.fill_value, + data_format=self.data_format, + ) + + if unbatched: + outputs = self.backend.numpy.squeeze(outputs, axis=0) + return outputs + + def _get_shear_matrix(self, shear_factors): + num_shear_factors = self.backend.shape(shear_factors)[0] + + # The shear matrix looks like: + # [[1 s_x 0] + # [s_y 1 0] + # [0 0 1]] + + return self.backend.numpy.stack( + [ + self.backend.numpy.ones((num_shear_factors,)), + shear_factors[:, 0], + self.backend.numpy.zeros((num_shear_factors,)), + shear_factors[:, 1], + self.backend.numpy.ones((num_shear_factors,)), + self.backend.numpy.zeros((num_shear_factors,)), + self.backend.numpy.zeros((num_shear_factors,)), + self.backend.numpy.zeros((num_shear_factors,)), + ], + axis=1, + ) + + def transform_labels(self, labels, transformation, training=True): + return labels + + def get_transformed_x_y(self, x, y, transform): + a0, a1, a2, b0, b1, b2, c0, c1 = self.backend.numpy.split( + transform, 8, axis=-1 + ) + + k = c0 * x + c1 * y + 1 + x_transformed = (a0 * x + a1 * y + a2) / k + y_transformed = (b0 * x + b1 * y + b2) / k + return x_transformed, y_transformed + + def get_shifted_bbox(self, bounding_boxes, w_shift_factor, h_shift_factor): + bboxes = bounding_boxes["boxes"] + x1, x2, x3, x4 = self.backend.numpy.split(bboxes, 4, axis=-1) + + w_shift_factor = self.backend.convert_to_tensor( + w_shift_factor, dtype=x1.dtype + ) + h_shift_factor = self.backend.convert_to_tensor( + h_shift_factor, dtype=x1.dtype + ) + + if len(bboxes.shape) == 3: + w_shift_factor = self.backend.numpy.expand_dims(w_shift_factor, -1) + h_shift_factor = self.backend.numpy.expand_dims(h_shift_factor, -1) + + bounding_boxes["boxes"] = self.backend.numpy.concatenate( + [ + x1 - w_shift_factor, + x2 - h_shift_factor, + x3 - w_shift_factor, + x4 - h_shift_factor, + ], + axis=-1, + ) + return bounding_boxes + + def transform_bounding_boxes( + self, + bounding_boxes, + transformation, + training=True, + ): + def _get_height_width(transformation): + if self.data_format == "channels_first": + height_axis = -2 + width_axis = -1 + else: + height_axis = -3 + width_axis = -2 + input_height, input_width = ( + transformation["input_shape"][height_axis], + transformation["input_shape"][width_axis], + ) + return input_height, input_width + + if training: + if backend_utils.in_tf_graph(): + self.backend.set_backend("tensorflow") + + input_height, input_width = _get_height_width(transformation) + + bounding_boxes = convert_format( + bounding_boxes, + source=self.bounding_box_format, + target="rel_xyxy", + height=input_height, + width=input_width, + dtype=self.compute_dtype, + ) + + bounding_boxes = self._shear_bboxes(bounding_boxes, transformation) + + bounding_boxes = clip_to_image_size( + bounding_boxes=bounding_boxes, + height=input_height, + width=input_width, + bounding_box_format="rel_xyxy", + ) + + bounding_boxes = convert_format( + bounding_boxes, + source="rel_xyxy", + target=self.bounding_box_format, + height=input_height, + width=input_width, + dtype=self.compute_dtype, + ) + + self.backend.reset() + + return bounding_boxes + + def _shear_bboxes(self, bounding_boxes, transformation): + shear_factor = self.backend.cast( + transformation["shear_factor"], dtype=self.compute_dtype + ) + shear_x_amount, shear_y_amount = self.backend.numpy.split( + shear_factor, 2, axis=-1 + ) + + x1, y1, x2, y2 = self.backend.numpy.split( + bounding_boxes["boxes"], 4, axis=-1 + ) + x1 = self.backend.numpy.squeeze(x1, axis=-1) + y1 = self.backend.numpy.squeeze(y1, axis=-1) + x2 = self.backend.numpy.squeeze(x2, axis=-1) + y2 = self.backend.numpy.squeeze(y2, axis=-1) + + if shear_x_amount is not None: + x1_top = x1 - (shear_x_amount * y1) + x1_bottom = x1 - (shear_x_amount * y2) + x1 = self.backend.numpy.where(shear_x_amount < 0, x1_top, x1_bottom) + + x2_top = x2 - (shear_x_amount * y1) + x2_bottom = x2 - (shear_x_amount * y2) + x2 = self.backend.numpy.where(shear_x_amount < 0, x2_bottom, x2_top) + + if shear_y_amount is not None: + y1_left = y1 - (shear_y_amount * x1) + y1_right = y1 - (shear_y_amount * x2) + y1 = self.backend.numpy.where(shear_y_amount > 0, y1_right, y1_left) + + y2_left = y2 - (shear_y_amount * x1) + y2_right = y2 - (shear_y_amount * x2) + y2 = self.backend.numpy.where(shear_y_amount > 0, y2_left, y2_right) + + boxes = self.backend.numpy.concatenate( + [ + self.backend.numpy.expand_dims(x1, axis=-1), + self.backend.numpy.expand_dims(y1, axis=-1), + self.backend.numpy.expand_dims(x2, axis=-1), + self.backend.numpy.expand_dims(y2, axis=-1), + ], + axis=-1, + ) + bounding_boxes["boxes"] = boxes + + return bounding_boxes + + def transform_segmentation_masks( + self, segmentation_masks, transformation, training=True + ): + return self.transform_images( + segmentation_masks, transformation, training=training + ) + + def get_config(self): + base_config = super().get_config() + config = { + "x_factor": self.x_factor, + "y_factor": self.y_factor, + "fill_mode": self.fill_mode, + "interpolation": self.interpolation, + "seed": self.seed, + "fill_value": self.fill_value, + "data_format": self.data_format, + } + return {**base_config, **config} + + def compute_output_shape(self, input_shape): + return input_shape diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_translation.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_translation.py new file mode 100644 index 0000000000000000000000000000000000000000..1dc69a0db45aa73ee8eac8b05fd5c03d4048a624 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_translation.py @@ -0,0 +1,384 @@ +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.image_preprocessing.base_image_preprocessing_layer import ( # noqa: E501 + BaseImagePreprocessingLayer, +) +from keras.src.layers.preprocessing.image_preprocessing.bounding_boxes.converters import ( # noqa: E501 + clip_to_image_size, +) +from keras.src.layers.preprocessing.image_preprocessing.bounding_boxes.converters import ( # noqa: E501 + convert_format, +) +from keras.src.random.seed_generator import SeedGenerator +from keras.src.utils import backend_utils + + +@keras_export("keras.layers.RandomTranslation") +class RandomTranslation(BaseImagePreprocessingLayer): + """A preprocessing layer which randomly translates images during training. + + This layer will apply random translations to each image during training, + filling empty space according to `fill_mode`. + + Input pixel values can be of any range (e.g. `[0., 1.)` or `[0, 255]`) and + of integer or floating point dtype. By default, the layer will output + floats. + + Input shape: + 3D (unbatched) or 4D (batched) tensor with shape: + `(..., height, width, channels)`, in `"channels_last"` format, + or `(..., channels, height, width)`, in `"channels_first"` format. + + Output shape: + 3D (unbatched) or 4D (batched) tensor with shape: + `(..., target_height, target_width, channels)`, + or `(..., channels, target_height, target_width)`, + in `"channels_first"` format. + + **Note:** This layer is safe to use inside a `tf.data` pipeline + (independently of which backend you're using). + + Args: + height_factor: a float represented as fraction of value, or a tuple of + size 2 representing lower and upper bound for shifting vertically. A + negative value means shifting image up, while a positive value means + shifting image down. When represented as a single positive float, + this value is used for both the upper and lower bound. For instance, + `height_factor=(-0.2, 0.3)` results in an output shifted by a random + amount in the range `[-20%, +30%]`. `height_factor=0.2` results in + an output height shifted by a random amount in the range + `[-20%, +20%]`. + width_factor: a float represented as fraction of value, or a tuple of + size 2 representing lower and upper bound for shifting horizontally. + A negative value means shifting image left, while a positive value + means shifting image right. When represented as a single positive + float, this value is used for both the upper and lower bound. For + instance, `width_factor=(-0.2, 0.3)` results in an output shifted + left by 20%, and shifted right by 30%. `width_factor=0.2` results + in an output height shifted left or right by 20%. + fill_mode: Points outside the boundaries of the input are filled + according to the given mode. Available methods are `"constant"`, + `"nearest"`, `"wrap"` and `"reflect"`. Defaults to `"constant"`. + - `"reflect"`: `(d c b a | a b c d | d c b a)` + The input is extended by reflecting about the edge of the last + pixel. + - `"constant"`: `(k k k k | a b c d | k k k k)` + The input is extended by filling all values beyond + the edge with the same constant value k specified by + `fill_value`. + - `"wrap"`: `(a b c d | a b c d | a b c d)` + The input is extended by wrapping around to the opposite edge. + - `"nearest"`: `(a a a a | a b c d | d d d d)` + The input is extended by the nearest pixel. + Note that when using torch backend, `"reflect"` is redirected to + `"mirror"` `(c d c b | a b c d | c b a b)` because torch does not + support `"reflect"`. + Note that torch backend does not support `"wrap"`. + interpolation: Interpolation mode. Supported values: `"nearest"`, + `"bilinear"`. + seed: Integer. Used to create a random seed. + fill_value: a float represents the value to be filled outside the + boundaries when `fill_mode="constant"`. + data_format: string, either `"channels_last"` or `"channels_first"`. + The ordering of the dimensions in the inputs. `"channels_last"` + corresponds to inputs with shape `(batch, height, width, channels)` + while `"channels_first"` corresponds to inputs with shape + `(batch, channels, height, width)`. It defaults to the + `image_data_format` value found in your Keras config file at + `~/.keras/keras.json`. If you never set it, then it will be + `"channels_last"`. + **kwargs: Base layer keyword arguments, such as `name` and `dtype`. + """ + + _USE_BASE_FACTOR = False + _FACTOR_VALIDATION_ERROR = ( + "The `factor` argument should be a number (or a list of two numbers) " + "in the range [-1.0, 1.0]. " + ) + _SUPPORTED_FILL_MODE = ("reflect", "wrap", "constant", "nearest") + _SUPPORTED_INTERPOLATION = ("nearest", "bilinear") + + def __init__( + self, + height_factor, + width_factor, + fill_mode="reflect", + interpolation="bilinear", + seed=None, + fill_value=0.0, + data_format=None, + **kwargs, + ): + super().__init__(data_format=data_format, **kwargs) + self.height_factor = height_factor + self.height_lower, self.height_upper = self._set_factor( + height_factor, "height_factor" + ) + self.width_factor = width_factor + self.width_lower, self.width_upper = self._set_factor( + width_factor, "width_factor" + ) + + if fill_mode not in self._SUPPORTED_FILL_MODE: + raise NotImplementedError( + f"Unknown `fill_mode` {fill_mode}. Expected of one " + f"{self._SUPPORTED_FILL_MODE}." + ) + if interpolation not in self._SUPPORTED_INTERPOLATION: + raise NotImplementedError( + f"Unknown `interpolation` {interpolation}. Expected of one " + f"{self._SUPPORTED_INTERPOLATION}." + ) + + self.fill_mode = fill_mode + self.fill_value = fill_value + self.interpolation = interpolation + self.seed = seed + self.generator = SeedGenerator(seed) + self.supports_jit = False + + def _set_factor(self, factor, factor_name): + if isinstance(factor, (tuple, list)): + if len(factor) != 2: + raise ValueError( + self._FACTOR_VALIDATION_ERROR + + f"Received: {factor_name}={factor}" + ) + self._check_factor_range(factor[0]) + self._check_factor_range(factor[1]) + lower, upper = sorted(factor) + elif isinstance(factor, (int, float)): + self._check_factor_range(factor) + factor = abs(factor) + lower, upper = [-factor, factor] + else: + raise ValueError( + self._FACTOR_VALIDATION_ERROR + + f"Received: {factor_name}={factor}" + ) + return lower, upper + + def _check_factor_range(self, input_number): + if input_number > 1.0 or input_number < -1.0: + raise ValueError( + self._FACTOR_VALIDATION_ERROR + + f"Received: input_number={input_number}" + ) + + def transform_images(self, images, transformation, training=True): + images = self.backend.cast(images, self.compute_dtype) + if training: + return self._translate_inputs(images, transformation) + return images + + def transform_labels(self, labels, transformation, training=True): + return labels + + def get_transformed_x_y(self, x, y, transform): + a0, a1, a2, b0, b1, b2, c0, c1 = self.backend.numpy.split( + transform, 8, axis=-1 + ) + + k = c0 * x + c1 * y + 1 + x_transformed = (a0 * x + a1 * y + a2) / k + y_transformed = (b0 * x + b1 * y + b2) / k + return x_transformed, y_transformed + + def get_shifted_bbox(self, bounding_boxes, w_shift_factor, h_shift_factor): + bboxes = bounding_boxes["boxes"] + x1, x2, x3, x4 = self.backend.numpy.split(bboxes, 4, axis=-1) + + w_shift_factor = self.backend.convert_to_tensor( + w_shift_factor, dtype=x1.dtype + ) + h_shift_factor = self.backend.convert_to_tensor( + h_shift_factor, dtype=x1.dtype + ) + + if len(bboxes.shape) == 3: + w_shift_factor = self.backend.numpy.expand_dims(w_shift_factor, -1) + h_shift_factor = self.backend.numpy.expand_dims(h_shift_factor, -1) + + bounding_boxes["boxes"] = self.backend.numpy.concatenate( + [ + x1 - w_shift_factor, + x2 - h_shift_factor, + x3 - w_shift_factor, + x4 - h_shift_factor, + ], + axis=-1, + ) + return bounding_boxes + + def transform_bounding_boxes( + self, + bounding_boxes, + transformation, + training=True, + ): + if training: + if backend_utils.in_tf_graph(): + self.backend.set_backend("tensorflow") + + if self.data_format == "channels_first": + height_axis = -2 + width_axis = -1 + else: + height_axis = -3 + width_axis = -2 + + input_height, input_width = ( + transformation["input_shape"][height_axis], + transformation["input_shape"][width_axis], + ) + + bounding_boxes = convert_format( + bounding_boxes, + source=self.bounding_box_format, + target="xyxy", + height=input_height, + width=input_width, + ) + + translations = transformation["translations"] + transform = self._get_translation_matrix(translations) + + w_shift_factor, h_shift_factor = self.get_transformed_x_y( + 0, 0, transform + ) + bounding_boxes = self.get_shifted_bbox( + bounding_boxes, w_shift_factor, h_shift_factor + ) + + bounding_boxes = clip_to_image_size( + bounding_boxes=bounding_boxes, + height=input_height, + width=input_width, + bounding_box_format="xyxy", + ) + + bounding_boxes = convert_format( + bounding_boxes, + source="xyxy", + target=self.bounding_box_format, + height=input_height, + width=input_width, + ) + + self.backend.reset() + + return bounding_boxes + + def transform_segmentation_masks( + self, segmentation_masks, transformation, training=True + ): + return self.transform_images( + segmentation_masks, transformation, training=training + ) + + def get_random_transformation(self, data, training=True, seed=None): + if not training: + return None + + if isinstance(data, dict): + images = data["images"] + else: + images = data + + images_shape = self.backend.shape(images) + unbatched = len(images_shape) == 3 + if unbatched: + images_shape = self.backend.shape(images) + batch_size = 1 + else: + batch_size = images_shape[0] + if self.data_format == "channels_first": + height = images_shape[-2] + width = images_shape[-1] + else: + height = images_shape[-3] + width = images_shape[-2] + + if seed is None: + seed = self._get_seed_generator(self.backend._backend) + + height_translate = self.backend.random.uniform( + minval=self.height_lower, + maxval=self.height_upper, + shape=[batch_size, 1], + seed=seed, + ) + height_translate = self.backend.numpy.multiply(height_translate, height) + width_translate = self.backend.random.uniform( + minval=self.width_lower, + maxval=self.width_upper, + shape=[batch_size, 1], + seed=seed, + ) + width_translate = self.backend.numpy.multiply(width_translate, width) + translations = self.backend.cast( + self.backend.numpy.concatenate( + [width_translate, height_translate], axis=1 + ), + dtype="float32", + ) + return {"translations": translations, "input_shape": images_shape} + + def _translate_inputs(self, inputs, transformation): + if transformation is None: + return inputs + + inputs_shape = self.backend.shape(inputs) + unbatched = len(inputs_shape) == 3 + if unbatched: + inputs = self.backend.numpy.expand_dims(inputs, axis=0) + + translations = transformation["translations"] + outputs = self.backend.image.affine_transform( + inputs, + transform=self._get_translation_matrix(translations), + interpolation=self.interpolation, + fill_mode=self.fill_mode, + fill_value=self.fill_value, + data_format=self.data_format, + ) + + if unbatched: + outputs = self.backend.numpy.squeeze(outputs, axis=0) + return outputs + + def _get_translation_matrix(self, translations): + num_translations = self.backend.shape(translations)[0] + # The translation matrix looks like: + # [[1 0 -dx] + # [0 1 -dy] + # [0 0 1]] + # where the last entry is implicit. + # translation matrices are always float32. + return self.backend.numpy.concatenate( + [ + self.backend.numpy.ones((num_translations, 1)), + self.backend.numpy.zeros((num_translations, 1)), + -translations[:, 0:1], + self.backend.numpy.zeros((num_translations, 1)), + self.backend.numpy.ones((num_translations, 1)), + -translations[:, 1:], + self.backend.numpy.zeros((num_translations, 2)), + ], + axis=1, + ) + + def compute_output_shape(self, input_shape): + return input_shape + + def get_config(self): + base_config = super().get_config() + config = { + "height_factor": self.height_factor, + "width_factor": self.width_factor, + "fill_mode": self.fill_mode, + "interpolation": self.interpolation, + "seed": self.seed, + "fill_value": self.fill_value, + "data_format": self.data_format, + } + return {**base_config, **config} diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_zoom.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_zoom.py new file mode 100644 index 0000000000000000000000000000000000000000..80b29b8e6ad3ffe16ab0b6bb55f22f42c698c702 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/random_zoom.py @@ -0,0 +1,430 @@ +from keras.src import backend +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.image_preprocessing.base_image_preprocessing_layer import ( # noqa: E501 + BaseImagePreprocessingLayer, +) +from keras.src.layers.preprocessing.image_preprocessing.bounding_boxes.converters import ( # noqa: E501 + clip_to_image_size, +) +from keras.src.layers.preprocessing.image_preprocessing.bounding_boxes.converters import ( # noqa: E501 + convert_format, +) +from keras.src.random.seed_generator import SeedGenerator +from keras.src.utils import backend_utils + + +@keras_export("keras.layers.RandomZoom") +class RandomZoom(BaseImagePreprocessingLayer): + """A preprocessing layer which randomly zooms images during training. + + This layer will randomly zoom in or out on each axis of an image + independently, filling empty space according to `fill_mode`. + + Input pixel values can be of any range (e.g. `[0., 1.)` or `[0, 255]`) and + of integer or floating point dtype. + By default, the layer will output floats. + + Input shape: + 3D (unbatched) or 4D (batched) tensor with shape: + `(..., height, width, channels)`, in `"channels_last"` format, + or `(..., channels, height, width)`, in `"channels_first"` format. + + Output shape: + 3D (unbatched) or 4D (batched) tensor with shape: + `(..., target_height, target_width, channels)`, + or `(..., channels, target_height, target_width)`, + in `"channels_first"` format. + + **Note:** This layer is safe to use inside a `tf.data` pipeline + (independently of which backend you're using). + + Args: + height_factor: a float represented as fraction of value, or a tuple of + size 2 representing lower and upper bound for zooming vertically. + When represented as a single float, this value is used for both the + upper and lower bound. A positive value means zooming out, while a + negative value means zooming in. For instance, + `height_factor=(0.2, 0.3)` result in an output zoomed out by a + random amount in the range `[+20%, +30%]`. + `height_factor=(-0.3, -0.2)` result in an output zoomed in by a + random amount in the range `[+20%, +30%]`. + width_factor: a float represented as fraction of value, or a tuple of + size 2 representing lower and upper bound for zooming horizontally. + When represented as a single float, this value is used for both the + upper and lower bound. For instance, `width_factor=(0.2, 0.3)` + result in an output zooming out between 20% to 30%. + `width_factor=(-0.3, -0.2)` result in an output zooming in between + 20% to 30%. `None` means i.e., zooming vertical and horizontal + directions by preserving the aspect ratio. Defaults to `None`. + fill_mode: Points outside the boundaries of the input are filled + according to the given mode. Available methods are `"constant"`, + `"nearest"`, `"wrap"` and `"reflect"`. Defaults to `"constant"`. + - `"reflect"`: `(d c b a | a b c d | d c b a)` + The input is extended by reflecting about the edge of the last + pixel. + - `"constant"`: `(k k k k | a b c d | k k k k)` + The input is extended by filling all values beyond + the edge with the same constant value k specified by + `fill_value`. + - `"wrap"`: `(a b c d | a b c d | a b c d)` + The input is extended by wrapping around to the opposite edge. + - `"nearest"`: `(a a a a | a b c d | d d d d)` + The input is extended by the nearest pixel. + Note that when using torch backend, `"reflect"` is redirected to + `"mirror"` `(c d c b | a b c d | c b a b)` because torch does not + support `"reflect"`. + Note that torch backend does not support `"wrap"`. + interpolation: Interpolation mode. Supported values: `"nearest"`, + `"bilinear"`. + seed: Integer. Used to create a random seed. + fill_value: a float that represents the value to be filled outside + the boundaries when `fill_mode="constant"`. + data_format: string, either `"channels_last"` or `"channels_first"`. + The ordering of the dimensions in the inputs. `"channels_last"` + corresponds to inputs with shape `(batch, height, width, channels)` + while `"channels_first"` corresponds to inputs with shape + `(batch, channels, height, width)`. It defaults to the + `image_data_format` value found in your Keras config file at + `~/.keras/keras.json`. If you never set it, then it will be + `"channels_last"`. + **kwargs: Base layer keyword arguments, such as `name` and `dtype`. + + Example: + + >>> input_img = np.random.random((32, 224, 224, 3)) + >>> layer = keras.layers.RandomZoom(.5, .2) + >>> out_img = layer(input_img) + """ + + _USE_BASE_FACTOR = False + _FACTOR_VALIDATION_ERROR = ( + "The `height_factor` and `width_factor` arguments " + "should be a number (or a list of two numbers) " + "in the range [-1.0, 1.0]. " + ) + _SUPPORTED_FILL_MODE = ("reflect", "wrap", "constant", "nearest") + _SUPPORTED_INTERPOLATION = ("nearest", "bilinear") + + def __init__( + self, + height_factor, + width_factor=None, + fill_mode="reflect", + interpolation="bilinear", + seed=None, + fill_value=0.0, + data_format=None, + **kwargs, + ): + super().__init__(**kwargs) + self.height_factor = height_factor + self.height_lower, self.height_upper = self._set_factor( + height_factor, "height_factor" + ) + self.width_factor = width_factor + if width_factor is not None: + self.width_lower, self.width_upper = self._set_factor( + width_factor, "width_factor" + ) + if fill_mode not in self._SUPPORTED_FILL_MODE: + raise NotImplementedError( + f"Unknown `fill_mode` {fill_mode}. Expected of one " + f"{self._SUPPORTED_FILL_MODE}." + ) + if interpolation not in self._SUPPORTED_INTERPOLATION: + raise NotImplementedError( + f"Unknown `interpolation` {interpolation}. Expected of one " + f"{self._SUPPORTED_INTERPOLATION}." + ) + + self.fill_mode = fill_mode + self.fill_value = fill_value + self.interpolation = interpolation + self.seed = seed + self.generator = SeedGenerator(seed) + self.data_format = backend.standardize_data_format(data_format) + self.supports_jit = False + + def _set_factor(self, factor, factor_name): + if isinstance(factor, (tuple, list)): + if len(factor) != 2: + raise ValueError( + self._FACTOR_VALIDATION_ERROR + + f"Received: {factor_name}={factor}" + ) + self._check_factor_range(factor[0]) + self._check_factor_range(factor[1]) + lower, upper = sorted(factor) + elif isinstance(factor, (int, float)): + self._check_factor_range(factor) + factor = abs(factor) + lower, upper = [-factor, factor] + else: + raise ValueError( + self._FACTOR_VALIDATION_ERROR + + f"Received: {factor_name}={factor}" + ) + return lower, upper + + def _check_factor_range(self, input_number): + if input_number > 1.0 or input_number < -1.0: + raise ValueError( + self._FACTOR_VALIDATION_ERROR + + f"Received: input_number={input_number}" + ) + + def transform_images(self, images, transformation, training=True): + images = self.backend.cast(images, self.compute_dtype) + if training: + return self._zoom_inputs(images, transformation) + return images + + def transform_labels(self, labels, transformation, training=True): + return labels + + def get_transformed_x_y(self, x, y, transform): + a0, a1, a2, b0, b1, b2, c0, c1 = self.backend.numpy.split( + transform, 8, axis=-1 + ) + + k = c0 * x + c1 * y + 1 + x_transformed = (a0 * x + a1 * y + a2) / k + y_transformed = (b0 * x + b1 * y + b2) / k + return x_transformed, y_transformed + + def get_clipped_bbox(self, bounding_boxes, h_end, h_start, w_end, w_start): + bboxes = bounding_boxes["boxes"] + x1, y1, x2, y2 = self.backend.numpy.split(bboxes, 4, axis=-1) + + if len(bboxes.shape) == 3: + h_end = self.backend.numpy.expand_dims(h_end, -1) + h_start = self.backend.numpy.expand_dims(h_start, -1) + w_end = self.backend.numpy.expand_dims(w_end, -1) + w_start = self.backend.numpy.expand_dims(w_start, -1) + + x1 = self.backend.numpy.clip(x1, w_start, w_end) - w_start + y1 = self.backend.numpy.clip(y1, h_start, h_end) - h_start + x2 = self.backend.numpy.clip(x2, w_start, w_end) - w_start + y2 = self.backend.numpy.clip(y2, h_start, h_end) - h_start + bounding_boxes["boxes"] = self.backend.numpy.concatenate( + [x1, y1, x2, y2], axis=-1 + ) + return bounding_boxes + + def transform_bounding_boxes( + self, + bounding_boxes, + transformation, + training=True, + ): + if training: + if backend_utils.in_tf_graph(): + self.backend.set_backend("tensorflow") + + width_zoom = transformation["width_zoom"] + height_zoom = transformation["height_zoom"] + inputs_shape = transformation["input_shape"] + + if self.data_format == "channels_first": + height = inputs_shape[-2] + width = inputs_shape[-1] + else: + height = inputs_shape[-3] + width = inputs_shape[-2] + + bounding_boxes = convert_format( + bounding_boxes, + source=self.bounding_box_format, + target="xyxy", + height=height, + width=width, + ) + + zooms = self.backend.cast( + self.backend.numpy.concatenate( + [width_zoom, height_zoom], axis=1 + ), + dtype="float32", + ) + transform = self._get_zoom_matrix(zooms, height, width) + + w_start, h_start = self.get_transformed_x_y( + 0, + 0, + transform, + ) + + w_end, h_end = self.get_transformed_x_y( + width, + height, + transform, + ) + + bounding_boxes = self.get_clipped_bbox( + bounding_boxes, h_end, h_start, w_end, w_start + ) + + height_transformed = h_end - h_start + width_transformed = w_end - w_start + + height_transformed = self.backend.numpy.expand_dims( + height_transformed, -1 + ) + width_transformed = self.backend.numpy.expand_dims( + width_transformed, -1 + ) + + bounding_boxes = convert_format( + bounding_boxes, + source="xyxy", + target="rel_xyxy", + height=height_transformed, + width=width_transformed, + ) + + bounding_boxes = clip_to_image_size( + bounding_boxes=bounding_boxes, + height=height_transformed, + width=width_transformed, + bounding_box_format="rel_xyxy", + ) + + bounding_boxes = convert_format( + bounding_boxes, + source="rel_xyxy", + target=self.bounding_box_format, + height=height, + width=width, + ) + + self.backend.reset() + + return bounding_boxes + + def transform_segmentation_masks( + self, segmentation_masks, transformation, training=True + ): + return self.transform_images( + segmentation_masks, transformation, training=training + ) + + def get_random_transformation(self, data, training=True, seed=None): + if not training: + return None + if isinstance(data, dict): + images = data["images"] + else: + images = data + images_shape = self.backend.shape(images) + if len(images_shape) == 4: + zoom_factor_shape = (images_shape[0], 1) + else: + zoom_factor_shape = (1, 1) + + if not training: + return { + "height_zoom": self.backend.numpy.zeros(zoom_factor_shape), + "width_zoom": self.backend.numpy.zeros(zoom_factor_shape), + } + if seed is None: + seed = self._get_seed_generator(self.backend._backend) + + height_zoom = self.backend.random.uniform( + minval=1.0 + self.height_lower, + maxval=1.0 + self.height_upper, + shape=zoom_factor_shape, + seed=seed, + ) + if self.width_factor is not None: + width_zoom = self.backend.random.uniform( + minval=1.0 + self.width_lower, + maxval=1.0 + self.width_upper, + shape=zoom_factor_shape, + seed=seed, + ) + else: + width_zoom = height_zoom + return { + "height_zoom": height_zoom, + "width_zoom": width_zoom, + "input_shape": images_shape, + } + + def _zoom_inputs(self, inputs, transformation): + if transformation is None: + return inputs + + width_zoom = transformation["width_zoom"] + height_zoom = transformation["height_zoom"] + zooms = self.backend.cast( + self.backend.numpy.concatenate([width_zoom, height_zoom], axis=1), + dtype="float32", + ) + + inputs_shape = self.backend.shape(inputs) + unbatched = len(inputs_shape) == 3 + if unbatched: + inputs = self.backend.numpy.expand_dims(inputs, axis=0) + inputs_shape = self.backend.shape(inputs) + if self.data_format == "channels_first": + height = inputs_shape[-2] + width = inputs_shape[-1] + else: + height = inputs_shape[-3] + width = inputs_shape[-2] + + outputs = self.backend.image.affine_transform( + inputs, + transform=self._get_zoom_matrix(zooms, height, width), + interpolation=self.interpolation, + fill_mode=self.fill_mode, + fill_value=self.fill_value, + data_format=self.data_format, + ) + + if unbatched: + outputs = self.backend.numpy.squeeze(outputs, axis=0) + return outputs + + def _get_zoom_matrix(self, zooms, image_height, image_width): + num_zooms = self.backend.shape(zooms)[0] + # The zoom matrix looks like: + # [[zx 0 0] + # [0 zy 0] + # [0 0 1]] + # where the last entry is implicit. + # zoom matrices are always float32. + x_offset = ((self.backend.cast(image_width, "float32") - 1.0) / 2.0) * ( + 1.0 - zooms[:, 0:1] + ) + y_offset = ( + (self.backend.cast(image_height, "float32") - 1.0) / 2.0 + ) * (1.0 - zooms[:, 1:]) + return self.backend.numpy.concatenate( + [ + zooms[:, 0:1], + self.backend.numpy.zeros((num_zooms, 1)), + x_offset, + self.backend.numpy.zeros((num_zooms, 1)), + zooms[:, 1:], + y_offset, + self.backend.numpy.zeros((num_zooms, 2)), + ], + axis=1, + ) + + def compute_output_shape(self, input_shape): + return input_shape + + def get_config(self): + base_config = super().get_config() + config = { + "height_factor": self.height_factor, + "width_factor": self.width_factor, + "fill_mode": self.fill_mode, + "interpolation": self.interpolation, + "seed": self.seed, + "fill_value": self.fill_value, + "data_format": self.data_format, + } + return {**base_config, **config} diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/resizing.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/resizing.py new file mode 100644 index 0000000000000000000000000000000000000000..a132e69bfd3454a86e0b41d280325a887a81f7f6 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/resizing.py @@ -0,0 +1,304 @@ +from keras.src import backend +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.image_preprocessing.base_image_preprocessing_layer import ( # noqa: E501 + BaseImagePreprocessingLayer, +) +from keras.src.layers.preprocessing.image_preprocessing.bounding_boxes.converters import ( # noqa: E501 + clip_to_image_size, +) +from keras.src.layers.preprocessing.image_preprocessing.bounding_boxes.converters import ( # noqa: E501 + convert_format, +) +from keras.src.ops.core import _saturate_cast + + +@keras_export("keras.layers.Resizing") +class Resizing(BaseImagePreprocessingLayer): + """A preprocessing layer which resizes images. + + This layer resizes an image input to a target height and width. The input + should be a 4D (batched) or 3D (unbatched) tensor in `"channels_last"` + format. Input pixel values can be of any range + (e.g. `[0., 1.)` or `[0, 255]`). + + Input shape: + 3D (unbatched) or 4D (batched) tensor with shape: + `(..., height, width, channels)`, in `"channels_last"` format, + or `(..., channels, height, width)`, in `"channels_first"` format. + + Output shape: + 3D (unbatched) or 4D (batched) tensor with shape: + `(..., target_height, target_width, channels)`, + or `(..., channels, target_height, target_width)`, + in `"channels_first"` format. + + **Note:** This layer is safe to use inside a `tf.data` pipeline + (independently of which backend you're using). + + Args: + height: Integer, the height of the output shape. + width: Integer, the width of the output shape. + interpolation: String, the interpolation method. + Supports `"bilinear"`, `"nearest"`, `"bicubic"`, + `"lanczos3"`, `"lanczos5"`. Defaults to `"bilinear"`. + crop_to_aspect_ratio: If `True`, resize the images without aspect + ratio distortion. When the original aspect ratio differs + from the target aspect ratio, the output image will be + cropped so as to return the + largest possible window in the image (of size `(height, width)`) + that matches the target aspect ratio. By default + (`crop_to_aspect_ratio=False`), aspect ratio may not be preserved. + pad_to_aspect_ratio: If `True`, pad the images without aspect + ratio distortion. When the original aspect ratio differs + from the target aspect ratio, the output image will be + evenly padded on the short side. + fill_mode: When using `pad_to_aspect_ratio=True`, padded areas + are filled according to the given mode. Only `"constant"` is + supported at this time + (fill with constant value, equal to `fill_value`). + fill_value: Float. Padding value to use when `pad_to_aspect_ratio=True`. + data_format: string, either `"channels_last"` or `"channels_first"`. + The ordering of the dimensions in the inputs. `"channels_last"` + corresponds to inputs with shape `(batch, height, width, channels)` + while `"channels_first"` corresponds to inputs with shape + `(batch, channels, height, width)`. It defaults to the + `image_data_format` value found in your Keras config file at + `~/.keras/keras.json`. If you never set it, then it will be + `"channels_last"`. + **kwargs: Base layer keyword arguments, such as `name` and `dtype`. + """ + + _USE_BASE_FACTOR = False + + def __init__( + self, + height, + width, + interpolation="bilinear", + crop_to_aspect_ratio=False, + pad_to_aspect_ratio=False, + fill_mode="constant", + fill_value=0.0, + data_format=None, + **kwargs, + ): + super().__init__(**kwargs) + self.height = height + self.width = width + self.interpolation = interpolation + self.data_format = backend.standardize_data_format(data_format) + self.crop_to_aspect_ratio = crop_to_aspect_ratio + self.pad_to_aspect_ratio = pad_to_aspect_ratio + self.fill_mode = fill_mode + self.fill_value = fill_value + if self.data_format == "channels_first": + self.height_axis = -2 + self.width_axis = -1 + elif self.data_format == "channels_last": + self.height_axis = -3 + self.width_axis = -2 + + def transform_images(self, images, transformation=None, training=True): + size = (self.height, self.width) + resized = self.backend.image.resize( + images, + size=size, + interpolation=self.interpolation, + data_format=self.data_format, + crop_to_aspect_ratio=self.crop_to_aspect_ratio, + pad_to_aspect_ratio=self.pad_to_aspect_ratio, + fill_mode=self.fill_mode, + fill_value=self.fill_value, + ) + if resized.dtype == images.dtype: + return resized + if backend.is_int_dtype(images.dtype): + resized = self.backend.numpy.round(resized) + return _saturate_cast(resized, images.dtype, self.backend) + + def transform_segmentation_masks( + self, segmentation_masks, transformation=None, training=True + ): + return self.transform_images(segmentation_masks) + + def transform_labels(self, labels, transformation=None, training=True): + return labels + + def get_random_transformation(self, data, training=True, seed=None): + if isinstance(data, dict): + input_shape = self.backend.shape(data["images"]) + else: + input_shape = self.backend.shape(data) + + input_height, input_width = ( + input_shape[self.height_axis], + input_shape[self.width_axis], + ) + + return input_height, input_width + + def transform_bounding_boxes( + self, + bounding_boxes, + transformation, + training=True, + ): + ops = self.backend + input_height, input_width = transformation + mask_negative_1s = ops.numpy.all(bounding_boxes["boxes"] == -1, axis=-1) + mask_zeros = ops.numpy.all(bounding_boxes["boxes"] == 0, axis=-1) + boxes_mask = ops.numpy.logical_or(mask_negative_1s, mask_zeros) + + bounding_boxes = convert_format( + bounding_boxes, + source=self.bounding_box_format, + target="xyxy", + height=input_height, + width=input_width, + ) + + bounding_boxes["boxes"] = self._transform_xyxy( + bounding_boxes["boxes"], + input_height=input_height, + input_width=input_width, + ) + + bounding_boxes = clip_to_image_size( + bounding_boxes=bounding_boxes, + height=self.height, + width=self.width, + ) + + bounding_boxes["boxes"] = ops.numpy.where( + ops.numpy.expand_dims(boxes_mask, axis=-1), + ops.convert_to_tensor( + [0.0, 0.0, 0.0, 0.0], dtype=bounding_boxes["boxes"].dtype + ), + bounding_boxes["boxes"], + ) + + bounding_boxes = convert_format( + bounding_boxes, + source="xyxy", + target=self.bounding_box_format, + height=self.height, + width=self.width, + ) + + return bounding_boxes + + def _transform_xyxy(self, boxes, input_height, input_width): + ops = self.backend + input_height = ops.cast(input_height, dtype=boxes.dtype) + input_width = ops.cast(input_width, dtype=boxes.dtype) + + if self.pad_to_aspect_ratio: + return self._transform_boxes_pad_to_aspect_ratio( + boxes, input_height, input_width + ) + elif self.crop_to_aspect_ratio: + return self._transform_boxes_crop_to_aspect_ratio( + boxes, input_height, input_width + ) + else: + return self._transform_boxes_stretch( + boxes, input_height, input_width + ) + + def _transform_boxes_pad_to_aspect_ratio( + self, boxes, input_height, input_width + ): + """Transforms bounding boxes for padding to aspect ratio.""" + ops = self.backend + height_ratio = ops.cast(self.height / input_height, dtype=boxes.dtype) + width_ratio = ops.cast(self.width / input_width, dtype=boxes.dtype) + min_aspect_ratio = ops.numpy.minimum(height_ratio, width_ratio) + y_offset = (self.height - input_height * min_aspect_ratio) // 2 + x_offset = (self.width - input_width * min_aspect_ratio) // 2 + return ops.numpy.stack( + [ + boxes[..., 0] * min_aspect_ratio + x_offset, + boxes[..., 1] * min_aspect_ratio + y_offset, + boxes[..., 2] * min_aspect_ratio + x_offset, + boxes[..., 3] * min_aspect_ratio + y_offset, + ], + axis=-1, + ) + + def _transform_boxes_crop_to_aspect_ratio( + self, boxes, input_height, input_width + ): + """Transforms bounding boxes for cropping to aspect ratio.""" + ops = self.backend + source_aspect_ratio = input_width / input_height + target_aspect_ratio = self.width / self.height + new_width = ops.numpy.where( + source_aspect_ratio > target_aspect_ratio, + self.height * source_aspect_ratio, + self.width, + ) + new_height = ops.numpy.where( + source_aspect_ratio > target_aspect_ratio, + self.height, + self.width / source_aspect_ratio, + ) + scale_x = new_width / input_width + scale_y = new_height / input_height + crop_left = (new_width - self.width) // 2 + crop_top = (new_height - self.height) // 2 + return ops.numpy.stack( + [ + boxes[..., 0] * scale_x - crop_left, + boxes[..., 1] * scale_y - crop_top, + boxes[..., 2] * scale_x - crop_left, + boxes[..., 3] * scale_y - crop_top, + ], + axis=-1, + ) + + def _transform_boxes_stretch(self, boxes, input_height, input_width): + """Transforms bounding boxes by simple stretching.""" + ops = self.backend + height_ratio = ops.cast(self.height / input_height, dtype=boxes.dtype) + width_ratio = ops.cast(self.width / input_width, dtype=boxes.dtype) + return ops.numpy.stack( + [ + boxes[..., 0] * width_ratio, + boxes[..., 1] * height_ratio, + boxes[..., 2] * width_ratio, + boxes[..., 3] * height_ratio, + ], + axis=-1, + ) + + def compute_output_shape(self, input_shape): + input_shape = list(input_shape) + if len(input_shape) == 4: + if self.data_format == "channels_last": + input_shape[1] = self.height + input_shape[2] = self.width + else: + input_shape[2] = self.height + input_shape[3] = self.width + else: + if self.data_format == "channels_last": + input_shape[0] = self.height + input_shape[1] = self.width + else: + input_shape[1] = self.height + input_shape[2] = self.width + return tuple(input_shape) + + def get_config(self): + base_config = super().get_config() + config = { + "height": self.height, + "width": self.width, + "interpolation": self.interpolation, + "crop_to_aspect_ratio": self.crop_to_aspect_ratio, + "pad_to_aspect_ratio": self.pad_to_aspect_ratio, + "fill_mode": self.fill_mode, + "fill_value": self.fill_value, + "data_format": self.data_format, + } + return {**base_config, **config} diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/solarization.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/solarization.py new file mode 100644 index 0000000000000000000000000000000000000000..2a8fcee5efa30f11c3e74bf16c4460fbd587ce94 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/image_preprocessing/solarization.py @@ -0,0 +1,214 @@ +from keras.src import backend +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.image_preprocessing.base_image_preprocessing_layer import ( # noqa: E501 + BaseImagePreprocessingLayer, +) +from keras.src.ops.core import _saturate_cast +from keras.src.random.seed_generator import SeedGenerator + + +@keras_export("keras.layers.Solarization") +class Solarization(BaseImagePreprocessingLayer): + """Applies `(max_value - pixel + min_value)` for each pixel in the image. + + When created without `threshold` parameter, the layer performs solarization + to all values. When created with specified `threshold` the layer only + augments pixels that are above the `threshold` value. + + Args: + addition_factor: (Optional) A tuple of two floats or a single float, + between 0 and 1. + For each augmented image a value is + sampled from the provided range. If a float is passed, the range is + interpreted as `(0, addition_factor)`. If specified, this value + (times the value range of input images, e.g. 255), is + added to each pixel before solarization and thresholding. + Defaults to 0.0. + threshold_factor: (Optional) A tuple of two floats or a single float. + For each augmented image a value is + sampled from the provided range. If a float is passed, the range is + interpreted as `(0, threshold_factor)`. If specified, only pixel + values above this threshold will be solarized. + value_range: a tuple or a list of two elements. The first value + represents the lower bound for values in input images, the second + represents the upper bound. Images passed to the layer should have + values within `value_range`. Typical values to pass + are `(0, 255)` (RGB image) or `(0., 1.)` (scaled image). + seed: Integer. Used to create a random seed. + **kwargs: Base layer keyword arguments, such as `name` and `dtype`. + + Example: + + ```python + (images, labels), _ = keras.datasets.cifar10.load_data() + print(images[0, 0, 0]) + # [59 62 63] + # Note that images are Tensor with values in the range [0, 255] + solarization = Solarization(value_range=(0, 255)) + images = solarization(images) + print(images[0, 0, 0]) + # [196, 193, 192] + ``` + """ + + _USE_BASE_FACTOR = False + _VALUE_RANGE_VALIDATION_ERROR = ( + "The `value_range` argument should be a list of two numbers. " + ) + _FACTOR_VALIDATION_ERROR = ( + "The `addition_factor` and `threshold_factor` arguments " + "should be a number (or a list of two numbers) " + "in the range [0, 1]. " + ) + + def __init__( + self, + addition_factor=0.0, + threshold_factor=0.0, + value_range=(0, 255), + seed=None, + **kwargs, + ): + super().__init__(**kwargs) + self.seed = seed + self.generator = SeedGenerator(seed) + self.addition_factor = self._set_factor( + addition_factor, "addition_factor" + ) + self.threshold_factor = self._set_factor( + threshold_factor, "threshold_factor" + ) + self._set_value_range(value_range) + + def _set_value_range(self, value_range): + if not isinstance(value_range, (tuple, list)): + raise ValueError( + self._VALUE_RANGE_VALIDATION_ERROR + + f"Received: value_range={value_range}" + ) + if len(value_range) != 2: + raise ValueError( + self._VALUE_RANGE_VALIDATION_ERROR + + f"Received: value_range={value_range}" + ) + self.value_range = sorted(value_range) + + def _set_factor(self, factor, factor_name): + if isinstance(factor, (tuple, list)): + if len(factor) != 2: + raise ValueError( + self._FACTOR_VALIDATION_ERROR + + f"Received: {factor_name}={factor}" + ) + self._check_factor_range(factor[0]) + self._check_factor_range(factor[1]) + lower, upper = sorted(factor) + elif isinstance(factor, (int, float)): + self._check_factor_range(factor) + lower, upper = [0, factor] + else: + raise ValueError( + self._FACTOR_VALIDATION_ERROR + + f"Received: {factor_name}={factor}" + ) + return lower, upper + + def _check_factor_range(self, input_number): + if input_number > 1.0 or input_number < 0: + raise ValueError( + self._FACTOR_VALIDATION_ERROR + + f"Received: input_number={input_number}" + ) + + def get_random_transformation(self, data, training=True, seed=None): + if not training: + return None + + if isinstance(data, dict): + images = data["images"] + else: + images = data + images_shape = self.backend.shape(images) + if len(images_shape) == 4: + factor_shape = (images_shape[0], 1, 1, 1) + else: + factor_shape = (1, 1, 1) + + if seed is None: + seed = self._get_seed_generator(self.backend._backend) + + return { + "additions": self.backend.random.uniform( + minval=self.addition_factor[0], + maxval=self.addition_factor[1] * 255, + shape=factor_shape, + seed=seed, + dtype=self.compute_dtype, + ), + "thresholds": self.backend.random.uniform( + minval=self.threshold_factor[0], + maxval=self.threshold_factor[1] * 255, + shape=factor_shape, + seed=seed, + dtype=self.compute_dtype, + ), + } + + def transform_images(self, images, transformation, training=True): + images = self.backend.cast(images, self.compute_dtype) + + if training: + if transformation is None: + return images + + thresholds = transformation["thresholds"] + additions = transformation["additions"] + images = self._transform_value_range( + images, + original_range=self.value_range, + target_range=(0, 255), + dtype=self.compute_dtype, + ) + results = images + additions + results = self.backend.numpy.clip(results, 0, 255) + results = self.backend.numpy.where( + results < thresholds, results, 255 - results + ) + results = self._transform_value_range( + results, + original_range=(0, 255), + target_range=self.value_range, + dtype=self.compute_dtype, + ) + if results.dtype == images.dtype: + return results + if backend.is_int_dtype(images.dtype): + results = self.backend.numpy.round(results) + return _saturate_cast(results, images.dtype, self.backend) + return images + + def transform_labels(self, labels, transformation, training=True): + return labels + + def transform_bounding_boxes( + self, bounding_boxes, transformation, training=True + ): + return bounding_boxes + + def transform_segmentation_masks( + self, segmentation_masks, transformation, training=True + ): + return segmentation_masks + + def get_config(self): + base_config = super().get_config() + config = { + "value_range": self.value_range, + "addition_factor": self.addition_factor, + "threshold_factor": self.threshold_factor, + "seed": self.seed, + } + return {**base_config, **config} + + def compute_output_shape(self, input_shape): + return input_shape diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/index_lookup.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/index_lookup.py new file mode 100644 index 0000000000000000000000000000000000000000..27cee5c11d854f142cc3e10680f0b7d685e16a6a --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/index_lookup.py @@ -0,0 +1,1012 @@ +import collections + +import numpy as np + +from keras.src import backend +from keras.src.layers.layer import Layer +from keras.src.utils import argument_validation +from keras.src.utils import numerical_utils +from keras.src.utils import tf_utils +from keras.src.utils.module_utils import tensorflow as tf + + +class IndexLookup(Layer): + """Maps values from a vocabulary to integer indices. + + This layer translates a set of arbitrary hashables into an integer output + via a table-based lookup, with optional out-of-vocabulary handling. This is + the basis layer for both IntegerLookup and StringLookup; it holds the common + logic but is not intended to be exported as part of the Keras API. + + Args: + max_tokens: The maximum size of the vocabulary for this layer. + If `None`, there is no cap on the size of the vocabulary. + Note that this size includes the OOV and mask tokens. + num_oov_indices: The number of out-of-vocabulary tokens to use. + If this value is more than 1, OOV inputs are hashed to determine + their OOV value. If this value is 0, + OOV inputs will cause an error when calling the layer. + mask_token: A token that represents masked inputs. + When `output_mode` is `"int"`, + the token is included in vocabulary and mapped to index 0. + In other output modes, the token will not appear in the vocabulary + and instances of the mask token in the input will be dropped. + If set to `None`, no mask term will be added. + oov_token: Only used when `invert` is `True`. + The token to return for OOV indices. + vocabulary: Optional. Either an array or a string path to a text file. + If passing an array, can pass a tuple, list, 1D numpy array, + or 1D tensor containing the vocbulary terms. + If passing a file path, the file should contain one line per term + in the vocabulary. If this argument is set, + there is no need to `adapt` the layer. + vocabulary_dtype: The dtype of the vocabulary terms. + For example, `"int64"` or `"string"`. + idf_weights: Only valid when `output_mode` is `"tf_idf"`. + A tuple, list, 1D numpy array, or 1D tensor or the same length + as the vocabulary, containing the floating point + inverse document frequency weights, which will be multiplied + by per sample term counts for the final TF-IDF + weight. If the `vocabulary` argument is set, and `output_mode` + is `"tf_idf"`, this argument must be supplied. + invert: Only valid when `output_mode` is `"int"`. + If `True`, this layer will map indices to vocabulary items + instead of mapping vocabulary items to indices. + Defaults to `False`. + output_mode: Specification for the output of the layer. Values can be + `"int"`, `"one_hot"`, `"multi_hot"`, `"count"`, or `"tf_idf"` + configuring the layer as follows: + - `"int"`: Return the raw integer indices of the input tokens. + - `"one_hot"`: Encodes each individual element in the input into an + array the same size as the vocabulary, containing a 1 + at the element index. If the last dimension is size 1, + will encode on that dimension. + If the last dimension is not size 1, + will append a new dimension for the encoded output. + - `"multi_hot"`: Encodes each sample in the input into + a single array the same size as the vocabulary, + containing a 1 for each vocabulary term present in the sample. + Treats the last dimension as the sample dimension, + if input shape is `(..., sample_length)`, output shape will + be `(..., num_tokens)`. + - `"count"`: As `"multi_hot"`, but the int array contains a count + of the number of times the token at that index appeared + in the sample. + - `"tf_idf"`: As `"multi_hot"`, but the TF-IDF algorithm + is applied to find the value in each token slot. + Defaults to `"int"`. + pad_to_max_tokens: Only valid when `output_mode` is `"multi_hot"`, + `"count"`, or `"tf_idf"`. If `True`, the output will have its + feature axis padded to `max_tokens` even if the number + of unique tokens in the vocabulary is less than max_tokens, + resulting in a tensor of shape `(batch_size, max_tokens)` + regardless of vocabulary size. Defaults to `False`. + sparse: Boolean. Only applicable to `"one_hot"`, `"multi_hot"`, + `"count"` and `"tf-idf"` output modes. + If `True`, returns a `SparseTensor` instead of a dense `Tensor`. + Defaults to `False`. + """ + + def __init__( + self, + max_tokens, + num_oov_indices, + mask_token, + oov_token, + vocabulary_dtype, + vocabulary=None, + idf_weights=None, + invert=False, + output_mode="int", + sparse=False, + pad_to_max_tokens=False, + name=None, + **kwargs, + ): + # If max_tokens is set, the value must be greater than 1 - otherwise we + # are creating a 0-element vocab, which doesn't make sense. + if max_tokens is not None and max_tokens <= 1: + raise ValueError( + "If set, `max_tokens` must be greater than 1. " + f"Received: max_tokens={max_tokens}" + ) + + if pad_to_max_tokens and max_tokens is None: + raise ValueError( + "If pad_to_max_tokens is True, must set `max_tokens`. " + f"Received: max_tokens={max_tokens}" + ) + + if num_oov_indices < 0: + raise ValueError( + "`num_oov_indices` must be greater than or equal to 0. " + f"Received: num_oov_indices={num_oov_indices}" + ) + + # Support deprecated names for output_modes. + if output_mode == "binary": + output_mode = "multi_hot" + if output_mode == "tf-idf": + output_mode = "tf_idf" + argument_validation.validate_string_arg( + output_mode, + allowable_strings=( + "int", + "one_hot", + "multi_hot", + "count", + "tf_idf", + ), + caller_name=self.__class__.__name__, + arg_name="output_mode", + ) + + if invert and output_mode != "int": + raise ValueError( + "`output_mode` must be `'int'` when `invert` is true. " + f"Received: output_mode={output_mode}" + ) + + if sparse and output_mode == "int": + raise ValueError( + "`sparse` may only be true if `output_mode` is " + "`'one_hot'`, `'multi_hot'`, `'count'` or `'tf_idf'`. " + f"Received: sparse={sparse} and " + f"output_mode={output_mode}" + ) + + if idf_weights is not None and output_mode != "tf_idf": + raise ValueError( + "`idf_weights` should only be set if `output_mode` is " + f"`'tf_idf'`. Received: idf_weights={idf_weights} and " + f"output_mode={output_mode}" + ) + + super().__init__(name=name) + self._convert_input_args = False + self._allow_non_tensor_positional_args = True + self.supports_jit = False + + self.invert = invert + self.max_tokens = max_tokens + self.num_oov_indices = num_oov_indices + self.mask_token = mask_token + self.oov_token = oov_token + self.output_mode = output_mode + self.sparse = sparse + self.pad_to_max_tokens = pad_to_max_tokens + self.vocabulary_dtype = tf.as_dtype(vocabulary_dtype).name + self._frozen_vocab_size = kwargs.pop("vocabulary_size", None) + + self.input_vocabulary = vocabulary + self.input_idf_weights = idf_weights + + # We set this hidden attr to + # persist the fact that we have have a non-adaptable layer with a + # manually set vocab. + self._has_input_vocabulary = kwargs.pop( + "has_input_vocabulary", (vocabulary is not None) + ) + kwargs.pop("trainable", None) + kwargs.pop("dtype", None) + if kwargs: + raise ValueError(f"Unrecognized keyword argument(s): {kwargs}") + + if invert: + self._key_dtype = "int64" + self._value_dtype = self.vocabulary_dtype + mask_key = 0 + mask_value = mask_token + self._default_value = self.oov_token + else: + self._key_dtype = self.vocabulary_dtype + self._value_dtype = "int64" + mask_key = mask_token + # Masks should map to 0 for int output and be dropped otherwise. Max + # ints will be dropped from the bincount op. + mask_value = ( + 0 + if self.output_mode == "int" + else tf.as_dtype(self._value_dtype).max + ) + if self.num_oov_indices == 0: + # If there are no OOV indices, we map OOV tokens to -1 and error + # out during call if we find a negative index. + self._default_value = -1 + elif self.num_oov_indices == 1: + # If there is only one OOV index, we can set that index as the + # default value of the index_lookup table. + self._default_value = self._oov_start_index() + else: + # If we have multiple OOV values, we need to do a further + # hashing step; to make this easier, we set the OOV value to -1. + # (This lets us do a vectorized add and cast to boolean to + # determine locations where we need to do extra hashing.) + self._default_value = -1 + if self.mask_token is not None: + self._mask_key = tf.convert_to_tensor(mask_key, self._key_dtype) + self._mask_value = tf.convert_to_tensor( + mask_value, self._value_dtype + ) + + if self.output_mode == "tf_idf": + if self._has_input_vocabulary and idf_weights is None: + raise ValueError( + "When specifying the `vocabulary` argument, " + "in TF-IDF output mode, the `idf_weights` argument " + "must also be provided." + ) + if idf_weights is not None: + self.idf_weights = tf.Variable( + idf_weights, + dtype=backend.floatx(), + trainable=False, + ) + self.idf_weights_const = self.idf_weights.value() + + if vocabulary is not None: + self.set_vocabulary(vocabulary, idf_weights) + else: + # When restoring from a keras SavedModel, the loading code will + # expect to find and restore a lookup_table attribute on the layer. + # This table needs to be uninitialized as a StaticHashTable cannot + # be initialized twice. + self.lookup_table = self._uninitialized_lookup_table() + + # Only set up adapt state if we did not receive a vocab on construction. + if not self._has_input_vocabulary: + # Set adapt state. + self.token_counts = tf.lookup.experimental.MutableHashTable( + key_dtype=vocabulary_dtype, + value_dtype="int64", + default_value=0, + ) + if self.output_mode == "tf_idf": + self.token_document_counts = ( + tf.lookup.experimental.MutableHashTable( + key_dtype=vocabulary_dtype, + value_dtype="int64", + default_value=0, + ) + ) + self.num_documents = tf.Variable( + 0, dtype="int64", trainable=False + ) + + def get_vocabulary(self, include_special_tokens=True): + """Returns the current vocabulary of the layer. + + Args: + include_special_tokens: If `True`, the returned vocabulary + will include mask and OOV tokens, + and a term's index in the vocabulary + will equal the term's index when calling the layer. + If `False`, the returned vocabulary will not include + any mask or OOV tokens. + """ + # The lookup table data will not be sorted, so we will create a inverted + # lookup here, and use that to lookup a range of indices + # [0, vocab_size). + if self.lookup_table.size() == 0: + vocab, indices = [], [] + else: + keys, values = self.lookup_table.export() + vocab, indices = (values, keys) if self.invert else (keys, values) + vocab, indices = ( + self._tensor_vocab_to_numpy(vocab), + indices.numpy(), + ) + lookup = collections.defaultdict( + lambda: self.oov_token, zip(indices, vocab) + ) + vocab = [lookup[x] for x in range(self.vocabulary_size())] + if self.mask_token is not None and self.output_mode == "int": + vocab[0] = self.mask_token + if not include_special_tokens: + vocab = vocab[self._token_start_index() :] + if self.vocabulary_dtype == "string": + return [ + i.decode("utf-8") if isinstance(i, bytes) else i for i in vocab + ] + else: + return vocab + + def vocabulary_size(self): + """Gets the current size of the layer's vocabulary. + + Returns: + The integer size of the vocabulary, including optional mask and oov + indices. + """ + if tf.executing_eagerly(): + return ( + int(self.lookup_table.size().numpy()) + + self._token_start_index() + ) + else: + return self.lookup_table.size() + self._token_start_index() + + def get_config(self): + config = { + "invert": self.invert, + "max_tokens": self.max_tokens, + "num_oov_indices": self.num_oov_indices, + "oov_token": self.oov_token, + "mask_token": self.mask_token, + "output_mode": self.output_mode, + "sparse": self.sparse, + "pad_to_max_tokens": self.pad_to_max_tokens, + "vocabulary_dtype": self.vocabulary_dtype, + "idf_weights": listify_tensors(self.input_idf_weights), + "vocabulary": listify_tensors(self.input_vocabulary), + "vocabulary_size": self._frozen_vocab_size, + } + base_config = super().get_config() + return dict(list(base_config.items()) + list(config.items())) + + def _record_vocabulary_size(self): + self._ensure_vocab_size_unchanged() + with tf.init_scope(): + self._frozen_vocab_size = self.vocabulary_size() + + def set_vocabulary(self, vocabulary, idf_weights=None): + """Sets vocabulary (and optionally document frequency) for this layer. + + This method sets the vocabulary and idf weights for this layer directly, + instead of analyzing a dataset through `adapt`. It should be used + whenever the vocab (and optionally document frequency) information is + already known. If vocabulary data is already present in the layer, this + method will replace it. + + Args: + vocabulary: Either an array or a string path to a text file. + If passing an array, can pass a tuple, list, + 1D numpy array, or 1D tensor containing the vocbulary terms. + If passing a file path, the file should contain one line + per term in the vocabulary. + idf_weights: A tuple, list, 1D numpy array, or 1D tensor + of inverse document frequency weights with equal + length to vocabulary. Must be set if `output_mode` + is `"tf_idf"`. Should not be set otherwise. + """ + if self.output_mode == "tf_idf": + if idf_weights is None: + raise ValueError( + "`idf_weights` must be set if output_mode is 'tf_idf'." + ) + elif idf_weights is not None: + raise ValueError( + "`idf_weights` should only be set if output_mode is " + f"`'tf_idf'`. Received: output_mode={self.output_mode} " + f"and idf_weights={idf_weights}" + ) + + if isinstance(vocabulary, str): + if not tf.io.gfile.exists(vocabulary): + raise ValueError( + f"Vocabulary file {vocabulary} does not exist." + ) + if self.output_mode == "tf_idf": + raise ValueError( + "output_mode `'tf_idf'` does not support loading a " + "vocabulary from file." + ) + self.lookup_table = self._lookup_table_from_file(vocabulary) + self._record_vocabulary_size() + return + + if not tf.executing_eagerly() and ( + tf.is_tensor(vocabulary) or tf.is_tensor(idf_weights) + ): + raise RuntimeError( + f"Cannot set a tensor vocabulary on layer {self.name} " + "when not executing eagerly. " + "Create this layer or call `set_vocabulary()` " + "outside of any traced function." + ) + + # TODO(mattdangerw): for better performance we should rewrite this + # entire function to operate on tensors and convert vocabulary to a + # tensor here. + if tf.is_tensor(vocabulary): + vocabulary = self._tensor_vocab_to_numpy(vocabulary) + elif isinstance(vocabulary, (list, tuple)): + vocabulary = np.array(vocabulary) + if tf.is_tensor(idf_weights): + idf_weights = idf_weights.numpy() + elif isinstance(idf_weights, (list, tuple)): + idf_weights = np.array(idf_weights) + + if vocabulary.size == 0: + raise ValueError( + "Cannot set an empty vocabulary. " + f"Received: vocabulary={vocabulary}" + ) + + oov_start = self._oov_start_index() + token_start = self._token_start_index() + special_tokens = [self.mask_token] * oov_start + [ + self.oov_token + ] * self.num_oov_indices + found_special_tokens = np.array_equal( + special_tokens, vocabulary[:token_start] + ) + if found_special_tokens: + tokens = vocabulary[token_start:] + else: + tokens = vocabulary + + repeated_tokens = self._find_repeated_tokens(tokens) + if repeated_tokens: + raise ValueError( + "The passed vocabulary has at least one repeated " + "term. Please uniquify your dataset. The repeated terms " + f"are: {repeated_tokens}" + ) + + if self.mask_token is not None and self.mask_token in tokens: + mask_index = np.argwhere(vocabulary == self.mask_token)[-1] + raise ValueError( + "Found reserved mask token at unexpected location in " + "`vocabulary`. Note that passed `vocabulary` does not need to " + "include the OOV and mask tokens. Either remove all mask and " + "OOV tokens, or include them only at the start of the " + f"vocabulary in precisely this order: {special_tokens}. " + f"Received: mask_token={self.mask_token} at " + f"vocabulary index {mask_index}" + ) + # Only error out for oov_token when invert=True. When invert=False, + # oov_token is unused during lookup. + if ( + self.oov_token is not None + and self.invert + and self.oov_token in tokens + ): + oov_index = np.argwhere(vocabulary == self.oov_token)[-1] + raise ValueError( + "Found reserved OOV token at unexpected location in " + "`vocabulary`. Note that passed `vocabulary` does not need to " + "include the OOV and mask tokens. Either remove all mask and " + "OOV tokens, or include them only at the start of the " + f"vocabulary in precisely this order: {special_tokens}. " + f"Received: oov_token={self.oov_token} at " + f"vocabulary index {oov_index}" + ) + + new_vocab_size = token_start + len(tokens) + if self.max_tokens is not None and (new_vocab_size > self.max_tokens): + raise ValueError( + "Attempted to set a vocabulary larger than the maximum vocab " + f"size. Received vocabulary size is {new_vocab_size}; " + f"`max_tokens` is {self.max_tokens}." + ) + self.lookup_table = self._lookup_table_from_tokens(tokens) + self._record_vocabulary_size() + + if self.output_mode == "tf_idf" and idf_weights is not None: + if len(vocabulary) != len(idf_weights): + raise ValueError( + "`idf_weights` must be the same length as vocabulary. " + f"len(idf_weights) is {len(idf_weights)}; " + f"len(vocabulary) is {len(vocabulary)}" + ) + idf_weights = self._convert_to_ndarray(idf_weights) + if idf_weights.ndim != 1: + raise ValueError( + "TF-IDF data must be a 1-index array. " + f"Received: type(idf_weights)={type(idf_weights)}" + ) + + # If the passed vocabulary has no special tokens, we need to pad the + # front of idf_weights. We don't have real document frequencies for + # these tokens so we will use an average of all idf_weights passed + # in as a reasonable default. + if found_special_tokens: + front_padding = 0 + front_padding_value = 0 + else: + front_padding = token_start + front_padding_value = np.average(idf_weights) + # If pad_to_max_tokens is true, and max_tokens is greater than our + # total vocab size, we need to pad the back of idf_weights with + # zeros as well. + back_padding_value = 0 + if self.pad_to_max_tokens and self.max_tokens is not None: + back_padding = ( + self.max_tokens - front_padding - len(idf_weights) + ) + else: + back_padding = 0 + weights = np.pad( + idf_weights, + (front_padding, back_padding), + "constant", + constant_values=(front_padding_value, back_padding_value), + ) + weights = tf.convert_to_tensor(weights, dtype=backend.floatx()) + self.idf_weights = tf.Variable( + weights, + trainable=False, + ) + self.idf_weights_const = self.idf_weights.value() + + def build(self): + self.built = True + + def get_build_config(self): + return {} + + def build_from_config(self, config): + self.build() + + @property + def compute_dtype(self): + return self.vocabulary_dtype + + @property + def variable_dtype(self): + return self.vocabulary_dtype + + def compute_output_shape(self, input_shape): + if self.output_mode == "int": + return input_shape + depth = ( + self.max_tokens + if self.pad_to_max_tokens + else self._frozen_vocab_size + ) + return (input_shape[0], depth) + + def compute_output_spec(self, inputs): + if self.output_mode == "int": + output_dtype = "int64" + else: + output_dtype = backend.floatx() + output_shape = self.compute_output_shape(inputs.shape) + return backend.KerasTensor(output_shape, dtype=output_dtype) + + def adapt(self, data, steps=None): + self.reset_state() + if isinstance(data, tf.data.Dataset): + if steps is not None: + data = data.take(steps) + for batch in data: + self.update_state(batch) + else: + data = tf_utils.ensure_tensor(data, dtype=self.vocabulary_dtype) + if data.shape.rank == 1: + # A plain list of strings + # is treated as as many documents + data = tf.expand_dims(data, -1) + self.update_state(data) + self.finalize_state() + + def update_state(self, data): + if self._has_input_vocabulary: + raise ValueError( + f"Cannot adapt layer '{self.name}' after setting a static " + "vocabulary via `vocabulary` argument or " + "`set_vocabulary()` method." + ) + + data = tf_utils.ensure_tensor(data, dtype=self.vocabulary_dtype) + if data.shape.rank == 0: + data = tf.expand_dims(data, 0) + if data.shape.rank == 1: + # Expand dims on axis 0 for tf-idf. A 1-d tensor + # is a single document. + data = tf.expand_dims(data, 0) + + tokens, counts = self._num_tokens(data) + self.token_counts.insert( + tokens, counts + self.token_counts.lookup(tokens) + ) + + if self.output_mode == "tf_idf": + # Dedupe each row of our dataset. + if isinstance(data, tf.RaggedTensor): + deduped_doc_data = tf.map_fn(lambda x: tf.unique(x)[0], data) + else: + deduped_doc_data = [tf.unique(x)[0] for x in data] + deduped_doc_data = tf.concat(deduped_doc_data, axis=0) + # Flatten and count tokens. + tokens, counts = self._num_tokens(deduped_doc_data) + + self.token_document_counts.insert( + tokens, counts + self.token_document_counts.lookup(tokens) + ) + if isinstance(data, tf.RaggedTensor): + self.num_documents.assign_add(data.nrows()) + else: + self.num_documents.assign_add( + tf.shape(data, out_type="int64")[0] + ) + + def finalize_state(self): + if self._has_input_vocabulary or tf.equal(self.token_counts.size(), 0): + # Finalize idf_weights to a const for call even if we don't need to + # compute a new vocabulary. + if self.output_mode == "tf_idf": + self.idf_weights_const = self.idf_weights.value() + self._record_vocabulary_size() + return + + # Remove special tokens from our counts. + if self.mask_token is not None: + self.token_counts.remove( + tf.convert_to_tensor([self.mask_token], self.vocabulary_dtype) + ) + if self.oov_token is not None: + self.token_counts.remove( + tf.convert_to_tensor([self.oov_token], self.vocabulary_dtype) + ) + + tokens, counts = self.token_counts.export() + # To keep vocabs deterministic, we sort our tokens by count and break + # ties by sorting the tokens themselves. Tensorflow has no ops for + # sorting strings, so we need to use numpy for the sort. + sorted_indices = np.lexsort((tokens.numpy(), counts.numpy()))[::-1] + token_start = self._token_start_index() + if self.max_tokens: + max_learned_tokens = self.max_tokens - token_start + sorted_indices = sorted_indices[:max_learned_tokens] + tokens = tf.gather(tokens, sorted_indices) + self.lookup_table = self._lookup_table_from_tokens(tokens) + + if self.output_mode == "tf_idf": + token_document_counts = self.token_document_counts.lookup(tokens) + idf_weights = self._inverse_document_frequency( + token_document_counts, self.num_documents + ) + idf_weights = tf.cast(idf_weights, backend.floatx()) + # Pad the front of idf_weights with the average idf weight for OOV + # tokens. We cannot compute the real idf weight of OOV in a single + # pass. + idf_weights = tf.pad( + idf_weights, + [[self._token_start_index(), 0]], + constant_values=tf.reduce_mean(idf_weights), + ) + if self.pad_to_max_tokens and self.max_tokens is not None: + # Pad the back of idf_weights with zeros. + idf_weights = tf.pad( + idf_weights, + [[0, self.max_tokens - tf.size(idf_weights)]], + constant_values=0, + ) + self.idf_weights = tf.Variable( + idf_weights, + dtype=backend.floatx(), + trainable=False, + ) + self.idf_weights_const = self.idf_weights.value() + + # We call this here to save memory, now that we've built our vocabulary, + # we don't want to keep every token we've seen in separate lookup + # tables. + self.reset_state() + self._record_vocabulary_size() + + def reset_state(self): + if self._has_input_vocabulary: + return + + self.token_counts.remove(self.token_counts.export()[0]) + if self.output_mode == "tf_idf": + self.token_document_counts.remove( + self.token_document_counts.export()[0] + ) + self.num_documents.assign(0) + + def call(self, inputs): + from keras.src.backend import tensorflow as tf_backend + + self._ensure_known_vocab_size() + + inputs = tf_utils.ensure_tensor(inputs, dtype=self._key_dtype) + original_shape = inputs.shape + # Some ops will not handle scalar input, so uprank to rank 1. + if inputs.shape.rank == 0: + inputs = self._expand_dims(inputs, -1) + + if isinstance(inputs, tf.SparseTensor): + lookups = tf.SparseTensor( + inputs.indices, + self._lookup_dense(inputs.values), + inputs.dense_shape, + ) + elif isinstance(inputs, tf.RaggedTensor): + lookups = tf.ragged.map_flat_values(self._lookup_dense, inputs) + else: + lookups = self._lookup_dense(inputs) + + if self.output_mode == "int": + # If we received a scalar input, downrank back to a scalar. + if original_shape.rank == 0: + lookups = tf.squeeze(lookups, -1) + return lookups + + depth = ( + self.max_tokens + if self.pad_to_max_tokens + else self._frozen_vocab_size + ) + idf_weights = ( + self.idf_weights_const if self.output_mode == "tf_idf" else None + ) + output = numerical_utils.encode_categorical_inputs( + lookups, + output_mode=( + "count" if self.output_mode == "tf_idf" else self.output_mode + ), + depth=depth, + dtype=self._value_dtype, + sparse=self.sparse, + backend_module=tf_backend, + ) + if self.output_mode == "tf_idf": + if idf_weights is None: + raise ValueError( + "When `output_mode` is `'tf_idf'`, `idf_weights` must be " + "provided." + ) + output = tf_backend.numpy.multiply( + tf_backend.core.cast(output, idf_weights.dtype), idf_weights + ) + return output + + def _lookup_dense(self, inputs): + """Lookup table values for a dense Tensor, handling masking and OOV.""" + # When executing eagerly and tracing keras.Input objects, + # do not call lookup. + # This is critical for restoring SavedModel, which will first trace + # layer.call and then attempt to restore the table. We need the table to + # be uninitialized for the restore to work, but calling the table + # uninitialized would error. + if tf.executing_eagerly() and backend.is_keras_tensor(inputs): + lookups = tf.zeros_like(inputs, dtype=self._value_dtype) + else: + lookups = self.lookup_table.lookup(inputs) + + if self.mask_token is not None: + mask_locations = tf.equal(inputs, self._mask_key) + lookups = tf.where(mask_locations, self._mask_value, lookups) + + if self.invert: + return lookups + + lookup_checks = [] + + if self.num_oov_indices == 0: + # If we have zero oov indices, we need to check for oov inputs. + oov_indices = tf.where(tf.equal(lookups, -1)) + oov_inputs = tf.gather_nd(inputs, oov_indices) + msg = tf.strings.format( + "When `num_oov_indices=0` all inputs should be in vocabulary, " + "found OOV values {}, consider setting `num_oov_indices=1`.", + (oov_inputs,), + ) + assertion = tf.Assert(tf.equal(tf.size(oov_indices), 0), [msg]) + lookup_checks.append(assertion) + elif self.num_oov_indices > 1: + # If we have multiple oov indices, we need a further hashing step. + if tf.as_dtype(self._key_dtype).is_integer: + oov_indices = tf.math.floormod(inputs, self.num_oov_indices) + else: + oov_indices = tf.strings.to_hash_bucket_fast( + inputs, num_buckets=self.num_oov_indices + ) + oov_indices = oov_indices + self._oov_start_index() + oov_locations = tf.equal(lookups, self._default_value) + lookups = tf.where(oov_locations, oov_indices, lookups) + + with tf.control_dependencies(lookup_checks): + return tf.identity(lookups) + + def save_own_variables(self, store): + if self.output_mode == "tf_idf": + store["idf_weights"] = self.idf_weights_const.numpy() + + def load_own_variables(self, store): + if self.output_mode == "tf_idf": + self.idf_weights.assign(store["idf_weights"]) + self.idf_weights_const = self.idf_weights.value() + + def save_assets(self, dir_path): + if self.input_vocabulary is not None: + # Vocab saved in config. + # TODO: consider unifying both paths. + return + vocabulary = self.get_vocabulary(include_special_tokens=True) + vocabulary_filepath = tf.io.gfile.join(dir_path, "vocabulary.txt") + with open(vocabulary_filepath, "w") as f: + f.write("\n".join([str(w) for w in vocabulary])) + + def load_assets(self, dir_path): + if self.input_vocabulary is not None: + # Vocab saved in config. + # TODO: consider unifying both paths. + return + vocabulary_filepath = tf.io.gfile.join(dir_path, "vocabulary.txt") + # TODO: fix bug with include_special_tokens and set reload from file. + with open(vocabulary_filepath, "r") as f: + lines = f.read().split("\n") + if tf.as_dtype(self.vocabulary_dtype) == tf.string: + values = [str(line) for line in lines] + else: + values = [int(line) for line in lines] + if self.output_mode == "tf_idf": + self.set_vocabulary(values, idf_weights=False) + else: + self.set_vocabulary(values) + + def _uninitialized_lookup_table(self): + with tf.init_scope(): + initializer = get_null_initializer( + self._key_dtype, self._value_dtype + ) + return tf.lookup.StaticHashTable(initializer, self._default_value) + + def _lookup_table_from_tokens(self, tokens): + with tf.init_scope(): + token_start = self._token_start_index() + token_end = token_start + tf.size(tokens) + indices_dtype = ( + self._key_dtype if self.invert else self._value_dtype + ) + indices = tf.range(token_start, token_end, dtype=indices_dtype) + keys, values = ( + (indices, tokens) if self.invert else (tokens, indices) + ) + initializer = tf.lookup.KeyValueTensorInitializer( + keys, values, self._key_dtype, self._value_dtype + ) + return tf.lookup.StaticHashTable(initializer, self._default_value) + + def _lookup_table_from_file(self, filename): + if self.invert: + key_index = tf.lookup.TextFileIndex.LINE_NUMBER + value_index = tf.lookup.TextFileIndex.WHOLE_LINE + else: + key_index = tf.lookup.TextFileIndex.WHOLE_LINE + value_index = tf.lookup.TextFileIndex.LINE_NUMBER + with tf.init_scope(): + initializer = tf.lookup.TextFileInitializer( + filename=filename, + key_dtype=self._key_dtype, + key_index=key_index, + value_dtype=self._value_dtype, + value_index=value_index, + value_index_offset=self._token_start_index(), + ) + return tf.lookup.StaticHashTable(initializer, self._default_value) + + def _convert_to_ndarray(self, x): + return np.array(x) if isinstance(x, (list, tuple)) else x + + def _expand_dims(self, inputs, axis): + if isinstance(inputs, tf.SparseTensor): + return tf.sparse.expand_dims(inputs, axis) + else: + return tf.expand_dims(inputs, axis) + + def _oov_start_index(self): + return ( + 1 + if self.mask_token is not None and self.output_mode == "int" + else 0 + ) + + def _token_start_index(self): + return self._oov_start_index() + self.num_oov_indices + + def _ensure_known_vocab_size(self): + if self.output_mode == "int" or self.pad_to_max_tokens: + return + if self._frozen_vocab_size is None: + raise RuntimeError( + f"When using `output_mode={self.output_mode}` " + "and `pad_to_max_tokens=False`, " + "you must set the layer's vocabulary before calling it. Either " + "pass a `vocabulary` argument to the layer, or call `adapt` " + "with some sample data." + ) + + def _ensure_vocab_size_unchanged(self): + if self.output_mode == "int" or self.pad_to_max_tokens: + return + + with tf.init_scope(): + new_vocab_size = self.vocabulary_size() + + if ( + self._frozen_vocab_size is not None + and new_vocab_size != self._frozen_vocab_size + ): + raise RuntimeError( + f"When using `output_mode={self.output_mode}` " + "and `pad_to_max_tokens=False`, " + "the vocabulary size cannot be changed after the layer is " + f"called. Old vocab size is {self._frozen_vocab_size}, " + f"new vocab size is {new_vocab_size}" + ) + + def _find_repeated_tokens(self, vocabulary): + """Return all repeated tokens in a vocabulary.""" + vocabulary_set = set(vocabulary) + if len(vocabulary) != len(vocabulary_set): + return [ + item + for item, count in collections.Counter(vocabulary).items() + if count > 1 + ] + else: + return [] + + def _num_tokens(self, data): + """Count the number of tokens in a ragged, sparse or dense tensor.""" + if isinstance(data, tf.SparseTensor): + flat_values = data.values + elif isinstance(data, tf.RaggedTensor): + flat_values = data.flat_values + else: + flat_values = tf.reshape(data, [-1]) + tokens, _, counts = tf.unique_with_counts(flat_values, out_idx="int64") + return tokens, counts + + def _inverse_document_frequency(self, token_document_counts, num_documents): + """Computes the inverse-document-frequency (IDF) component of "tf_idf". + Args: + token_document_counts: An array of the # of documents each token + appears in. + num_documents: An int representing the total number of documents + + Returns: + An array of "inverse document frequency" weights. + """ + return tf.math.log(1 + num_documents / (1 + token_document_counts)) + + # Override points for IntegerLookup and StringLookup. + def _tensor_vocab_to_numpy(self, vocabulary): + """Converts a tensor vocabulary to a numpy vocabulary.""" + return vocabulary.numpy() + + +def get_null_initializer(key_dtype, value_dtype): + class NullInitializer(tf.lookup.KeyValueTensorInitializer): + """A placeholder initializer for restoring from a SavedModel.""" + + def __init__(self, key_dtype, value_dtype): + """Construct a table initializer object. + + Args: + key_dtype: Type of the table keys. + value_dtype: Type of the table values. + """ + self._key_dtype = key_dtype + self._value_dtype = value_dtype + + @property + def key_dtype(self): + """The expected table key dtype.""" + return self._key_dtype + + @property + def value_dtype(self): + """The expected table value dtype.""" + return self._value_dtype + + def initialize(self, table): + """Returns the table initialization op.""" + pass + + return NullInitializer(key_dtype, value_dtype) + + +def listify_tensors(x): + """Convert any tensors or numpy arrays to lists for config serialization.""" + if tf.is_tensor(x): + x = x.numpy() + if isinstance(x, np.ndarray): + x = x.tolist() + return x diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/integer_lookup.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/integer_lookup.py new file mode 100644 index 0000000000000000000000000000000000000000..bf357552e57e60f1824c05be161cd70662a7fb10 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/integer_lookup.py @@ -0,0 +1,405 @@ +import numpy as np + +from keras.src import backend +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.index_lookup import IndexLookup +from keras.src.utils import backend_utils +from keras.src.utils.module_utils import tensorflow as tf + + +@keras_export("keras.layers.IntegerLookup") +class IntegerLookup(IndexLookup): + """A preprocessing layer that maps integers to (possibly encoded) indices. + + This layer maps a set of arbitrary integer input tokens into indexed integer + output via a table-based vocabulary lookup. The layer's output indices will + be contiguously arranged up to the maximum vocab size, even if the input + tokens are non-continguous or unbounded. The layer supports multiple options + for encoding the output via `output_mode`, and has optional support for + out-of-vocabulary (OOV) tokens and masking. + + The vocabulary for the layer must be either supplied on construction or + learned via `adapt()`. During `adapt()`, the layer will analyze a data set, + determine the frequency of individual integer tokens, and create a + vocabulary from them. If the vocabulary is capped in size, the most frequent + tokens will be used to create the vocabulary and all others will be treated + as OOV. + + There are two possible output modes for the layer. When `output_mode` is + `"int"`, input integers are converted to their index in the vocabulary (an + integer). When `output_mode` is `"multi_hot"`, `"count"`, or `"tf_idf"`, + input integers are encoded into an array where each dimension corresponds to + an element in the vocabulary. + + The vocabulary can optionally contain a mask token as well as an OOV token + (which can optionally occupy multiple indices in the vocabulary, as set + by `num_oov_indices`). + The position of these tokens in the vocabulary is fixed. When `output_mode` + is `"int"`, the vocabulary will begin with the mask token at index 0, + followed by OOV indices, followed by the rest of the vocabulary. When + `output_mode` is `"multi_hot"`, `"count"`, or `"tf_idf"` the vocabulary will + begin with OOV indices and instances of the mask token will be dropped. + + **Note:** This layer uses TensorFlow internally. It cannot + be used as part of the compiled computation graph of a model with + any backend other than TensorFlow. + It can however be used with any backend when running eagerly. + It can also always be used as part of an input preprocessing pipeline + with any backend (outside the model itself), which is how we recommend + to use this layer. + + **Note:** This layer is safe to use inside a `tf.data` pipeline + (independently of which backend you're using). + + Args: + max_tokens: Maximum size of the vocabulary for this layer. This should + only be specified when adapting the vocabulary or when setting + `pad_to_max_tokens=True`. If None, there is no cap on the size of + the vocabulary. Note that this size includes the OOV + and mask tokens. Defaults to `None`. + num_oov_indices: The number of out-of-vocabulary tokens to use. + If this value is more than 1, OOV inputs are modulated to + determine their OOV value. + If this value is 0, OOV inputs will cause an error when calling + the layer. Defaults to `1`. + mask_token: An integer token that represents masked inputs. When + `output_mode` is `"int"`, the token is included in vocabulary + and mapped to index 0. In other output modes, + the token will not appear in the vocabulary and instances + of the mask token in the input will be dropped. + If set to None, no mask term will be added. Defaults to `None`. + oov_token: Only used when `invert` is `True`. The token to return + for OOV indices. Defaults to `-1`. + vocabulary: Optional. Either an array of integers or a string path to a + text file. If passing an array, can pass a tuple, list, + 1D NumPy array, or 1D tensor containing the integer vocbulary terms. + If passing a file path, the file should contain one line per term + in the vocabulary. If this argument is set, + there is no need to `adapt()` the layer. + vocabulary_dtype: The dtype of the vocabulary terms, for example + `"int64"` or `"int32"`. Defaults to `"int64"`. + idf_weights: Only valid when `output_mode` is `"tf_idf"`. + A tuple, list, 1D NumPy array, or 1D tensor or the same length + as the vocabulary, containing the floating point inverse document + frequency weights, which will be multiplied by per sample term + counts for the final TF-IDF weight. + If the `vocabulary` argument is set, and `output_mode` is + `"tf_idf"`, this argument must be supplied. + invert: Only valid when `output_mode` is `"int"`. + If `True`, this layer will map indices to vocabulary items + instead of mapping vocabulary items to indices. + Defaults to `False`. + output_mode: Specification for the output of the layer. Values can be + `"int"`, `"one_hot"`, `"multi_hot"`, `"count"`, or `"tf_idf"` + configuring the layer as follows: + - `"int"`: Return the vocabulary indices of the input tokens. + - `"one_hot"`: Encodes each individual element in the input into an + array the same size as the vocabulary, + containing a 1 at the element index. If the last dimension + is size 1, will encode on that dimension. + If the last dimension is not size 1, will append a new + dimension for the encoded output. + - `"multi_hot"`: Encodes each sample in the input into a single + array the same size as the vocabulary, + containing a 1 for each vocabulary term present in the sample. + Treats the last dimension as the sample dimension, + if input shape is `(..., sample_length)`, + output shape will be `(..., num_tokens)`. + - `"count"`: As `"multi_hot"`, but the int array contains + a count of the number of times the token at that index + appeared in the sample. + - `"tf_idf"`: As `"multi_hot"`, but the TF-IDF algorithm is + applied to find the value in each token slot. + For `"int"` output, any shape of input and output is supported. + For all other output modes, currently only output up to rank 2 + is supported. Defaults to `"int"`. + pad_to_max_tokens: Only applicable when `output_mode` is `"multi_hot"`, + `"count"`, or `"tf_idf"`. If `True`, the output will have + its feature axis padded to `max_tokens` even if the number + of unique tokens in the vocabulary is less than `max_tokens`, + resulting in a tensor of shape `(batch_size, max_tokens)` + regardless of vocabulary size. Defaults to `False`. + sparse: Boolean. Only applicable to `"multi_hot"`, `"count"`, and + `"tf_idf"` output modes. Only supported with TensorFlow + backend. If `True`, returns a `SparseTensor` + instead of a dense `Tensor`. Defaults to `False`. + + Examples: + + **Creating a lookup layer with a known vocabulary** + + This example creates a lookup layer with a pre-existing vocabulary. + + >>> vocab = [12, 36, 1138, 42] + >>> data = np.array([[12, 1138, 42], [42, 1000, 36]]) # Note OOV tokens + >>> layer = IntegerLookup(vocabulary=vocab) + >>> layer(data) + array([[1, 3, 4], + [4, 0, 2]]) + + **Creating a lookup layer with an adapted vocabulary** + + This example creates a lookup layer and generates the vocabulary by + analyzing the dataset. + + >>> data = np.array([[12, 1138, 42], [42, 1000, 36]]) + >>> layer = IntegerLookup() + >>> layer.adapt(data) + >>> layer.get_vocabulary() + [-1, 42, 1138, 1000, 36, 12] + + Note that the OOV token -1 have been added to the vocabulary. The remaining + tokens are sorted by frequency (42, which has 2 occurrences, is first) then + by inverse sort order. + + >>> data = np.array([[12, 1138, 42], [42, 1000, 36]]) + >>> layer = IntegerLookup() + >>> layer.adapt(data) + >>> layer(data) + array([[5, 2, 1], + [1, 3, 4]]) + + **Lookups with multiple OOV indices** + + This example demonstrates how to use a lookup layer with multiple OOV + indices. When a layer is created with more than one OOV index, any OOV + tokens are hashed into the number of OOV buckets, distributing OOV tokens in + a deterministic fashion across the set. + + >>> vocab = [12, 36, 1138, 42] + >>> data = np.array([[12, 1138, 42], [37, 1000, 36]]) + >>> layer = IntegerLookup(vocabulary=vocab, num_oov_indices=2) + >>> layer(data) + array([[2, 4, 5], + [1, 0, 3]]) + + Note that the output for OOV token 37 is 1, while the output for OOV token + 1000 is 0. The in-vocab terms have their output index increased by 1 from + earlier examples (12 maps to 2, etc) in order to make space for the extra + OOV token. + + **One-hot output** + + Configure the layer with `output_mode='one_hot'`. Note that the first + `num_oov_indices` dimensions in the ont_hot encoding represent OOV values. + + >>> vocab = [12, 36, 1138, 42] + >>> data = np.array([12, 36, 1138, 42, 7]) # Note OOV tokens + >>> layer = IntegerLookup(vocabulary=vocab, output_mode='one_hot') + >>> layer(data) + array([[0., 1., 0., 0., 0.], + [0., 0., 1., 0., 0.], + [0., 0., 0., 1., 0.], + [0., 0., 0., 0., 1.], + [1., 0., 0., 0., 0.]], dtype=float32) + + **Multi-hot output** + + Configure the layer with `output_mode='multi_hot'`. Note that the first + `num_oov_indices` dimensions in the multi_hot encoding represent OOV tokens + + >>> vocab = [12, 36, 1138, 42] + >>> data = np.array([[12, 1138, 42, 42], + ... [42, 7, 36, 7]]) # Note OOV tokens + >>> layer = IntegerLookup(vocabulary=vocab, output_mode='multi_hot') + >>> layer(data) + array([[0., 1., 0., 1., 1.], + [1., 0., 1., 0., 1.]], dtype=float32) + + **Token count output** + + Configure the layer with `output_mode='count'`. As with multi_hot output, + the first `num_oov_indices` dimensions in the output represent OOV tokens. + + >>> vocab = [12, 36, 1138, 42] + >>> data = np.array([[12, 1138, 42, 42], + ... [42, 7, 36, 7]]) # Note OOV tokens + >>> layer = IntegerLookup(vocabulary=vocab, output_mode='count') + >>> layer(data) + array([[0., 1., 0., 1., 2.], + [2., 0., 1., 0., 1.]], dtype=float32) + + **TF-IDF output** + + Configure the layer with `output_mode='tf_idf'`. As with multi_hot output, + the first `num_oov_indices` dimensions in the output represent OOV tokens. + + Each token bin will output `token_count * idf_weight`, where the idf weights + are the inverse document frequency weights per token. These should be + provided along with the vocabulary. Note that the `idf_weight` for OOV + tokens will default to the average of all idf weights passed in. + + >>> vocab = [12, 36, 1138, 42] + >>> idf_weights = [0.25, 0.75, 0.6, 0.4] + >>> data = np.array([[12, 1138, 42, 42], + ... [42, 7, 36, 7]]) # Note OOV tokens + >>> layer = IntegerLookup( + ... output_mode='tf_idf', vocabulary=vocab, idf_weights=idf_weights) + >>> layer(data) + array([[0. , 0.25, 0. , 0.6 , 0.8 ], + [1.0 , 0. , 0.75, 0. , 0.4 ]], dtype=float32) + + To specify the idf weights for oov tokens, you will need to pass the entire + vocabulary including the leading oov token. + + >>> vocab = [-1, 12, 36, 1138, 42] + >>> idf_weights = [0.9, 0.25, 0.75, 0.6, 0.4] + >>> data = np.array([[12, 1138, 42, 42], + ... [42, 7, 36, 7]]) # Note OOV tokens + >>> layer = IntegerLookup( + ... output_mode='tf_idf', vocabulary=vocab, idf_weights=idf_weights) + >>> layer(data) + array([[0. , 0.25, 0. , 0.6 , 0.8 ], + [1.8 , 0. , 0.75, 0. , 0.4 ]], dtype=float32) + + When adapting the layer in `"tf_idf"` mode, each input sample will + be considered a document, and IDF weight per token will be + calculated as: + `log(1 + num_documents / (1 + token_document_count))`. + + **Inverse lookup** + + This example demonstrates how to map indices to tokens using this layer. + (You can also use `adapt()` with `inverse=True`, but for simplicity we'll + pass the vocab in this example.) + + >>> vocab = [12, 36, 1138, 42] + >>> data = np.array([[1, 3, 4], [4, 0, 2]]) + >>> layer = IntegerLookup(vocabulary=vocab, invert=True) + >>> layer(data) + array([[ 12, 1138, 42], + [ 42, -1, 36]]) + + Note that the first index correspond to the oov token by default. + + **Forward and inverse lookup pairs** + + This example demonstrates how to use the vocabulary of a standard lookup + layer to create an inverse lookup layer. + + >>> vocab = [12, 36, 1138, 42] + >>> data = np.array([[12, 1138, 42], [42, 1000, 36]]) + >>> layer = IntegerLookup(vocabulary=vocab) + >>> i_layer = IntegerLookup( + ... vocabulary=layer.get_vocabulary(), invert=True) + >>> int_data = layer(data) + >>> i_layer(int_data) + array([[ 12, 1138, 42], + [ 42, -1, 36]]) + + In this example, the input token 1000 resulted in an output of -1, since + 1000 was not in the vocabulary - it got represented as an OOV, and all OOV + tokens are returned as -1 in the inverse layer. Also, note that for the + inverse to work, you must have already set the forward layer vocabulary + either directly or via `adapt()` before calling `get_vocabulary()`. + """ + + def __init__( + self, + max_tokens=None, + num_oov_indices=1, + mask_token=None, + oov_token=-1, + vocabulary=None, + vocabulary_dtype="int64", + idf_weights=None, + invert=False, + output_mode="int", + sparse=False, + pad_to_max_tokens=False, + name=None, + **kwargs, + ): + if not tf.available: + raise ImportError( + "Layer IntegerLookup requires TensorFlow. " + "Install it via `pip install tensorflow`." + ) + if max_tokens is not None and max_tokens <= 1: + raise ValueError( + "If `max_tokens` is set for `IntegerLookup`, it must be " + f"greater than 1. Received: max_tokens={max_tokens}" + ) + if num_oov_indices < 0: + raise ValueError( + "The value of `num_oov_indices` argument for `IntegerLookup` " + "must >= 0. Received: num_oov_indices=" + f"{num_oov_indices}" + ) + if sparse and backend.backend() != "tensorflow": + raise ValueError( + "`sparse=True` can only be used with the " "TensorFlow backend." + ) + if vocabulary_dtype != "int64": + raise ValueError( + "Only `vocabulary_dtype='int64'` is supported " + "at this time. Received: " + f"vocabulary_dtype={vocabulary_dtype}" + ) + super().__init__( + max_tokens=max_tokens, + num_oov_indices=num_oov_indices, + mask_token=mask_token, + oov_token=oov_token, + vocabulary=vocabulary, + vocabulary_dtype=vocabulary_dtype, + idf_weights=idf_weights, + invert=invert, + output_mode=output_mode, + sparse=sparse, + pad_to_max_tokens=pad_to_max_tokens, + name=name, + **kwargs, + ) + self._convert_input_args = False + self._allow_non_tensor_positional_args = True + self.supports_jit = False + + def adapt(self, data, steps=None): + """Computes a vocabulary of integer terms from tokens in a dataset. + + Calling `adapt()` on an `IntegerLookup` layer is an alternative to + passing in a precomputed vocabulary on construction via the + `vocabulary` argument. An `IntegerLookup` layer should always be either + adapted over a dataset or supplied with a vocabulary. + + During `adapt()`, the layer will build a vocabulary of all integer + tokens seen in the dataset, sorted by occurrence count, with ties broken + by sort order of the tokens (high to low). At the end of `adapt()`, if + `max_tokens` is set, the vocabulary will be truncated to `max_tokens` + size. For example, adapting a layer with `max_tokens=1000` will compute + the 1000 most frequent tokens occurring in the input dataset. If + `output_mode='tf-idf'`, `adapt()` will also learn the document + frequencies of each token in the input dataset. + + Arguments: + data: The data to train on. It can be passed either as a + batched `tf.data.Dataset`, as a list of integers, + or as a NumPy array. + steps: Integer or `None`. + Total number of steps (batches of samples) to process. + If `data` is a `tf.data.Dataset`, and `steps` is `None`, + `adapt()` will run until the input dataset is exhausted. + When passing an infinitely + repeating dataset, you must specify the `steps` argument. This + argument is not supported with array inputs or list inputs. + """ + super().adapt(data, steps=steps) + + def get_config(self): + config = super().get_config() + if config["oov_token"] is not None: + config["oov_token"] = int(config["oov_token"]) + if config["mask_token"] is not None: + config["mask_token"] = int(config["mask_token"]) + if config["vocabulary"] is not None: + config["vocabulary"] = [int(v) for v in config["vocabulary"]] + return config + + def call(self, inputs): + if not isinstance( + inputs, (tf.Tensor, tf.RaggedTensor, np.ndarray, list, tuple) + ): + inputs = tf.convert_to_tensor(backend.convert_to_numpy(inputs)) + outputs = super().call(inputs) + return backend_utils.convert_tf_tensor(outputs) diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/mel_spectrogram.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/mel_spectrogram.py new file mode 100644 index 0000000000000000000000000000000000000000..f91a4ccd8ceb829d40b4301f005228fbfd1add6d --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/mel_spectrogram.py @@ -0,0 +1,370 @@ +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.tf_data_layer import TFDataLayer + +# mel spectrum constants. +_MEL_BREAK_FREQUENCY_HERTZ = 700.0 +_MEL_HIGH_FREQUENCY_Q = 1127.0 + + +@keras_export("keras.layers.MelSpectrogram") +class MelSpectrogram(TFDataLayer): + """A preprocessing layer to convert raw audio signals to Mel spectrograms. + + This layer takes `float32`/`float64` single or batched audio signal as + inputs and computes the Mel spectrogram using Short-Time Fourier Transform + and Mel scaling. The input should be a 1D (unbatched) or 2D (batched) tensor + representing audio signals. The output will be a 2D or 3D tensor + representing Mel spectrograms. + + A spectrogram is an image-like representation that shows the frequency + spectrum of a signal over time. It uses x-axis to represent time, y-axis to + represent frequency, and each pixel to represent intensity. + Mel spectrograms are a special type of spectrogram that use the mel scale, + which approximates how humans perceive sound. They are commonly used in + speech and music processing tasks like speech recognition, speaker + identification, and music genre classification. + + References: + - [Spectrogram](https://en.wikipedia.org/wiki/Spectrogram), + - [Mel scale](https://en.wikipedia.org/wiki/Mel_scale). + + Examples: + + **Unbatched audio signal** + + >>> layer = keras.layers.MelSpectrogram(num_mel_bins=64, + ... sampling_rate=8000, + ... sequence_stride=256, + ... fft_length=2048) + >>> layer(keras.random.uniform(shape=(16000,))).shape + (64, 63) + + **Batched audio signal** + + >>> layer = keras.layers.MelSpectrogram(num_mel_bins=80, + ... sampling_rate=8000, + ... sequence_stride=128, + ... fft_length=2048) + >>> layer(keras.random.uniform(shape=(2, 16000))).shape + (2, 80, 125) + + Input shape: + 1D (unbatched) or 2D (batched) tensor with shape:`(..., samples)`. + + Output shape: + 2D (unbatched) or 3D (batched) tensor with + shape:`(..., num_mel_bins, time)`. + + Args: + fft_length: Integer, size of the FFT window. + sequence_stride: Integer, number of samples between successive STFT + columns. + sequence_length: Integer, size of the window used for applying + `window` to each audio frame. If `None`, defaults to `fft_length`. + window: String, name of the window function to use. Available values + are `"hann"` and `"hamming"`. If `window` is a tensor, it will be + used directly as the window and its length must be + `sequence_length`. If `window` is `None`, no windowing is + used. Defaults to `"hann"`. + sampling_rate: Integer, sample rate of the input signal. + num_mel_bins: Integer, number of mel bins to generate. + min_freq: Float, minimum frequency of the mel bins. + max_freq: Float, maximum frequency of the mel bins. + If `None`, defaults to `sampling_rate / 2`. + power_to_db: If True, convert the power spectrogram to decibels. + top_db: Float, minimum negative cut-off `max(10 * log10(S)) - top_db`. + mag_exp: Float, exponent for the magnitude spectrogram. + 1 for magnitude, 2 for power, etc. Default is 2. + ref_power: Float, the power is scaled relative to it + `10 * log10(S / ref_power)`. + min_power: Float, minimum value for power and `ref_power`. + """ + + def __init__( + self, + fft_length=2048, + sequence_stride=512, + sequence_length=None, + window="hann", + sampling_rate=16000, + num_mel_bins=128, + min_freq=20.0, + max_freq=None, + power_to_db=True, + top_db=80.0, + mag_exp=2.0, + min_power=1e-10, + ref_power=1.0, + **kwargs, + ): + self.fft_length = fft_length + self.sequence_stride = sequence_stride + self.sequence_length = sequence_length or fft_length + self.window = window + self.sampling_rate = sampling_rate + self.num_mel_bins = num_mel_bins + self.min_freq = min_freq + self.max_freq = max_freq or int(sampling_rate / 2) + self.power_to_db = power_to_db + self.top_db = top_db + self.mag_exp = mag_exp + self.min_power = min_power + self.ref_power = ref_power + super().__init__(**kwargs) + + def call(self, inputs): + dtype = ( + "float32" + if self.compute_dtype not in ["float32", "float64"] + else self.compute_dtype + ) # jax, tf supports only "float32" and "float64" in stft + inputs = self.backend.convert_to_tensor(inputs, dtype=dtype) + outputs = self._spectrogram(inputs) + outputs = self._melscale(outputs) + if self.power_to_db: + outputs = self._dbscale(outputs) + # swap time & freq axis to have shape of (..., num_mel_bins, time) + outputs = self.backend.numpy.swapaxes(outputs, -1, -2) + outputs = self.backend.cast(outputs, self.compute_dtype) + return outputs + + def _spectrogram(self, inputs): + real, imag = self.backend.math.stft( + inputs, + sequence_length=self.sequence_length, + sequence_stride=self.sequence_stride, + fft_length=self.fft_length, + window=self.window, + center=True, + ) + # abs of complex = sqrt(real^2 + imag^2) + spec = self.backend.numpy.sqrt( + self.backend.numpy.add( + self.backend.numpy.square(real), self.backend.numpy.square(imag) + ) + ) + spec = self.backend.numpy.power(spec, self.mag_exp) + return spec + + def _melscale(self, inputs): + matrix = self.linear_to_mel_weight_matrix( + num_mel_bins=self.num_mel_bins, + num_spectrogram_bins=self.backend.shape(inputs)[-1], + sampling_rate=self.sampling_rate, + lower_edge_hertz=self.min_freq, + upper_edge_hertz=self.max_freq, + ) + return self.backend.numpy.tensordot(inputs, matrix, axes=1) + + def _dbscale(self, inputs): + log_spec = 10.0 * ( + self.backend.numpy.log10( + self.backend.numpy.maximum(inputs, self.min_power) + ) + ) + ref_value = self.backend.numpy.abs( + self.backend.convert_to_tensor(self.ref_power) + ) + log_spec -= 10.0 * self.backend.numpy.log10( + self.backend.numpy.maximum(ref_value, self.min_power) + ) + log_spec = self.backend.numpy.maximum( + log_spec, self.backend.numpy.max(log_spec) - self.top_db + ) + return log_spec + + def _hertz_to_mel(self, frequencies_hertz): + """Converts frequencies in `frequencies_hertz` in Hertz to the + mel scale. + + Args: + frequencies_hertz: A tensor of frequencies in Hertz. + name: An optional name for the operation. + + Returns: + A tensor of the same shape and type of `frequencies_hertz` + containing frequencies in the mel scale. + """ + return _MEL_HIGH_FREQUENCY_Q * self.backend.numpy.log( + 1.0 + (frequencies_hertz / _MEL_BREAK_FREQUENCY_HERTZ) + ) + + def linear_to_mel_weight_matrix( + self, + num_mel_bins=20, + num_spectrogram_bins=129, + sampling_rate=8000, + lower_edge_hertz=125.0, + upper_edge_hertz=3800.0, + dtype="float32", + ): + """Returns a matrix to warp linear scale spectrograms to the mel scale. + + Returns a weight matrix that can be used to re-weight a tensor + containing `num_spectrogram_bins` linearly sampled frequency information + from `[0, sampling_rate / 2]` into `num_mel_bins` frequency information + from `[lower_edge_hertz, upper_edge_hertz]` on the mel scale. + + This function follows the [Hidden Markov Model Toolkit (HTK)]( + http://htk.eng.cam.ac.uk/) convention, defining the mel scale in + terms of a frequency in hertz according to the following formula: + + ```mel(f) = 2595 * log10( 1 + f/700)``` + + In the returned matrix, all the triangles (filterbanks) have a peak + value of 1.0. + + For example, the returned matrix `A` can be used to right-multiply a + spectrogram `S` of shape `[frames, num_spectrogram_bins]` of linear + scale spectrum values (e.g. STFT magnitudes) to generate a + "mel spectrogram" `M` of shape `[frames, num_mel_bins]`. + + ``` + # `S` has shape [frames, num_spectrogram_bins] + # `M` has shape [frames, num_mel_bins] + M = keras.ops.matmul(S, A) + ``` + + The matrix can be used with `keras.ops.tensordot` to convert an + arbitrary rank `Tensor` of linear-scale spectral bins into the + mel scale. + + ``` + # S has shape [..., num_spectrogram_bins]. + # M has shape [..., num_mel_bins]. + M = keras.ops.tensordot(S, A, 1) + ``` + + References: + - [Mel scale (Wikipedia)](https://en.wikipedia.org/wiki/Mel_scale) + + Args: + num_mel_bins: Python int. How many bands in the resulting + mel spectrum. + num_spectrogram_bins: An integer `Tensor`. How many bins there are + in the source spectrogram data, which is understood to be + `fft_size // 2 + 1`, i.e. the spectrogram only contains the + nonredundant FFT bins. + sampling_rate: An integer or float `Tensor`. Samples per second of + the input signal used to create the spectrogram. Used to figure + out the frequencies corresponding to each spectrogram bin, + which dictates how they are mapped into the mel scale. + lower_edge_hertz: Python float. Lower bound on the frequencies to be + included in the mel spectrum. This corresponds to the lower + edge of the lowest triangular band. + upper_edge_hertz: Python float. The desired top edge of the highest + frequency band. + dtype: The `DType` of the result matrix. Must be a floating point + type. + + Returns: + A tensor of shape `[num_spectrogram_bins, num_mel_bins]`. + """ + + # This function can be constant folded by graph optimization since + # there are no Tensor inputs. + sampling_rate = self.backend.cast(sampling_rate, dtype) + lower_edge_hertz = self.backend.convert_to_tensor( + lower_edge_hertz, + dtype, + ) + upper_edge_hertz = self.backend.convert_to_tensor( + upper_edge_hertz, + dtype, + ) + zero = self.backend.convert_to_tensor(0.0, dtype) + + # HTK excludes the spectrogram DC bin. + bands_to_zero = 1 + nyquist_hertz = sampling_rate / 2.0 + linear_frequencies = self.backend.numpy.linspace( + zero, nyquist_hertz, num_spectrogram_bins + )[bands_to_zero:] + spectrogram_bins_mel = self.backend.numpy.expand_dims( + self._hertz_to_mel(linear_frequencies), 1 + ) + + # Compute num_mel_bins triples of (lower_edge, center, upper_edge). The + # center of each band is the lower and upper edge of the adjacent bands. + # Accordingly, we divide [lower_edge_hertz, upper_edge_hertz] into + # num_mel_bins + 2 pieces. + band_edges_mel = self.backend.math.extract_sequences( + self.backend.numpy.linspace( + self._hertz_to_mel(lower_edge_hertz), + self._hertz_to_mel(upper_edge_hertz), + num_mel_bins + 2, + ), + sequence_length=3, + sequence_stride=1, + ) + + # Split the triples up and reshape them into [1, num_mel_bins] tensors. + lower_edge_mel, center_mel, upper_edge_mel = tuple( + self.backend.numpy.reshape(t, [1, num_mel_bins]) + for t in self.backend.numpy.split(band_edges_mel, 3, axis=1) + ) + + # Calculate lower and upper slopes for every spectrogram bin. + # Line segments are linear in the mel domain, not Hertz. + lower_slopes = (spectrogram_bins_mel - lower_edge_mel) / ( + center_mel - lower_edge_mel + ) + upper_slopes = (upper_edge_mel - spectrogram_bins_mel) / ( + upper_edge_mel - center_mel + ) + + # Intersect the line segments with each other and zero. + mel_weights_matrix = self.backend.numpy.maximum( + zero, self.backend.numpy.minimum(lower_slopes, upper_slopes) + ) + + # Re-add the zeroed lower bins we sliced out above. + return self.backend.numpy.pad( + mel_weights_matrix, + [[bands_to_zero, 0], [0, 0]], + ) + + def compute_output_shape(self, input_shape): + if len(input_shape) == 1: + output_shape = [ + self.num_mel_bins, + ( + (input_shape[0] + self.sequence_stride + 1) + // self.sequence_stride + if input_shape[0] is not None + else None + ), + ] + else: + output_shape = [ + input_shape[0], + self.num_mel_bins, + ( + (input_shape[1] + self.sequence_stride + 1) + // self.sequence_stride + if input_shape[1] is not None + else None + ), + ] + return output_shape + + def get_config(self): + config = super().get_config() + config.update( + { + "fft_length": self.fft_length, + "sequence_stride": self.sequence_stride, + "sequence_length": self.sequence_length, + "window": self.window, + "sampling_rate": self.sampling_rate, + "num_mel_bins": self.num_mel_bins, + "min_freq": self.min_freq, + "max_freq": self.max_freq, + "power_to_db": self.power_to_db, + "top_db": self.top_db, + "mag_exp": self.mag_exp, + "min_power": self.min_power, + "ref_power": self.ref_power, + } + ) + return config diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/normalization.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/normalization.py new file mode 100644 index 0000000000000000000000000000000000000000..54ed025c98886404b6360768e0a99952a09e6c9c --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/normalization.py @@ -0,0 +1,359 @@ +import math + +import numpy as np + +from keras.src import backend +from keras.src import ops +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.tf_data_layer import TFDataLayer +from keras.src.utils.module_utils import tensorflow as tf + + +@keras_export("keras.layers.Normalization") +class Normalization(TFDataLayer): + """A preprocessing layer that normalizes continuous features. + + This layer will shift and scale inputs into a distribution centered around + 0 with standard deviation 1. It accomplishes this by precomputing the mean + and variance of the data, and calling `(input - mean) / sqrt(var)` at + runtime. + + The mean and variance values for the layer must be either supplied on + construction or learned via `adapt()`. `adapt()` will compute the mean and + variance of the data and store them as the layer's weights. `adapt()` should + be called before `fit()`, `evaluate()`, or `predict()`. + + Args: + axis: Integer, tuple of integers, or None. The axis or axes that should + have a separate mean and variance for each index in the shape. + For example, if shape is `(None, 5)` and `axis=1`, the layer will + track 5 separate mean and variance values for the last axis. + If `axis` is set to `None`, the layer will normalize + all elements in the input by a scalar mean and variance. + When `-1`, the last axis of the input is assumed to be a + feature dimension and is normalized per index. + Note that in the specific case of batched scalar inputs where + the only axis is the batch axis, the default will normalize + each index in the batch separately. + In this case, consider passing `axis=None`. Defaults to `-1`. + mean: The mean value(s) to use during normalization. The passed value(s) + will be broadcast to the shape of the kept axes above; + if the value(s) cannot be broadcast, an error will be raised when + this layer's `build()` method is called. + variance: The variance value(s) to use during normalization. The passed + value(s) will be broadcast to the shape of the kept axes above; + if the value(s) cannot be broadcast, an error will be raised when + this layer's `build()` method is called. + invert: If `True`, this layer will apply the inverse transformation + to its inputs: it would turn a normalized input back into its + original form. + + Examples: + + Calculate a global mean and variance by analyzing the dataset in `adapt()`. + + >>> adapt_data = np.array([1., 2., 3., 4., 5.], dtype='float32') + >>> input_data = np.array([1., 2., 3.], dtype='float32') + >>> layer = keras.layers.Normalization(axis=None) + >>> layer.adapt(adapt_data) + >>> layer(input_data) + array([-1.4142135, -0.70710677, 0.], dtype=float32) + + Calculate a mean and variance for each index on the last axis. + + >>> adapt_data = np.array([[0., 7., 4.], + ... [2., 9., 6.], + ... [0., 7., 4.], + ... [2., 9., 6.]], dtype='float32') + >>> input_data = np.array([[0., 7., 4.]], dtype='float32') + >>> layer = keras.layers.Normalization(axis=-1) + >>> layer.adapt(adapt_data) + >>> layer(input_data) + array([-1., -1., -1.], dtype=float32) + + Pass the mean and variance directly. + + >>> input_data = np.array([[1.], [2.], [3.]], dtype='float32') + >>> layer = keras.layers.Normalization(mean=3., variance=2.) + >>> layer(input_data) + array([[-1.4142135 ], + [-0.70710677], + [ 0. ]], dtype=float32) + + Use the layer to de-normalize inputs (after adapting the layer). + + >>> adapt_data = np.array([[0., 7., 4.], + ... [2., 9., 6.], + ... [0., 7., 4.], + ... [2., 9., 6.]], dtype='float32') + >>> input_data = np.array([[1., 2., 3.]], dtype='float32') + >>> layer = keras.layers.Normalization(axis=-1, invert=True) + >>> layer.adapt(adapt_data) + >>> layer(input_data) + array([2., 10., 8.], dtype=float32) + """ + + def __init__( + self, axis=-1, mean=None, variance=None, invert=False, **kwargs + ): + super().__init__(**kwargs) + # Standardize `axis` to a tuple. + if axis is None: + axis = () + elif isinstance(axis, int): + axis = (axis,) + else: + axis = tuple(axis) + self.axis = axis + + self.input_mean = mean + self.input_variance = variance + self.invert = invert + self.supports_masking = True + self._build_input_shape = None + self.mean = None + + # Set `mean` and `variance` if passed. + if (mean is not None) != (variance is not None): + raise ValueError( + "When setting values directly, both `mean` and `variance` " + f"must be set. Received: mean={mean} and variance={variance}" + ) + + def build(self, input_shape): + if input_shape is None: + return + + ndim = len(input_shape) + self._build_input_shape = input_shape + + if any(a < -ndim or a >= ndim for a in self.axis): + raise ValueError( + "All `axis` values must be in the range [-ndim, ndim). " + f"Received inputs with ndim={ndim}, while axis={self.axis}" + ) + + # Axes to be kept, replacing negative values with positive equivalents. + # Sorted to avoid transposing axes. + self._keep_axis = tuple( + sorted([d if d >= 0 else d + ndim for d in self.axis]) + ) + # All axes to be kept should have known shape. + for d in self._keep_axis: + if input_shape[d] is None: + raise ValueError( + "All `axis` values to be kept must have a known shape. " + f"Received axis={self.axis}, " + f"inputs.shape={input_shape}, " + f"with unknown axis at index {d}" + ) + # Axes to be reduced. + self._reduce_axis = tuple( + d for d in range(ndim) if d not in self._keep_axis + ) + # 1 if an axis should be reduced, 0 otherwise. + self._reduce_axis_mask = [ + 0 if d in self._keep_axis else 1 for d in range(ndim) + ] + # Broadcast any reduced axes. + self._broadcast_shape = [ + input_shape[d] if d in self._keep_axis else 1 for d in range(ndim) + ] + mean_and_var_shape = tuple(input_shape[d] for d in self._keep_axis) + self._mean_and_var_shape = mean_and_var_shape + + if self.input_mean is None: + self.adapt_mean = self.add_weight( + name="mean", + shape=mean_and_var_shape, + initializer="zeros", + trainable=False, + ) + self.adapt_variance = self.add_weight( + name="variance", + shape=mean_and_var_shape, + initializer="ones", + trainable=False, + ) + # For backwards compatibility with older saved models. + self.count = self.add_weight( + name="count", + shape=(), + dtype="int", + initializer="zeros", + trainable=False, + ) + self.built = True + self.finalize_state() + else: + # In the no adapt case, make constant tensors for mean and variance + # with proper broadcast shape for use during call. + mean = ops.convert_to_tensor(self.input_mean) + variance = ops.convert_to_tensor(self.input_variance) + mean = ops.broadcast_to(mean, self._broadcast_shape) + variance = ops.broadcast_to(variance, self._broadcast_shape) + self.mean = ops.cast(mean, dtype=self.compute_dtype) + self.variance = ops.cast(variance, dtype=self.compute_dtype) + self.built = True + + def adapt(self, data): + """Computes the mean and variance of values in a dataset. + + Calling `adapt()` on a `Normalization` layer is an alternative to + passing in `mean` and `variance` arguments during layer construction. A + `Normalization` layer should always either be adapted over a dataset or + passed `mean` and `variance`. + + During `adapt()`, the layer will compute a `mean` and `variance` + separately for each position in each axis specified by the `axis` + argument. To calculate a single `mean` and `variance` over the input + data, simply pass `axis=None` to the layer. + + Arg: + data: The data to train on. It can be passed either as a + `tf.data.Dataset`, as a NumPy array, or as a backend-native + eager tensor. + If a dataset, *it must be batched*. Keras will assume that the + data is batched, and if that assumption doesn't hold, the mean + and variance may be incorrectly computed. + """ + if isinstance(data, np.ndarray) or backend.is_tensor(data): + input_shape = data.shape + elif isinstance(data, tf.data.Dataset): + input_shape = tuple(data.element_spec.shape) + if len(input_shape) == 1: + # Batch dataset if it isn't batched + data = data.batch(128) + input_shape = tuple(data.element_spec.shape) + + if not self.built: + self.build(input_shape) + else: + for d in self._keep_axis: + if input_shape[d] != self._build_input_shape[d]: + raise ValueError( + "The layer was built with " + f"input_shape={self._build_input_shape}, " + "but adapt() is being called with data with " + f"an incompatible shape, data.shape={input_shape}" + ) + + if isinstance(data, np.ndarray): + total_mean = np.mean(data, axis=self._reduce_axis) + total_var = np.var(data, axis=self._reduce_axis) + elif backend.is_tensor(data): + total_mean = ops.mean(data, axis=self._reduce_axis) + total_var = ops.var(data, axis=self._reduce_axis) + elif isinstance(data, tf.data.Dataset): + total_mean = ops.zeros(self._mean_and_var_shape) + total_var = ops.zeros(self._mean_and_var_shape) + total_count = 0 + for batch in data: + batch = backend.convert_to_tensor( + batch, dtype=self.compute_dtype + ) + batch_mean = ops.mean(batch, axis=self._reduce_axis) + batch_var = ops.var(batch, axis=self._reduce_axis) + if self._reduce_axis: + batch_reduce_shape = ( + batch.shape[d] for d in self._reduce_axis + ) + batch_count = math.prod(batch_reduce_shape) + else: + batch_count = 1 + + total_count += batch_count + batch_weight = float(batch_count) / total_count + existing_weight = 1.0 - batch_weight + new_total_mean = ( + total_mean * existing_weight + batch_mean * batch_weight + ) + # The variance is computed using the lack-of-fit sum of squares + # formula (see + # https://en.wikipedia.org/wiki/Lack-of-fit_sum_of_squares). + total_var = ( + total_var + (total_mean - new_total_mean) ** 2 + ) * existing_weight + ( + batch_var + (batch_mean - new_total_mean) ** 2 + ) * batch_weight + total_mean = new_total_mean + else: + raise NotImplementedError(f"Unsupported data type: {type(data)}") + + self.adapt_mean.assign(total_mean) + self.adapt_variance.assign(total_var) + self.finalize_state() + + def finalize_state(self): + if self.input_mean is not None or not self.built: + return + + # In the adapt case, we make constant tensors for mean and variance with + # proper broadcast shape and dtype each time `finalize_state` is called. + self.mean = ops.reshape(self.adapt_mean, self._broadcast_shape) + self.mean = ops.cast(self.mean, self.compute_dtype) + self.variance = ops.reshape(self.adapt_variance, self._broadcast_shape) + self.variance = ops.cast(self.variance, self.compute_dtype) + + def call(self, inputs): + # This layer can be called in tf.data + # even with another backend after it has been adapted. + # However it must use backend-native logic for adapt(). + if self.mean is None: + # May happen when in tf.data when mean/var was passed explicitly + raise ValueError( + "You must call `.build(input_shape)` " + "on the layer before using it." + ) + inputs = self.backend.core.convert_to_tensor( + inputs, dtype=self.compute_dtype + ) + # Ensure the weights are in the correct backend. Without this, it is + # possible to cause breakage when using this layer in tf.data. + mean = self.convert_weight(self.mean) + variance = self.convert_weight(self.variance) + if self.invert: + return self.backend.numpy.add( + mean, + self.backend.numpy.multiply( + inputs, + self.backend.numpy.maximum( + self.backend.numpy.sqrt(variance), backend.epsilon() + ), + ), + ) + else: + return self.backend.numpy.divide( + self.backend.numpy.subtract(inputs, mean), + self.backend.numpy.maximum( + self.backend.numpy.sqrt(variance), backend.epsilon() + ), + ) + + def compute_output_shape(self, input_shape): + return input_shape + + def get_config(self): + config = super().get_config() + config.update( + { + "axis": self.axis, + "invert": self.invert, + "mean": np.array(self.input_mean).tolist(), + "variance": np.array(self.input_variance).tolist(), + } + ) + return config + + def load_own_variables(self, store): + super().load_own_variables(store) + # Ensure that we call finalize_state after variable loading. + self.finalize_state() + + def get_build_config(self): + if self._build_input_shape: + return {"input_shape": self._build_input_shape} + + def build_from_config(self, config): + if config: + self.build(config["input_shape"]) diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/pipeline.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/pipeline.py new file mode 100644 index 0000000000000000000000000000000000000000..7890eff9553396f4f65aea0cd9d0070604d52e67 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/pipeline.py @@ -0,0 +1,84 @@ +from keras.src import tree +from keras.src.api_export import keras_export +from keras.src.layers.layer import Layer +from keras.src.saving import serialization_lib + + +@keras_export("keras.layers.Pipeline") +class Pipeline(Layer): + """Applies a series of layers to an input. + + This class is useful to build a preprocessing pipeline, + in particular an image data augmentation pipeline. + Compared to a `Sequential` model, `Pipeline` features + a few important differences: + + - It's not a `Model`, just a plain layer. + - When the layers in the pipeline are compatible + with `tf.data`, the pipeline will also + remain `tf.data` compatible. That is to say, + the pipeline will not attempt to convert + its inputs to backend-native tensors + when in a tf.data context (unlike a `Sequential` + model). + + Example: + + ```python + from keras import layers + preprocessing_pipeline = layers.Pipeline([ + layers.AutoContrast(), + layers.RandomZoom(0.2), + layers.RandomRotation(0.2), + ]) + + # `ds` is a tf.data.Dataset + preprocessed_ds = ds.map( + preprocessing_pipeline, + num_parallel_calls=4, + ) + ``` + """ + + def __init__(self, layers, name=None): + super().__init__(name=name) + self._pipeline_layers = layers + self._convert_input_args = False + self._allow_non_tensor_positional_args = True + + @property + def layers(self): + return self._pipeline_layers + + def call(self, inputs, training=True, mask=None): + for layer in self._pipeline_layers: + kwargs = {} + if layer._call_has_mask_arg: + kwargs["mask"] = mask + if layer._call_has_training_arg and training is not None: + kwargs["training"] = training + outputs = layer(inputs, **kwargs) + inputs = outputs + + def _get_mask_from_keras_tensor(kt): + return getattr(kt, "_keras_mask", None) + + mask = tree.map_structure(_get_mask_from_keras_tensor, outputs) + return outputs + + @classmethod + def from_config(cls, config): + config["layers"] = [ + serialization_lib.deserialize_keras_object(x) + for x in config["layers"] + ] + return cls(**config) + + def get_config(self): + config = { + "layers": serialization_lib.serialize_keras_object( + self._pipeline_layers + ), + "name": self.name, + } + return config diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/rescaling.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/rescaling.py new file mode 100644 index 0000000000000000000000000000000000000000..862f854bcf50d39b3840ac6a983afc5e6c0d8fa6 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/rescaling.py @@ -0,0 +1,78 @@ +from keras.src import backend +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.tf_data_layer import TFDataLayer +from keras.src.saving import serialization_lib + + +@keras_export("keras.layers.Rescaling") +class Rescaling(TFDataLayer): + """A preprocessing layer which rescales input values to a new range. + + This layer rescales every value of an input (often an image) by multiplying + by `scale` and adding `offset`. + + For instance: + + 1. To rescale an input in the `[0, 255]` range + to be in the `[0, 1]` range, you would pass `scale=1./255`. + + 2. To rescale an input in the `[0, 255]` range to be in the `[-1, 1]` range, + you would pass `scale=1./127.5, offset=-1`. + + The rescaling is applied both during training and inference. Inputs can be + of integer or floating point dtype, and by default the layer will output + floats. + + **Note:** This layer is safe to use inside a `tf.data` pipeline + (independently of which backend you're using). + + Args: + scale: Float, the scale to apply to the inputs. + offset: Float, the offset to apply to the inputs. + **kwargs: Base layer keyword arguments, such as `name` and `dtype`. + """ + + def __init__(self, scale, offset=0.0, **kwargs): + super().__init__(**kwargs) + self.scale = scale + self.offset = offset + self.supports_masking = True + + def call(self, inputs): + dtype = self.compute_dtype + scale = self.backend.cast(self.scale, dtype) + offset = self.backend.cast(self.offset, dtype) + scale_shape = self.backend.core.shape(scale) + if ( + len(scale_shape) > 0 + and backend.image_data_format() == "channels_first" + ): + scale = self.backend.numpy.reshape( + scale, scale_shape + (1,) * (3 - len(scale_shape)) + ) + return self.backend.cast(inputs, dtype) * scale + offset + + def compute_output_shape(self, input_shape): + return input_shape + + def get_config(self): + config = super().get_config() + config.update( + { + # `scale` and `offset` might be numpy array. + "scale": serialization_lib.serialize_keras_object(self.scale), + "offset": serialization_lib.serialize_keras_object(self.offset), + } + ) + return config + + @classmethod + def from_config(cls, config, custom_objects=None): + config = config.copy() + config["scale"] = serialization_lib.deserialize_keras_object( + config["scale"], custom_objects=custom_objects + ) + config["offset"] = serialization_lib.deserialize_keras_object( + config["offset"], custom_objects=custom_objects + ) + return cls(**config) diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/stft_spectrogram.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/stft_spectrogram.py new file mode 100644 index 0000000000000000000000000000000000000000..6834bc356c2fd8d87ed164c0ff4305e152d94a42 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/stft_spectrogram.py @@ -0,0 +1,384 @@ +import math +import warnings + +from keras.src import backend +from keras.src import initializers +from keras.src import layers +from keras.src import ops +from keras.src.api_export import keras_export +from keras.src.utils.module_utils import scipy + + +@keras_export("keras.layers.STFTSpectrogram") +class STFTSpectrogram(layers.Layer): + """Layer to compute the Short-Time Fourier Transform (STFT) on a 1D signal. + + A layer that computes Spectrograms of the input signal to produce + a spectrogram. This layers utilizes Short-Time Fourier Transform (STFT) by + The layer computes Spectrograms based on STFT by utilizing convolution + kernels, which allows parallelization on GPUs and trainable kernels for + fine-tuning support. This layer allows different modes of output + (e.g., log-scaled magnitude, phase, power spectral density, etc.) and + provides flexibility in windowing, padding, and scaling options for the + STFT calculation. + + Examples: + + Apply it as a non-trainable preprocessing layer on 3 audio tracks of + 1 channel, 10 seconds and sampled at 16 kHz. + + >>> layer = keras.layers.STFTSpectrogram( + ... mode='log', + ... frame_length=256, + ... frame_step=128, # 50% overlap + ... fft_length=512, + ... window="hann", + ... padding="valid", + ... trainable=False, # non-trainable, preprocessing only + ... ) + >>> layer(keras.random.uniform(shape=(3, 160000, 1))).shape + (3, 1249, 257) + + Apply it as a trainable processing layer on 3 stereo audio tracks of + 2 channels, 10 seconds and sampled at 16 kHz. This is initialized as the + non-trainable layer, but then can be trained jointly within a model. + + >>> layer = keras.layers.STFTSpectrogram( + ... mode='log', + ... frame_length=256, + ... frame_step=128, # 50% overlap + ... fft_length=512, + ... window="hamming", # hamming windowing function + ... padding="same", # padding to preserve the time dimension + ... trainable=True, # trainable, this is the default in keras + ... ) + >>> layer(keras.random.uniform(shape=(3, 160000, 2))).shape + (3, 1250, 514) + + Similar to the last example, but add an extra dimension so the output is + an image to be used with image models. We apply this here on a signal of + 3 input channels to output an image tensor, hence is directly applicable + with an image model. + + >>> layer = keras.layers.STFTSpectrogram( + ... mode='log', + ... frame_length=256, + ... frame_step=128, + ... fft_length=512, + ... padding="same", + ... expand_dims=True, # this adds the extra dimension + ... ) + >>> layer(keras.random.uniform(shape=(3, 160000, 3))).shape + (3, 1250, 257, 3) + + Args: + mode: String, the output type of the spectrogram. Can be one of + `"log"`, `"magnitude`", `"psd"`, `"real`", `"imag`", `"angle`", + `"stft`". Defaults to `"log`". + frame_length: Integer, The length of each frame (window) for STFT in + samples. Defaults to 256. + frame_step: Integer, the step size (hop length) between + consecutive frames. If not provided, defaults to half the + frame_length. Defaults to `frame_length // 2`. + fft_length: Integer, the size of frequency bins used in the Fast-Fourier + Transform (FFT) to apply to each frame. Should be greater than or + equal to `frame_length`. Recommended to be a power of two. Defaults + to the smallest power of two that is greater than or equal + to `frame_length`. + window: (String or array_like), the windowing function to apply to each + frame. Can be `"hann`" (default), `"hamming`", or a custom window + provided as an array_like. + periodic: Boolean, if True, the window function will be treated as + periodic. Defaults to `False`. + scaling: String, type of scaling applied to the window. Can be + `"density`", `"spectrum`", or None. Default is `"density`". + padding: String, padding strategy. Can be `"valid`" or `"same`". + Defaults to `"valid"`. + expand_dims: Boolean, if True, will expand the output into spectrograms + into two dimensions to be compatible with image models. + Defaults to `False`. + data_format: String, either `"channels_last"` or `"channels_first"`. + The ordering of the dimensions in the inputs. `"channels_last"` + corresponds to inputs with shape `(batch, height, width, channels)` + while `"channels_first"` corresponds to inputs with shape + `(batch, channels, height, weight)`. Defaults to `"channels_last"`. + + Raises: + ValueError: If an invalid value is provided for `"mode`", `"scaling`", + `"padding`", or other input arguments. + TypeError: If the input data type is not one of `"float16`", + `"float32`", or `"float64`". + + Input shape: + A 3D tensor of shape `(batch_size, time_length, input_channels)`, if + `data_format=="channels_last"`, and of shape + `(batch_size, input_channels, time_length)` if + `data_format=="channels_first"`, where `time_length` is the length of + the input signal, and `input_channels` is the number of input channels. + The same kernels are applied to each channel independently. + + Output shape: + If `data_format=="channels_first" and not expand_dims`, a 3D tensor: + `(batch_size, input_channels * freq_channels, new_time_length)` + If `data_format=="channels_last" and not expand_dims`, a 3D tensor: + `(batch_size, new_time_length, input_channels * freq_channels)` + If `data_format=="channels_first" and expand_dims`, a 4D tensor: + `(batch_size, input_channels, new_time_length, freq_channels)` + If `data_format=="channels_last" and expand_dims`, a 4D tensor: + `(batch_size, new_time_length, freq_channels, input_channels)` + + where `new_time_length` depends on the padding, and `freq_channels` is + the number of FFT bins `(fft_length // 2 + 1)`. + """ + + def __init__( + self, + mode="log", + frame_length=256, + frame_step=None, + fft_length=None, + window="hann", + periodic=False, + scaling="density", + padding="valid", + expand_dims=False, + data_format=None, + **kwargs, + ): + if frame_step is not None and ( + frame_step > frame_length or frame_step < 1 + ): + raise ValueError( + "`frame_step` should be a positive integer not greater than " + f"`frame_length`. Received frame_step={frame_step}, " + f"frame_length={frame_length}" + ) + + if fft_length is not None and fft_length < frame_length: + raise ValueError( + "`fft_length` should be not less than `frame_length`. " + f"Received fft_length={fft_length}, frame_length={frame_length}" + ) + + if fft_length is not None and (fft_length & -fft_length) != fft_length: + warnings.warn( + "`fft_length` is recommended to be a power of two. " + f"Received fft_length={fft_length}" + ) + + all_modes = ["log", "magnitude", "psd", "real", "imag", "angle", "stft"] + + if mode not in all_modes: + raise ValueError( + "Output mode is invalid, it must be one of " + f"{', '.join(all_modes)}. Received: mode={mode}" + ) + + if scaling is not None and scaling not in ["density", "spectrum"]: + raise ValueError( + "Scaling is invalid, it must be `None`, 'density' " + f"or 'spectrum'. Received scaling={scaling}" + ) + + if padding not in ["valid", "same"]: + raise ValueError( + "Padding is invalid, it should be 'valid', 'same'. " + f"Received: padding={padding}" + ) + + if isinstance(window, str): + # throws an exception for invalid window function + scipy.signal.get_window(window, 1) + + super().__init__(**kwargs) + + self.mode = mode + + self.frame_length = frame_length + self.frame_step = frame_step + self._frame_step = frame_step or self.frame_length // 2 + self.fft_length = fft_length + self._fft_length = fft_length or ( + 2 ** int(math.ceil(math.log2(frame_length))) + ) + + self.window = window + self.periodic = periodic + self.scaling = scaling + self.padding = padding + self.expand_dims = expand_dims + self.data_format = backend.standardize_data_format(data_format) + self.input_spec = layers.input_spec.InputSpec(ndim=3) + + def build(self, input_shape): + shape = (self.frame_length, 1, self._fft_length // 2 + 1) + + if self.mode != "imag": + self.real_kernel = self.add_weight( + name="real_kernel", + shape=shape, + initializer=initializers.STFT( + "real", self.window, self.scaling, self.periodic + ), + ) + if self.mode != "real": + self.imag_kernel = self.add_weight( + name="imag_kernel", + shape=shape, + initializer=initializers.STFT( + "imag", self.window, self.scaling, self.periodic + ), + ) + self.built = True + + def _adjust_shapes(self, outputs): + _, channels, freq_channels, time_seq = outputs.shape + batch_size = -1 + if self.data_format == "channels_last": + if self.expand_dims: + outputs = ops.transpose(outputs, [0, 3, 2, 1]) + # [batch_size, time_seq, freq_channels, input_channels] + else: + outputs = ops.reshape( + outputs, + [batch_size, channels * freq_channels, time_seq], + ) + # [batch_size, input_channels * freq_channels, time_seq] + outputs = ops.transpose(outputs, [0, 2, 1]) + else: + if self.expand_dims: + outputs = ops.transpose(outputs, [0, 1, 3, 2]) + # [batch_size, channels, time_seq, freq_channels] + else: + outputs = ops.reshape( + outputs, + [batch_size, channels * freq_channels, time_seq], + ) + return outputs + + def _apply_conv(self, inputs, kernel): + if self.data_format == "channels_last": + _, time_seq, channels = inputs.shape + inputs = ops.transpose(inputs, [0, 2, 1]) + inputs = ops.reshape(inputs, [-1, time_seq, 1]) + else: + _, channels, time_seq = inputs.shape + inputs = ops.reshape(inputs, [-1, 1, time_seq]) + + outputs = ops.conv( + inputs, + ops.cast(kernel, backend.standardize_dtype(inputs.dtype)), + padding=self.padding, + strides=self._frame_step, + data_format=self.data_format, + ) + batch_size = -1 + if self.data_format == "channels_last": + _, time_seq, freq_channels = outputs.shape + outputs = ops.transpose(outputs, [0, 2, 1]) + outputs = ops.reshape( + outputs, + [batch_size, channels, freq_channels, time_seq], + ) + else: + _, freq_channels, time_seq = outputs.shape + outputs = ops.reshape( + outputs, + [batch_size, channels, freq_channels, time_seq], + ) + return outputs + + def call(self, inputs): + dtype = inputs.dtype + if backend.standardize_dtype(dtype) not in { + "float16", + "float32", + "float64", + }: + raise TypeError( + "Invalid input type. Expected `float16`, `float32` or " + f"`float64`. Received: input type={dtype}" + ) + + real_signal = None + imag_signal = None + power = None + + if self.mode != "imag": + real_signal = self._apply_conv(inputs, self.real_kernel) + if self.mode != "real": + imag_signal = self._apply_conv(inputs, self.imag_kernel) + + if self.mode == "real": + return self._adjust_shapes(real_signal) + elif self.mode == "imag": + return self._adjust_shapes(imag_signal) + elif self.mode == "angle": + return self._adjust_shapes(ops.arctan2(imag_signal, real_signal)) + elif self.mode == "stft": + return self._adjust_shapes( + ops.concatenate([real_signal, imag_signal], axis=2) + ) + else: + power = ops.square(real_signal) + ops.square(imag_signal) + + if self.mode == "psd": + return self._adjust_shapes( + power + + ops.pad( + power[:, :, 1:-1, :], [[0, 0], [0, 0], [1, 1], [0, 0]] + ) + ) + linear_stft = self._adjust_shapes( + ops.sqrt(ops.maximum(power, backend.epsilon())) + ) + + if self.mode == "magnitude": + return linear_stft + else: + return ops.log(ops.maximum(linear_stft, backend.epsilon())) + + def compute_output_shape(self, input_shape): + if self.data_format == "channels_last": + channels = input_shape[-1] + else: + channels = input_shape[1] + freq_channels = self._fft_length // 2 + 1 + if self.mode == "stft": + freq_channels *= 2 + shape = ops.operation_utils.compute_conv_output_shape( + input_shape, + freq_channels * channels, + (self.frame_length,), + strides=self._frame_step, + padding=self.padding, + data_format=self.data_format, + ) + if self.data_format == "channels_last": + batch_size, time_seq, _ = shape + else: + batch_size, _, time_seq = shape + if self.expand_dims: + if self.data_format == "channels_last": + return (batch_size, time_seq, freq_channels, channels) + else: + return (batch_size, channels, time_seq, freq_channels) + return shape + + def get_config(self): + config = super().get_config() + config.update( + { + "mode": self.mode, + "frame_length": self.frame_length, + "frame_step": self.frame_step, + "fft_length": self.fft_length, + "window": self.window, + "periodic": self.periodic, + "scaling": self.scaling, + "padding": self.padding, + "data_format": self.data_format, + "expand_dims": self.expand_dims, + } + ) + return config diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/string_lookup.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/string_lookup.py new file mode 100644 index 0000000000000000000000000000000000000000..5ae1a584a05e2cea95d45930a4189481e54cd924 --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/string_lookup.py @@ -0,0 +1,394 @@ +import numpy as np + +from keras.src import backend +from keras.src.api_export import keras_export +from keras.src.layers.preprocessing.index_lookup import IndexLookup +from keras.src.utils import backend_utils +from keras.src.utils.module_utils import tensorflow as tf + + +@keras_export("keras.layers.StringLookup") +class StringLookup(IndexLookup): + """A preprocessing layer that maps strings to (possibly encoded) indices. + + This layer translates a set of arbitrary strings into integer output via a + table-based vocabulary lookup. This layer will perform no splitting or + transformation of input strings. For a layer that can split and tokenize + natural language, see the `keras.layers.TextVectorization` layer. + + The vocabulary for the layer must be either supplied on construction or + learned via `adapt()`. During `adapt()`, the layer will analyze a data set, + determine the frequency of individual strings tokens, and create a + vocabulary from them. If the vocabulary is capped in size, the most frequent + tokens will be used to create the vocabulary and all others will be treated + as out-of-vocabulary (OOV). + + There are two possible output modes for the layer. + When `output_mode` is `"int"`, + input strings are converted to their index in the vocabulary (an integer). + When `output_mode` is `"multi_hot"`, `"count"`, or `"tf_idf"`, input strings + are encoded into an array where each dimension corresponds to an element in + the vocabulary. + + The vocabulary can optionally contain a mask token as well as an OOV token + (which can optionally occupy multiple indices in the vocabulary, as set + by `num_oov_indices`). + The position of these tokens in the vocabulary is fixed. When `output_mode` + is `"int"`, the vocabulary will begin with the mask token (if set), followed + by OOV indices, followed by the rest of the vocabulary. When `output_mode` + is `"multi_hot"`, `"count"`, or `"tf_idf"` the vocabulary will begin with + OOV indices and instances of the mask token will be dropped. + + **Note:** This layer uses TensorFlow internally. It cannot + be used as part of the compiled computation graph of a model with + any backend other than TensorFlow. + It can however be used with any backend when running eagerly. + It can also always be used as part of an input preprocessing pipeline + with any backend (outside the model itself), which is how we recommend + to use this layer. + + **Note:** This layer is safe to use inside a `tf.data` pipeline + (independently of which backend you're using). + + Args: + max_tokens: Maximum size of the vocabulary for this layer. This should + only be specified when adapting the vocabulary or when setting + `pad_to_max_tokens=True`. If None, there is no cap on the size of + the vocabulary. Note that this size includes the OOV + and mask tokens. Defaults to `None`. + num_oov_indices: The number of out-of-vocabulary tokens to use. + If this value is more than 1, OOV inputs are modulated to + determine their OOV value. + If this value is 0, OOV inputs will cause an error when calling + the layer. Defaults to `1`. + mask_token: A token that represents masked inputs. When `output_mode` is + `"int"`, the token is included in vocabulary and mapped to index 0. + In other output modes, the token will not appear + in the vocabulary and instances of the mask token + in the input will be dropped. If set to `None`, + no mask term will be added. Defaults to `None`. + oov_token: Only used when `invert` is True. The token to return for OOV + indices. Defaults to `"[UNK]"`. + vocabulary: Optional. Either an array of integers or a string path to a + text file. If passing an array, can pass a tuple, list, + 1D NumPy array, or 1D tensor containing the integer vocbulary terms. + If passing a file path, the file should contain one line per term + in the vocabulary. If this argument is set, + there is no need to `adapt()` the layer. + vocabulary_dtype: The dtype of the vocabulary terms, for example + `"int64"` or `"int32"`. Defaults to `"int64"`. + idf_weights: Only valid when `output_mode` is `"tf_idf"`. + A tuple, list, 1D NumPy array, or 1D tensor or the same length + as the vocabulary, containing the floating point inverse document + frequency weights, which will be multiplied by per sample term + counts for the final TF-IDF weight. + If the `vocabulary` argument is set, and `output_mode` is + `"tf_idf"`, this argument must be supplied. + invert: Only valid when `output_mode` is `"int"`. + If `True`, this layer will map indices to vocabulary items + instead of mapping vocabulary items to indices. + Defaults to `False`. + output_mode: Specification for the output of the layer. Values can be + `"int"`, `"one_hot"`, `"multi_hot"`, `"count"`, or `"tf_idf"` + configuring the layer as follows: + - `"int"`: Return the vocabulary indices of the input tokens. + - `"one_hot"`: Encodes each individual element in the input into an + array the same size as the vocabulary, + containing a 1 at the element index. If the last dimension + is size 1, will encode on that dimension. + If the last dimension is not size 1, will append a new + dimension for the encoded output. + - `"multi_hot"`: Encodes each sample in the input into a single + array the same size as the vocabulary, + containing a 1 for each vocabulary term present in the sample. + Treats the last dimension as the sample dimension, + if input shape is `(..., sample_length)`, + output shape will be `(..., num_tokens)`. + - `"count"`: As `"multi_hot"`, but the int array contains + a count of the number of times the token at that index + appeared in the sample. + - `"tf_idf"`: As `"multi_hot"`, but the TF-IDF algorithm is + applied to find the value in each token slot. + For `"int"` output, any shape of input and output is supported. + For all other output modes, currently only output up to rank 2 + is supported. Defaults to `"int"`. + pad_to_max_tokens: Only applicable when `output_mode` is `"multi_hot"`, + `"count"`, or `"tf_idf"`. If `True`, the output will have + its feature axis padded to `max_tokens` even if the number + of unique tokens in the vocabulary is less than `max_tokens`, + resulting in a tensor of shape `(batch_size, max_tokens)` + regardless of vocabulary size. Defaults to `False`. + sparse: Boolean. Only applicable to `"multi_hot"`, `"count"`, and + `"tf_idf"` output modes. Only supported with TensorFlow + backend. If `True`, returns a `SparseTensor` + instead of a dense `Tensor`. Defaults to `False`. + encoding: Optional. The text encoding to use to interpret the input + strings. Defaults to `"utf-8"`. + + Examples: + + **Creating a lookup layer with a known vocabulary** + + This example creates a lookup layer with a pre-existing vocabulary. + + >>> vocab = ["a", "b", "c", "d"] + >>> data = [["a", "c", "d"], ["d", "z", "b"]] + >>> layer = StringLookup(vocabulary=vocab) + >>> layer(data) + array([[1, 3, 4], + [4, 0, 2]]) + + **Creating a lookup layer with an adapted vocabulary** + + This example creates a lookup layer and generates the vocabulary by + analyzing the dataset. + + >>> data = [["a", "c", "d"], ["d", "z", "b"]] + >>> layer = StringLookup() + >>> layer.adapt(data) + >>> layer.get_vocabulary() + ['[UNK]', 'd', 'z', 'c', 'b', 'a'] + + Note that the OOV token `"[UNK]"` has been added to the vocabulary. + The remaining tokens are sorted by frequency + (`"d"`, which has 2 occurrences, is first) then by inverse sort order. + + >>> data = [["a", "c", "d"], ["d", "z", "b"]] + >>> layer = StringLookup() + >>> layer.adapt(data) + >>> layer(data) + array([[5, 3, 1], + [1, 2, 4]]) + + **Lookups with multiple OOV indices** + + This example demonstrates how to use a lookup layer with multiple OOV + indices. When a layer is created with more than one OOV index, any OOV + values are hashed into the number of OOV buckets, distributing OOV values in + a deterministic fashion across the set. + + >>> vocab = ["a", "b", "c", "d"] + >>> data = [["a", "c", "d"], ["m", "z", "b"]] + >>> layer = StringLookup(vocabulary=vocab, num_oov_indices=2) + >>> layer(data) + array([[2, 4, 5], + [0, 1, 3]]) + + Note that the output for OOV value 'm' is 0, while the output for OOV value + `"z"` is 1. The in-vocab terms have their output index increased by 1 from + earlier examples (a maps to 2, etc) in order to make space for the extra OOV + value. + + **One-hot output** + + Configure the layer with `output_mode='one_hot'`. Note that the first + `num_oov_indices` dimensions in the ont_hot encoding represent OOV values. + + >>> vocab = ["a", "b", "c", "d"] + >>> data = ["a", "b", "c", "d", "z"] + >>> layer = StringLookup(vocabulary=vocab, output_mode='one_hot') + >>> layer(data) + array([[0., 1., 0., 0., 0.], + [0., 0., 1., 0., 0.], + [0., 0., 0., 1., 0.], + [0., 0., 0., 0., 1.], + [1., 0., 0., 0., 0.]], dtype=int64) + + **Multi-hot output** + + Configure the layer with `output_mode='multi_hot'`. Note that the first + `num_oov_indices` dimensions in the multi_hot encoding represent OOV values. + + >>> vocab = ["a", "b", "c", "d"] + >>> data = [["a", "c", "d", "d"], ["d", "z", "b", "z"]] + >>> layer = StringLookup(vocabulary=vocab, output_mode='multi_hot') + >>> layer(data) + array([[0., 1., 0., 1., 1.], + [1., 0., 1., 0., 1.]], dtype=int64) + + **Token count output** + + Configure the layer with `output_mode='count'`. As with multi_hot output, + the first `num_oov_indices` dimensions in the output represent OOV values. + + >>> vocab = ["a", "b", "c", "d"] + >>> data = [["a", "c", "d", "d"], ["d", "z", "b", "z"]] + >>> layer = StringLookup(vocabulary=vocab, output_mode='count') + >>> layer(data) + array([[0., 1., 0., 1., 2.], + [2., 0., 1., 0., 1.]], dtype=int64) + + **TF-IDF output** + + Configure the layer with `output_mode="tf_idf"`. As with multi_hot output, + the first `num_oov_indices` dimensions in the output represent OOV values. + + Each token bin will output `token_count * idf_weight`, where the idf weights + are the inverse document frequency weights per token. These should be + provided along with the vocabulary. Note that the `idf_weight` for OOV + values will default to the average of all idf weights passed in. + + >>> vocab = ["a", "b", "c", "d"] + >>> idf_weights = [0.25, 0.75, 0.6, 0.4] + >>> data = [["a", "c", "d", "d"], ["d", "z", "b", "z"]] + >>> layer = StringLookup(output_mode="tf_idf") + >>> layer.set_vocabulary(vocab, idf_weights=idf_weights) + >>> layer(data) + array([[0. , 0.25, 0. , 0.6 , 0.8 ], + [1.0 , 0. , 0.75, 0. , 0.4 ]], dtype=float32) + + To specify the idf weights for oov values, you will need to pass the entire + vocabulary including the leading oov token. + + >>> vocab = ["[UNK]", "a", "b", "c", "d"] + >>> idf_weights = [0.9, 0.25, 0.75, 0.6, 0.4] + >>> data = [["a", "c", "d", "d"], ["d", "z", "b", "z"]] + >>> layer = StringLookup(output_mode="tf_idf") + >>> layer.set_vocabulary(vocab, idf_weights=idf_weights) + >>> layer(data) + array([[0. , 0.25, 0. , 0.6 , 0.8 ], + [1.8 , 0. , 0.75, 0. , 0.4 ]], dtype=float32) + + When adapting the layer in `"tf_idf"` mode, each input sample will be + considered a document, and IDF weight per token will be calculated as + `log(1 + num_documents / (1 + token_document_count))`. + + **Inverse lookup** + + This example demonstrates how to map indices to strings using this layer. + (You can also use `adapt()` with `inverse=True`, but for simplicity we'll + pass the vocab in this example.) + + >>> vocab = ["a", "b", "c", "d"] + >>> data = [[1, 3, 4], [4, 0, 2]] + >>> layer = StringLookup(vocabulary=vocab, invert=True) + >>> layer(data) + array([[b'a', b'c', b'd'], + [b'd', b'[UNK]', b'b']], dtype=object) + + Note that the first index correspond to the oov token by default. + + + **Forward and inverse lookup pairs** + + This example demonstrates how to use the vocabulary of a standard lookup + layer to create an inverse lookup layer. + + >>> vocab = ["a", "b", "c", "d"] + >>> data = [["a", "c", "d"], ["d", "z", "b"]] + >>> layer = StringLookup(vocabulary=vocab) + >>> i_layer = StringLookup(vocabulary=vocab, invert=True) + >>> int_data = layer(data) + >>> i_layer(int_data) + array([[b'a', b'c', b'd'], + [b'd', b'[UNK]', b'b']], dtype=object) + + In this example, the input value `"z"` resulted in an output of `"[UNK]"`, + since 1000 was not in the vocabulary - it got represented as an OOV, and all + OOV values are returned as `"[UNK]"` in the inverse layer. Also, note that + for the inverse to work, you must have already set the forward layer + vocabulary either directly or via `adapt()` before calling + `get_vocabulary()`. + """ + + def __init__( + self, + max_tokens=None, + num_oov_indices=1, + mask_token=None, + oov_token="[UNK]", + vocabulary=None, + idf_weights=None, + invert=False, + output_mode="int", + pad_to_max_tokens=False, + sparse=False, + encoding="utf-8", + name=None, + **kwargs, + ): + if not tf.available: + raise ImportError( + "Layer StringLookup requires TensorFlow. " + "Install it via `pip install tensorflow`." + ) + if sparse and backend.backend() != "tensorflow": + raise ValueError( + "`sparse=True` can only be used with the " "TensorFlow backend." + ) + self.encoding = encoding + super().__init__( + max_tokens=max_tokens, + num_oov_indices=num_oov_indices, + mask_token=mask_token, + oov_token=oov_token, + vocabulary=vocabulary, + idf_weights=idf_weights, + invert=invert, + output_mode=output_mode, + pad_to_max_tokens=pad_to_max_tokens, + sparse=sparse, + name=name, + vocabulary_dtype="string", + **kwargs, + ) + self._convert_input_args = False + self._allow_non_tensor_positional_args = True + self.supports_jit = False + + def adapt(self, data, steps=None): + """Computes a vocabulary of integer terms from tokens in a dataset. + + Calling `adapt()` on a `StringLookup` layer is an alternative to passing + in a precomputed vocabulary on construction via the `vocabulary` + argument. A `StringLookup` layer should always be either adapted over a + dataset or supplied with a vocabulary. + + During `adapt()`, the layer will build a vocabulary of all string tokens + seen in the dataset, sorted by occurrence count, with ties broken by + sort order of the tokens (high to low). At the end of `adapt()`, if + `max_tokens` is set, the vocabulary will be truncated to `max_tokens` + size. For example, adapting a layer with `max_tokens=1000` will compute + the 1000 most frequent tokens occurring in the input dataset. If + `output_mode='tf-idf'`, `adapt()` will also learn the document + frequencies of each token in the input dataset. + + Arguments: + data: The data to train on. It can be passed either as a + batched `tf.data.Dataset`, as a list of strings, + or as a NumPy array. + steps: Integer or `None`. + Total number of steps (batches of samples) to process. + If `data` is a `tf.data.Dataset`, and `steps` is `None`, + `adapt()` will run until the input dataset is exhausted. + When passing an infinitely + repeating dataset, you must specify the `steps` argument. This + argument is not supported with array inputs or list inputs. + """ + super().adapt(data, steps=steps) + + # Overridden methods from IndexLookup. + def _tensor_vocab_to_numpy(self, vocabulary): + vocabulary = vocabulary.numpy() + return np.array( + [tf.compat.as_text(x, self.encoding) for x in vocabulary] + ) + + def get_config(self): + config = {"encoding": self.encoding} + base_config = super().get_config() + # There is only one valid dtype for strings, so we don't expose this. + del base_config["vocabulary_dtype"] + return {**base_config, **config} + + def call(self, inputs): + if isinstance(inputs, (tf.Tensor, tf.RaggedTensor, tf.SparseTensor)): + tf_inputs = True + else: + tf_inputs = False + if not isinstance(inputs, (np.ndarray, list, tuple)): + inputs = tf.convert_to_tensor(backend.convert_to_numpy(inputs)) + outputs = super().call(inputs) + if not tf_inputs: + outputs = backend_utils.convert_tf_tensor(outputs) + return outputs diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/text_vectorization.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/text_vectorization.py new file mode 100644 index 0000000000000000000000000000000000000000..2f7bf18223d4a59cda9a89563fbeef72de4a858d --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/text_vectorization.py @@ -0,0 +1,626 @@ +import numpy as np + +from keras.src import backend +from keras.src.api_export import keras_export +from keras.src.layers.layer import Layer +from keras.src.layers.preprocessing.index_lookup import listify_tensors +from keras.src.layers.preprocessing.string_lookup import StringLookup +from keras.src.saving import serialization_lib +from keras.src.utils import argument_validation +from keras.src.utils import backend_utils +from keras.src.utils import tf_utils +from keras.src.utils.module_utils import tensorflow as tf + + +@keras_export("keras.layers.TextVectorization") +class TextVectorization(Layer): + """A preprocessing layer which maps text features to integer sequences. + + This layer has basic options for managing text in a Keras model. It + transforms a batch of strings (one example = one string) into either a list + of token indices (one example = 1D tensor of integer token indices) or a + dense representation (one example = 1D tensor of float values representing + data about the example's tokens). This layer is meant to handle natural + language inputs. To handle simple string inputs (categorical strings or + pre-tokenized strings) see `kers_core.layers.StringLookup`. + + The vocabulary for the layer must be either supplied on construction or + learned via `adapt()`. When this layer is adapted, it will analyze the + dataset, determine the frequency of individual string values, and create a + vocabulary from them. This vocabulary can have unlimited size or be capped, + depending on the configuration options for this layer; if there are more + unique values in the input than the maximum vocabulary size, the most + frequent terms will be used to create the vocabulary. + + The processing of each example contains the following steps: + + 1. Standardize each example (usually lowercasing + punctuation stripping) + 2. Split each example into substrings (usually words) + 3. Recombine substrings into tokens (usually ngrams) + 4. Index tokens (associate a unique int value with each token) + 5. Transform each example using this index, either into a vector of ints or + a dense float vector. + + Some notes on passing callables to customize splitting and normalization for + this layer: + + 1. Any callable can be passed to this Layer, but if you want to serialize + this object you should only pass functions that are registered Keras + serializables (see `keras.saving.register_keras_serializable` + for more details). + 2. When using a custom callable for `standardize`, the data received + by the callable will be exactly as passed to this layer. The callable + should return a tensor of the same shape as the input. + 3. When using a custom callable for `split`, the data received by the + callable will have the 1st dimension squeezed out - instead of + `[["string to split"], ["another string to split"]]`, the Callable will + see `["string to split", "another string to split"]`. + The callable should return a `tf.Tensor` of dtype `string` + with the first dimension containing the split tokens - + in this example, we should see something like `[["string", "to", + "split"], ["another", "string", "to", "split"]]`. + + **Note:** This layer uses TensorFlow internally. It cannot + be used as part of the compiled computation graph of a model with + any backend other than TensorFlow. + It can however be used with any backend when running eagerly. + It can also always be used as part of an input preprocessing pipeline + with any backend (outside the model itself), which is how we recommend + to use this layer. + + **Note:** This layer is safe to use inside a `tf.data` pipeline + (independently of which backend you're using). + + Args: + max_tokens: Maximum size of the vocabulary for this layer. This should + only be specified when adapting a vocabulary or when setting + `pad_to_max_tokens=True`. Note that this vocabulary + contains 1 OOV token, so the effective number of tokens is + `(max_tokens - 1 - (1 if output_mode == "int" else 0))`. + standardize: Optional specification for standardization to apply to the + input text. Values can be: + - `None`: No standardization. + - `"lower_and_strip_punctuation"`: Text will be lowercased and all + punctuation removed. + - `"lower"`: Text will be lowercased. + - `"strip_punctuation"`: All punctuation will be removed. + - Callable: Inputs will passed to the callable function, + which should be standardized and returned. + split: Optional specification for splitting the input text. + Values can be: + - `None`: No splitting. + - `"whitespace"`: Split on whitespace. + - `"character"`: Split on each unicode character. + - Callable: Standardized inputs will passed to the callable + function, which should be split and returned. + ngrams: Optional specification for ngrams to create from the + possibly-split input text. Values can be `None`, an integer + or tuple of integers; passing an integer will create ngrams + up to that integer, and passing a tuple of integers will + create ngrams for the specified values in the tuple. + Passing `None` means that no ngrams will be created. + output_mode: Optional specification for the output of the layer. + Values can be `"int"`, `"multi_hot"`, `"count"` or `"tf_idf"`, + configuring the layer as follows: + - `"int"`: Outputs integer indices, one integer index per split + string token. When `output_mode == "int"`, + 0 is reserved for masked locations; + this reduces the vocab size to `max_tokens - 2` + instead of `max_tokens - 1`. + - `"multi_hot"`: Outputs a single int array per batch, of either + vocab_size or max_tokens size, containing 1s in all elements + where the token mapped to that index exists at least + once in the batch item. + - `"count"`: Like `"multi_hot"`, but the int array contains + a count of the number of times the token at that index + appeared in the batch item. + - `"tf_idf"`: Like `"multi_hot"`, but the TF-IDF algorithm + is applied to find the value in each token slot. + For `"int"` output, any shape of input and output is supported. + For all other output modes, currently only rank 1 inputs + (and rank 2 outputs after splitting) are supported. + output_sequence_length: Only valid in INT mode. If set, the output will + have its time dimension padded or truncated to exactly + `output_sequence_length` values, resulting in a tensor of shape + `(batch_size, output_sequence_length)` regardless of how many tokens + resulted from the splitting step. Defaults to `None`. If `ragged` + is `True` then `output_sequence_length` may still truncate the + output. + pad_to_max_tokens: Only valid in `"multi_hot"`, `"count"`, + and `"tf_idf"` modes. If `True`, the output will have + its feature axis padded to `max_tokens` even if the number + of unique tokens in the vocabulary is less than `max_tokens`, + resulting in a tensor of shape `(batch_size, max_tokens)` + regardless of vocabulary size. Defaults to `False`. + vocabulary: Optional. Either an array of strings or a string path to a + text file. If passing an array, can pass a tuple, list, + 1D NumPy array, or 1D tensor containing the string vocabulary terms. + If passing a file path, the file should contain one line per term + in the vocabulary. If this argument is set, + there is no need to `adapt()` the layer. + idf_weights: Only valid when `output_mode` is `"tf_idf"`. A tuple, list, + 1D NumPy array, or 1D tensor of the same length as the vocabulary, + containing the floating point inverse document frequency weights, + which will be multiplied by per sample term counts for + the final `tf_idf` weight. If the `vocabulary` argument is set, + and `output_mode` is `"tf_idf"`, this argument must be supplied. + ragged: Boolean. Only applicable to `"int"` output mode. + Only supported with TensorFlow backend. + If `True`, returns a `RaggedTensor` instead of a dense `Tensor`, + where each sequence may have a different length + after string splitting. Defaults to `False`. + sparse: Boolean. Only applicable to `"multi_hot"`, `"count"`, and + `"tf_idf"` output modes. Only supported with TensorFlow + backend. If `True`, returns a `SparseTensor` + instead of a dense `Tensor`. Defaults to `False`. + encoding: Optional. The text encoding to use to interpret the input + strings. Defaults to `"utf-8"`. + + Examples: + + This example instantiates a `TextVectorization` layer that lowercases text, + splits on whitespace, strips punctuation, and outputs integer vocab indices. + + >>> max_tokens = 5000 # Maximum vocab size. + >>> max_len = 4 # Sequence length to pad the outputs to. + >>> # Create the layer. + >>> vectorize_layer = TextVectorization( + ... max_tokens=max_tokens, + ... output_mode='int', + ... output_sequence_length=max_len) + + >>> # Now that the vocab layer has been created, call `adapt` on the + >>> # list of strings to create the vocabulary. + >>> vectorize_layer.adapt(["foo bar", "bar baz", "baz bada boom"]) + + >>> # Now, the layer can map strings to integers -- you can use an + >>> # embedding layer to map these integers to learned embeddings. + >>> input_data = [["foo qux bar"], ["qux baz"]] + >>> vectorize_layer(input_data) + array([[4, 1, 3, 0], + [1, 2, 0, 0]]) + + This example instantiates a `TextVectorization` layer by passing a list + of vocabulary terms to the layer's `__init__()` method. + + >>> vocab_data = ["earth", "wind", "and", "fire"] + >>> max_len = 4 # Sequence length to pad the outputs to. + >>> # Create the layer, passing the vocab directly. You can also pass the + >>> # vocabulary arg a path to a file containing one vocabulary word per + >>> # line. + >>> vectorize_layer = keras.layers.TextVectorization( + ... max_tokens=max_tokens, + ... output_mode='int', + ... output_sequence_length=max_len, + ... vocabulary=vocab_data) + + >>> # Because we've passed the vocabulary directly, we don't need to adapt + >>> # the layer - the vocabulary is already set. The vocabulary contains the + >>> # padding token ('') and OOV token ('[UNK]') + >>> # as well as the passed tokens. + >>> vectorize_layer.get_vocabulary() + ['', '[UNK]', 'earth', 'wind', 'and', 'fire'] + """ + + def __init__( + self, + max_tokens=None, + standardize="lower_and_strip_punctuation", + split="whitespace", + ngrams=None, + output_mode="int", + output_sequence_length=None, + pad_to_max_tokens=False, + vocabulary=None, + idf_weights=None, + sparse=False, + ragged=False, + encoding="utf-8", + name=None, + **kwargs, + ): + if not tf.available: + raise ImportError( + "Layer TextVectorization requires TensorFlow. " + "Install it via `pip install tensorflow`." + ) + if sparse and backend.backend() != "tensorflow": + raise ValueError( + "`sparse=True` can only be used with the " "TensorFlow backend." + ) + if ragged and backend.backend() != "tensorflow": + raise ValueError( + "`ragged=True` can only be used with the " "TensorFlow backend." + ) + + # 'standardize' must be one of + # (None, "lower_and_strip_punctuation", "lower", "strip_punctuation", + # callable) + argument_validation.validate_string_arg( + standardize, + allowable_strings=( + "lower_and_strip_punctuation", + "lower", + "strip_punctuation", + ), + caller_name=self.__class__.__name__, + arg_name="standardize", + allow_none=True, + allow_callables=True, + ) + + # 'split' must be one of (None, "whitespace", "character", callable) + argument_validation.validate_string_arg( + split, + allowable_strings=("whitespace", "character"), + caller_name=self.__class__.__name__, + arg_name="split", + allow_none=True, + allow_callables=True, + ) + + # Support deprecated names for output_modes. + if output_mode == "binary": + output_mode = "multi_hot" + if output_mode == "tf-idf": + output_mode = "tf_idf" + argument_validation.validate_string_arg( + output_mode, + allowable_strings=( + "int", + "one_hot", + "multi_hot", + "count", + "tf_idf", + ), + caller_name=self.__class__.__name__, + arg_name="output_mode", + ) + + # 'ngrams' must be one of (None, int, tuple(int)) + if not ( + ngrams is None + or isinstance(ngrams, int) + or isinstance(ngrams, tuple) + and all(isinstance(item, int) for item in ngrams) + ): + raise ValueError( + "`ngrams` must be None, an integer, or a tuple of " + f"integers. Received: ngrams={ngrams}" + ) + + # 'output_sequence_length' must be one of (None, int) and is only + # set if output_mode is "int"". + if output_mode == "int" and not ( + isinstance(output_sequence_length, int) + or (output_sequence_length is None) + ): + raise ValueError( + "`output_sequence_length` must be either None or an " + "integer when `output_mode` is 'int'. Received: " + f"output_sequence_length={output_sequence_length}" + ) + + if output_mode != "int" and output_sequence_length is not None: + raise ValueError( + "`output_sequence_length` must not be set if `output_mode` is " + "not 'int'. " + f"Received output_sequence_length={output_sequence_length}." + ) + + if ragged and output_mode != "int": + raise ValueError( + "`ragged` must not be true if `output_mode` is " + f"`'int'`. Received: ragged={ragged} and " + f"output_mode={output_mode}" + ) + + self._max_tokens = max_tokens + self._standardize = standardize + self._split = split + self._ngrams_arg = ngrams + if isinstance(ngrams, int): + self._ngrams = tuple(range(1, ngrams + 1)) + else: + self._ngrams = ngrams + self._ragged = ragged + + self._output_mode = output_mode + self._output_sequence_length = output_sequence_length + self._encoding = encoding + + # We save this hidden option to persist the fact + # that we have a non-adaptable layer with a + # manually set vocab. + self._has_input_vocabulary = kwargs.pop( + "has_input_vocabulary", (vocabulary is not None) + ) + vocabulary_size = kwargs.pop("vocabulary_size", None) + + super().__init__(name=name, **kwargs) + + self._lookup_layer = StringLookup( + max_tokens=max_tokens, + vocabulary=vocabulary, + idf_weights=idf_weights, + pad_to_max_tokens=pad_to_max_tokens, + mask_token="", + output_mode=output_mode, + sparse=sparse, + has_input_vocabulary=self._has_input_vocabulary, + encoding=encoding, + vocabulary_size=vocabulary_size, + ) + self._convert_input_args = False + self._allow_non_tensor_positional_args = True + self.supports_jit = False + + @property + def compute_dtype(self): + return "string" + + @property + def variable_dtype(self): + return "string" + + def build(self, input_shape=None): + pass + + def compute_output_shape(self, input_shape): + if self._output_mode == "int": + return (input_shape[0], self._output_sequence_length) + if self._split is None: + if len(input_shape) <= 1: + input_shape = tuple(input_shape) + (1,) + else: + input_shape = tuple(input_shape) + (None,) + return self._lookup_layer.compute_output_shape(input_shape) + + def compute_output_spec(self, inputs): + output_shape = self.compute_output_shape(inputs.shape) + if self._output_mode == "int": + output_dtype = "int64" + else: + output_dtype = backend.floatx() + return backend.KerasTensor(output_shape, dtype=output_dtype) + + def adapt(self, data, batch_size=None, steps=None): + """Computes a vocabulary of string terms from tokens in a dataset. + + Calling `adapt()` on a `TextVectorization` layer is an alternative to + passing in a precomputed vocabulary on construction via the `vocabulary` + argument. A `TextVectorization` layer should always be either adapted + over a dataset or supplied with a vocabulary. + + During `adapt()`, the layer will build a vocabulary of all string tokens + seen in the dataset, sorted by occurrence count, with ties broken by + sort order of the tokens (high to low). At the end of `adapt()`, if + `max_tokens` is set, the vocabulary will be truncated to `max_tokens` + size. For example, adapting a layer with `max_tokens=1000` will compute + the 1000 most frequent tokens occurring in the input dataset. If + `output_mode='tf-idf'`, `adapt()` will also learn the document + frequencies of each token in the input dataset. + + Arguments: + data: The data to train on. It can be passed either as a + batched `tf.data.Dataset`, as a list of strings, + or as a NumPy array. + steps: Integer or `None`. + Total number of steps (batches of samples) to process. + If `data` is a `tf.data.Dataset`, and `steps` is `None`, + `adapt()` will run until the input dataset is exhausted. + When passing an infinitely + repeating dataset, you must specify the `steps` argument. This + argument is not supported with array inputs or list inputs. + """ + self.reset_state() + if isinstance(data, tf.data.Dataset): + if steps is not None: + data = data.take(steps) + for batch in data: + self.update_state(batch) + else: + data = tf_utils.ensure_tensor(data, dtype="string") + if data.shape.rank == 1: + # A plain list of strings + # is treated as as many documents + data = tf.expand_dims(data, -1) + self.update_state(data) + self.finalize_state() + + def update_state(self, data): + self._lookup_layer.update_state(self._preprocess(data)) + + def finalize_state(self): + self._lookup_layer.finalize_state() + + def reset_state(self): + self._lookup_layer.reset_state() + + def get_vocabulary(self, include_special_tokens=True): + """Returns the current vocabulary of the layer. + + Args: + include_special_tokens: If `True`, the returned vocabulary + will include the padding and OOV tokens, + and a term's index in the vocabulary will equal + the term's index when calling the layer. If `False`, the + returned vocabulary will not include any padding + or OOV tokens. + """ + return self._lookup_layer.get_vocabulary(include_special_tokens) + + def vocabulary_size(self): + """Gets the current size of the layer's vocabulary. + + Returns: + The integer size of the vocabulary, including optional + mask and OOV indices. + """ + return self._lookup_layer.vocabulary_size() + + def get_config(self): + config = { + "max_tokens": self._lookup_layer.max_tokens, + "standardize": self._standardize, + "split": self._split, + "ngrams": self._ngrams_arg, + "output_mode": self._output_mode, + "output_sequence_length": self._output_sequence_length, + "pad_to_max_tokens": self._lookup_layer.pad_to_max_tokens, + "sparse": self._lookup_layer.sparse, + "ragged": self._ragged, + "vocabulary": listify_tensors(self._lookup_layer.input_vocabulary), + "idf_weights": listify_tensors( + self._lookup_layer.input_idf_weights + ), + "encoding": self._encoding, + "vocabulary_size": self.vocabulary_size(), + } + base_config = super().get_config() + return {**base_config, **config} + + @classmethod + def from_config(cls, config): + if not isinstance(config["standardize"], str): + config["standardize"] = serialization_lib.deserialize_keras_object( + config["standardize"] + ) + if not isinstance(config["split"], str): + config["split"] = serialization_lib.deserialize_keras_object( + config["split"] + ) + + if isinstance(config["ngrams"], list): + config["ngrams"] = tuple(config["ngrams"]) + + return cls(**config) + + def set_vocabulary(self, vocabulary, idf_weights=None): + """Sets vocabulary (and optionally document frequency) for this layer. + + This method sets the vocabulary and IDF weights for this layer directly, + instead of analyzing a dataset through `adapt()`. It should be used + whenever the vocab (and optionally document frequency) information is + already known. If vocabulary data is already present in the layer, this + method will replace it. + + Args: + vocabulary: Either an array or a string path to a text file. + If passing an array, can pass a tuple, list, 1D NumPy array, + or 1D tensor containing the vocabulary terms. + If passing a file path, the file should contain one line + per term in the vocabulary. + idf_weights: A tuple, list, 1D NumPy array, or 1D tensor of inverse + document frequency weights with equal length to vocabulary. + Must be set if `output_mode` is `"tf_idf"`. + Should not be set otherwise. + """ + self._lookup_layer.set_vocabulary(vocabulary, idf_weights=idf_weights) + + def _preprocess(self, inputs): + inputs = tf_utils.ensure_tensor(inputs, dtype=tf.string) + if self._standardize in ("lower", "lower_and_strip_punctuation"): + inputs = tf.strings.lower(inputs) + if self._standardize in ( + "strip_punctuation", + "lower_and_strip_punctuation", + ): + inputs = tf.strings.regex_replace( + inputs, r'[!"#$%&()\*\+,-\./:;<=>?@\[\\\]^_`{|}~\']', "" + ) + if callable(self._standardize): + inputs = self._standardize(inputs) + + if self._split is not None: + # If we are splitting, we validate that the 1st axis is of dimension + # 1 and so can be squeezed out. We do this here instead of after + # splitting for performance reasons - it's more expensive to squeeze + # a ragged tensor. + if inputs.shape.rank > 1: + if inputs.shape[-1] != 1: + raise ValueError( + "When using `TextVectorization` to tokenize strings, " + "the input rank must be 1 or the last shape dimension " + f"must be 1. Received: inputs.shape={inputs.shape} " + f"with rank={inputs.shape.rank}" + ) + else: + inputs = tf.squeeze(inputs, axis=-1) + if self._split == "whitespace": + # This treats multiple whitespaces as one whitespace, and strips + # leading and trailing whitespace. + inputs = tf.strings.split(inputs) + elif self._split == "character": + inputs = tf.strings.unicode_split(inputs, "UTF-8") + elif callable(self._split): + inputs = self._split(inputs) + + # Note that 'inputs' here can be either ragged or dense depending on the + # configuration choices for this Layer. The strings.ngrams op, however, + # does support both ragged and dense inputs. + if self._ngrams is not None: + inputs = tf.strings.ngrams( + inputs, ngram_width=self._ngrams, separator=" " + ) + return inputs + + def call(self, inputs): + if not isinstance( + inputs, (tf.Tensor, tf.RaggedTensor, np.ndarray, list, tuple) + ): + inputs = tf.convert_to_tensor(backend.convert_to_numpy(inputs)) + + inputs = self._preprocess(inputs) + + # If we're not doing any output processing, return right away. + if self._output_mode is None: + outputs = inputs + + lookup_data = self._lookup_layer.call(inputs) + + # For non-int output, we can return directly from the underlying layer. + if self._output_mode != "int": + return backend_utils.convert_tf_tensor(lookup_data) + + # If we have a ragged tensor, we can pad during the conversion to dense. + if isinstance(lookup_data, tf.RaggedTensor) and not self._ragged: + shape = lookup_data.shape.as_list() + # If output sequence length is None, to_tensor will pad the last + # dimension to the bounding shape of the ragged dimension. + shape[-1] = self._output_sequence_length + outputs = lookup_data.to_tensor(default_value=0, shape=shape) + else: + outputs = lookup_data + + # If we have a dense tensor, we need to pad/trim directly. + if self._output_sequence_length is not None: + # Maybe trim the output. + outputs = outputs[..., : self._output_sequence_length] + + # Maybe pad the output. We need to be careful to use dynamic shape + # here as required_space_to_batch_paddings requires a fully known + # shape. + if not self._ragged: + shape = tf.shape(outputs) + padded_shape = tf.concat( + (shape[:-1], [self._output_sequence_length]), 0 + ) + padding, _ = tf.required_space_to_batch_paddings( + shape, padded_shape + ) + outputs = tf.pad(outputs, padding) + + return backend_utils.convert_tf_tensor(outputs) + + def save_own_variables(self, store): + self._lookup_layer.save_own_variables(store) + + def load_own_variables(self, store): + self._lookup_layer.load_own_variables(store) + + def save_assets(self, dir_path): + self._lookup_layer.save_assets(dir_path) + + def load_assets(self, dir_path): + self._lookup_layer.load_assets(dir_path) diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/tf_data_layer.py b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/tf_data_layer.py new file mode 100644 index 0000000000000000000000000000000000000000..fcd8fac393456509d2e23b97ffbcf1777a543bad --- /dev/null +++ b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/preprocessing/tf_data_layer.py @@ -0,0 +1,69 @@ +import keras.src.backend +from keras.src import tree +from keras.src.layers.layer import Layer +from keras.src.random.seed_generator import SeedGenerator +from keras.src.utils import backend_utils +from keras.src.utils import jax_utils +from keras.src.utils import tracking + + +class TFDataLayer(Layer): + """Layer that can safely used in a tf.data pipeline. + + The `call()` method must solely rely on `self.backend` ops. + + Only supports a single input tensor argument. + """ + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.backend = backend_utils.DynamicBackend() + self._allow_non_tensor_positional_args = True + + def __call__(self, inputs, **kwargs): + sample_input = tree.flatten(inputs)[0] + if ( + not isinstance(sample_input, keras.KerasTensor) + and backend_utils.in_tf_graph() + and not jax_utils.is_in_jax_tracing_scope(sample_input) + ): + # We're in a TF graph, e.g. a tf.data pipeline. + self.backend.set_backend("tensorflow") + inputs = tree.map_structure( + lambda x: self.backend.convert_to_tensor( + x, dtype=self.compute_dtype + ), + inputs, + ) + switch_convert_input_args = False + if self._convert_input_args: + self._convert_input_args = False + switch_convert_input_args = True + try: + outputs = super().__call__(inputs, **kwargs) + finally: + self.backend.reset() + if switch_convert_input_args: + self._convert_input_args = True + return outputs + return super().__call__(inputs, **kwargs) + + @tracking.no_automatic_dependency_tracking + def _get_seed_generator(self, backend=None): + if backend is None or backend == keras.backend.backend(): + return self.generator + if not hasattr(self, "_backend_generators"): + self._backend_generators = {} + if backend in self._backend_generators: + return self._backend_generators[backend] + seed_generator = SeedGenerator(self.seed, backend=self.backend) + self._backend_generators[backend] = seed_generator + return seed_generator + + def convert_weight(self, weight): + """Convert the weight if it is from the a different backend.""" + if self.backend.name == keras.backend.backend(): + return weight + else: + weight = keras.ops.convert_to_numpy(weight) + return self.backend.convert_to_tensor(weight) diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/regularization/__pycache__/__init__.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/regularization/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..58bfe44350635d73356a383904e8fea64ee5f93d Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/regularization/__pycache__/__init__.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/regularization/__pycache__/activity_regularization.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/regularization/__pycache__/activity_regularization.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e726c21de19efde1ed2dfd6101f1e7f56db3081d Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/regularization/__pycache__/activity_regularization.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/regularization/__pycache__/alpha_dropout.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/regularization/__pycache__/alpha_dropout.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ecf18011959f49e9b8d18d5410b7e6d8ff8b9ea Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/regularization/__pycache__/alpha_dropout.cpython-310.pyc differ diff --git a/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/regularization/__pycache__/dropout.cpython-310.pyc b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/regularization/__pycache__/dropout.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3a43706a971ac9140e100dfe921bf34332b3e69e Binary files /dev/null and b/SwarmUI/dlbackend/ComfyUI/venv/lib/python3.10/site-packages/keras/src/layers/regularization/__pycache__/dropout.cpython-310.pyc differ