class_docstr: A subclass of ndarray that lets you give names to indices.
  
  This is a normal ndarray in the sense that you can always index by numbers and
  slices, though elipses don't work. Also, all elements have the same type,
  unlike a record array.
  
  Names should be a list of names per dimension in the ndarray shape. The names
  should be a list or tuple of strings, a namedtuple class (with names taken
  from _fields), or an IntEnum. Alternatively if you don't want to give a name
  to a particular dimension, use None. If your array only has one dimension, the
  second level of list can be skipped.
  
  Example usage:
    a = named_array.NamedNumpyArray([1, 3, 6], ["a", "b", "c"])
    a.a, a[1], a["c"] => 1, 3, 6
    b = named_array.NamedNumpyArray([[1, 3], [6, 8]], [["a", "b"], None])
    b.a, b[1], b["a", 1] => [1, 3], [6, 8], 3
    c = named_array.NamedNumpyArray([[1, 3], [6, 8]], [None, ["a", "b"]])
    c[0].a, b[1, 0], b[1, "b"] => 1, 6, 8
  Look at the tests for more examples including using enums and named tuples.
docstr: Get by indexing lookup.
docstr_tok: ["Get","by","indexing","lookup","."]
code: def __getitem__(self, indices):
      """Get by indexing lookup."""
      indices = self._indices(indices)
      obj = super(NamedNumpyArray, self).__getitem__(indices)
  
      if (
          isinstance(indices, np.ndarray)
          and len(indices.shape) > 1
          and indices.dtype == bool
      ):
          # Is this a multi-dimensional mask, eg: obj[obj == 5] ?
          # Multi-dimensional masks return a single dimensional array, and it's
          # unclear what it means for the result to have names, so return a normal
          # numpy array.
          return np.array(obj)
  
      if isinstance(
          obj, np.ndarray
      ):  # If this is a view, index the names too.
          if not isinstance(indices, tuple):
              indices = (indices,)
          new_names = []
          dim = 0
          for i, index in enumerate(indices):
              if isinstance(index, numbers.Integral):
                  dim += 1  # Drop this dimension's names.
              elif index is Ellipsis:
                  # Copy all the dimensions' names through.
                  end = len(self.shape) - len(indices) + i + 1
                  for j in range(dim, end):
                      new_names.append(self._index_names[j])
                  dim = end
              elif index is np.newaxis:  # Add an unnamed dimension.
                  new_names.append(None)
                  # Don't modify dim, as we're still working on the same one.
              elif self._index_names[dim] is None or (
                  isinstance(index, slice) and index == _NULL_SLICE
              ):
                  # Keep unnamed dimensions or ones where the slice is a no-op.
                  new_names.append(self._index_names[dim])
                  dim += 1
              elif isinstance(index, (slice, list, np.ndarray)):
                  if (
                      isinstance(index, np.ndarray)
                      and len(index.shape) > 1
                  ):
                      raise TypeError(
                          "What does it mean to index into a named array by "
                          "a multidimensional array? %s" % index
                      )
                  # Rebuild the index of names for the various forms of slicing.
                  names = sorted(
                      self._index_names[dim].items(),
                      key=lambda item: item[1],
                  )
                  names = np.array(
                      names, dtype=object
                  )  # Support full numpy slicing.
                  sliced = names[index]  # Actually slice it.
                  indexed = {
                      n: j for j, (n, _) in enumerate(sliced)
                  }  # Reindex.
                  if len(sliced) != len(indexed):
                      # Names aren't unique, so drop the names for this dimension.
                      indexed = None
                  new_names.append(indexed)
                  dim += 1
              else:
                  raise TypeError(
                      "Unknown index: %s; %s" % (type(index), index)
                  )
          obj._index_names = new_names + self._index_names[dim:]
          if len(obj._index_names) != len(obj.shape):
              raise IndexError(
                  "Names don't match object shape: %s != %s"
                  % (len(obj.shape), len(obj._index_names))
              )
      return obj
  
imports: import enum
  import numbers
  import re
  import numpy
label_desc: Use image node_id 5 for calling the NamedNumpyArray obj's underlying member method code with example usage: obj.__getitem__(indices) and returns: obj, np