class_docstr: Wrap multiple batchify functions together and apply it to merge inputs from a namedtuple.
  The generated batch samples are stored as a namedtuple with the same structure.
  Each data sample should be a namedtuple. The `i`th batchify
  function stored in `NamedTuple` will be applied on the `i`th attribute of the namedtuple data.
  For example, each data sample is Sample(data=nd_data, label=nd_label).
  You can wrap two batchify functions using
  `NamedTuple(Sample, {'data': DataBatchify, 'label': LabelBatchify})` to
  batchify nd_data and nd_label correspondingly. The result will be stored as a Sample object
  and you can access the data and label via `sample.data` and `sample.label`, correspondingly.
  Parameters
  ----------
  container
      The object that constructs the namedtuple.
  fn_info
      The information of the inner batchify functions.
  Examples
  --------
  >>> from codel.data.batchify import NamedTuple, Pad, Stack
  >>> from collections import namedtuple
  >>> SampleData = namedtuple('SampleData', ['data', 'label'])
  >>> a = SampleData([1, 2, 3, 4], 0)
  >>> b = SampleData([5, 7], 1)
  >>> c = SampleData([1, 2, 3, 4, 5, 6, 7], 0)
  >>> batchify_fn = NamedTuple(SampleData, {'data': Pad(), 'label': Stack()})
  >>> sample = batchify_fn([a, b, c])
  >>> sample
  SampleData(data=
  [[1. 2. 3. 4. 0. 0. 0.]
   [5. 7. 0. 0. 0. 0. 0.]
   [1. 2. 3. 4. 5. 6. 7.]]
  <NDArray 3x7 @cpu_shared(0)>, label=
  [0 1 0]
  <NDArray 3 @cpu_shared(0)>)
  >>> sample.data
  <BLANKLINE>
  [[1. 2. 3. 4. 0. 0. 0.]
   [5. 7. 0. 0. 0. 0. 0.]
   [1. 2. 3. 4. 5. 6. 7.]]
  <NDArray 3x7 @cpu_shared(0)>
  >>> # Let's consider to use a list
  >>> batchify_fn = NamedTuple(SampleData, [Pad(), Stack()])
  >>> batchify_fn([a, b, c])
  SampleData(data=
  [[1. 2. 3. 4. 0. 0. 0.]
   [5. 7. 0. 0. 0. 0. 0.]
   [1. 2. 3. 4. 5. 6. 7.]]
  <NDArray 3x7 @cpu_shared(0)>, label=
  [0 1 0]
  <NDArray 3 @cpu_shared(0)>)
code: def __init__(
      self,
      container: t_NamedTuple,
      fn_info: t_Union[
          t_List[t_Callable],
          t_Tuple[t_Callable],
          t_Dict[AnyStr, t_Callable],
      ],
  ):
      self._container = container
      if isinstance(fn_info, (list, tuple)):
          if len(container._fields) != len(fn_info):
              raise ValueError(
                  "Attributes mismatch! Required fields={}, fn_info={}".format(
                      container._fields, fn_info
                  )
              )
      elif isinstance(fn_info, dict):
          for name in container._fields:
              if name not in fn_info:
                  raise ValueError(
                      "Attribute {} has not been assigned a callable. "
                      "Required fields={}, Found fields={}".format(
                          name, container._fields, fn_info.keys()
                      )
                  )
          if len(container._fields) != len(fn_info):
              raise ValueError(
                  "Attributes mimatch! Required fields={}, Found fields={}".format(
                      container._fields, fn_info.keys()
                  )
              )
          fn_info = [fn_info[name] for name in container._fields]
      for fn in fn_info:
          if not hasattr(fn, "__call__"):
              raise ValueError(
                  "All batchify functions must be callable."
              )
      self._fn_l = fn_info
  
imports: import math
  from typing import AnyStr
  from typing import Callable
  from typing import Dict
  from typing import List
  from typing import NamedTuple
  from typing import Tuple
  from typing import Union
  import numpy
  import torch
  from ..utils import to_torch_dtype
label_desc: Use image node_id 1 to create a new NamedTuple object  with example: obj = NamedTuple(container, fn_info)