code: def conv2d(self, filter_size, output_channels, stride=1, padding='SAME', bn=True, activation_fn=tf.nn.relu,
                 b_value=0.0, s_value=1.0, trainable=True):
          
          self.count['conv'] += 1
          scope = 'conv_' + str(self.count['conv'])
          with tf.variable_scope(scope):
  
              # Conv function
              input_channels = self.input.get_shape()[3]
              if filter_size == 0:  # outputs a 1x1 feature map; used for FCN
                  filter_size = self.input.get_shape()[2]
                  padding = 'VALID'
              output_shape = [filter_size, filter_size, input_channels, output_channels]
              w = self.weight_variable(name='weights', shape=output_shape, trainable=trainable)
              self.input = tf.nn.conv2d(self.input, w, strides=[1, stride, stride, 1], padding=padding)
  
              if bn is True:  # batch normalization
                  self.input = self.batch_norm(self.input)
              if b_value is not None:  # bias value
                  b = self.const_variable(name='bias', shape=[output_channels], value=b_value, trainable=trainable)
                  self.input = tf.add(self.input, b)
              if s_value is not None:  # scale value
                  s = self.const_variable(name='scale', shape=[output_channels], value=s_value, trainable=trainable)
                  self.input = tf.multiply(self.input, s)
              if activation_fn is not None:  # activation function
                  self.input = activation_fn(self.input)
          print(scope + ' output: ' + str(self.input.get_shape()))
docs: 2D Convolutional Layer.
          :param filter_size: int. assumes square filter
          :param output_channels: int
          :param stride: int
          :param padding: 'VALID' or 'SAME'
          :param activation_fn: tf.nn function
          :param b_value: float
          :param s_value: float