<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def _do_read(self, collection, version, symbol, index_range=None):
 """ index_range is a 2-tuple of integers - a [from, to) range of segments to be read. Either from or to can be None, indicating no bound. """

        from_index = index_range[0] if index_range else None
        to_index = version['up_to']
        if index_range and index_range[1] and index_range[1] < version['up_to']:
            to_index = index_range[1]
        segment_count = version.get('segment_count') if from_index is None else None

        spec = _spec_fw_pointers_aware(symbol, version, from_index, to_index)

        data = bytearray()
        i = -1
        for i, x in enumerate(sorted(collection.find(spec), key=itemgetter('segment'))):
            data.extend(decompress(x['data']) if x['compressed'] else x['data'])

        # Check that the correct number of segments has been returned
        if segment_count is not None and i + 1 != segment_count:
            raise OperationFailure("Incorrect number of segments returned for {}:{}.  Expected: {}, but got {}. {}".format(
                                   symbol, version['version'], segment_count, i + 1, collection.database.name + '.' + collection.name))

        dtype = self._dtype(version['dtype'], version.get('dtype_metadata', {}))
        rtn = np.frombuffer(data, dtype=dtype).reshape(version.get('shape', (-1)))
        return rtn