class_docstr: References
  ----------
  * https://github.com/onnx/onnx/blob/master/docs/Operators.md
class_docstr_tok: ["References","--","--","--","--","--","*","https",":","\/\/github.com\/onnx\/onnx\/blob\/master\/docs\/Operators.md"]
docstr: Starting from a Conv node, consume nodes to form a convolution node with
  (optional) activation function.
docstr_tok: ["Starting","from","a","Conv","node",",","consume","nodes","to","form","a","convolution","node","with","(","optional",")","activation","function","."]
code: def _consume_conv_nodes(self, node, next_nodes):
      """
      Starting from a Conv node, consume nodes to form a convolution node with
      (optional) activation function.
      """
      if node.op_type != "Conv":
          raise ValueError(
              f"{node.name} is a {node.op_type} node, only Conv nodes can be used as starting points for consumption."
          )
      if len(node.input) not in [2, 3]:
          raise ValueError(
              f"{node.name} input has {len(node.input)} dimensions, only nodes with 2 or 3 input dimensions can be used as starting points for consumption."
          )
  
      if len(node.input) == 2:
          [in_0, in_1] = list(node.input)
          in_2 = None
      else:
          [in_0, in_1, in_2] = list(node.input)
      input_layer, transformer = self._node_input_and_transformer(in_0)
      input_output_size = _get_input_output_size(
          input_layer, transformer
      )
      weights = self._initializers[in_1]
      [out_channels, in_channels, *kernel_shape] = weights.shape
  
      if in_2 is None:
          biases = np.zeros(out_channels)
      else:
          biases = self._initializers[in_2]
  
      attr = _collect_attributes(node)
  
      strides = attr["strides"]
      # check only kernel shape and stride are set
      if attr["kernel_shape"] != kernel_shape:
          raise ValueError(
              f"Kernel shape attribute {attr['kernel_shape']} does not match initialized kernel shape {kernel_shape}."
          )
      if len(kernel_shape) != len(strides):
          raise ValueError(
              f"Initialized kernel shape {kernel_shape} has {len(kernel_shape)} dimensions. Strides attribute has {len(strides)} dimensions. These must be equal."
          )
      if len(input_output_size) != len(kernel_shape) + 1:
          raise ValueError(
              f"Input/output size ({input_output_size}) must have one more dimension than initialized kernel shape ({kernel_shape})."
          )
  
      # Check input, output have correct dimensions
      if biases.shape != (out_channels,):
          raise ValueError(
              f"Biases shape {biases.shape} must match output weights channels {(out_channels,)}."
          )
      if in_channels != input_output_size[0]:
          raise ValueError(
              f"Input/output size ({input_output_size}) first dimension must match input weights channels ({in_channels})."
          )
  
      # Other attributes are not supported
      if "dilations" in attr and attr["dilations"] != [1, 1]:
          raise ValueError(
              f"{node} has non-identity dilations ({attr['dilations']}). This is not supported."
          )
      if attr["group"] != 1:
          raise ValueError(
              f"{node} has multiple groups ({attr['group']}). This is not supported."
          )
      if "pads" in attr and np.any(attr["pads"]):
          raise ValueError(
              f"{node} has non-zero pads ({attr['pads']}). This is not supported."
          )
  
      # generate new nodes for the node output
      padding = 0
      output_size = [out_channels]
      for w, k, s in zip(input_output_size[1:], kernel_shape, strides):
          new_w = int((w - k + 2 * padding) / s) + 1
          output_size.append(new_w)
  
      activation = "linear"
      if len(next_nodes) == 1:
          # check if Relu
          type_, maybe_node, maybe_next_nodes = self._nodes[
              next_nodes[0]
          ]
          if maybe_node.op_type in _ACTIVATION_OP_TYPES:
              node = maybe_node
              activation = maybe_node.op_type.lower()
              next_nodes = maybe_next_nodes
  
      # convolute image one channel at the time
      # expect 2d image with channels
      if len(input_output_size) != 3:
          raise ValueError(
              f"Expected a 2D image with channels, got {input_output_size}."
          )
  
      conv_layer = ConvLayer2D(
          input_output_size,
          output_size,
          strides,
          weights,
          activation=activation,
          input_index_mapper=transformer,
      )
      self._node_map[node.name] = conv_layer
      self._node_map[node.output[0]] = conv_layer
  
      return next_nodes, conv_layer, [input_layer]
  
file_path: OMLT/src/omlt/io/onnx_parser.py
imports: import math
  import numpy
  from onnx import numpy_helper
  from omlt.neuralnet.layer import ConvLayer2D, DenseLayer, IndexMapper, InputLayer, PoolingLayer2D
  from omlt.neuralnet.network_definition import NetworkDefinition
label_desc: Use image node_id 7 for calling the NetworkParser obj's underlying member method code with example usage: obj._consume_conv_nodes(node, next_nodes) and returns: next_nodes,  conv_layer,  list