class_docstr: ### Description
      implementation of generator from
          Hu et al., "Freehand Ultrasound Image Simulation with Spatially-Conditioned
          Generative Adversarial Networks", MICCAI RAMBO 2017
          https://arxiv.org/abs/1707.05392
  
  ### Building blocks
  [FC]            - Fully connected layer
  [CONV]          - Convolutional layer, conditioning is concatenated to input before the convolution
                      (if conditioning is used)
                      kernel size = 3, activation = relu
  [UPCONV]        - Upsampling block composed of upsampling deconvolution (stride 2, kernel size = 3,
                      activation = relu) and concatenation of conditioning
  [fCONV]         - Final convolutional layer, with no conditioning. Kernel size = 3, activation = tanh
  
  ### Diagram
  
  RANDOM NOISE --> [FC] --> [CONV] --> [UPCONV]x3 --> [fCONV] --> OUTPUT IMAGE
  
  ### Constraints
class_docstr_tok: ["#","#","#","Description","implementation","of","generator","from","Hu","et","al.",",","``","Freehand","Ultrasound","Image","Simulation","with","Spatially-Conditioned","Generative","Adversarial","Networks","''",",","MICCAI","RAMBO","2017","https",":","\/\/arxiv.org\/abs\/1707.05392","#","#","#","Building","blocks","[","FC","]","-","Fully","connected","layer","[","CONV","]","-","Convolutional","layer",",","conditioning","is","concatenated","to","input","before","the","convolution","(","if","conditioning","is","used",")","kernel","size","=","3",",","activation","=","relu","[","UPCONV","]","-","Upsampling","block","composed","of","upsampling","deconvolution","(","stride","2",",","kernel","size","=","3",",","activation","=","relu",")","and","concatenation","of","conditioning","[","fCONV","]","-","Final","convolutional","layer",",","with","no","conditioning",".","Kernel","size","=","3",",","activation","=","tanh","#","#","#","Diagram","RANDOM","NOISE","--",">","[","FC","]","--",">","[","CONV","]","--",">","[","UPCONV","]","x3","--",">","[","fCONV","]","--",">","OUTPUT","IMAGE","#","#","#","Constraints"]
docstr: :param random_source: tensor, random noise to start generation
  :param image_size: output image size
  :param conditioning: tensor, conditioning information (e.g. coordinates in physical space)
  :param is_training: boolean, True if network is in training mode
  :return: tensor, generated image
docstr_tok: [":","param","random_source",":","tensor",",","random","noise","to","start","generation",":","param","image_size",":","output","image","size",":","param","conditioning",":","tensor",",","conditioning","information","(","e.g",".","coordinates","in","physical","space",")",":","param","is_training",":","boolean",",","True","if","network","is","in","training","mode",":","return",":","tensor",",","generated","image"]
code: def layer_op(
      self, random_source, image_size, conditioning, is_training
  ):
      """
  
      :param random_source: tensor, random noise to start generation
      :param image_size: output image size
      :param conditioning: tensor, conditioning information (e.g. coordinates in physical space)
      :param is_training: boolean, True if network is in training mode
      :return: tensor, generated image
      """
      keep_prob_ph = 1  # not passed in as a placeholder
      add_noise = self.noise_channels_per_layer
      if conditioning is not None:
          conditioning_channels = conditioning.shape.as_list()[-1]
          conditioning_channels = conditioning_channels + add_noise
      else:
          conditioning_channels = add_noise
  
      # feature channels design pattern
      ch = [512]
      sz = image_size[:-1]
      for i in range(4):
          # compute output n_feature_channels of i-th layer
          new_ch = (
              ch[-1]
              + conditioning_channels * self.with_conditionings[i]
          )
          new_ch = round(new_ch / 2)
          ch.append(new_ch)
          # compute output spatial size of i-th layer
          sz = [int(round(spatial_len / 2)) for spatial_len in sz]
      ch.append(1)  # last layer single channel image
  
      # resizing utilities
      spatial_rank = len(image_size) - 1
      if spatial_rank == 3:
  
          def resize_func(x, sz):
              sz_x = x.shape.as_list()
              r1 = tf.image.resize_images(
                  tf.reshape(x, sz_x[:3] + [-1]), sz[0:2]
              )
              r2 = tf.image.resize_images(
                  tf.reshape(r1, [sz_x[0], sz[0] * sz[1], sz_x[3], -1]),
                  [sz[0] * sz[1], sz[2]],
              )
              resized_3d = tf.reshape(r2, [sz_x[0]] + sz + [sz_x[-1]])
              return resized_3d
  
      elif spatial_rank == 2:
          resize_func = tf.image.resize_bilinear
  
      def concat_cond(x, with_conditioning):
          """
  
          :param x: tensor, input
          :param with_conditioning: boolean, True if conditioning is to be used
          :return: tensor, concatenation of x and conditioning, with noise addition
          """
          noise = []
          if add_noise:
              feature_shape = x.shape.as_list()[0:-1]
              noise_shape = feature_shape + [add_noise]
              noise = [tf.random_normal(noise_shape, 0.0, 0.1)]
  
          if with_conditioning and conditioning is not None:
              with tf.name_scope("concat_conditioning"):
                  spatial_shape = x.shape.as_list()[1:-1]
                  resized_cond = resize_func(
                      conditioning, spatial_shape
                  )
                  return tf.concat([x, resized_cond] + noise, axis=-1)
          return x
  
      def conv(ch, x):
          """
          Generates and applies a convolutional layer with relu as activation, kernel size 3,
          batch norm
          :param ch: int, number of output channels for convolutional layer
          :param x: tensor, input to convolutional layer
          :return: tensor, output of convolutiona layer
          """
          with tf.name_scope("conv"):
              conv_layer = ConvolutionalLayer(
                  n_output_chns=ch,
                  kernel_size=3,
                  feature_normalization="batch",
                  with_bias=False,
                  acti_func="relu",
                  w_initializer=self.initializers["w"],
              )
              return conv_layer(x, is_training=is_training)
  
      def up(ch, x):
          """
          Performs deconvolution operation with kernel size 3, stride 2, batch norm, and relu
          :param ch: int, number of output channels for deconvolutional layer
          :param x: tensor, input to deconvolutional layer
          :return: tensor, output of deconvolutiona layer
          """
          with tf.name_scope("up"):
              deconv_layer = DeconvolutionalLayer(
                  n_output_chns=ch,
                  kernel_size=3,
                  stride=2,
                  feature_normalization="batch",
                  with_bias=False,
                  acti_func="relu",
                  w_initializer=self.initializers["w"],
              )
              return deconv_layer(x, is_training=is_training)
  
      def up_block(ch, x, with_conditioning):
          """
          Performs upsampling and concatenation with conditioning
          :param ch: int, number of output channels for deconvolutional layer
          :param x: tensor, input to deconvolutional layer
          :param with_conditioning: boolean, True if conditioning is to be used
          :return: tensor, output of upsampling and concatenation
          """
          with tf.name_scope("up_block"):
              u = up(ch, x)
              cond = concat_cond(u, with_conditioning)
              return conv(cond.shape.as_list()[-1], cond)
  
      def noise_to_image(sz, ch, rand_tensor, with_conditioning):
          """
          Processes random noise with fully connected layer and then convolutional layer
          :param sz: image size
          :param ch: int, number of output channels
          :param rand_tensor: tensor, input random noise to generate image
          :param with_conditioning: boolean, True if conditioning is to be used
          :return: tensor, output of convolutional layer
          """
          batch_size = rand_tensor.shape.as_list()[0]
          output_shape = [batch_size] + sz + [ch]
          with tf.name_scope("noise_to_image"):
              g_no_0 = np.prod(sz) * ch
              fc_layer = FullyConnectedLayer(
                  n_output_chns=g_no_0,
                  feature_normalization=None,
                  with_bias=True,
                  w_initializer=self.initializers["w"],
                  b_initializer=self.initializers["b"],
              )
              g_h1p = fc_layer(rand_tensor, keep_prob=keep_prob_ph)
              g_h1p = tf.reshape(g_h1p, output_shape)
              g_h1p = concat_cond(g_h1p, with_conditioning)
              return conv(ch + conditioning_channels, g_h1p)
  
      def final_image(n_chns, x):
          """
  
          :param n_chns: int, number of output channels
          :param x: tensor, input tensor to layers
          :return: tensor, generated image
          """
          with tf.name_scope("final_image"):
              if add_noise > 0:
                  feature_shape = x.shape.as_list()[0:-1]
                  noise_shape = feature_shape + [add_noise]
                  noise = tf.random_normal(noise_shape, 0, 0.1)
                  x = tf.concat([x, noise], axis=3)
              conv_layer = ConvolutionalLayer(
                  n_output_chns=n_chns,
                  kernel_size=3,
                  acti_func="tanh",
                  feature_normalization=None,
                  with_bias=True,
                  w_initializer=self.initializers["w"],
                  b_initializer=self.initializers["b"],
              )
              x_sample = conv_layer(
                  x, is_training=is_training, keep_prob=keep_prob_ph
              )
              return tf.image.resize_images(x_sample, image_size[:-1])
  
      # let the tensors flow...
      flow = random_source
      for idx, chns in enumerate(ch):
          if idx == 0:  # first layer fully-connected
              flow = noise_to_image(
                  sz, chns, flow, self.with_conditionings[idx]
              )
          elif idx == len(ch) - 1:  # final conv without bn
              return final_image(chns, flow)
          else:  # upsampling block
              flow = up_block(chns, flow, self.with_conditionings[idx])
  
imports: from __future__ import absolute_import, print_function, division
  import numpy
  import tensorflow
  from niftynet.layer.activation import ActiLayer
  from niftynet.layer.convolution import ConvolutionalLayer
  from niftynet.layer.deconvolution import DeconvolutionalLayer
  from niftynet.layer.fully_connected import FullyConnectedLayer
  from niftynet.layer.gan_blocks import BaseDiscriminator
  from niftynet.layer.gan_blocks import BaseGenerator
  from niftynet.layer.gan_blocks import GANImageBlock
label_desc: Use image node_id 2 for calling the ImageGenerator obj's underlying member method code with example usage: obj.layer_op(random_source, image_size, conditioning, is_training) and returns: x, resized_3d, conv_layer, deconv_layer, conv, conv, tf, tf, final_image