class_docstr: ### Description
      This is a denoising, convolutional, variational autoencoder (VAE),
      composed of a sequence of {convolutions then downsampling} blocks,
      followed by a sequence of fully-connected layers,
      followed by a sequence of {transpose convolutions then upsampling} blocks.
      See Auto-Encoding Variational Bayes, Kingma & Welling, 2014.
      2DO: share the fully-connected parameters
      between the mean and logvar decoders.
  
  ### Building Blocks
  [ENCODER]               - See ConvEncoder class below
  [GAUSSIAN SAMPLER]      - See GaussianSampler class below
  [DECODER]               - See ConvDecoder class below
  
  ### Diagram
  
  INPUT --> [ENCODER] --> [GAUSSIAN SAMPLER] --> [FCDEC] ---> [DECODER] for means --- OUTPUTS
                                                      |                               |
                                                      ------> [DECODER] for logvars ---
  
  ### Constraints
class_docstr_tok: ["#","#","#","Description","This","is","a","denoising",",","convolutional",",","variational","autoencoder","(","VAE",")",",","composed","of","a","sequence","of","{","convolutions","then","downsampling","}","blocks",",","followed","by","a","sequence","of","fully-connected","layers",",","followed","by","a","sequence","of","{","transpose","convolutions","then","upsampling","}","blocks",".","See","Auto-Encoding","Variational","Bayes",",","Kingma","&","Welling",",","2014",".","2DO",":","share","the","fully-connected","parameters","between","the","mean","and","logvar","decoders",".","#","#","#","Building","Blocks","[","ENCODER","]","-","See","ConvEncoder","class","below","[","GAUSSIAN","SAMPLER","]","-","See","GaussianSampler","class","below","[","DECODER","]","-","See","ConvDecoder","class","below","#","#","#","Diagram","INPUT","--",">","[","ENCODER","]","--",">","[","GAUSSIAN","SAMPLER","]","--",">","[","FCDEC","]","--","-",">","[","DECODER","]","for","means","--","-","OUTPUTS","|","|","--","--","--",">","[","DECODER","]","for","logvars","--","-","#","#","#","Constraints"]
docstr: :param images: tensor, input to the network
  :param is_training: boolean, True if network is in training mode
  :param unused_kwargs: other conditional arguments, not in use
  :return: posterior_means: means output by gaussian sampler for KL divergence
          posterior_logvars: log of variances output by gaussian sampler for KL divergence
          data_means: output of means decoder branch (predicted image)
          data_logvars: output of variances decoder branch (capturing aleatoric uncertainty)
          images: input
          data_variances: exp of data_logvars
          posterior_variances: exp of posterior_logvars
          sample: random sample from latent space (from gaussian sampler)
docstr_tok: [":","param","images",":","tensor",",","input","to","the","network",":","param","is_training",":","boolean",",","True","if","network","is","in","training","mode",":","param","unused_kwargs",":","other","conditional","arguments",",","not","in","use",":","return",":","posterior_means",":","means","output","by","gaussian","sampler","for","KL","divergence","posterior_logvars",":","log","of","variances","output","by","gaussian","sampler","for","KL","divergence","data_means",":","output","of","means","decoder","branch","(","predicted","image",")","data_logvars",":","output","of","variances","decoder","branch","(","capturing","aleatoric","uncertainty",")","images",":","input","data_variances",":","exp","of","data_logvars","posterior_variances",":","exp","of","posterior_logvars","sample",":","random","sample","from","latent","space","(","from","gaussian","sampler",")"]
code: def layer_op(self, images, is_training=True, **unused_kwargs):
      """
  
      :param images: tensor, input to the network
      :param is_training: boolean, True if network is in training mode
      :param unused_kwargs: other conditional arguments, not in use
      :return: posterior_means: means output by gaussian sampler for KL divergence
              posterior_logvars: log of variances output by gaussian sampler for KL divergence
              data_means: output of means decoder branch (predicted image)
              data_logvars: output of variances decoder branch (capturing aleatoric uncertainty)
              images: input
              data_variances: exp of data_logvars
              posterior_variances: exp of posterior_logvars
              sample: random sample from latent space (from gaussian sampler)
      """
  
      def clip(x):
          """
          Clip input tensor using the lower and upper bounds
          :param x: tensor, input
          :return: clipped tensor
          """
          return tf.clip_by_value(
              x, self.logvars_lower_bound, self.logvars_upper_bound
          )
  
      def normalise(x):
          """
          Normalise input to [0, 255]
          :param x: tensor, input to normalise
          :return: normalised tensor in [0, 255]
          """
          min_val = tf.reduce_min(x)
          max_val = tf.reduce_max(x)
          return 255 * (x - min_val) / (max_val - min_val)
  
      def infer_downsampled_shape(x, output_channels, pooling_factors):
          """
          Calculate the shape of the data as it emerges from
          the convolutional part of the encoder
          :param x: tensor, input
          :param output_channels: int, number of output channels
          :param pooling_factors: array, pooling factors
          :return: array, shape of downsampled image
          """
          downsampled_shape = x.shape[1::].as_list()
          downsampled_shape[-1] = output_channels[-1]
          downsampled_shape[0:-1] = downsampled_shape[0:-1] / np.prod(
              pooling_factors
          )
          return [int(x) for x in downsampled_shape]
  
      # Derive shape information from the input
      input_shape = images.shape[1::].as_list()
      number_of_input_channels = input_shape[-1]
      downsampled_shape = infer_downsampled_shape(
          images, self.conv_output_channels, self.conv_pooling_factors
      )
      serialised_shape = int(np.prod(downsampled_shape))
  
      encoder = ConvEncoder(
          self.denoising_variance,
          self.conv_output_channels,
          self.conv_kernel_sizes,
          self.conv_pooling_factors,
          self.acti_func_conv,
          self.layer_sizes_encoder,
          self.acti_func_encoder,
          serialised_shape,
      )
  
      approximate_sampler = GaussianSampler(
          self.number_of_latent_variables,
          self.number_of_samples_from_posterior,
          self.logvars_upper_bound,
          self.logvars_lower_bound,
      )
  
      # Initialise the shared fully-connected layers,
      # if and only if they have been specified
      if len(self.layer_sizes_decoder_shared) > 0:
          self.shared_decoder = FCDecoder(
              self.layer_sizes_decoder_shared,
              self.acti_func_decoder_shared,
              name="FCDecoder",
          )
  
      self.decoder_means = ConvDecoder(
          self.layer_sizes_decoder + [serialised_shape],
          self.acti_func_decoder,
          self.trans_conv_output_channels_means
          + [number_of_input_channels],
          self.trans_conv_kernel_sizes_means,
          self.trans_conv_unpooling_factors_means,
          self.acti_func_trans_conv_means,
          self.upsampling_mode_means,
          downsampled_shape,
          name="ConvDecoder_means",
      )
  
      decoder_logvars = ConvDecoder(
          self.layer_sizes_decoder + [serialised_shape],
          self.acti_func_decoder,
          self.trans_conv_output_channels_logvars
          + [number_of_input_channels],
          self.trans_conv_kernel_sizes_logvars,
          self.trans_conv_unpooling_factors_logvars,
          self.acti_func_trans_conv_logvars,
          self.upsampling_mode_logvars,
          downsampled_shape,
          name="ConvDecoder_logvars",
      )
  
      # Encode the input
      encoding = encoder(images, is_training)
  
      # Sample from the posterior distribution P(latent variables|input)
      [
          sample,
          posterior_means,
          posterior_logvars,
      ] = approximate_sampler(encoding, is_training)
  
      if len(self.layer_sizes_decoder_shared) > 0:
          partially_decoded_sample = self.shared_decoder(
              sample, is_training
          )
      else:
          partially_decoded_sample = sample
  
      [data_means, data_logvars] = [
          self.decoder_means(partially_decoded_sample, is_training),
          clip(decoder_logvars(sample, is_training)),
      ]
  
      ## Monitor the KL divergence of
      ## the (approximate) posterior from the prior
      # KL_divergence = 1 + posterior_logvars \
      #                - tf.square(posterior_means) \
      #                - tf.exp(posterior_logvars)
      # KL_divergence = -0.5 * tf.reduce_mean(
      #    tf.reduce_sum(KL_divergence, axis=[1]))
      ## tf.add_to_collection(
      ##     logging.CONSOLE, tf.summary.scalar('KL_divergence', KL_divergence))
  
      ## Monitor the (negative log) likelihood of the parameters given the data
      # log_likelihood = data_logvars + \
      #                 np.log(2 * np.pi) + \
      #                 tf.exp(-data_logvars) * tf.square(data_means - images)
      # log_likelihood = -0.5 * tf.reduce_mean(tf.reduce_sum(
      #    log_likelihood, axis=[1, 2, 3, 4]))
      ## tf.add_to_collection(
      ##     logging.CONSOLE,
      ##     tf.summary.scalar('negative_log_likelihood', -log_likelihood))
  
      posterior_variances = tf.exp(posterior_logvars)
      data_variances = tf.exp(data_logvars)
  
      # Monitor reconstructions
      # logging.image3_coronal('Originals', normalise(images))
      # logging.image3_coronal('Means', normalise(data_means))
      # logging.image3_coronal('Variances', normalise(data_variances))
  
      return [
          posterior_means,
          posterior_logvars,
          data_means,
          data_logvars,
          images,
          data_variances,
          posterior_variances,
          sample,
      ]
  
imports: from __future__ import absolute_import, print_function, division
  import numpy
  import tensorflow
  from niftynet.layer.base_layer import TrainableLayer
  from niftynet.layer.convolution import ConvolutionalLayer
  from niftynet.layer.deconvolution import DeconvolutionalLayer
  from niftynet.layer.fully_connected import FullyConnectedLayer
  from niftynet.layer.upsample import UpSampleLayer
label_desc: Use image node_id 2 for calling the VAE obj's underlying member method code with example usage: obj.layer_op(images, is_training) and returns: list, tf, unknown, unknown