def conv2d(self, filter_size, output_channels, stride=1, padding='SAME', stoch=None, bn=True, test=False, activation_fn=tf.nn.relu, b_value=0.0, s_value=1.0):
        """
        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
        """
        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)
            self.input = tf.nn.conv2d(self.input, w, strides=[1, stride, stride, 1], padding=padding)

            # Additional functions
            if stoch is not None:  # Draw feature map values from a normal distribution
                if stoch == 'N':  # Normal
                    output_shape = [3, 3, output_channels, 1]
                    w2 = self.weight_variable(name='weights_mean', shape=output_shape)
                    mean = tf.nn.conv2d(self.input, w2, strides=[1, 1, 1, 1], padding=padding)
                    w3 = self.weight_variable(name='weights_std', shape=output_shape)
                    std = tf.nn.conv2d(self.input, w3, strides=[1, 1, 1, 1], padding=padding)
                    map_size = tf.pack([mean.get_shape()[1], mean.get_shape()[2]])
                    z = mean + tf.random_normal(map_size) * std
                if stoch == 'B':  # Bernoulli
                    mean = 0
                    map_size = tf.pack([mean.get_shape()[1], mean.get_shape()[2]])
                with tf.variable_scope("stoch"):
                    output_shape = tf.pack([self.input.get_shape()[1], self.input.get_shape()[2], 1, 1])
                    w3 = self.weight_variable(name='weights_refinement', shape=output_shape)
                    self.input = self.input + z * w3
            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)
                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)
                self.input = tf.multiply(self.input, s)
            if activation_fn is not None:  # activation function
                self.input = activation_fn(self.input)
        self.print_log(scope + ' output: ' + str(self.input.get_shape()))