code: def _create_homogeneous_sampled_graphs_from_tensors_perhop(
      tensors_batch_d, edge_dir, return_type
  ):
      """
      This helper function creates sampled DGL MFGS for
      homogeneous graphs from tensors per hop for a single
      batch
      Args:
          tensors_batch_d (dict): A dictionary of tensors, keyed by hop_id.
          edge_dir (str): Direction of edges from samples
          metagraph (dgl.metagraph): The metagraph for the sampled graph
          return_type (str): The type of graph to return
      Returns:
          tuple: A tuple of three elements:
              - input_nodes: The input nodes for the batch.
              - output_nodes: The output nodes for the batch.
              - graph_per_hop_ls: A list of MFGS for each hop.
      """
      if edge_dir not in ["in", "out"]:
          raise ValueError(f"Invalid edge_dir {edge_dir} provided")
      if edge_dir == "out":
          raise ValueError("Outwards edges not supported yet")
      graph_per_hop_ls = []
      seednodes_range = None
      for hop_id, tensor_per_hop_d in tensors_batch_d.items():
          if hop_id != "map":
              if return_type == "dgl.Block":
                  mfg = _create_homogeneous_dgl_block_from_tensor_d(
                      tensor_d=tensor_per_hop_d,
                      renumber_map=tensors_batch_d["map"],
                      seednodes_range=seednodes_range,
                  )
              elif return_type == "cugraph_dgl.nn.SparseGraph":
                  mfg = _create_homogeneous_cugraph_dgl_nn_sparse_graph(
                      tensor_d=tensor_per_hop_d,
                      seednodes_range=seednodes_range,
                  )
              else:
                  raise ValueError(
                      f"Invalid return_type {return_type} provided"
                  )
              seednodes_range = max(
                  tensor_per_hop_d["sources_range"],
                  tensor_per_hop_d["destinations_range"],
              )
              graph_per_hop_ls.append(mfg)
  
      # default DGL behavior
      if edge_dir == "in":
          graph_per_hop_ls.reverse()
      if return_type == "dgl.Block":
          input_nodes = graph_per_hop_ls[0].srcdata[dgl.NID]
          output_nodes = graph_per_hop_ls[-1].dstdata[dgl.NID]
      else:
          map = tensors_batch_d["map"]
          input_nodes = map[0 : graph_per_hop_ls[0].num_src_nodes()]
          output_nodes = map[0 : graph_per_hop_ls[-1].num_dst_nodes()]
      return input_nodes, output_nodes, graph_per_hop_ls
  
imports: from __future__ import annotations
  from typing import List, Tuple, Dict, Optional
  from collections import defaultdict
  import cudf
  from cugraph.utilities.utils import import_optional
  from cugraph_dgl.nn import SparseGraph
label_desc: Use image node_id 8 for calling a global function with example usage: _create_homogeneous_sampled_graphs_from_tensors_perhop(tensors_batch_d, edge_dir, return_type) and returns: input_nodes,  output_nodes,  graph_per_hop_ls