text: import math
  from typing import List
  
  import tensorflow.keras
  from tensorflow.keras import backend as K
  from tensorflow.keras import layers
  from tensorflow.keras.models import Model
  from tensorflow.keras.utils import get_file, get_source_inputs
  
  from keras_applications.imagenet_utils import _obtain_input_shape
  from keras_applications.imagenet_utils import preprocess_input as _preprocess
  
  from .custom_objects import BlockArgs, DEFAULT_BLOCK_LIST
  from .custom_objects import EfficientNetConvInitializer
  from .custom_objects import EfficientNetDenseInitializer
  from .custom_objects import Swish, DropConnect
  
  from tensorflow.keras.utils import get_file
  from . import retinanet
  from . import Backbone
  from crfnet.utils.image import preprocess_image
  
  
  class EfficientNetBackbone(Backbone):
      """ Describes backbone information and provides utility functions.
      """
  
      def retinanet(self, *args, **kwargs):
          """ Returns a retinanet model using the correct backbone.
          """
          return efficientnet_retinanet(*args, backbone=self.backbone, **kwargs)
  
      def download_imagenet(self):
          """ Downloads ImageNet weights and returns path to weights file.
          Weights can be downloaded at https://github.com/fizyr/keras-models/releases .
          """
          if self.backbone == 'vgg16':
              resource = keras.applications.vgg16.vgg16.WEIGHTS_PATH_NO_TOP
              checksum = '6d6bbae143d832006294945121d1f1fc'
          elif self.backbone == 'vgg19':
              resource = keras.applications.vgg19.vgg19.WEIGHTS_PATH_NO_TOP
              checksum = '253f8cb515780f3b799900260a226db6'
          else:
              raise ValueError("Backbone '{}' not recognized.".format(self.backbone))
  
          return get_file(
              '{}_weights_tf_dim_ordering_tf_kernels_notop.h5'.format(self.backbone),
              resource,
              cache_subdir='models',
              file_hash=checksum
          )
  
      def validate(self):
          """ Checks whether the backbone string is correct.
          """
          allowed_backbones = ['vgg16', 'vgg19', 'vgg-max', 'vgg-max2', 'vgg-max2-large', 'vgg-max2-kernelx2', \
              'vgg-min', 'vgg-min2', 'vgg-min2-large', 'vgg-min2-kernelx2', 'vgg-min3', 'efficientnet']
  
          if self.backbone not in allowed_backbones:
              raise ValueError('Backbone (\'{}\') not in allowed backbones ({}).'.format(self.backbone, allowed_backbones))
  
      def preprocess_image(self, inputs):
          """ Takes as input an image and prepares it for being passed through the network.
          """
          return preprocess_image(inputs, mode='caffe')
  
  
  def efficientnet_retinanet(num_classes, backbone='EfficientNet', inputs=None, modifier=None, distance=False, **kwargs):
      """ Constructs a retinanet model using a EfficientNet backbone.
  
      Args
          num_classes: Number of classes to predict.
          backbone: Which backbone to use (one of ('vgg16', 'vgg19')).
          inputs: The inputs to the network (defaults to a Tensor of shape (None, None, 3)).
          modifier: A function handler which can modify the backbone before using it in retinanet (this can be used to freeze backbone layers for example).
  
      Returns
          RetinaNet model with a VGG backbone.
      """
      # choose default input
      if inputs is None:
          inputs = keras.layers.Input(shape=(None, None, 3))
      elif isinstance(inputs, tuple):
          inputs = keras.layers.Input(inputs)
          
      # create the vgg backbone
      efficientnet = EfficientNet(input_shape=None, 
                                  block_args_list=DEFAULT_BLOCK_LIST, 
                                  width_coefficient=2.0, 
                                  depth_coefficient=1.0, 
                                  input_tensor=inputs, 
                                  pooling='max')
      if modifier:
          efficientnet = modifier(efficientnet)#
  
      layer_names = ["swish_11", "swish_17", "swish_47"]
      layer_outputs = [efficientnet.get_layer(name).output for name in layer_names]
      
      return retinanet.retinanet(inputs=inputs, num_classes=num_classes, backbone_layers=layer_outputs, distance=distance, **kwargs)
  
  
  
  
  
  
  
  
  # Obtained from https://github.com/tensorflow/tpu/blob/master/models/official/efficientnet/efficientnet_model.py
  def round_filters(filters, width_coefficient, depth_divisor, min_depth):
      """Round number of filters based on depth multiplier."""
      multiplier = float(width_coefficient)
      divisor = int(depth_divisor)
      min_depth = min_depth
  
      if not multiplier:
          return filters
  
      filters *= multiplier
      min_depth = min_depth or divisor
      new_filters = max(min_depth, int(filters + divisor / 2) // divisor * divisor)
      # Make sure that round down does not go down by more than 10%.
      if new_filters < 0.9 * filters:
          new_filters += divisor
  
      return int(new_filters)
  
  
  # Obtained from https://github.com/tensorflow/tpu/blob/master/models/official/efficientnet/efficientnet_model.py
  def round_repeats(repeats, depth_coefficient):
      """Round number of filters based on depth multiplier."""
      multiplier = depth_coefficient
  
      if not multiplier:
          return repeats
  
      return int(math.ceil(multiplier * repeats))
  
  
  # Obtained from https://github.com/tensorflow/tpu/blob/master/models/official/efficientnet/efficientnet_model.py
  def SEBlock(input_filters, se_ratio, expand_ratio, data_format=None):
      if data_format is None:
          data_format = K.image_data_format()
  
      num_reduced_filters = max(
          1, int(input_filters * se_ratio))
      filters = input_filters * expand_ratio
  
      if data_format == 'channels_first':
          channel_axis = 1
          spatial_dims = [2, 3]
      else:
          channel_axis = -1
          spatial_dims = [1, 2]
  
      def block(inputs):
          x = inputs
          x = layers.Lambda(lambda a: K.mean(a, axis=spatial_dims, keepdims=True))(x)
          x = layers.Conv2D(
              num_reduced_filters,
              kernel_size=[1, 1],
              strides=[1, 1],
              kernel_initializer=EfficientNetConvInitializer(),
              padding='same',
              use_bias=True)(x)
          x = Swish()(x)
          # Excite
          x = layers.Conv2D(
              filters,
              kernel_size=[1, 1],
              strides=[1, 1],
              kernel_initializer=EfficientNetConvInitializer(),
              padding='same',
              use_bias=True)(x)
          x = layers.Activation('sigmoid')(x)
          out = layers.Multiply()([x, inputs])
          return out
  
      return block
  
  
  # Obtained from https://github.com/tensorflow/tpu/blob/master/models/official/efficientnet/efficientnet_model.py
  def MBConvBlock(input_filters, output_filters,
                  kernel_size, strides,
                  expand_ratio, se_ratio,
                  id_skip, drop_connect_rate,
                  batch_norm_momentum=0.99,
                  batch_norm_epsilon=1e-3,
                  data_format=None):
  
      if data_format is None:
          data_format = K.image_data_format()
  
      if data_format == 'channels_first':
          channel_axis = 1
          spatial_dims = [2, 3]
      else:
          channel_axis = -1
          spatial_dims = [1, 2]
  
      has_se = (se_ratio is not None) and (se_ratio > 0) and (se_ratio <= 1)
      filters = input_filters * expand_ratio
  
      def block(inputs):
  
          if expand_ratio != 1:
              x = layers.Conv2D(
                  filters,
                  kernel_size=[1, 1],
                  strides=[1, 1],
                  kernel_initializer=EfficientNetConvInitializer(),
                  padding='same',
                  use_bias=False)(inputs)
              x = layers.BatchNormalization(
                  axis=channel_axis,
                  momentum=batch_norm_momentum,
                  epsilon=batch_norm_epsilon)(x)
              x = Swish()(x)
          else:
              x = inputs
  
          x = layers.DepthwiseConv2D(
              [kernel_size, kernel_size],
              strides=strides,
              depthwise_initializer=EfficientNetConvInitializer(),
              padding='same',
              use_bias=False)(x)
          x = layers.BatchNormalization(
              axis=channel_axis,
              momentum=batch_norm_momentum,
              epsilon=batch_norm_epsilon)(x)
          x = Swish()(x)
  
          if has_se:
              x = SEBlock(input_filters, se_ratio, expand_ratio,
                          data_format)(x)
  
          # output phase
  
          x = layers.Conv2D(
              output_filters,
              kernel_size=[1, 1],
              strides=[1, 1],
              kernel_initializer=EfficientNetConvInitializer(),
              padding='same',
              use_bias=False)(x)
          x = layers.BatchNormalization(
              axis=channel_axis,
              momentum=batch_norm_momentum,
              epsilon=batch_norm_epsilon)(x)
  
          if id_skip:
              if all(s == 1 for s in strides) and (
                      input_filters == output_filters):
  
                  # only apply drop_connect if skip presents.
                  if drop_connect_rate:
                      x = DropConnect(drop_connect_rate)(x)
  
                  x = layers.Add()([x, inputs])
  
          return x
  
      return block
  
  
  def EfficientNet(input_shape,
                   block_args_list: List[BlockArgs],
                   width_coefficient: float,
                   depth_coefficient: float,
                   include_top=False,
                   weights=None,
                   input_tensor=None,
                   pooling=None,
                   classes=None,
                   dropout_rate=0.,
                   drop_connect_rate=0.,
                   batch_norm_momentum=0.99,
                   batch_norm_epsilon=1e-3,
                   depth_divisor=8,
                   min_depth=None,
                   data_format=None,
                   default_size=None,
                   **kwargs):
      """
      Builder model for EfficientNets.
      # Arguments:
          input_shape: Optional shape tuple, the input shape
              depends on the configuration, with a minimum
              decided by the number of stride 2 operations.
              When None is provided, it defaults to 224.
              Considered the "Resolution" parameter from
              the paper (inherently Resolution coefficient).
          block_args_list: Optional List of BlockArgs, each
              of which detail the arguments of the MBConvBlock.
              If left as None, it defaults to the blocks
              from the paper.
          width_coefficient: Determines the number of channels
              available per layer. Compound Coefficient that
              needs to be found using grid search on a base
              configuration model.
          depth_coefficient: Determines the number of layers
              available to the model. Compound Coefficient that
              needs to be found using grid search on a base
              configuration model.
          include_top: Whether to include the fully-connected
              layer at the top of the network.
          weights: `None` (random initialization) or
              `imagenet` (ImageNet weights)
          input_tensor: Optional Keras tensor (i.e. output of
              `layers.Input()`)
              to use as image input for the model.
          pooling: Optional pooling mode for feature extraction
              when `include_top` is `False`.
              - `None` means that the output of the model
                  will be the 4D tensor output of the
                  last convolutional layer.
              - `avg` means that global average pooling
                  will be applied to the output of the
                  last convolutional layer, and thus
                  the output of the model will be a
                  2D tensor.
              - `max` means that global max pooling will
                  be applied.
          classes: Optional number of classes to classify images
              into, only to be specified if `include_top` is True, and
              if no `weights` argument is specified.
          dropout_rate: Float, percentage of random dropout.
          drop_connect_rate: Float, percentage of random droped
              connections.
          batch_norm_momentum: Float, default batch normalization
              momentum. Obtained from the paper.
          batch_norm_epsilon: Float, default batch normalization
              epsilon. Obtained from the paper.
          depth_divisor: Optional. Used when rounding off the coefficient
               scaled channels and depth of the layers.
          min_depth: Optional. Minimum depth value in order to
              avoid blocks with 0 layers.
          data_format: "channels_first" or "channels_last". If left
              as None, defaults to the value set in ~/.keras.
          default_size: Specifies the default image size of the model
      # Raises:
          - ValueError: If weights are not in 'imagenet' or None.
          - ValueError: If weights are 'imagenet' and `classes` is
              not 1000.
      # Returns:
          A Keras Model.
      """
      if not (weights in {'imagenet', None} or os.path.exists(weights)):
          raise ValueError('The `weights` argument should be either '
                           '`None` (random initialization), `imagenet` '
                           '(pre-training on ImageNet), '
                           'or the path to the weights file to be loaded.')
  
      if weights == 'imagenet' and include_top and classes != 1000:
          raise ValueError('If using `weights` as `"imagenet"` with `include_top` '
                           'as true, `classes` should be 1000')
  
      if data_format is None:
          data_format = K.image_data_format()
  
      if data_format == 'channels_first':
          channel_axis = 1
      else:
          channel_axis = -1
  
      if default_size is None:
          default_size = 224
  
      if block_args_list is None:
          block_args_list = DEFAULT_BLOCK_LIST
  
      # count number of strides to compute min size
      stride_count = 1
      for block_args in block_args_list:
          if block_args.strides is not None and block_args.strides[0] > 1:
              stride_count += 1
  
      min_size = int(2 ** stride_count)
  
      # Determine proper input shape and default size.
      input_shape = _obtain_input_shape(input_shape,
                                        default_size=default_size,
                                        min_size=min_size,
                                        data_format=data_format,
                                        require_flatten=include_top,
                                        weights=weights)
  
      # Stem part
      if input_tensor is None:
          inputs = layers.Input(shape=input_shape)
      else:
          if not K.is_keras_tensor(input_tensor):
              inputs = layers.Input(tensor=input_tensor, shape=input_shape)
          else:
              inputs = input_tensor
  
      outputs = []
  
      x = inputs
      x = layers.Conv2D(
          filters=round_filters(32, width_coefficient,
                                depth_divisor, min_depth),
          kernel_size=[3, 3],
          strides=[2, 2],
          kernel_initializer=EfficientNetConvInitializer(),
          padding='same',
          use_bias=False)(x)
      x = layers.BatchNormalization(
          axis=channel_axis,
          momentum=batch_norm_momentum,
          epsilon=batch_norm_epsilon)(x)
      x = Swish()(x)
  
      num_blocks = sum([block_args.num_repeat for block_args in block_args_list])
      drop_connect_rate_per_block = drop_connect_rate / float(num_blocks)
  
      # Blocks part
      for block_idx, block_args in enumerate(block_args_list):
          assert block_args.num_repeat > 0
  
          # Update block input and output filters based on depth multiplier.
          block_args.input_filters = round_filters(block_args.input_filters, width_coefficient, depth_divisor, min_depth)
          block_args.output_filters = round_filters(block_args.output_filters, width_coefficient, depth_divisor, min_depth)
          block_args.num_repeat = round_repeats(block_args.num_repeat, depth_coefficient)
  
          # The first block needs to take care of stride and filter size increase.
          x = MBConvBlock(block_args.input_filters, block_args.output_filters,
                          block_args.kernel_size, block_args.strides,
                          block_args.expand_ratio, block_args.se_ratio,
                          block_args.identity_skip, drop_connect_rate_per_block * block_idx,
                          batch_norm_momentum, batch_norm_epsilon, data_format)(x)
  
          if block_args.num_repeat > 1:
              block_args.input_filters = block_args.output_filters
              block_args.strides = [1, 1]
  
          for _ in range(block_args.num_repeat - 1):
              x = MBConvBlock(block_args.input_filters, block_args.output_filters,
                              block_args.kernel_size, block_args.strides,
                              block_args.expand_ratio, block_args.se_ratio,
                              block_args.identity_skip, drop_connect_rate_per_block * block_idx,
                              batch_norm_momentum, batch_norm_epsilon, data_format)(x)
          outputs.append(x)
  
      # Head part
      x = layers.Conv2D(
          filters=round_filters(1280, width_coefficient, depth_coefficient, min_depth),
          kernel_size=[1, 1],
          strides=[1, 1],
          kernel_initializer=EfficientNetConvInitializer(),
          padding='same',
          use_bias=False)(x)
      x = layers.BatchNormalization(
          axis=channel_axis,
          momentum=batch_norm_momentum,
          epsilon=batch_norm_epsilon)(x)
      x = Swish()(x)
  
      if include_top:
          x = layers.GlobalAveragePooling2D(data_format=data_format)(x)
  
          if dropout_rate > 0:
              x = layers.Dropout(dropout_rate)(x)
  
          x = layers.Dense(classes, kernel_initializer=EfficientNetDenseInitializer())(x)
          x = layers.Activation('softmax')(x)
  
      else:
          if pooling == 'avg':
              x = layers.GlobalAveragePooling2D()(x)
          elif pooling == 'max':
              x = layers.GlobalMaxPooling2D()(x)
          outputs.append(x)
  
      
  
      # Ensure that the model takes into account
      # any potential predecessors of `input_tensor`.
      if input_tensor is not None:
          inputs = get_source_inputs(input_tensor)
  
      model = Model(inputs, outputs)
  
      if weights is not None:
          model.load_weights(weights)
  
      return model