      """
      def __init__(self,
                   side: Literal["a", "b", "both"],
                   input_shape: Tuple[int, int, int],
                   config: dict) -> None:
          logger.debug("Initializing: %s (side: %s, input_shape: %s)",
                       self.__class__.__name__, side, input_shape)
          self._side = side
          self._input_shape = input_shape
          self._config = config
          self._is_dny = self._config["dec_upscale_method"].lower() == "upscale_dny"
          logger.debug("Initialized: %s", self.__class__.__name__,)
 
</s> remove                  side: Literal["a", "b", "both"],
 </s> add                  side: Literal["a", "b", "both", "shared"], </s> remove         return KerasModel(input_, var_x, name=f"fc_{self._side}")


class GBlock():  # pylint:disable=too-few-public-methods
    """ G-Block model, borrowing from Adain StyleGAN.

    Parameters
    ----------
    side: ["a", "b", "both"]
        The side of the model that the fully connected layers belong to. Used for naming
    input_shapes: list or tuple
        The shape tuples for the input to the decoder. The first item is the input from each side's
        fully connected model, the second item is the input shape from the combined fully connected
        model.
    config: dict
        The user configuration dictionary
    """
    def __init__(self,
                 side: Literal["a", "b", "both"],
                 input_shapes: Union[list, tuple],
                 config: dict) -> None:
        logger.debug("Initializing: %s (side: %s, input_shapes: %s)",
                     self.__class__.__name__, side, input_shapes)
        self._side = side
        self._config = config
        self._inputs = [Input(shape=shape) for shape in input_shapes]
        self._dense_nodes = 512
        self._dense_recursions = 3
        logger.debug("Initialized: %s", self.__class__.__name__)

    @classmethod
    def _g_block(cls, inputs: Tensor, style: Tensor, filters: int, recursions: int = 2) -> Tensor:
        """ G_block adapted from ADAIN StyleGAN.

        Parameters
        ----------
        inputs: tensor
            The input tensor to the G-Block model
        style: tensor
            The input combined 'style' tensor to the G-Block model
        filters: int
            The number of filters to use for the G-Block Convolutional layers
        recursions: int, optional
            The number of recursive Convolutions to process. Default: `2`

        Returns
        -------
        tensor
            The output tensor from the G-Block model
        """
        var_x = inputs
        for i in range(recursions):
            styles = [Reshape([1, 1, filters])(Dense(filters)(style)) for _ in range(2)]
            noise = KConv2D(filters, 1, padding="same")(GaussianNoise(1.0)(var_x))

            if i == recursions - 1:
                var_x = KConv2D(filters, 3, padding="same")(var_x)

            var_x = AdaInstanceNormalization(dtype="float32")([var_x, *styles])
            var_x = Add()([var_x, noise])
            var_x = LeakyReLU(0.2)(var_x)

        return var_x
 </s> add             num_upscales = self._config["dec_upscales_in_fc"]
            if num_upscales:
                var_x = UpscaleBlocks(self._side,
                                      K.int_shape(var_x)[1:],
                                      self._config,
                                      layer_indicies=(0, num_upscales))(var_x) </s> remove     side: ["a", "b", "both"]
        The side of the model that the fully connected layers belong to. Used for naming
 </s> add     side: ["a", "b", "both", "shared"]
        The side of the model that the Decoder belongs to. Used for naming
                 config: dict,
                 layer_indicies: Optional[Tuple[int, int]] = None) -> None:
        logger.debug("Initializing: %s (side: %s, input_shape: %s, layer_indicies: %s)",
                     self.__class__.__name__, side, input_shape, layer_indicies)
      """
      def __init__(self,
                   side: Literal["a", "b", "both"],
                   input_shape: Tuple[int, int, int],
                 config: dict,
                 layer_indicies: Optional[Tuple[int, int]] = None) -> None:
        logger.debug("Initializing: %s (side: %s, input_shape: %s, layer_indicies: %s)",
                     self.__class__.__name__, side, input_shape, layer_indicies)
                 config: dict,
                 layer_indicies: Optional[Tuple[int, int]] = None) -> None:
        logger.debug("Initializing: %s (side: %s, input_shape: %s, layer_indicies: %s)",
                     self.__class__.__name__, side, input_shape, layer_indicies)
                 config: dict,
                 layer_indicies: Optional[Tuple[int, int]] = None) -> None:
        logger.debug("Initializing: %s (side: %s, input_shape: %s, layer_indicies: %s)",
                     self.__class__.__name__, side, input_shape, layer_indicies)
          self._side = side
          self._input_shape = input_shape
          self._config = config
          self._is_dny = self._config["dec_upscale_method"].lower() == "upscale_dny"
          logger.debug("Initialized: %s", self.__class__.__name__,)
keep keep keep keep replace replace replace keep keep keep keep keep
 Train updates
  - Output full model summary last
  - Fix activations in Phaze-A upscales
  - Phaze-A Add option to place some upscales in fc model 
plugins/train/model/phaze_a.py