class_docstr: ### Description
  implementation of Dense-V-Net:
     Gibson et al., "Automatic multi-organ segmentation on abdominal CT with
     dense V-networks" 2018
  
  ### Diagram
  
  DFS = Dense Feature Stack Block
  
  - Initial image is first downsampled to a given size.
  - Each DFS+SD outputs a skip link + a downsampled output.
  - All outputs are upscaled to the initial downsampled size.
  - If initial prior is given add it to the output prediction.
  
  Input
    |
    --[ DFS ]-----------------------[ Conv ]------------[ Conv ]------[+]-->
         |                                       |  |              |
         -----[ DFS ]---------------[ Conv ]------  |              |
                 |                                  |              |
                 -----[ DFS ]-------[ Conv ]---------              |
                                                        [ Prior ]---
  
  The layer DenseFeatureStackBlockWithSkipAndDownsample layer implements
  [DFS + Conv + Downsampling] in a single module, and outputs 2 elements:
      - Skip layer:          [ DFS + Conv]
      - Downsampled output:  [ DFS + Down]
  
  ### Constraints
  - Input size has to be divisible by 2*dilation_rates
docstr: :param input_tensor: tensor to input to the network, size has to be divisible by 2*dilation_rates
  :param is_training: boolean, True if network is in training mode
  :param layer_id: not in use
  :param keep_prob: double, percentage of nodes to keep for drop-out
  :param unused_kwargs:
  :return: network prediction
docstr_tok: [":","param","input_tensor",":","tensor","to","input","to","the","network",",","size","has","to","be","divisible","by","2","*","dilation_rates",":","param","is_training",":","boolean",",","True","if","network","is","in","training","mode",":","param","layer_id",":","not","in","use",":","param","keep_prob",":","double",",","percentage","of","nodes","to","keep","for","drop-out",":","param","unused_kwargs",":",":","return",":","network","prediction"]
code: def layer_op(
      self,
      input_tensor,
      is_training=True,
      layer_id=-1,
      keep_prob=0.5,
      **unused_kwargs,
  ):
      """
  
      :param input_tensor: tensor to input to the network, size has to be divisible by 2*dilation_rates
      :param is_training: boolean, True if network is in training mode
      :param layer_id: not in use
      :param keep_prob: double, percentage of nodes to keep for drop-out
      :param unused_kwargs:
      :return: network prediction
      """
      hyperparams = self.hyperparams
  
      # Validate that dilation rates are compatible with input dimensions
      modulo = 2 ** (len(hyperparams["dilation_rates"]))
      assert layer_util.check_spatial_dims(
          input_tensor, lambda x: x % modulo == 0
      )
  
      # Perform on the fly data augmentation
      if is_training and hyperparams["augmentation_scale"] > 0:
          augment_layer = AffineAugmentationLayer(
              hyperparams["augmentation_scale"], "LINEAR", "ZERO"
          )
          input_tensor = augment_layer(input_tensor)
  
      ###################
      ### Feedforward ###
      ###################
  
      # Initialize network components
      dense_vnet = self.create_network()
  
      # Store output feature maps from each component
      feature_maps = []
  
      # Downsample input to the network
      downsample_layer = DownSampleLayer(
          func="AVG", kernel_size=3, stride=2
      )
      downsampled_tensor = downsample_layer(input_tensor)
      bn_layer = BNLayer()
      downsampled_tensor = bn_layer(
          downsampled_tensor, is_training=is_training
      )
      feature_maps.append(downsampled_tensor)
  
      # All feature maps should match the downsampled tensor's shape
      feature_map_shape = downsampled_tensor.shape.as_list()[1:-1]
  
      # Prepare initial input to dense_vblocks
      initial_features = dense_vnet.initial_conv(
          input_tensor, is_training=is_training
      )
      channel_dim = len(input_tensor.shape) - 1
      down = tf.concat(
          [downsampled_tensor, initial_features], channel_dim
      )
  
      # Feed downsampled input through dense_vblocks
      for dblock in dense_vnet.dense_vblocks:
          # Get skip layer and activation output
          skip, down = dblock(
              down, is_training=is_training, keep_prob=keep_prob
          )
          # Resize skip layer to original shape and add to feature maps
          skip = LinearResizeLayer(feature_map_shape)(skip)
          feature_maps.append(skip)
  
      # Merge feature maps
      all_features = tf.concat(feature_maps, channel_dim)
  
      # Perform final convolution to segment structures
      output = dense_vnet.final_conv(
          all_features, is_training=is_training
      )
  
      ######################
      ### Postprocessing ###
      ######################
  
      # Get the number of spatial dimensions of input tensor
      n_spatial_dims = input_tensor.shape.ndims - 2
  
      # Refine segmentation with prior
      if hyperparams["use_prior"]:
          spatial_prior_shape = [
              hyperparams["prior_size"]
          ] * n_spatial_dims
          # Prior shape must be 4 or 5 dim to work with linear_resize layer
          # ie to conform to shape=[batch, X, Y, Z, channels]
          prior_shape = [1] + spatial_prior_shape + [1]
          spatial_prior = SpatialPriorBlock(
              prior_shape, feature_map_shape
          )
          output += spatial_prior()
  
      # Invert augmentation
      if is_training and hyperparams["augmentation_scale"] > 0:
          inverse_aug = augment_layer.inverse()
          output = inverse_aug(output)
  
      # Resize output to original size
      input_tensor_spatial_size = input_tensor.shape.as_list()[1:-1]
      output = LinearResizeLayer(input_tensor_spatial_size)(output)
  
      # Segmentation summary
      seg_argmax = tf.to_float(
          tf.expand_dims(tf.argmax(output, -1), -1)
      )
      seg_summary = seg_argmax * (255.0 / self.num_classes - 1)
  
      # Image Summary
      norm_axes = list(range(1, n_spatial_dims + 1))
      mean, var = tf.nn.moments(
          input_tensor, axes=norm_axes, keep_dims=True
      )
      timg = tf.to_float(input_tensor - mean) / (tf.sqrt(var) * 2.0)
      timg = (timg + 1.0) * 127.0
      single_channel = tf.reduce_mean(timg, -1, True)
      img_summary = tf.minimum(255.0, tf.maximum(0.0, single_channel))
  
      if n_spatial_dims == 2:
          tf.summary.image(
              tf.get_default_graph().unique_name("imgseg"),
              tf.concat([img_summary, seg_summary], 1),
              5,
              [tf.GraphKeys.SUMMARIES],
          )
      elif n_spatial_dims == 3:
          image3_axial(
              tf.get_default_graph().unique_name("imgseg"),
              tf.concat([img_summary, seg_summary], 1),
              5,
              [tf.GraphKeys.SUMMARIES],
          )
      else:
          raise NotImplementedError(
              "Image Summary only supports 2D and 3D images"
          )
  
      return output
  
imports: from __future__ import absolute_import, print_function
  import tensorflow
  from niftynet.io.misc_io import image3_axial
  from niftynet.layer import layer_util
  from niftynet.layer.affine_augmentation import AffineAugmentationLayer
  from niftynet.layer.base_layer import TrainableLayer
  from niftynet.layer.bn import BNLayer
  from niftynet.layer.channel_sparse_convolution import ChannelSparseConvolutionalLayer
  from niftynet.layer.convolution import ConvolutionalLayer
  from niftynet.layer.linear_resize import LinearResizeLayer
  from niftynet.layer.downsample import DownSampleLayer
  from niftynet.network.base_net import BaseNet
  from collections import namedtuple
label_desc: Use image node_id 3 for calling the DenseVNet obj's underlying member method code with example usage: obj.layer_op(input_tensor, is_training, layer_id, keep_prob) and returns: output