class_docstr: ### Description
      implementation of discrimator 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
  [FEATURE BLOCK]     - Convolutional layer (kernel size = 5, activation = selu)
                        + residual convolutiona layer (kernel size = 3, activation = selu, batch norm)
                        + convolutional layer (kernel size = 3, activation = selu, batch norm)
  
  [DOWN BLOCK]        - Downsampling block with residual connections:
                        Downsampling convolutional layer (stride = 2, kernel size = 3,
                        activation = selu, batch norm)
                        + residual convolutiona layer (kernel size = 3, activation = selu, batch norm)
                        + convolutional layer (kernel size = 3, activation = selu, batch norm)
  
  [FC]                - Fully connected layer
  
  If conditioning is used, it gets concatenated to the image at the discriminator input
  
  ### Diagram
  
  INPUT IMAGE --> [FEATURE BLOCK] --> [DOWN BLOCK]x5 --> [FC] --> OUTPUT LOGITS
  
  ### Constraints
class_docstr_tok: ["#","#","#","Description","implementation","of","discrimator","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","[","FEATURE","BLOCK","]","-","Convolutional","layer","(","kernel","size","=","5",",","activation","=","selu",")","+","residual","convolutiona","layer","(","kernel","size","=","3",",","activation","=","selu",",","batch","norm",")","+","convolutional","layer","(","kernel","size","=","3",",","activation","=","selu",",","batch","norm",")","[","DOWN","BLOCK","]","-","Downsampling","block","with","residual","connections",":","Downsampling","convolutional","layer","(","stride","=","2",",","kernel","size","=","3",",","activation","=","selu",",","batch","norm",")","+","residual","convolutiona","layer","(","kernel","size","=","3",",","activation","=","selu",",","batch","norm",")","+","convolutional","layer","(","kernel","size","=","3",",","activation","=","selu",",","batch","norm",")","[","FC","]","-","Fully","connected","layer","If","conditioning","is","used",",","it","gets","concatenated","to","the","image","at","the","discriminator","input","#","#","#","Diagram","INPUT","IMAGE","--",">","[","FEATURE","BLOCK","]","--",">","[","DOWN","BLOCK","]","x5","--",">","[","FC","]","--",">","OUTPUT","LOGITS","#","#","#","Constraints"]
docstr: :param image: tensor, input to the network
  :param conditioning: tensor, conditioning information (e.g. coordinates in physical space)
  :param is_training: boolean, True if network is in training mode
  :return: tensor, classification logits
docstr_tok: [":","param","image",":","tensor",",","input","to","the","network",":","param","conditioning",":","tensor",",","conditioning","information","(","e.g",".","coordinates","in","physical","space",")",":","param","is_training",":","boolean",",","True","if","network","is","in","training","mode",":","return",":","tensor",",","classification","logits"]
code: def layer_op(self, image, conditioning, is_training):
      """
  
      :param image: tensor, input to the network
      :param conditioning: tensor, conditioning information (e.g. coordinates in physical space)
      :param is_training: boolean, True if network is in training mode
      :return: tensor, classification logits
      """
  
      batch_size = image.shape.as_list()[0]
  
      def down(ch, x):
          """
          Downsampling convolutional layer (stride 2, kernel size 3, activation selu, batch norm)
          :param ch: int, number of output channels
          :param x: tensor, input to the convolutional layer
          :return: tensor, output of the convolutional layer
          """
          with tf.name_scope("downsample"):
              conv_layer = ConvolutionalLayer(
                  n_output_chns=ch,
                  kernel_size=3,
                  stride=2,
                  feature_normalization="batch",
                  acti_func="selu",
                  w_initializer=self.initializers["w"],
              )
              return conv_layer(x, is_training=is_training)
  
      def convr(ch, x):
          """
          Convolutional layer for residuals (stride 1, kernel size 3, activation selu, batch norm)
          :param ch: int, number of output channels
          :param x: tensor, input to the convolutional layer
          :return: tensor, output of the convolutional layer
          """
          conv_layer = ConvolutionalLayer(
              n_output_chns=ch,
              kernel_size=3,
              feature_normalization="batch",
              acti_func="selu",
              w_initializer=self.initializers["w"],
          )
          return conv_layer(x, is_training=is_training)
  
      def conv(ch, x, s):
          """
          Convolutional layer (stride 1, kernel size 3, batch norm)
          combining two flows
          :param ch: int, number of output channels
          :param x: tensor, input to the convolutional layer
          :param s: flow to be added after convolution
          :return: tensor, output of selu activation layer (selu(conv(x) + s))
          """
          conv_layer = ConvolutionalLayer(
              n_output_chns=ch,
              kernel_size=3,
              feature_normalization="batch",
              w_initializer=self.initializers["w"],
          )
          acti_layer = ActiLayer(func="selu")
  
          # combining two flows
          res_flow = conv_layer(x, is_training=is_training) + s
          return acti_layer(res_flow)
  
      def down_block(ch, x):
          """
          Downsampling block with residual connections
          :param ch: int, number of output channels
          :param x: tensor, input to the convolutional layer
          :return: tensor, output of downsampling + conv + conv
          """
          with tf.name_scope("down_resnet"):
              s = down(ch, x)
              r = convr(ch, s)
              return conv(ch, r, s)
  
      def feature_block(ch, image):
          """
          First discriminator processing block
          :param ch: int, number of output channels
          :param image: tensor, input image to discriminator
          :return: tensor, output of conv(image) --> conv --> conv
          """
          with tf.name_scope("feature"):
              conv_layer = ConvolutionalLayer(
                  n_output_chns=ch,
                  kernel_size=5,
                  with_bias=True,
                  feature_normalization=None,
                  acti_func="selu",
                  w_initializer=self.initializers["w"],
                  b_initializer=self.initializers["b"],
              )
              d_h1s = conv_layer(image, is_training=is_training)
              d_h1r = convr(ch, d_h1s)
              return conv(ch, d_h1r, d_h1s)
  
      def fully_connected(ch, features):
          """
          Final discriminator processing block
          :param ch: int, number of output channels
          :param features: tensor, input features for final classification
          :return: tensor, output logits of discriminator classification
          """
          with tf.name_scope("fully_connected"):
              # with bn?
              fc_layer = FullyConnectedLayer(
                  n_output_chns=ch,
                  feature_normalization=None,
                  with_bias=True,
              )
              return fc_layer(features, is_training=is_training)
  
      if conditioning is not None:
          image = tf.concat([image, conditioning], axis=-1)
  
      # let the tensors flow...
      flow = image
      for idx, n_chns in enumerate(self.chns):
          if idx == 0:  # first layer
              flow = feature_block(n_chns, flow)
          elif idx == len(self.chns) - 1:  # last layer
              return fully_connected(n_chns, flow)
          else:
              flow = down_block(n_chns, flow)
  
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 ImageDiscriminator obj's underlying member method code with example usage: obj.layer_op(image, conditioning, is_training) and returns: conv_layer, acti_layer, conv_layer, conv, conv, fc_layer, fully_connected