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