class_docstr: ### Description
  reimplementation of DeepMedic:
      Kamnitsas et al., "Efficient multi-scale 3D CNN with fully connected
      CRF for accurate brain lesion segmentation", MedIA '17
  
  ### Building blocks
  [CONV]          - 3x3x3 convolutional layer
  [denseCONV]     - 1x1x1 convolutional layer
  
  ### Diagram
  INPUT --> CROP -------> [CONV]x8 ------> [SUM] ----> [denseCONV]x3 --> OUTPUT
              |                             |
          DOWNSAMPLE ---> [CONV]x8 ---> UPSAMPLE
  
  
  ### Constraints:
  - The downsampling factor (d_factor) should be odd
  - Label size = [(image_size / d_factor) - 16]* d_factor
  - Image size should be divisible by d_factor
  
  # Examples:
  - Appropriate configuration for training:
  image spatial window size = 57, label spatial window size = 9, d_ factor = 3
  - Appropriate configuration for inference:
  image spatial window size = 105, label spatial window size = 57, d_ factor = 3
docstr: :param images: tensor, input to the network, size should be divisible by d_factor
  :param is_training: boolean, True if network is in training mode
  :param layer_id: not in use
  :param unused_kwargs:
  :return: tensor, network output
code: def layer_op(self, images, is_training, layer_id=-1, **unused_kwargs):
      """
  
      :param images: tensor, input to the network, size should be divisible by d_factor
      :param is_training: boolean, True if network is in training mode
      :param layer_id: not in use
      :param unused_kwargs:
      :return: tensor, network output
      """
      # image_size is defined as the largest context, then:
      #   downsampled path size: image_size / d_factor
      #   downsampled path output: image_size / d_factor - 16
  
      # to make sure same size of feature maps from both pathways:
      #   normal path size: (image_size / d_factor - 16) * d_factor + 16
      #   normal path output: (image_size / d_factor - 16) * d_factor
  
      # where 16 is fixed by the receptive field of conv layers
      # TODO: make sure label_size = image_size/d_factor - 16
  
      # image_size has to be an odd number and divisible by 3 and
      # smaller than the smallest image size of the input volumes
  
      # label_size should be (image_size/d_factor - 16) * d_factor
  
      assert self.d_factor % 2 == 1  # to make the downsampling centered
      assert layer_util.check_spatial_dims(
          images, lambda x: x % self.d_factor == 0
      )
      assert layer_util.check_spatial_dims(
          images, lambda x: x % 2 == 1
      )  # to make the crop centered
      assert layer_util.check_spatial_dims(
          images, lambda x: x > self.d_factor * 16
      )  # required by receptive field
  
      # crop 25x25x25 from 57x57x57
      crop_op = CropLayer(border=self.crop_diff, name="cropping_input")
      normal_path = crop_op(images)
      print(crop_op)
  
      # downsample 19x19x19 from 57x57x57
      downsample_op = DownSampleLayer(
          func="CONSTANT",
          kernel_size=self.d_factor,
          stride=self.d_factor,
          padding="VALID",
          name="downsample_input",
      )
      downsample_path = downsample_op(images)
      print(downsample_op)
  
      # convolutions for both pathways
      for n_features in self.conv_features:
          # normal pathway convolutions
          conv_path_1 = ConvolutionalLayer(
              n_output_chns=n_features,
              kernel_size=3,
              padding="VALID",
              w_initializer=self.initializers["w"],
              w_regularizer=self.regularizers["w"],
              acti_func=self.acti_func,
              name="normal_conv",
          )
          normal_path = conv_path_1(normal_path, is_training)
          print(conv_path_1)
  
          # downsampled pathway convolutions
          conv_path_2 = ConvolutionalLayer(
              n_output_chns=n_features,
              kernel_size=3,
              padding="VALID",
              w_initializer=self.initializers["w"],
              w_regularizer=self.regularizers["w"],
              acti_func=self.acti_func,
              name="downsample_conv",
          )
          downsample_path = conv_path_2(downsample_path, is_training)
          print(conv_path_2)
  
      # upsampling the downsampled pathway
      downsample_path = UpSampleLayer(
          "REPLICATE", kernel_size=self.d_factor, stride=self.d_factor
      )(downsample_path)
  
      # concatenate both pathways
      output_tensor = ElementwiseLayer("CONCAT")(
          normal_path, downsample_path
      )
  
      # 1x1x1 convolution layer
      for n_features in self.fc_features:
          conv_fc = ConvolutionalLayer(
              n_output_chns=n_features,
              kernel_size=1,
              acti_func=self.acti_func,
              w_initializer=self.initializers["w"],
              w_regularizer=self.regularizers["w"],
              name="conv_1x1x1_{}".format(n_features),
          )
          output_tensor = conv_fc(output_tensor, is_training)
          print(conv_fc)
  
      return output_tensor
  
file_path: NiftyNet/niftynet/network/deepmedic.py
imports: from __future__ import absolute_import, print_function
  from niftynet.layer import layer_util
  from niftynet.layer.convolution import ConvolutionalLayer
  from niftynet.layer.crop import CropLayer
  from niftynet.layer.downsample import DownSampleLayer
  from niftynet.layer.elementwise import ElementwiseLayer
  from niftynet.layer.upsample import UpSampleLayer
  from niftynet.network.base_net import BaseNet
label_desc: Use image node_id 2 for calling the DeepMedic obj's underlying member method code with example usage: obj.layer_op(images, is_training, layer_id) and returns: output_tensor