code: def full_space_conv2d_layer(net_block, net, layer_block, layer):
      r"""
      Add full-space formulation of the 2-D convolutional layer to the block
  
      A 2-D convolutional layer applies cross-correlation kernels to a 2-D input.
      Specifically, the input is convolved by sliding the kernels along the input vertically and horizontally.
      At each location, the preactivation is computed as the dot product of the kernel weights and the input plus a bias term.
  
      """
      # If activation is an increasing function,
      #  move it onto successor max pooling layer (if it exists) for tighter max pooling formulation
      succ_layers = list(net.successors(layer))
      succ_layer = succ_layers[0] if len(succ_layers) == 1 else None
      if (
          isinstance(succ_layer, PoolingLayer2D)
          and layer.activation not in NON_INCREASING_ACTIVATIONS
          and layer.activation != "linear"
      ):
          # activation applied after convolution layer, so there shouldn't be an activation after max pooling too
          if succ_layer.activation != "linear":
              raise ValueError(
                  f"Activation is applied after convolution layer, but the successor max pooling layer {succ_layer} has an activation function also."
              )
          succ_layer.activation = layer.activation
          layer.activation = "linear"
  
      input_layer, input_layer_block = _input_layer_and_block(
          net_block, net, layer
      )
  
      # for out_d, out_r, out_c in layer.output_indexes:
      #   output_index = (out_d, out_r, out_c)
      @layer_block.Constraint(layer.output_indexes)
      def convolutional_layer(b, *output_index):
          out_d, out_r, out_c = output_index
          expr = 0.0
          for weight, input_index in layer.kernel_with_input_indexes(
              out_d, out_r, out_c
          ):
              expr += weight * input_layer_block.z[input_index]
  
          lb, ub = compute_bounds_on_expr(expr)
          layer_block.zhat[output_index].setlb(lb)
          layer_block.zhat[output_index].setub(ub)
          # layer_block.constraints.add(layer_block.zhat[output_index] == expr)
          return layer_block.zhat[output_index] == expr
  
file_path: OMLT/src/omlt/neuralnet/layers/full_space.py
imports: import numpy
  import pyomo.environ
  from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr
  from omlt.neuralnet.activations import NON_INCREASING_ACTIVATIONS
  from omlt.neuralnet.layer import ConvLayer2D, IndexMapper, PoolingLayer2D
label_desc: Use image node_id 2 for calling a global function with example usage: full_space_conv2d_layer(net_block, net, layer_block, layer) and returns: unknown