docs: def convert_to_numpy_str(data, length=None):
      """ Decodes data to Numpy unicode string (numpy.unicode\_).
  
      Decodes `data` to Numpy unicode string (UTF-32), which is
      ``numpy.unicode_``, or an array of them. If it can't be decoded, it
      is returned as is. Unsigned integers, Python string types (``str``,
      ``bytes``), and ``numpy.bytes_`` are supported. If it is an array of
      ``numpy.bytes_``, an array of those all converted to
      ``numpy.unicode_`` is returned. Python 3.x ``bytes``, Python 2.x
      ``str``, and ``numpy.bytes_`` are assumed to be encoded in UTF-8.
  
      For an array of unsigned integers, it may be desirable to make an
      array with strings of some specified length as opposed to an array
      of the same size with each element being a one element string. This
      naturally arises when converting strings to unsigned integer types
      in the first place, so it needs to be reversible.  The `length`
      parameter specifies how many to group together into a string
      (desired string length). For 1d arrays, this is along its only
      dimension. For higher dimensional arrays, it is done along each row
      (across columns). So, for a 3x10x5 input array of uints and a
      `length` of 5, the output array would be a 3x2x5 of 5 element
      strings.
  
      Parameters
      ----------
      data : some type
          Data decode into a Numpy unicode string.
      length : int or None, optional
          The number of consecutive elements (in the case of unsigned
          integer `data`) to compose each string in the output array from.
          ``None`` indicates the full amount for a 1d array or the number
          of columns (full length of row) for a higher dimension array.
  
      Returns
      -------
      s : numpy.unicode\_ or numpy.ndarray of numpy.unicode\_ or data
          If `data` can be decoded into a ``numpy.unicode_`` or a
          ``numpy.ndarray`` of them, the decoded version is returned.
          Otherwise, `data` is returned unchanged.
  
      See Also
      --------
      convert_to_str
      convert_to_numpy_bytes
      numpy.unicode\_
  
      """
      # The method of conversion depends on its type.
      if isinstance(data, np.unicode_) or (isinstance(data, np.ndarray) \
              and data.dtype.char == 'U'):
          # It is already an np.str_ or array of them, so nothing needs to
          # be done.
          return data
      elif (sys.hexversion >= 0x03000000 and isinstance(data, str)) \
             or (sys.hexversion < 0x03000000 \
             and isinstance(data, unicode)):
          # Easily converted through constructor.
          return np.unicode_(data)
      elif isinstance(data, (bytes, bytearray, np.bytes_)):
          # All of them can be decoded and then passed through the
          # constructor.
          return np.unicode_(data.decode('UTF-8'))
      elif isinstance(data, (np.uint8, np.uint16)):
          # They are single UTF-8 or UTF-16 scalars, which can be wrapped
          # into an array and recursed.
          return convert_to_numpy_str(np.atleast_1d(data))[0]
      elif isinstance(data, np.uint32):
          # It is just the uint32 version of the character, so it just
          # needs to be have the dtype essentially changed by having its
          # bytes read into ndarray.
          return np.ndarray(shape=tuple(), dtype='U1',
                            buffer=data.tostring())[()]
      elif isinstance(data, np.ndarray) and data.dtype.char == 'S':
          return np.char.encode(data, 'UTF-32')
      elif isinstance(data, np.ndarray) \
              and data.dtype.name in ('uint8', 'uint16', 'uint32'):
          # It is an ndarray of some uint type. How it is converted
          # depends on its shape. If its shape is just (), then it is just
          # a scalar wrapped in an array, which can be converted by
          # recursing the scalar value back into this function.
          shape = list(data.shape)
          if len(shape) == 0:
              return convert_to_numpy_str(data[()])
  
          # As there are more than one element, it gets a bit more
          # complicated. We need to take the subarrays of the specified
          # length along columns (1D arrays will be treated as row arrays
          # here), each of those converted to an str_ scalar (normal
          # string) and stuffed into a new array.
          #
          # If the length was not given, it needs to be set to full. Then
          # the shape of the new array needs to be calculated (divide the
          # appropriate dimension, which depends on the number of
          # dimentions).
          if len(shape) == 1:
              if length is None:
                  length = shape[0]
              new_shape = (shape[0]//length,)
          else:
              if length is None:
                  length = shape[-1]
              new_shape = copy.deepcopy(shape)
              new_shape[-1] //= length
  
          # numpy.char.decode will be used to decode. It needs the
          # encoding (UTF-8/16/32) which is gotten from the dtype. But it
          # also needs the data to be in big endian format, so it must be
          # byteswapped if it isn't. Without the swapping, an error occurs
          # since trailing nulls are dropped in numpy bytes_ arrays. The
          # dtype for each string element is just 'SX' where X is the
          # number of bytes.
          if data.dtype.name == 'uint8':
              encoding = 'UTF-8'
              swapbytes = False
              dt = 'S' + str(length)
          else:
              if data.dtype.name == 'uint16':
                  encoding = 'UTF-16BE'
                  dt = 'S' + str(2 * length)
              else:
                  encoding = 'UTF-32BE'
                  dt = 'S' + str(4 * length)
              if (data.dtype.byteorder == '<'
                  or (sys.byteorder == 'little'
                      and data.dtype.byteorder == '=')):
                  swapbytes = True
              else:
                  swapbytes = False
          # Copy is needed to prevent errors.
          if swapbytes:
              return np.char.decode(data.copy().byteswap().view(dt),
                                    encoding)
          else:
              return np.char.decode(data.copy().view(dt), encoding)
      else:
          # Couldn't figure out what it is, so nothing can be done but
          # return it as is.
          return data