1# Copyright 2019 The TensorFlow Authors. All Rights Reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14# ============================================================================== 15"""Keras image preprocessing layers.""" 16# pylint: disable=g-classes-have-attributes 17from __future__ import absolute_import 18from __future__ import division 19from __future__ import print_function 20 21import numpy as np 22 23from tensorflow.python.eager import context 24from tensorflow.python.compat import compat 25from tensorflow.python.framework import dtypes 26from tensorflow.python.framework import ops 27from tensorflow.python.framework import tensor_shape 28from tensorflow.python.framework import tensor_util 29from tensorflow.python.keras import backend as K 30from tensorflow.python.keras.engine import base_preprocessing_layer 31from tensorflow.python.keras.engine.base_preprocessing_layer import PreprocessingLayer 32from tensorflow.python.keras.engine.input_spec import InputSpec 33from tensorflow.python.keras.utils import control_flow_util 34from tensorflow.python.ops import array_ops 35from tensorflow.python.ops import check_ops 36from tensorflow.python.ops import control_flow_ops 37from tensorflow.python.ops import gen_image_ops 38from tensorflow.python.ops import image_ops 39from tensorflow.python.ops import math_ops 40from tensorflow.python.ops import stateful_random_ops 41from tensorflow.python.ops import stateless_random_ops 42from tensorflow.python.util.tf_export import keras_export 43 44ResizeMethod = image_ops.ResizeMethod 45 46_RESIZE_METHODS = { 47 'bilinear': ResizeMethod.BILINEAR, 48 'nearest': ResizeMethod.NEAREST_NEIGHBOR, 49 'bicubic': ResizeMethod.BICUBIC, 50 'area': ResizeMethod.AREA, 51 'lanczos3': ResizeMethod.LANCZOS3, 52 'lanczos5': ResizeMethod.LANCZOS5, 53 'gaussian': ResizeMethod.GAUSSIAN, 54 'mitchellcubic': ResizeMethod.MITCHELLCUBIC 55} 56 57H_AXIS = 1 58W_AXIS = 2 59 60 61def check_fill_mode_and_interpolation(fill_mode, interpolation): 62 if fill_mode not in {'reflect', 'wrap', 'constant', 'nearest'}: 63 raise NotImplementedError( 64 'Unknown `fill_mode` {}. Only `reflect`, `wrap`, ' 65 '`constant` and `nearest` are supported.'.format(fill_mode)) 66 if interpolation not in {'nearest', 'bilinear'}: 67 raise NotImplementedError('Unknown `interpolation` {}. Only `nearest` and ' 68 '`bilinear` are supported.'.format(interpolation)) 69 70 71@keras_export('keras.layers.experimental.preprocessing.Resizing') 72class Resizing(PreprocessingLayer): 73 """Image resizing layer. 74 75 Resize the batched image input to target height and width. The input should 76 be a 4-D tensor in the format of NHWC. 77 78 Args: 79 height: Integer, the height of the output shape. 80 width: Integer, the width of the output shape. 81 interpolation: String, the interpolation method. Defaults to `bilinear`. 82 Supports `bilinear`, `nearest`, `bicubic`, `area`, `lanczos3`, `lanczos5`, 83 `gaussian`, `mitchellcubic` 84 name: A string, the name of the layer. 85 """ 86 87 def __init__(self, 88 height, 89 width, 90 interpolation='bilinear', 91 name=None, 92 **kwargs): 93 self.target_height = height 94 self.target_width = width 95 self.interpolation = interpolation 96 self._interpolation_method = get_interpolation(interpolation) 97 self.input_spec = InputSpec(ndim=4) 98 super(Resizing, self).__init__(name=name, **kwargs) 99 base_preprocessing_layer.keras_kpl_gauge.get_cell('Resizing').set(True) 100 101 def call(self, inputs): 102 outputs = image_ops.resize_images_v2( 103 images=inputs, 104 size=[self.target_height, self.target_width], 105 method=self._interpolation_method) 106 return outputs 107 108 def compute_output_shape(self, input_shape): 109 input_shape = tensor_shape.TensorShape(input_shape).as_list() 110 return tensor_shape.TensorShape( 111 [input_shape[0], self.target_height, self.target_width, input_shape[3]]) 112 113 def get_config(self): 114 config = { 115 'height': self.target_height, 116 'width': self.target_width, 117 'interpolation': self.interpolation, 118 } 119 base_config = super(Resizing, self).get_config() 120 return dict(list(base_config.items()) + list(config.items())) 121 122 123@keras_export('keras.layers.experimental.preprocessing.CenterCrop') 124class CenterCrop(PreprocessingLayer): 125 """Crop the central portion of the images to target height and width. 126 127 Input shape: 128 4D tensor with shape: 129 `(samples, height, width, channels)`, data_format='channels_last'. 130 131 Output shape: 132 4D tensor with shape: 133 `(samples, target_height, target_width, channels)`. 134 135 If the input height/width is even and the target height/width is odd (or 136 inversely), the input image is left-padded by 1 pixel. 137 138 Args: 139 height: Integer, the height of the output shape. 140 width: Integer, the width of the output shape. 141 name: A string, the name of the layer. 142 """ 143 144 def __init__(self, height, width, name=None, **kwargs): 145 self.target_height = height 146 self.target_width = width 147 self.input_spec = InputSpec(ndim=4) 148 super(CenterCrop, self).__init__(name=name, **kwargs) 149 base_preprocessing_layer.keras_kpl_gauge.get_cell('CenterCrop').set(True) 150 151 def call(self, inputs): 152 inputs_shape = array_ops.shape(inputs) 153 img_hd = inputs_shape[H_AXIS] 154 img_wd = inputs_shape[W_AXIS] 155 img_hd_diff = img_hd - self.target_height 156 img_wd_diff = img_wd - self.target_width 157 checks = [] 158 checks.append( 159 check_ops.assert_non_negative( 160 img_hd_diff, 161 message='The crop height {} should not be greater than input ' 162 'height.'.format(self.target_height))) 163 checks.append( 164 check_ops.assert_non_negative( 165 img_wd_diff, 166 message='The crop width {} should not be greater than input ' 167 'width.'.format(self.target_width))) 168 with ops.control_dependencies(checks): 169 bbox_h_start = math_ops.cast(img_hd_diff / 2, dtypes.int32) 170 bbox_w_start = math_ops.cast(img_wd_diff / 2, dtypes.int32) 171 bbox_begin = array_ops.stack([0, bbox_h_start, bbox_w_start, 0]) 172 bbox_size = array_ops.stack( 173 [-1, self.target_height, self.target_width, -1]) 174 outputs = array_ops.slice(inputs, bbox_begin, bbox_size) 175 return outputs 176 177 def compute_output_shape(self, input_shape): 178 input_shape = tensor_shape.TensorShape(input_shape).as_list() 179 return tensor_shape.TensorShape( 180 [input_shape[0], self.target_height, self.target_width, input_shape[3]]) 181 182 def get_config(self): 183 config = { 184 'height': self.target_height, 185 'width': self.target_width, 186 } 187 base_config = super(CenterCrop, self).get_config() 188 return dict(list(base_config.items()) + list(config.items())) 189 190 191@keras_export('keras.layers.experimental.preprocessing.RandomCrop') 192class RandomCrop(PreprocessingLayer): 193 """Randomly crop the images to target height and width. 194 195 This layer will crop all the images in the same batch to the same cropping 196 location. 197 By default, random cropping is only applied during training. At inference 198 time, the images will be first rescaled to preserve the shorter side, and 199 center cropped. If you need to apply random cropping at inference time, 200 set `training` to True when calling the layer. 201 202 Input shape: 203 4D tensor with shape: 204 `(samples, height, width, channels)`, data_format='channels_last'. 205 206 Output shape: 207 4D tensor with shape: 208 `(samples, target_height, target_width, channels)`. 209 210 Args: 211 height: Integer, the height of the output shape. 212 width: Integer, the width of the output shape. 213 seed: Integer. Used to create a random seed. 214 name: A string, the name of the layer. 215 """ 216 217 def __init__(self, height, width, seed=None, name=None, **kwargs): 218 self.height = height 219 self.width = width 220 self.seed = seed 221 self._rng = make_generator(self.seed) 222 self.input_spec = InputSpec(ndim=4) 223 super(RandomCrop, self).__init__(name=name, **kwargs) 224 base_preprocessing_layer.keras_kpl_gauge.get_cell('RandomCrop').set(True) 225 226 def call(self, inputs, training=True): 227 if training is None: 228 training = K.learning_phase() 229 230 def random_cropped_inputs(): 231 """Cropped inputs with stateless random ops.""" 232 input_shape = array_ops.shape(inputs) 233 crop_size = array_ops.stack( 234 [input_shape[0], self.height, self.width, input_shape[3]]) 235 check = control_flow_ops.Assert( 236 math_ops.reduce_all(input_shape >= crop_size), 237 [self.height, self.width]) 238 with ops.control_dependencies([check]): 239 limit = input_shape - crop_size + 1 240 offset = stateless_random_ops.stateless_random_uniform( 241 array_ops.shape(input_shape), 242 dtype=crop_size.dtype, 243 maxval=crop_size.dtype.max, 244 seed=self._rng.make_seeds()[:, 0]) % limit 245 return array_ops.slice(inputs, offset, crop_size) 246 247 # TODO(b/143885775): Share logic with Resize and CenterCrop. 248 def resize_and_center_cropped_inputs(): 249 """Deterministically resize to shorter side and center crop.""" 250 input_shape = array_ops.shape(inputs) 251 input_height_t = input_shape[H_AXIS] 252 input_width_t = input_shape[W_AXIS] 253 ratio_cond = (input_height_t / input_width_t > (self.height / self.width)) 254 # pylint: disable=g-long-lambda 255 resized_height = control_flow_util.smart_cond( 256 ratio_cond, 257 lambda: math_ops.cast(self.width * input_height_t / input_width_t, 258 input_height_t.dtype), lambda: self.height) 259 resized_width = control_flow_util.smart_cond( 260 ratio_cond, lambda: self.width, 261 lambda: math_ops.cast(self.height * input_width_t / input_height_t, 262 input_width_t.dtype)) 263 # pylint: enable=g-long-lambda 264 resized_inputs = image_ops.resize_images_v2( 265 images=inputs, size=array_ops.stack([resized_height, resized_width])) 266 267 img_hd_diff = resized_height - self.height 268 img_wd_diff = resized_width - self.width 269 bbox_h_start = math_ops.cast(img_hd_diff / 2, dtypes.int32) 270 bbox_w_start = math_ops.cast(img_wd_diff / 2, dtypes.int32) 271 bbox_begin = array_ops.stack([0, bbox_h_start, bbox_w_start, 0]) 272 bbox_size = array_ops.stack([-1, self.height, self.width, -1]) 273 outputs = array_ops.slice(resized_inputs, bbox_begin, bbox_size) 274 return outputs 275 276 output = control_flow_util.smart_cond(training, random_cropped_inputs, 277 resize_and_center_cropped_inputs) 278 original_shape = inputs.shape.as_list() 279 batch_size, num_channels = original_shape[0], original_shape[3] 280 output_shape = [batch_size] + [self.height, self.width] + [num_channels] 281 output.set_shape(output_shape) 282 return output 283 284 def compute_output_shape(self, input_shape): 285 input_shape = tensor_shape.TensorShape(input_shape).as_list() 286 return tensor_shape.TensorShape( 287 [input_shape[0], self.height, self.width, input_shape[3]]) 288 289 def get_config(self): 290 config = { 291 'height': self.height, 292 'width': self.width, 293 'seed': self.seed, 294 } 295 base_config = super(RandomCrop, self).get_config() 296 return dict(list(base_config.items()) + list(config.items())) 297 298 299@keras_export('keras.layers.experimental.preprocessing.Rescaling') 300class Rescaling(PreprocessingLayer): 301 """Multiply inputs by `scale` and adds `offset`. 302 303 For instance: 304 305 1. To rescale an input in the `[0, 255]` range 306 to be in the `[0, 1]` range, you would pass `scale=1./255`. 307 308 2. To rescale an input in the `[0, 255]` range to be in the `[-1, 1]` range, 309 you would pass `scale=1./127.5, offset=-1`. 310 311 The rescaling is applied both during training and inference. 312 313 Input shape: 314 Arbitrary. 315 316 Output shape: 317 Same as input. 318 319 Args: 320 scale: Float, the scale to apply to the inputs. 321 offset: Float, the offset to apply to the inputs. 322 name: A string, the name of the layer. 323 """ 324 325 def __init__(self, scale, offset=0., name=None, **kwargs): 326 self.scale = scale 327 self.offset = offset 328 super(Rescaling, self).__init__(name=name, **kwargs) 329 base_preprocessing_layer.keras_kpl_gauge.get_cell('Rescaling').set(True) 330 331 def call(self, inputs): 332 dtype = self._compute_dtype 333 scale = math_ops.cast(self.scale, dtype) 334 offset = math_ops.cast(self.offset, dtype) 335 return math_ops.cast(inputs, dtype) * scale + offset 336 337 def compute_output_shape(self, input_shape): 338 return input_shape 339 340 def get_config(self): 341 config = { 342 'scale': self.scale, 343 'offset': self.offset, 344 } 345 base_config = super(Rescaling, self).get_config() 346 return dict(list(base_config.items()) + list(config.items())) 347 348 349HORIZONTAL = 'horizontal' 350VERTICAL = 'vertical' 351HORIZONTAL_AND_VERTICAL = 'horizontal_and_vertical' 352 353 354@keras_export('keras.layers.experimental.preprocessing.RandomFlip') 355class RandomFlip(PreprocessingLayer): 356 """Randomly flip each image horizontally and vertically. 357 358 This layer will flip the images based on the `mode` attribute. 359 During inference time, the output will be identical to input. Call the layer 360 with `training=True` to flip the input. 361 362 Input shape: 363 4D tensor with shape: 364 `(samples, height, width, channels)`, data_format='channels_last'. 365 366 Output shape: 367 4D tensor with shape: 368 `(samples, height, width, channels)`, data_format='channels_last'. 369 370 Attributes: 371 mode: String indicating which flip mode to use. Can be "horizontal", 372 "vertical", or "horizontal_and_vertical". Defaults to 373 "horizontal_and_vertical". "horizontal" is a left-right flip and 374 "vertical" is a top-bottom flip. 375 seed: Integer. Used to create a random seed. 376 name: A string, the name of the layer. 377 """ 378 379 def __init__(self, 380 mode=HORIZONTAL_AND_VERTICAL, 381 seed=None, 382 name=None, 383 **kwargs): 384 super(RandomFlip, self).__init__(name=name, **kwargs) 385 base_preprocessing_layer.keras_kpl_gauge.get_cell('RandomFlip').set(True) 386 self.mode = mode 387 if mode == HORIZONTAL: 388 self.horizontal = True 389 self.vertical = False 390 elif mode == VERTICAL: 391 self.horizontal = False 392 self.vertical = True 393 elif mode == HORIZONTAL_AND_VERTICAL: 394 self.horizontal = True 395 self.vertical = True 396 else: 397 raise ValueError('RandomFlip layer {name} received an unknown mode ' 398 'argument {arg}'.format(name=name, arg=mode)) 399 self.seed = seed 400 self._rng = make_generator(self.seed) 401 self.input_spec = InputSpec(ndim=4) 402 403 def call(self, inputs, training=True): 404 if training is None: 405 training = K.learning_phase() 406 407 def random_flipped_inputs(): 408 flipped_outputs = inputs 409 if self.horizontal: 410 flipped_outputs = image_ops.random_flip_left_right( 411 flipped_outputs, self.seed) 412 if self.vertical: 413 flipped_outputs = image_ops.random_flip_up_down(flipped_outputs, 414 self.seed) 415 return flipped_outputs 416 417 output = control_flow_util.smart_cond(training, random_flipped_inputs, 418 lambda: inputs) 419 output.set_shape(inputs.shape) 420 return output 421 422 def compute_output_shape(self, input_shape): 423 return input_shape 424 425 def get_config(self): 426 config = { 427 'mode': self.mode, 428 'seed': self.seed, 429 } 430 base_config = super(RandomFlip, self).get_config() 431 return dict(list(base_config.items()) + list(config.items())) 432 433 434# TODO(tanzheny): Add examples, here and everywhere. 435@keras_export('keras.layers.experimental.preprocessing.RandomTranslation') 436class RandomTranslation(PreprocessingLayer): 437 """Randomly translate each image during training. 438 439 Args: 440 height_factor: a float represented as fraction of value, or a tuple of size 441 2 representing lower and upper bound for shifting vertically. A negative 442 value means shifting image up, while a positive value means shifting image 443 down. When represented as a single positive float, this value is used for 444 both the upper and lower bound. For instance, `height_factor=(-0.2, 0.3)` 445 results in an output shifted by a random amount in the range [-20%, +30%]. 446 `height_factor=0.2` results in an output height shifted by a random amount 447 in the range [-20%, +20%]. 448 width_factor: a float represented as fraction of value, or a tuple of size 2 449 representing lower and upper bound for shifting horizontally. A negative 450 value means shifting image left, while a positive value means shifting 451 image right. When represented as a single positive float, this value is 452 used for both the upper and lower bound. For instance, 453 `width_factor=(-0.2, 0.3)` results in an output shifted left by 20%, and 454 shifted right by 30%. `width_factor=0.2` results in an output height 455 shifted left or right by 20%. 456 fill_mode: Points outside the boundaries of the input are filled according 457 to the given mode (one of `{'constant', 'reflect', 'wrap', 'nearest'}`). 458 - *reflect*: `(d c b a | a b c d | d c b a)` The input is extended by 459 reflecting about the edge of the last pixel. 460 - *constant*: `(k k k k | a b c d | k k k k)` The input is extended by 461 filling all values beyond the edge with the same constant value k = 0. 462 - *wrap*: `(a b c d | a b c d | a b c d)` The input is extended by 463 wrapping around to the opposite edge. 464 - *nearest*: `(a a a a | a b c d | d d d d)` The input is extended by the 465 nearest pixel. 466 interpolation: Interpolation mode. Supported values: "nearest", "bilinear". 467 seed: Integer. Used to create a random seed. 468 name: A string, the name of the layer. 469 fill_value: a float represents the value to be filled outside the boundaries 470 when `fill_mode` is "constant". 471 Input shape: 472 4D tensor with shape: `(samples, height, width, channels)`, 473 data_format='channels_last'. 474 Output shape: 475 4D tensor with shape: `(samples, height, width, channels)`, 476 data_format='channels_last'. 477 Raise: 478 ValueError: if either bound is not between [0, 1], or upper bound is less 479 than lower bound. 480 """ 481 482 def __init__(self, 483 height_factor, 484 width_factor, 485 fill_mode='reflect', 486 interpolation='bilinear', 487 seed=None, 488 name=None, 489 fill_value=0.0, 490 **kwargs): 491 self.height_factor = height_factor 492 if isinstance(height_factor, (tuple, list)): 493 self.height_lower = height_factor[0] 494 self.height_upper = height_factor[1] 495 else: 496 self.height_lower = -height_factor 497 self.height_upper = height_factor 498 if self.height_upper < self.height_lower: 499 raise ValueError('`height_factor` cannot have upper bound less than ' 500 'lower bound, got {}'.format(height_factor)) 501 if abs(self.height_lower) > 1. or abs(self.height_upper) > 1.: 502 raise ValueError('`height_factor` must have values between [-1, 1], ' 503 'got {}'.format(height_factor)) 504 505 self.width_factor = width_factor 506 if isinstance(width_factor, (tuple, list)): 507 self.width_lower = width_factor[0] 508 self.width_upper = width_factor[1] 509 else: 510 self.width_lower = -width_factor 511 self.width_upper = width_factor 512 if self.width_upper < self.width_lower: 513 raise ValueError('`width_factor` cannot have upper bound less than ' 514 'lower bound, got {}'.format(width_factor)) 515 if abs(self.width_lower) > 1. or abs(self.width_upper) > 1.: 516 raise ValueError('`width_factor` must have values between [-1, 1], ' 517 'got {}'.format(width_factor)) 518 519 check_fill_mode_and_interpolation(fill_mode, interpolation) 520 521 self.fill_mode = fill_mode 522 self.fill_value = fill_value 523 self.interpolation = interpolation 524 self.seed = seed 525 self._rng = make_generator(self.seed) 526 self.input_spec = InputSpec(ndim=4) 527 super(RandomTranslation, self).__init__(name=name, **kwargs) 528 base_preprocessing_layer.keras_kpl_gauge.get_cell('RandomTranslation').set( 529 True) 530 531 def call(self, inputs, training=True): 532 if training is None: 533 training = K.learning_phase() 534 535 def random_translated_inputs(): 536 """Translated inputs with random ops.""" 537 inputs_shape = array_ops.shape(inputs) 538 batch_size = inputs_shape[0] 539 h_axis, w_axis = H_AXIS, W_AXIS 540 img_hd = math_ops.cast(inputs_shape[h_axis], dtypes.float32) 541 img_wd = math_ops.cast(inputs_shape[w_axis], dtypes.float32) 542 height_translate = self._rng.uniform( 543 shape=[batch_size, 1], 544 minval=self.height_lower, 545 maxval=self.height_upper, 546 dtype=dtypes.float32) 547 height_translate = height_translate * img_hd 548 width_translate = self._rng.uniform( 549 shape=[batch_size, 1], 550 minval=self.width_lower, 551 maxval=self.width_upper, 552 dtype=dtypes.float32) 553 width_translate = width_translate * img_wd 554 translations = math_ops.cast( 555 array_ops.concat([width_translate, height_translate], axis=1), 556 dtype=dtypes.float32) 557 return transform( 558 inputs, 559 get_translation_matrix(translations), 560 interpolation=self.interpolation, 561 fill_mode=self.fill_mode, 562 fill_value=self.fill_value) 563 564 output = control_flow_util.smart_cond(training, random_translated_inputs, 565 lambda: inputs) 566 output.set_shape(inputs.shape) 567 return output 568 569 def compute_output_shape(self, input_shape): 570 return input_shape 571 572 def get_config(self): 573 config = { 574 'height_factor': self.height_factor, 575 'width_factor': self.width_factor, 576 'fill_mode': self.fill_mode, 577 'fill_value': self.fill_value, 578 'interpolation': self.interpolation, 579 'seed': self.seed, 580 } 581 base_config = super(RandomTranslation, self).get_config() 582 return dict(list(base_config.items()) + list(config.items())) 583 584 585def get_translation_matrix(translations, name=None): 586 """Returns projective transform(s) for the given translation(s). 587 588 Args: 589 translations: A matrix of 2-element lists representing [dx, dy] to translate 590 for each image (for a batch of images). 591 name: The name of the op. 592 593 Returns: 594 A tensor of shape (num_images, 8) projective transforms which can be given 595 to `transform`. 596 """ 597 with K.name_scope(name or 'translation_matrix'): 598 num_translations = array_ops.shape(translations)[0] 599 # The translation matrix looks like: 600 # [[1 0 -dx] 601 # [0 1 -dy] 602 # [0 0 1]] 603 # where the last entry is implicit. 604 # Translation matrices are always float32. 605 return array_ops.concat( 606 values=[ 607 array_ops.ones((num_translations, 1), dtypes.float32), 608 array_ops.zeros((num_translations, 1), dtypes.float32), 609 -translations[:, 0, None], 610 array_ops.zeros((num_translations, 1), dtypes.float32), 611 array_ops.ones((num_translations, 1), dtypes.float32), 612 -translations[:, 1, None], 613 array_ops.zeros((num_translations, 2), dtypes.float32), 614 ], 615 axis=1) 616 617 618def transform(images, 619 transforms, 620 fill_mode='reflect', 621 fill_value=0.0, 622 interpolation='bilinear', 623 output_shape=None, 624 name=None): 625 """Applies the given transform(s) to the image(s). 626 627 Args: 628 images: A tensor of shape (num_images, num_rows, num_columns, num_channels) 629 (NHWC), (num_rows, num_columns, num_channels) (HWC), or (num_rows, 630 num_columns) (HW). The rank must be statically known (the shape is not 631 `TensorShape(None)`. 632 transforms: Projective transform matrix/matrices. A vector of length 8 or 633 tensor of size N x 8. If one row of transforms is [a0, a1, a2, b0, b1, b2, 634 c0, c1], then it maps the *output* point `(x, y)` to a transformed *input* 635 point `(x', y') = ((a0 x + a1 y + a2) / k, (b0 x + b1 y + b2) / k)`, where 636 `k = c0 x + c1 y + 1`. The transforms are *inverted* compared to the 637 transform mapping input points to output points. Note that gradients are 638 not backpropagated into transformation parameters. 639 fill_mode: Points outside the boundaries of the input are filled according 640 to the given mode (one of `{'constant', 'reflect', 'wrap', 'nearest'}`). 641 fill_value: a float represents the value to be filled outside the boundaries 642 when `fill_mode` is "constant". 643 interpolation: Interpolation mode. Supported values: "nearest", "bilinear". 644 output_shape: Output dimesion after the transform, [height, width]. If None, 645 output is the same size as input image. 646 name: The name of the op. ## Fill mode. 647 Behavior for each valid value is as follows: reflect (d c b a | a b c d | d c 648 b a) The input is extended by reflecting about the edge of the last pixel. 649 constant (k k k k | a b c d | k k k k) The input is extended by filling all 650 values beyond the edge with the same constant value k = 0. wrap (a b c d | 651 a b c d | a b c d) The input is extended by wrapping around to the opposite 652 edge. nearest (a a a a | a b c d | d d d d) The input is extended by the 653 nearest pixel. 654 Input shape: 655 4D tensor with shape: `(samples, height, width, channels)`, 656 data_format='channels_last'. 657 Output shape: 658 4D tensor with shape: `(samples, height, width, channels)`, 659 data_format='channels_last'. 660 661 Returns: 662 Image(s) with the same type and shape as `images`, with the given 663 transform(s) applied. Transformed coordinates outside of the input image 664 will be filled with zeros. 665 666 Raises: 667 TypeError: If `image` is an invalid type. 668 ValueError: If output shape is not 1-D int32 Tensor. 669 """ 670 with K.name_scope(name or 'transform'): 671 if output_shape is None: 672 output_shape = array_ops.shape(images)[1:3] 673 if not context.executing_eagerly(): 674 output_shape_value = tensor_util.constant_value(output_shape) 675 if output_shape_value is not None: 676 output_shape = output_shape_value 677 678 output_shape = ops.convert_to_tensor_v2_with_dispatch( 679 output_shape, dtypes.int32, name='output_shape') 680 681 if not output_shape.get_shape().is_compatible_with([2]): 682 raise ValueError('output_shape must be a 1-D Tensor of 2 elements: ' 683 'new_height, new_width, instead got ' 684 '{}'.format(output_shape)) 685 686 fill_value = ops.convert_to_tensor_v2_with_dispatch( 687 fill_value, dtypes.float32, name='fill_value') 688 689 if compat.forward_compatible(2020, 8, 5): 690 return gen_image_ops.ImageProjectiveTransformV3( 691 images=images, 692 output_shape=output_shape, 693 fill_value=fill_value, 694 transforms=transforms, 695 fill_mode=fill_mode.upper(), 696 interpolation=interpolation.upper()) 697 698 return gen_image_ops.ImageProjectiveTransformV2( 699 images=images, 700 output_shape=output_shape, 701 transforms=transforms, 702 fill_mode=fill_mode.upper(), 703 interpolation=interpolation.upper()) 704 705 706def get_rotation_matrix(angles, image_height, image_width, name=None): 707 """Returns projective transform(s) for the given angle(s). 708 709 Args: 710 angles: A scalar angle to rotate all images by, or (for batches of images) a 711 vector with an angle to rotate each image in the batch. The rank must be 712 statically known (the shape is not `TensorShape(None)`). 713 image_height: Height of the image(s) to be transformed. 714 image_width: Width of the image(s) to be transformed. 715 name: The name of the op. 716 717 Returns: 718 A tensor of shape (num_images, 8). Projective transforms which can be given 719 to operation `image_projective_transform_v2`. If one row of transforms is 720 [a0, a1, a2, b0, b1, b2, c0, c1], then it maps the *output* point 721 `(x, y)` to a transformed *input* point 722 `(x', y') = ((a0 x + a1 y + a2) / k, (b0 x + b1 y + b2) / k)`, 723 where `k = c0 x + c1 y + 1`. 724 """ 725 with K.name_scope(name or 'rotation_matrix'): 726 x_offset = ((image_width - 1) - (math_ops.cos(angles) * 727 (image_width - 1) - math_ops.sin(angles) * 728 (image_height - 1))) / 2.0 729 y_offset = ((image_height - 1) - (math_ops.sin(angles) * 730 (image_width - 1) + math_ops.cos(angles) * 731 (image_height - 1))) / 2.0 732 num_angles = array_ops.shape(angles)[0] 733 return array_ops.concat( 734 values=[ 735 math_ops.cos(angles)[:, None], 736 -math_ops.sin(angles)[:, None], 737 x_offset[:, None], 738 math_ops.sin(angles)[:, None], 739 math_ops.cos(angles)[:, None], 740 y_offset[:, None], 741 array_ops.zeros((num_angles, 2), dtypes.float32), 742 ], 743 axis=1) 744 745 746@keras_export('keras.layers.experimental.preprocessing.RandomRotation') 747class RandomRotation(PreprocessingLayer): 748 """Randomly rotate each image. 749 750 By default, random rotations are only applied during training. 751 At inference time, the layer does nothing. If you need to apply random 752 rotations at inference time, set `training` to True when calling the layer. 753 754 Input shape: 755 4D tensor with shape: 756 `(samples, height, width, channels)`, data_format='channels_last'. 757 758 Output shape: 759 4D tensor with shape: 760 `(samples, height, width, channels)`, data_format='channels_last'. 761 762 Attributes: 763 factor: a float represented as fraction of 2pi, or a tuple of size 2 764 representing lower and upper bound for rotating clockwise and 765 counter-clockwise. A positive values means rotating counter clock-wise, 766 while a negative value means clock-wise. When represented as a single 767 float, this value is used for both the upper and lower bound. For 768 instance, `factor=(-0.2, 0.3)` results in an output rotation by a random 769 amount in the range `[-20% * 2pi, 30% * 2pi]`. `factor=0.2` results in an 770 output rotating by a random amount in the range `[-20% * 2pi, 20% * 2pi]`. 771 fill_mode: Points outside the boundaries of the input are filled according 772 to the given mode (one of `{'constant', 'reflect', 'wrap', 'nearest'}`). 773 - *reflect*: `(d c b a | a b c d | d c b a)` The input is extended by 774 reflecting about the edge of the last pixel. 775 - *constant*: `(k k k k | a b c d | k k k k)` The input is extended by 776 filling all values beyond the edge with the same constant value k = 0. 777 - *wrap*: `(a b c d | a b c d | a b c d)` The input is extended by 778 wrapping around to the opposite edge. 779 - *nearest*: `(a a a a | a b c d | d d d d)` The input is extended by the 780 nearest pixel. 781 interpolation: Interpolation mode. Supported values: "nearest", "bilinear". 782 seed: Integer. Used to create a random seed. 783 name: A string, the name of the layer. 784 fill_value: a float represents the value to be filled outside the boundaries 785 when `fill_mode` is "constant". 786 Raise: 787 ValueError: if either bound is not between [0, 1], or upper bound is less 788 than lower bound. 789 """ 790 791 def __init__(self, 792 factor, 793 fill_mode='reflect', 794 interpolation='bilinear', 795 seed=None, 796 name=None, 797 fill_value=0.0, 798 **kwargs): 799 self.factor = factor 800 if isinstance(factor, (tuple, list)): 801 self.lower = factor[0] 802 self.upper = factor[1] 803 else: 804 self.lower = -factor 805 self.upper = factor 806 if self.upper < self.lower: 807 raise ValueError('Factor cannot have negative values, ' 808 'got {}'.format(factor)) 809 check_fill_mode_and_interpolation(fill_mode, interpolation) 810 self.fill_mode = fill_mode 811 self.fill_value = fill_value 812 self.interpolation = interpolation 813 self.seed = seed 814 self._rng = make_generator(self.seed) 815 self.input_spec = InputSpec(ndim=4) 816 super(RandomRotation, self).__init__(name=name, **kwargs) 817 base_preprocessing_layer.keras_kpl_gauge.get_cell('RandomRotation').set( 818 True) 819 820 def call(self, inputs, training=True): 821 if training is None: 822 training = K.learning_phase() 823 824 def random_rotated_inputs(): 825 """Rotated inputs with random ops.""" 826 inputs_shape = array_ops.shape(inputs) 827 batch_size = inputs_shape[0] 828 img_hd = math_ops.cast(inputs_shape[H_AXIS], dtypes.float32) 829 img_wd = math_ops.cast(inputs_shape[W_AXIS], dtypes.float32) 830 min_angle = self.lower * 2. * np.pi 831 max_angle = self.upper * 2. * np.pi 832 angles = self._rng.uniform( 833 shape=[batch_size], minval=min_angle, maxval=max_angle) 834 return transform( 835 inputs, 836 get_rotation_matrix(angles, img_hd, img_wd), 837 fill_mode=self.fill_mode, 838 fill_value=self.fill_value, 839 interpolation=self.interpolation) 840 841 output = control_flow_util.smart_cond(training, random_rotated_inputs, 842 lambda: inputs) 843 output.set_shape(inputs.shape) 844 return output 845 846 def compute_output_shape(self, input_shape): 847 return input_shape 848 849 def get_config(self): 850 config = { 851 'factor': self.factor, 852 'fill_mode': self.fill_mode, 853 'fill_value': self.fill_value, 854 'interpolation': self.interpolation, 855 'seed': self.seed, 856 } 857 base_config = super(RandomRotation, self).get_config() 858 return dict(list(base_config.items()) + list(config.items())) 859 860 861@keras_export('keras.layers.experimental.preprocessing.RandomZoom') 862class RandomZoom(PreprocessingLayer): 863 """Randomly zoom each image during training. 864 865 Args: 866 height_factor: a float represented as fraction of value, or a tuple of size 867 2 representing lower and upper bound for zooming vertically. When 868 represented as a single float, this value is used for both the upper and 869 lower bound. A positive value means zooming out, while a negative value 870 means zooming in. For instance, `height_factor=(0.2, 0.3)` result in an 871 output zoomed out by a random amount in the range [+20%, +30%]. 872 `height_factor=(-0.3, -0.2)` result in an output zoomed in by a random 873 amount in the range [+20%, +30%]. 874 width_factor: a float represented as fraction of value, or a tuple of size 2 875 representing lower and upper bound for zooming horizontally. When 876 represented as a single float, this value is used for both the upper and 877 lower bound. For instance, `width_factor=(0.2, 0.3)` result in an output 878 zooming out between 20% to 30%. `width_factor=(-0.3, -0.2)` result in an 879 output zooming in between 20% to 30%. Defaults to `None`, i.e., zooming 880 vertical and horizontal directions by preserving the aspect ratio. 881 fill_mode: Points outside the boundaries of the input are filled according 882 to the given mode (one of `{'constant', 'reflect', 'wrap', 'nearest'}`). 883 - *reflect*: `(d c b a | a b c d | d c b a)` The input is extended by 884 reflecting about the edge of the last pixel. 885 - *constant*: `(k k k k | a b c d | k k k k)` The input is extended by 886 filling all values beyond the edge with the same constant value k = 0. 887 - *wrap*: `(a b c d | a b c d | a b c d)` The input is extended by 888 wrapping around to the opposite edge. 889 - *nearest*: `(a a a a | a b c d | d d d d)` The input is extended by the 890 nearest pixel. 891 interpolation: Interpolation mode. Supported values: "nearest", "bilinear". 892 seed: Integer. Used to create a random seed. 893 name: A string, the name of the layer. 894 fill_value: a float represents the value to be filled outside the boundaries 895 when `fill_mode` is "constant". 896 Example: >>> input_img = np.random.random((32, 224, 224, 3)) >>> layer = 897 tf.keras.layers.experimental.preprocessing.RandomZoom(.5, .2) >>> out_img = 898 layer(input_img) >>> out_img.shape TensorShape([32, 224, 224, 3]) 899 Input shape: 900 4D tensor with shape: `(samples, height, width, channels)`, 901 data_format='channels_last'. 902 Output shape: 903 4D tensor with shape: `(samples, height, width, channels)`, 904 data_format='channels_last'. 905 Raise: 906 ValueError: if lower bound is not between [0, 1], or upper bound is 907 negative. 908 """ 909 910 def __init__(self, 911 height_factor, 912 width_factor=None, 913 fill_mode='reflect', 914 interpolation='bilinear', 915 seed=None, 916 name=None, 917 fill_value=0.0, 918 **kwargs): 919 self.height_factor = height_factor 920 if isinstance(height_factor, (tuple, list)): 921 self.height_lower = height_factor[0] 922 self.height_upper = height_factor[1] 923 else: 924 self.height_lower = -height_factor 925 self.height_upper = height_factor 926 927 if abs(self.height_lower) > 1. or abs(self.height_upper) > 1.: 928 raise ValueError('`height_factor` must have values between [-1, 1], ' 929 'got {}'.format(height_factor)) 930 931 self.width_factor = width_factor 932 if width_factor is not None: 933 if isinstance(width_factor, (tuple, list)): 934 self.width_lower = width_factor[0] 935 self.width_upper = width_factor[1] 936 else: 937 self.width_lower = -width_factor # pylint: disable=invalid-unary-operand-type 938 self.width_upper = width_factor 939 940 if self.width_lower < -1. or self.width_upper < -1.: 941 raise ValueError('`width_factor` must have values larger than -1, ' 942 'got {}'.format(width_factor)) 943 944 check_fill_mode_and_interpolation(fill_mode, interpolation) 945 946 self.fill_mode = fill_mode 947 self.fill_value = fill_value 948 self.interpolation = interpolation 949 self.seed = seed 950 self._rng = make_generator(self.seed) 951 self.input_spec = InputSpec(ndim=4) 952 super(RandomZoom, self).__init__(name=name, **kwargs) 953 base_preprocessing_layer.keras_kpl_gauge.get_cell('RandomZoom').set(True) 954 955 def call(self, inputs, training=True): 956 if training is None: 957 training = K.learning_phase() 958 959 def random_zoomed_inputs(): 960 """Zoomed inputs with random ops.""" 961 inputs_shape = array_ops.shape(inputs) 962 batch_size = inputs_shape[0] 963 img_hd = math_ops.cast(inputs_shape[H_AXIS], dtypes.float32) 964 img_wd = math_ops.cast(inputs_shape[W_AXIS], dtypes.float32) 965 height_zoom = self._rng.uniform( 966 shape=[batch_size, 1], 967 minval=1. + self.height_lower, 968 maxval=1. + self.height_upper) 969 if self.width_factor is not None: 970 width_zoom = self._rng.uniform( 971 shape=[batch_size, 1], 972 minval=1. + self.width_lower, 973 maxval=1. + self.width_upper) 974 else: 975 width_zoom = height_zoom 976 zooms = math_ops.cast( 977 array_ops.concat([width_zoom, height_zoom], axis=1), 978 dtype=dtypes.float32) 979 return transform( 980 inputs, 981 get_zoom_matrix(zooms, img_hd, img_wd), 982 fill_mode=self.fill_mode, 983 fill_value=self.fill_value, 984 interpolation=self.interpolation) 985 986 output = control_flow_util.smart_cond(training, random_zoomed_inputs, 987 lambda: inputs) 988 output.set_shape(inputs.shape) 989 return output 990 991 def compute_output_shape(self, input_shape): 992 return input_shape 993 994 def get_config(self): 995 config = { 996 'height_factor': self.height_factor, 997 'width_factor': self.width_factor, 998 'fill_mode': self.fill_mode, 999 'fill_value': self.fill_value, 1000 'interpolation': self.interpolation, 1001 'seed': self.seed, 1002 } 1003 base_config = super(RandomZoom, self).get_config() 1004 return dict(list(base_config.items()) + list(config.items())) 1005 1006 1007def get_zoom_matrix(zooms, image_height, image_width, name=None): 1008 """Returns projective transform(s) for the given zoom(s). 1009 1010 Args: 1011 zooms: A matrix of 2-element lists representing [zx, zy] to zoom for each 1012 image (for a batch of images). 1013 image_height: Height of the image(s) to be transformed. 1014 image_width: Width of the image(s) to be transformed. 1015 name: The name of the op. 1016 1017 Returns: 1018 A tensor of shape (num_images, 8). Projective transforms which can be given 1019 to operation `image_projective_transform_v2`. If one row of transforms is 1020 [a0, a1, a2, b0, b1, b2, c0, c1], then it maps the *output* point 1021 `(x, y)` to a transformed *input* point 1022 `(x', y') = ((a0 x + a1 y + a2) / k, (b0 x + b1 y + b2) / k)`, 1023 where `k = c0 x + c1 y + 1`. 1024 """ 1025 with K.name_scope(name or 'zoom_matrix'): 1026 num_zooms = array_ops.shape(zooms)[0] 1027 # The zoom matrix looks like: 1028 # [[zx 0 0] 1029 # [0 zy 0] 1030 # [0 0 1]] 1031 # where the last entry is implicit. 1032 # Zoom matrices are always float32. 1033 x_offset = ((image_width - 1.) / 2.0) * (1.0 - zooms[:, 0, None]) 1034 y_offset = ((image_height - 1.) / 2.0) * (1.0 - zooms[:, 1, None]) 1035 return array_ops.concat( 1036 values=[ 1037 zooms[:, 0, None], 1038 array_ops.zeros((num_zooms, 1), dtypes.float32), 1039 x_offset, 1040 array_ops.zeros((num_zooms, 1), dtypes.float32), 1041 zooms[:, 1, None], 1042 y_offset, 1043 array_ops.zeros((num_zooms, 2), dtypes.float32), 1044 ], 1045 axis=1) 1046 1047 1048@keras_export('keras.layers.experimental.preprocessing.RandomContrast') 1049class RandomContrast(PreprocessingLayer): 1050 """Adjust the contrast of an image or images by a random factor. 1051 1052 Contrast is adjusted independently for each channel of each image during 1053 training. 1054 1055 For each channel, this layer computes the mean of the image pixels in the 1056 channel and then adjusts each component `x` of each pixel to 1057 `(x - mean) * contrast_factor + mean`. 1058 1059 Input shape: 1060 4D tensor with shape: 1061 `(samples, height, width, channels)`, data_format='channels_last'. 1062 1063 Output shape: 1064 4D tensor with shape: 1065 `(samples, height, width, channels)`, data_format='channels_last'. 1066 1067 Attributes: 1068 factor: a positive float represented as fraction of value, or a tuple of 1069 size 2 representing lower and upper bound. When represented as a single 1070 float, lower = upper. The contrast factor will be randomly picked between 1071 [1.0 - lower, 1.0 + upper]. 1072 seed: Integer. Used to create a random seed. 1073 name: A string, the name of the layer. 1074 Raise: 1075 ValueError: if lower bound is not between [0, 1], or upper bound is 1076 negative. 1077 """ 1078 1079 def __init__(self, factor, seed=None, name=None, **kwargs): 1080 self.factor = factor 1081 if isinstance(factor, (tuple, list)): 1082 self.lower = factor[0] 1083 self.upper = factor[1] 1084 else: 1085 self.lower = self.upper = factor 1086 if self.lower < 0. or self.upper < 0. or self.lower > 1.: 1087 raise ValueError('Factor cannot have negative values or greater than 1.0,' 1088 ' got {}'.format(factor)) 1089 self.seed = seed 1090 self.input_spec = InputSpec(ndim=4) 1091 super(RandomContrast, self).__init__(name=name, **kwargs) 1092 base_preprocessing_layer.keras_kpl_gauge.get_cell('RandomContrast').set( 1093 True) 1094 1095 def call(self, inputs, training=True): 1096 if training is None: 1097 training = K.learning_phase() 1098 1099 def random_contrasted_inputs(): 1100 return image_ops.random_contrast(inputs, 1. - self.lower, 1. + self.upper, 1101 self.seed) 1102 1103 output = control_flow_util.smart_cond(training, random_contrasted_inputs, 1104 lambda: inputs) 1105 output.set_shape(inputs.shape) 1106 return output 1107 1108 def compute_output_shape(self, input_shape): 1109 return input_shape 1110 1111 def get_config(self): 1112 config = { 1113 'factor': self.factor, 1114 'seed': self.seed, 1115 } 1116 base_config = super(RandomContrast, self).get_config() 1117 return dict(list(base_config.items()) + list(config.items())) 1118 1119 1120@keras_export('keras.layers.experimental.preprocessing.RandomHeight') 1121class RandomHeight(PreprocessingLayer): 1122 """Randomly vary the height of a batch of images during training. 1123 1124 Adjusts the height of a batch of images by a random factor. The input 1125 should be a 4-D tensor in the "channels_last" image data format. 1126 1127 By default, this layer is inactive during inference. 1128 1129 Args: 1130 factor: A positive float (fraction of original height), or a tuple of size 2 1131 representing lower and upper bound for resizing vertically. When 1132 represented as a single float, this value is used for both the upper and 1133 lower bound. For instance, `factor=(0.2, 0.3)` results in an output with 1134 height changed by a random amount in the range `[20%, 30%]`. 1135 `factor=(-0.2, 0.3)` results in an output with height changed by a random 1136 amount in the range `[-20%, +30%]. `factor=0.2` results in an output with 1137 height changed by a random amount in the range `[-20%, +20%]`. 1138 interpolation: String, the interpolation method. Defaults to `bilinear`. 1139 Supports `bilinear`, `nearest`, `bicubic`, `area`, `lanczos3`, `lanczos5`, 1140 `gaussian`, `mitchellcubic` 1141 seed: Integer. Used to create a random seed. 1142 name: A string, the name of the layer. 1143 Input shape: 1144 4D tensor with shape: `(samples, height, width, channels)` 1145 (data_format='channels_last'). 1146 Output shape: 1147 4D tensor with shape: `(samples, random_height, width, channels)`. 1148 """ 1149 1150 def __init__(self, 1151 factor, 1152 interpolation='bilinear', 1153 seed=None, 1154 name=None, 1155 **kwargs): 1156 self.factor = factor 1157 if isinstance(factor, (tuple, list)): 1158 self.height_lower = factor[0] 1159 self.height_upper = factor[1] 1160 else: 1161 self.height_lower = -factor 1162 self.height_upper = factor 1163 1164 if self.height_upper < self.height_lower: 1165 raise ValueError('`factor` cannot have upper bound less than ' 1166 'lower bound, got {}'.format(factor)) 1167 if self.height_lower < -1. or self.height_upper < -1.: 1168 raise ValueError('`factor` must have values larger than -1, ' 1169 'got {}'.format(factor)) 1170 self.interpolation = interpolation 1171 self._interpolation_method = get_interpolation(interpolation) 1172 self.input_spec = InputSpec(ndim=4) 1173 self.seed = seed 1174 self._rng = make_generator(self.seed) 1175 super(RandomHeight, self).__init__(name=name, **kwargs) 1176 base_preprocessing_layer.keras_kpl_gauge.get_cell('RandomHeight').set(True) 1177 1178 def call(self, inputs, training=True): 1179 if training is None: 1180 training = K.learning_phase() 1181 1182 def random_height_inputs(): 1183 """Inputs height-adjusted with random ops.""" 1184 inputs_shape = array_ops.shape(inputs) 1185 img_hd = math_ops.cast(inputs_shape[H_AXIS], dtypes.float32) 1186 img_wd = inputs_shape[W_AXIS] 1187 height_factor = self._rng.uniform( 1188 shape=[], 1189 minval=(1.0 + self.height_lower), 1190 maxval=(1.0 + self.height_upper)) 1191 adjusted_height = math_ops.cast(height_factor * img_hd, dtypes.int32) 1192 adjusted_size = array_ops.stack([adjusted_height, img_wd]) 1193 output = image_ops.resize_images_v2( 1194 images=inputs, size=adjusted_size, method=self._interpolation_method) 1195 original_shape = inputs.shape.as_list() 1196 output_shape = [original_shape[0]] + [None] + original_shape[2:4] 1197 output.set_shape(output_shape) 1198 return output 1199 1200 return control_flow_util.smart_cond(training, random_height_inputs, 1201 lambda: inputs) 1202 1203 def compute_output_shape(self, input_shape): 1204 input_shape = tensor_shape.TensorShape(input_shape).as_list() 1205 return tensor_shape.TensorShape( 1206 [input_shape[0], None, input_shape[2], input_shape[3]]) 1207 1208 def get_config(self): 1209 config = { 1210 'factor': self.factor, 1211 'interpolation': self.interpolation, 1212 'seed': self.seed, 1213 } 1214 base_config = super(RandomHeight, self).get_config() 1215 return dict(list(base_config.items()) + list(config.items())) 1216 1217 1218@keras_export('keras.layers.experimental.preprocessing.RandomWidth') 1219class RandomWidth(PreprocessingLayer): 1220 """Randomly vary the width of a batch of images during training. 1221 1222 Adjusts the width of a batch of images by a random factor. The input 1223 should be a 4-D tensor in the "channels_last" image data format. 1224 1225 By default, this layer is inactive during inference. 1226 1227 Args: 1228 factor: A positive float (fraction of original height), or a tuple of size 2 1229 representing lower and upper bound for resizing vertically. When 1230 represented as a single float, this value is used for both the upper and 1231 lower bound. For instance, `factor=(0.2, 0.3)` results in an output with 1232 width changed by a random amount in the range `[20%, 30%]`. `factor=(-0.2, 1233 0.3)` results in an output with width changed by a random amount in the 1234 range `[-20%, +30%]. `factor=0.2` results in an output with width changed 1235 by a random amount in the range `[-20%, +20%]`. 1236 interpolation: String, the interpolation method. Defaults to `bilinear`. 1237 Supports `bilinear`, `nearest`, `bicubic`, `area`, `lanczos3`, `lanczos5`, 1238 `gaussian`, `mitchellcubic` 1239 seed: Integer. Used to create a random seed. 1240 name: A string, the name of the layer. 1241 Input shape: 1242 4D tensor with shape: `(samples, height, width, channels)` 1243 (data_format='channels_last'). 1244 Output shape: 1245 4D tensor with shape: `(samples, height, random_width, channels)`. 1246 """ 1247 1248 def __init__(self, 1249 factor, 1250 interpolation='bilinear', 1251 seed=None, 1252 name=None, 1253 **kwargs): 1254 self.factor = factor 1255 if isinstance(factor, (tuple, list)): 1256 self.width_lower = factor[0] 1257 self.width_upper = factor[1] 1258 else: 1259 self.width_lower = -factor 1260 self.width_upper = factor 1261 if self.width_upper < self.width_lower: 1262 raise ValueError('`factor` cannot have upper bound less than ' 1263 'lower bound, got {}'.format(factor)) 1264 if self.width_lower < -1. or self.width_upper < -1.: 1265 raise ValueError('`factor` must have values larger than -1, ' 1266 'got {}'.format(factor)) 1267 self.interpolation = interpolation 1268 self._interpolation_method = get_interpolation(interpolation) 1269 self.input_spec = InputSpec(ndim=4) 1270 self.seed = seed 1271 self._rng = make_generator(self.seed) 1272 super(RandomWidth, self).__init__(name=name, **kwargs) 1273 base_preprocessing_layer.keras_kpl_gauge.get_cell('RandomWidth').set(True) 1274 1275 def call(self, inputs, training=True): 1276 if training is None: 1277 training = K.learning_phase() 1278 1279 def random_width_inputs(): 1280 """Inputs width-adjusted with random ops.""" 1281 inputs_shape = array_ops.shape(inputs) 1282 img_hd = inputs_shape[H_AXIS] 1283 img_wd = math_ops.cast(inputs_shape[W_AXIS], dtypes.float32) 1284 width_factor = self._rng.uniform( 1285 shape=[], 1286 minval=(1.0 + self.width_lower), 1287 maxval=(1.0 + self.width_upper)) 1288 adjusted_width = math_ops.cast(width_factor * img_wd, dtypes.int32) 1289 adjusted_size = array_ops.stack([img_hd, adjusted_width]) 1290 output = image_ops.resize_images_v2( 1291 images=inputs, size=adjusted_size, method=self._interpolation_method) 1292 original_shape = inputs.shape.as_list() 1293 output_shape = original_shape[0:2] + [None] + [original_shape[3]] 1294 output.set_shape(output_shape) 1295 return output 1296 1297 return control_flow_util.smart_cond(training, random_width_inputs, 1298 lambda: inputs) 1299 1300 def compute_output_shape(self, input_shape): 1301 input_shape = tensor_shape.TensorShape(input_shape).as_list() 1302 return tensor_shape.TensorShape( 1303 [input_shape[0], input_shape[1], None, input_shape[3]]) 1304 1305 def get_config(self): 1306 config = { 1307 'factor': self.factor, 1308 'interpolation': self.interpolation, 1309 'seed': self.seed, 1310 } 1311 base_config = super(RandomWidth, self).get_config() 1312 return dict(list(base_config.items()) + list(config.items())) 1313 1314 1315def make_generator(seed=None): 1316 """Creates a random generator. 1317 1318 Args: 1319 seed: the seed to initialize the generator. If None, the generator will be 1320 initialized non-deterministically. 1321 1322 Returns: 1323 A generator object. 1324 """ 1325 if seed: 1326 return stateful_random_ops.Generator.from_seed(seed) 1327 else: 1328 return stateful_random_ops.Generator.from_non_deterministic_state() 1329 1330 1331def get_interpolation(interpolation): 1332 interpolation = interpolation.lower() 1333 if interpolation not in _RESIZE_METHODS: 1334 raise NotImplementedError( 1335 'Value not recognized for `interpolation`: {}. Supported values ' 1336 'are: {}'.format(interpolation, _RESIZE_METHODS.keys())) 1337 return _RESIZE_METHODS[interpolation] 1338