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 fillmode 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 channelslast format or channels height width in channelsfirst format output shape 3d unbatched or 4d batched tensor with shape targetheight targetwidth channels or channels targetheight targetwidth in channelsfirst format note this layer is safe to use inside a tf data pipeline independently of which backend you re using args heightfactor 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 heightfactor0 2 0 3 result in an output zoomed out by a random amount in the range 20 30 heightfactor0 3 0 2 result in an output zoomed in by a random amount in the range 20 30 widthfactor 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 widthfactor0 2 0 3 result in an output zooming out between 20 to 30 widthfactor0 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 fillmode 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 fillvalue 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 fillvalue a float represents the value to be filled outside the boundaries when fillmodeconstant dataformat string either channelslast or channelsfirst the ordering of the dimensions in the inputs channelslast corresponds to inputs with shape batch height width channels while channelsfirst corresponds to inputs with shape batch channels height width it defaults to the imagedataformat value found in your keras config file at keraskeras json if you never set it then it will be channelslast kwargs base layer keyword arguments such as name and dtype example inputimg np random random32 224 224 3 layer keras layers randomzoom 5 2 outimg layerinputimg 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 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 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 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
from keras import backend
from keras.api_export import keras_export
from keras.layers.preprocessing.tf_data_layer import TFDataLayer
from keras.random.seed_generator import SeedGenerator


@keras_export("keras.layers.RandomZoom")
class RandomZoom(TFDataLayer):
    

    _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=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 call(self, inputs, training=True):
        inputs = self.backend.cast(inputs, self.compute_dtype)
        if training:
            return self._randomly_zoom_inputs(inputs)
        else:
            return inputs

    def _randomly_zoom_inputs(self, inputs):
        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)

        batch_size = inputs_shape[0]
        if self.data_format == "channels_first":
            height = inputs_shape[-2]
            width = inputs_shape[-1]
        else:
            height = inputs_shape[-3]
            width = inputs_shape[-2]

        seed_generator = 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=[batch_size, 1],
            seed=seed_generator,
        )
        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=[batch_size, 1],
                seed=seed_generator,
            )
        else:
            width_zoom = height_zoom
        zooms = self.backend.cast(
            self.backend.numpy.concatenate([width_zoom, height_zoom], axis=1),
            dtype="float32",
        )

        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]
        
        
        
        
        
        
        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}