code: def get_layer_neuronwise_io(
      layer: Layer,
      node_index: int = 0,
      Xs: list[Tensor] = None,
      Ys: list[Tensor] = None,
      return_i: bool = True,
      return_o: bool = True,
  ) -> tuple[list[Tensor], list[Tensor]] | list[Tensor]:
      """Returns the input and output for each neuron in a layer
  
      Returns the symbolic input and output for each neuron in a layer.
      For a dense layer this is the input output itself.
      For convolutional layers this method extracts for each neuron
      the input output mapping.
  
      At the moment this function is designed
      to work with dense and conv2d layers.
  
      :param layer: The targeted layer.
      :param node_index: Index of the layer node to use.
      :param Xs: Ignore the layer's input but use Xs instead.
      :param Ys: Ignore the layer's output but use Ys instead.
      :param return_i: Return the inputs.
      :param return_o: Return the outputs.
      :return: Inputs and outputs, if specified, for each individual neuron.
      """
      if not ichecks.contains_kernel(layer):
          raise NotImplementedError()
  
      if Xs is None:
          Xs = ibackend.to_list(layer.get_input_at(node_index))
      if Ys is None:
          Ys = ibackend.to_list(layer.get_output_at(node_index))
  
      if isinstance(layer, klayers.Dense):
          # Xs and Ys are already in shape.
          ret_Xs = Xs
          ret_Ys = Ys
      elif isinstance(layer, klayers.Conv2D):
          kernel = get_kernel(layer)
          # Expect filter dimension to be last.
          n_channels = kernel.shape[-1]
  
          if return_i:
              extract_patches = ilayers.ExtractConv2DPatches(
                  kernel.shape[:2],
                  kernel.shape[2],
                  layer.strides,
                  layer.dilation_rate,
                  layer.padding,
              )
              # shape [samples, out_row, out_col, weight_size]
              reshape = ilayers.Reshape(
                  (-1, np.product(kernel.shape[:3]))
              )
              ret_Xs = [reshape(extract_patches(x)) for x in Xs]
  
          if return_o:
              # Get Ys into shape (samples, channels)
              if kbackend.image_data_format() == "channels_first":
                  # Ys shape is [samples, channels, out_row, out_col]
                  def _reshape(x):
                      x = kbackend.permute_dimensions(x, (0, 2, 3, 1))
                      x = ilayers.Reshape((-1, n_channels))(x)
                      return x
  
              else:
                  # Ys shape is [samples, out_row, out_col, channels]
                  def _reshape(x):
                      x = ilayers.Reshape((-1, n_channels))(x)
                      return x
  
              ret_Ys = [_reshape(x) for x in Ys]
  
      else:
          raise NotImplementedError()
  
      # Xs is (n, d) and Ys is (d, channels)
      if return_i and return_o:
          return ret_Xs, ret_Ys
      if return_i:
          return ret_Xs
      if return_o:
          return ret_Ys
      raise Exception()
  
imports: from __future__ import annotations
  import inspect
  import warnings
  from abc import ABCMeta, abstractmethod
  from typing import Any, Callable
  import numpy
  import tensorflow.keras.backend
  import tensorflow.keras.layers
  import tensorflow.keras.models
  import innvestigate.backend
  import innvestigate.backend.checks
  import innvestigate.layers
  from innvestigate.backend.types import Layer, LayerCheck, Model, NodeDict, OptionalList, ReverseTensorDict, Tensor
label_desc: Use image node_id 3 for calling a global function with example usage: get_layer_neuronwise_io(layer, node_index, Xs, Ys, return_i, return_o) and returns: ret_Xs,  ret_Ys, ret_Xs, ret_Ys, x, x