class_docstr: ### Description
     The network estimates dense displacement fields from a pair
     of moving and fixed images:
  
         Hu et al., Label-driven weakly-supervised learning for
         multimodal deformable image registration, arXiv:1711.01666
         https://arxiv.org/abs/1711.01666
  
         Hu et al., Weakly-Supervised Convolutional Neural Networks for
         Multimodal Image Registration, Medical Image Analysis (2018)
         https://doi.org/10.1016/j.media.2018.07.002
  
     see also:
         https://github.com/YipengHu/label-reg
  
  ### Building blocks
  [DOWN CONV]         - Convolutional layer + Residual Unit + Downsampling (Max pooling)
  [CONV]              - Convolutional layer
  [UP CONV]           - Upsampling + Sum + Residual Unit
  [FUSION]            - Multi-scale displacement fields fusion
  [DISPtoDEF]         - (Smoothing if required) Conversion to deformation field (adding base grid)
  
  
  ### Diagram
  INPUT PAIR -->  [DOWN CONV]              [UP CONV] --> [CONV] --[FUSION] --> [DISPtoDEF] --> DENSE FIELD
                      |                        |                     |
                  [DOWN CONV]              [UP CONV] --> [CONV] -----|
                      |                        |                     |
                  [DOWN CONV]              [UP CONV] --> [CONV] -----|
                      |                        |                     |
                  [DOWN CONV]              [UP CONV] --> [CONV] -----|
                       |                       |                     |
                       -------- [CONV]------------------ [CONV]-------
  
  
  ### Constraints
     - input spatial rank should be either 2 or 3 (2D or 3D images only)
     - fixed image size should be divisible by 16
docstr: :param fixed_image: tensor, fixed image for registration (defines reference space)
  :param moving_image: tensor, moving image to be registered to fixed
  :param base_grid: initial identity or affine displacement field
  :param is_training: boolean, True if network is in training mode
  :return: estimated dense displacement fields
docstr_tok: [":","param","fixed_image",":","tensor",",","fixed","image","for","registration","(","defines","reference","space",")",":","param","moving_image",":","tensor",",","moving","image","to","be","registered","to","fixed",":","param","base_grid",":","initial","identity","or","affine","displacement","field",":","param","is_training",":","boolean",",","True","if","network","is","in","training","mode",":","return",":","estimated","dense","displacement","fields"]
code: def layer_op(
      self,
      fixed_image,
      moving_image,
      base_grid=None,
      is_training=True,
      **unused_kwargs,
  ):
      """
  
      :param fixed_image: tensor, fixed image for registration (defines reference space)
      :param moving_image: tensor, moving image to be registered to fixed
      :param base_grid: initial identity or affine displacement field
      :param is_training: boolean, True if network is in training mode
      :return: estimated dense displacement fields
      """
  
      spatial_rank = infer_spatial_rank(fixed_image)
      spatial_shape = fixed_image.get_shape().as_list()[1:-1]
      check_spatial_dims(fixed_image, lambda x: x % 16 == 0)
  
      #  resize the moving image to match the fixed
      moving_image = Resize(spatial_shape)(moving_image)
      img = tf.concat([moving_image, fixed_image], axis=-1)
      down_res_0, conv_0_0, _ = DownRes(
          self.fea[0], kernel_size=7, **self.down_res_param
      )(img, is_training)
      down_res_1, conv_0_1, _ = DownRes(
          self.fea[1], **self.down_res_param
      )(down_res_0, is_training)
      down_res_2, conv_0_2, _ = DownRes(
          self.fea[2], **self.down_res_param
      )(down_res_1, is_training)
      down_res_3, conv_0_3, _ = DownRes(
          self.fea[3], **self.down_res_param
      )(down_res_2, is_training)
  
      conv_4 = Conv(
          n_output_chns=self.fea[4],
          kernel_size=self.k_conv,
          **self.down_res_param,
      )(down_res_3, is_training)
  
      up_res_0 = UpRes(self.fea[3], **self.up_res_param)(
          conv_4, conv_0_3, is_training
      )
      up_res_1 = UpRes(self.fea[2], **self.up_res_param)(
          up_res_0, conv_0_2, is_training
      )
      up_res_2 = UpRes(self.fea[1], **self.up_res_param)(
          up_res_1, conv_0_1, is_training
      )
      up_res_3 = UpRes(self.fea[0], **self.up_res_param)(
          up_res_2, conv_0_0, is_training
      )
  
      if self.multi_scale_fusion:
          output_list = [up_res_3, up_res_2, up_res_1, up_res_0, conv_4]
      else:
          output_list = [up_res_3]
  
      # converting all output layers to displacement fields
      dense_fields = []
      for scale_out in output_list:
          field = Conv(
              n_output_chns=spatial_rank,
              kernel_size=self.k_conv,
              with_bias=True,
              feature_normalization=None,
              acti_func=None,
              **self.disp_param,
          )(scale_out)
          resized_field = Resize(new_size=spatial_shape)(field)
          dense_fields.append(resized_field)
  
      if base_grid is None:
          # adding a reference grid if it doesn't exist
          in_spatial_size = [None] * spatial_rank
          base_grid = _create_affine_features(
              output_shape=spatial_shape, source_shape=in_spatial_size
          )
          base_grid = np.asarray(base_grid[:-1])
          base_grid = np.reshape(
              base_grid.T, [-1] + spatial_shape + [spatial_rank]
          )
          base_grid = tf.constant(base_grid, dtype=resized_field.dtype)
  
      if self.multi_scale_fusion and len(dense_fields) > 1:
          dense_field = tf.reduce_sum(dense_fields, axis=0)
      else:
          dense_field = dense_fields[0]
  
      # TODO filtering
      if self.smoothing_func is not None:
          dense_field = self.smoothing_func(dense_field, spatial_rank)
  
      tf.add_to_collection(
          "bending_energy", _computing_bending_energy(dense_field)
      )
      tf.add_to_collection(
          "gradient_norm", _computing_gradient_norm(dense_field)
      )
  
      dense_field = dense_field + base_grid
      return dense_field
  
imports: from __future__ import absolute_import, print_function
  import numpy
  import tensorflow
  from tensorflow.contrib.layers.python.layers import regularizers
  from niftynet.engine.application_initializer import GlorotUniform
  from niftynet.layer.convolution import ConvolutionalLayer
  from niftynet.layer.downsample_res_block import DownBlock
  from niftynet.layer.grid_warper import _create_affine_features
  from niftynet.layer.layer_util import infer_spatial_rank, check_spatial_dims
  from niftynet.layer.linear_resize import LinearResizeLayer
  from niftynet.layer.spatial_gradient import SpatialGradientLayer
  from niftynet.layer.upsample_res_block import UpBlock
  from niftynet.network.base_net import BaseNet
label_desc: Use image node_id 2 for calling the INetDense obj's underlying member method code with example usage: obj.layer_op(fixed_image, moving_image, base_grid, is_training) and returns: dense_field