def _read_data(self, byte_stream, data_type, num_recs, num_elems, dimensions=None):
        '''
        This is the primary routine that converts streams of bytes into usable data.

        To do so, we need the bytes, the type of data, the number of records,
        the number of elements in a record, and dimension information.
        '''

        squeeze_needed = False
        # If the dimension is [n], it needs to be [n,1]
        # for the numpy dtype.  This requires us to squeeze
        # the matrix later, to get rid of this extra dimension.
        dt_string = self._convert_option()
        if dimensions != None:
            if (len(dimensions) == 1):
                dimensions.append(1)
                squeeze_needed = True
            dt_string += '('
            count = 0
            for dim in dimensions:
                count += 1
                dt_string += str(dim)
                if count < len(dimensions):
                    dt_string += ','
            dt_string += ')'
        if data_type == 52 or data_type == 51:
            # string
            if dimensions == None:
                byte_data = bytearray(byte_stream[0:num_recs*num_elems])
                # In each record, check for the first '\x00' (null character).
                # If found, make all the characters after it null as well.
                for x in range(0, num_recs):
                    y = x * num_elems
                    z = byte_data[y:y+num_elems].find(b'\x00')
                    if (z > -1 and z < (num_elems-1)):
                        byte_data[y+z+1:y+num_elems] = b'\x00' * (num_elems - z - 1)
                ret = byte_data[0:num_recs*num_elems].decode('utf-8').replace('\x00', '')
            else:
                # Count total number of strings
                count = 1
                for x in range(0, len(dimensions)):
                    count = count * dimensions[x]
                strings = []
                if (len(dimensions) == 0):
                    for i in range(0, num_recs*count*num_elems, num_elems):
                        string1 = byte_stream[i:i+num_elems].decode('utf-8').\
                            replace('\x00', '')
                        strings.append(string1)
                else:
                    for x in range(0, num_recs):
                        onerec = []
                        for i in range(x*count*num_elems, (x+1)*count*num_elems,
                                       num_elems):
                            string1 = byte_stream[i:i+num_elems].decode('utf-8')\
                                .replace('\x00', '')
                            onerec.append(string1)
                        strings.append(onerec)
                ret = strings
            return ret
        else:
            if (data_type == 1) or (data_type == 41):
                dt_string += 'i1'
            elif data_type == 2:
                dt_string += 'i2'
            elif data_type == 4:
                dt_string += 'i4'
            elif (data_type == 8) or (data_type == 33):
                dt_string += 'i8'
            elif data_type == 11:
                dt_string += 'u1'
            elif data_type == 12:
                dt_string += 'u2'
            elif data_type == 14:
                dt_string += 'u4'
            elif (data_type == 21) or (data_type == 44):
                dt_string += 'f'
            elif (data_type == 22) or (data_type == 45) or (data_type == 31):
                dt_string += 'd'
            elif (data_type == 32):
                dt_string += 'c16'
            dt = np.dtype(dt_string)
            ret = np.frombuffer(byte_stream, dtype=dt, count=num_recs*num_elems)
            try:
                ret.setflags('WRITEABLE')
            except ValueError:
                # If we can't set the writable flag, just continue
                pass

        if squeeze_needed:
            ret = np.squeeze(ret, axis=(ret.ndim-1))

        # Put the data into system byte order
        if self._convert_option() != '=':
            ret = ret.byteswap().newbyteorder()

        return ret
This is the primary routine that converts streams of bytes into usable data.

        To do so, we need the bytes, the type of data, the number of records,
        the number of elements in a record, and dimension information.