1try:
2    from collections.abc import Sequence # noqa
3except ImportError:
4    from collections import Sequence # noqa
5
6import contextlib
7import itertools
8import warnings
9import sys
10try: from future_builtins import zip
11except ImportError: pass
12
13import numpy as np
14
15from .line import HeaderLine
16from .field import Field
17from .utils import castarray
18
19class Sequence(Sequence):
20
21    # unify the common optimisations and boilerplate of Trace, RawTrace, and
22    # Header, which all obey the same index-oriented interface, and all share
23    # length and wrap-around properties.
24    #
25    # It provides a useful negative-wrap index method which deals
26    # appropriately with IndexError and python2-3 differences.
27
28    def __init__(self, length):
29        self.length = length
30
31    def __len__(self):
32        """x.__len__() <==> len(x)"""
33        return self.length
34
35    def __iter__(self):
36        """x.__iter__() <==> iter(x)"""
37        # __iter__ has a reasonable default implementation from Sequence. It's
38        # essentially this loop:
39        # for i in range(len(self)): yield self[i]
40        # However, in segyio that means the double-buffering, buffer reuse does
41        # not happen, which is *much* slower (the allocation of otherwised
42        # reused numpy objects takes about half the execution time), so
43        # explicitly implement it as [:]
44        return self[:]
45
46    def wrapindex(self, i):
47        if i < 0:
48            i += len(self)
49
50        if not 0 <= i < len(self):
51            # in python2, int-slice comparison does not raise a type error,
52            # (but returns False), so force a type-error if this still isn't an
53            # int-like.
54            _ = i + 0
55            raise IndexError('trace index out of range')
56
57        return i
58
59class Trace(Sequence):
60    """
61    The Trace implements the array interface, where every array element, the
62    data trace, is a numpy.ndarray. As all arrays, it can be random accessed,
63    iterated over, and read strided. Data is read lazily from disk, so
64    iteration does not consume much memory. If you want eager reading, use
65    Trace.raw.
66
67    This mode gives access to reading and writing functionality for traces.
68    The primary data type is ``numpy.ndarray``. Traces can be accessed
69    individually or with python slices, and writing is done via assignment.
70
71    Notes
72    -----
73    .. versionadded:: 1.1
74
75    .. versionchanged:: 1.6
76        common list operations (Sequence)
77
78    Examples
79    --------
80    Read all traces in file f and store in a list:
81
82    >>> l = [numpy.copy(tr) for tr in trace[:]]
83
84    Do numpy operations on a trace:
85
86    >>> tr = trace[10]
87    >>> tr = tr * 2
88    >>> tr = tr - 100
89    >>> avg = numpy.average(tr)
90
91    Perform some seismic processing on a trace. E.g resample from 2ms spacing
92    to 4ms spacing (note there is no anti-alias filtering in this example):
93
94    >>> tr = scipy.signal.resample(tr, len(tr)/2)
95
96    Double every trace value and write to disk. Since accessing a trace
97    gives a numpy value, to write to the respective trace we need its index:
98
99    >>> for i, tr in enumerate(trace):
100    ...     tr = tr * 2
101    ...     trace[i] = tr
102
103    """
104
105    def __init__(self, filehandle, dtype, tracecount, samples, readonly):
106        super(Trace, self).__init__(tracecount)
107        self.filehandle = filehandle
108        self.dtype = dtype
109        self.shape = samples
110        self.readonly = readonly
111
112    def __getitem__(self, i):
113        """trace[i] or trace[i, j]
114
115        ith trace of the file, starting at 0. trace[i] returns a numpy array,
116        and changes to this array will *not* be reflected on disk.
117
118        When i is a tuple, the second index j (int or slice) is the depth index
119        or interval, respectively. j starts at 0.
120
121        When i is a slice, a generator of numpy arrays is returned.
122
123        Parameters
124        ----------
125        i : int or slice
126        j : int or slice
127
128        Returns
129        -------
130        trace : numpy.ndarray of dtype or generator of numpy.ndarray of dtype
131
132        Notes
133        -----
134        .. versionadded:: 1.1
135
136        Behaves like [] for lists.
137
138        .. note::
139
140            This operator reads lazily from the file, meaning the file is read
141            on ``next()``, and only one trace is fixed in memory. This means
142            segyio can run through arbitrarily large files without consuming
143            much memory, but it is potentially slow if the goal is to read the
144            entire file into memory. If that is the case, consider using
145            `trace.raw`, which reads eagerly.
146
147        Examples
148        --------
149        Read every other trace:
150
151        >>> for tr in trace[::2]:
152        ...     print(tr)
153
154        Read all traces, last-to-first:
155
156        >>> for tr in trace[::-1]:
157        ...     tr.mean()
158
159        Read a single value. The second [] is regular numpy array indexing, and
160        supports all numpy operations, including negative indexing and slicing:
161
162        >>> trace[0][0]
163        1490.2
164        >>> trace[0][1]
165        1490.8
166        >>> trace[0][-1]
167        1871.3
168        >>> trace[-1][100]
169        1562.0
170
171        Read only an interval in a trace:
172        >>> trace[0, 5:10]
173        """
174
175        try:
176            # optimize for the default case when i is a single trace index
177            i = self.wrapindex(i)
178            buf = np.zeros(self.shape, dtype = self.dtype)
179            return self.filehandle.gettr(buf, i, 1, 1, 0, self.shape, 1, self.shape)
180        except TypeError:
181            pass
182
183        try:
184            i, j = i
185        except TypeError:
186            # index is not a tuple. Set j to be a slice that causes the entire
187            # trace to be loaded.
188            j = slice(0, self.shape, 1)
189
190        single = False
191        try:
192            start, stop, step = j.indices(self.shape)
193        except AttributeError:
194            # j is not a slice, set start stop and step so that a single sample
195            # at position j is loaded.
196            start = int(j) % self.shape
197            stop = start + 1
198            step = 1
199            single = True
200
201        n_elements = len(range(start, stop, step))
202
203        try:
204            i = self.wrapindex(i)
205            buf = np.zeros(n_elements, dtype = self.dtype)
206            tr = self.filehandle.gettr(buf, i, 1, 1, start, stop, step, n_elements)
207            return tr[0] if single else tr
208        except TypeError:
209            pass
210
211        try:
212            indices = i.indices(len(self))
213            def gen():
214                # double-buffer the trace. when iterating over a range, we want
215                # to make sure the visible change happens as late as possible,
216                # and that in the case of exception the last valid trace was
217                # untouched. this allows for some fancy control flow, and more
218                # importantly helps debugging because you can fully inspect and
219                # interact with the last good value.
220                x = np.zeros(n_elements, dtype=self.dtype)
221                y = np.zeros(n_elements, dtype=self.dtype)
222
223                for k in range(*indices):
224                    self.filehandle.gettr(x, k, 1, 1, start, stop, step, n_elements)
225                    x, y = y, x
226                    yield y
227
228            return gen()
229        except AttributeError:
230            # At this point we have tried to unpack index as a single int, a
231            # slice and a pair with either element being an int or slice.
232            msg = 'trace indices must be integers or slices, not {}'
233            raise TypeError(msg.format(type(i).__name__))
234
235
236    def __setitem__(self, i, val):
237        """trace[i] = val
238
239        Write the ith trace of the file, starting at 0. It accepts any
240        array_like, but val must be at least as big as the underlying data
241        trace.
242
243        If val is longer than the underlying trace, it is essentially
244        truncated.
245
246        For the best performance, val should be a numpy.ndarray of sufficient
247        size and same dtype as the file. segyio will warn on mismatched types,
248        and attempt a conversion for you.
249
250        Data is written immediately to disk. If writing multiple traces at
251        once, and a write fails partway through, the resulting file is left in
252        an unspecified state.
253
254        Parameters
255        ----------
256        i   : int or slice
257        val : array_like
258
259        Notes
260        -----
261        .. versionadded:: 1.1
262
263        Behaves like [] for lists.
264
265        Examples
266        --------
267        Write a single trace:
268
269        >>> trace[10] = list(range(1000))
270
271        Write multiple traces:
272
273        >>> trace[10:15] = np.array([cube[i] for i in range(5)])
274
275        Write multiple traces with stride:
276
277        >>> trace[10:20:2] = np.array([cube[i] for i in range(5)])
278
279        """
280        if isinstance(i, slice):
281            for j, x in zip(range(*i.indices(len(self))), val):
282                self[j] = x
283
284            return
285
286        xs = castarray(val, self.dtype)
287
288        # TODO:  check if len(xs) > shape, and optionally warn on truncating
289        # writes
290        self.filehandle.puttr(self.wrapindex(i), xs)
291
292    def __repr__(self):
293        return "Trace(traces = {}, samples = {})".format(len(self), self.shape)
294
295    @property
296    def raw(self):
297        """
298        An eager version of Trace
299
300        Returns
301        -------
302        raw : RawTrace
303        """
304        return RawTrace(self.filehandle,
305                        self.dtype,
306                        len(self),
307                        self.shape,
308                        self.readonly,
309                       )
310
311    @property
312    @contextlib.contextmanager
313    def ref(self):
314        """
315        A write-back version of Trace
316
317        Returns
318        -------
319        ref : RefTrace
320            `ref` is returned in a context manager, and must be in a ``with``
321            statement
322
323        Notes
324        -----
325        .. versionadded:: 1.6
326
327        Examples
328        --------
329        >>> with trace.ref as ref:
330        ...     ref[10] += 1.617
331        """
332
333        x = RefTrace(self.filehandle,
334                     self.dtype,
335                     len(self),
336                     self.shape,
337                     self.readonly,
338                    )
339        yield x
340        x.flush()
341
342class RawTrace(Trace):
343    """
344    Behaves exactly like trace, except reads are done eagerly and returned as
345    numpy.ndarray, instead of generators of numpy.ndarray.
346    """
347    def __init__(self, *args):
348        super(RawTrace, self).__init__(*args)
349
350    def __getitem__(self, i):
351        """trace[i]
352
353        Eagerly read the ith trace of the file, starting at 0. trace[i] returns
354        a numpy array, and changes to this array will *not* be reflected on
355        disk.
356
357        When i is a slice, this returns a 2-dimensional numpy.ndarray .
358
359        Parameters
360        ----------
361        i : int or slice
362
363        Returns
364        -------
365        trace : numpy.ndarray of dtype
366
367        Notes
368        -----
369        .. versionadded:: 1.1
370
371        Behaves like [] for lists.
372
373        .. note::
374
375            Reading this way is more efficient if you know you can afford the
376            extra memory usage. It reads the requested traces immediately to
377            memory.
378
379        """
380        try:
381            i = self.wrapindex(i)
382            buf = np.zeros(self.shape, dtype = self.dtype)
383            return self.filehandle.gettr(buf, i, 1, 1, 0, self.shape, 1, self.shape)
384        except TypeError:
385            try:
386                indices = i.indices(len(self))
387            except AttributeError:
388                msg = 'trace indices must be integers or slices, not {}'
389                raise TypeError(msg.format(type(i).__name__))
390            start, _, step = indices
391            length = len(range(*indices))
392            buf = np.empty((length, self.shape), dtype = self.dtype)
393            return self.filehandle.gettr(buf, start, step, length, 0, self.shape, 1, self.shape)
394
395
396def fingerprint(x):
397    return hash(bytes(x.data))
398
399class RefTrace(Trace):
400    """
401    Behaves like trace, except changes to the returned numpy arrays *are*
402    reflected on disk. Operations have to be in-place on the numpy array, so
403    assignment on a trace will not work.
404
405    This feature exists to support code like::
406
407        >>> with ref as r:
408        ...     for x, y in zip(r, src):
409        ...         numpy.copyto(x, y + 10)
410
411    This class is not meant to be instantiated directly, but returned by
412    :attr:`Trace.ref`. This feature requires a context manager, to guarantee
413    modifications are written back to disk.
414    """
415    def __init__(self, *args):
416        super(RefTrace, self).__init__(*args)
417        self.refs = {}
418
419    def flush(self):
420        """
421        Commit cached writes to the file handle. Does not flush libc buffers or
422        notifies the kernel, so these changes may not immediately be visible to
423        other processes.
424
425        Updates the fingerprints whena writes happen, so successive ``flush()``
426        invocations are no-ops.
427
428        It is not necessary to call this method in user code.
429
430        Notes
431        -----
432        .. versionadded:: 1.6
433
434        This method is not intended as user-oriented functionality, but might
435        be useful in certain contexts to provide stronger guarantees.
436        """
437        garbage = []
438        for i, (x, signature) in self.refs.items():
439            if sys.getrefcount(x) == 3:
440                garbage.append(i)
441
442            if fingerprint(x) == signature: continue
443
444            self.filehandle.puttr(i, x)
445            signature = fingerprint(x)
446
447
448        # to avoid too many resource leaks, when this dict is the only one
449        # holding references to already-produced traces, clear them
450        for i in garbage:
451            del self.refs[i]
452
453    def fetch(self, i, buf = None):
454        if buf is None:
455            buf = np.zeros(self.shape, dtype = self.dtype)
456
457        try:
458            self.filehandle.gettr(buf, i, 1, 1, 0, self.shape, 1, self.shape)
459        except IOError:
460            if not self.readonly:
461                # if the file is opened read-only and this happens, there's no
462                # way to actually write and the error is an actual error
463                buf.fill(0)
464            else: raise
465
466        return buf
467
468    def __getitem__(self, i):
469        """trace[i]
470
471        Read the ith trace of the file, starting at 0. trace[i] returns a numpy
472        array, but unlike Trace, changes to this array *will* be reflected on
473        disk. The modifications must happen to the actual array (views are ok),
474        so in-place operations work, but assignments will not::
475
476            >>> with ref as ref:
477            ...     x = ref[10]
478            ...     x += 1.617 # in-place, works
479            ...     numpy.copyto(x, x + 10) # works
480            ...     x = x + 10 # re-assignment, won't change the original x
481
482        Works on newly created files that has yet to have any traces written,
483        which opens up a natural way of filling newly created files with data.
484        When getting unwritten traces, a trace filled with zeros is returned.
485
486        Parameters
487        ----------
488        i : int or slice
489
490        Returns
491        -------
492        trace : numpy.ndarray of dtype
493
494        Notes
495        -----
496        .. versionadded:: 1.6
497
498        Behaves like [] for lists.
499
500        Examples
501        --------
502        Merge two files with a binary operation. Relies on python3 iterator
503        zip:
504
505        >>> with ref as ref:
506        ...     for x, lhs, rhs in zip(ref, L, R):
507        ...         numpy.copyto(x, lhs + rhs)
508
509        Create a file and fill with data (the repeated trace index):
510
511        >>> f = create()
512        >>> with f.trace.ref as ref:
513        ...     for i, x in enumerate(ref):
514        ...         x.fill(i)
515        """
516        try:
517            i = self.wrapindex(i)
518
519            # we know this class is only used in context managers, so we know
520            # refs don't escape (with expectation of being written), so
521            # preserve all refs yielded with getitem(int)
522            #
523            # using ref[int] is problematic and pointless, we need to handle
524            # this scenario gracefully:
525            # with f.trace.ref as ref:
526            #     x = ref[10]
527            #     x[5] = 0
528            #     # invalidate other refs
529            #     y = ref[11]
530            #     y[6] = 1.6721
531            #
532            #     # if we don't preserve returned individual getitems, this
533            #     # write is lost
534            #     x[5] = 52
535            #
536            # for slices, we know that references terminate with every
537            # iteration anyway, multiple live references cannot happen
538
539            if i in self.refs:
540                return self.refs[i][0]
541
542            x = self.fetch(i)
543            self.refs[i] = (x, fingerprint(x))
544            return x
545
546        except TypeError:
547            try:
548                indices = i.indices(len(self))
549            except AttributeError:
550                msg = 'trace indices must be integers or slices, not {}'
551                raise TypeError(msg.format(type(i).__name__))
552
553            def gen():
554                x = np.zeros(self.shape, dtype = self.dtype)
555                try:
556                    for j in range(*indices):
557                        x = self.fetch(j, x)
558                        y = fingerprint(x)
559
560                        yield x
561
562                        if not fingerprint(x) == y:
563                            self.filehandle.puttr(j, x)
564
565                finally:
566                    # the last yielded item is available after the loop, so
567                    # preserve it and check if it's been updated on exit
568                    self.refs[j] = (x, y)
569
570            return gen()
571
572class Header(Sequence):
573    """Interact with segy in header mode
574
575    This mode gives access to reading and writing functionality of headers,
576    both in individual (trace) mode and line mode. The returned header
577    implements a dict_like object with a fixed set of keys, given by the SEG-Y
578    standard.
579
580    The Header implements the array interface, where every array element, the
581    data trace, is a numpy.ndarray. As all arrays, it can be random accessed,
582    iterated over, and read strided. Data is read lazily from disk, so
583    iteration does not consume much memory.
584
585    Notes
586    -----
587    .. versionadded:: 1.1
588
589    .. versionchanged:: 1.6
590        common list operations (Sequence)
591
592    """
593    def __init__(self, segy):
594        self.segy = segy
595        super(Header, self).__init__(segy.tracecount)
596
597    def __getitem__(self, i):
598        """header[i]
599
600        ith header of the file, starting at 0.
601
602        Parameters
603        ----------
604        i : int or slice
605
606        Returns
607        -------
608        field : Field
609            dict_like header
610
611        Notes
612        -----
613        .. versionadded:: 1.1
614
615        Behaves like [] for lists.
616
617        Examples
618        --------
619        Reading a header:
620
621        >>> header[10]
622
623        Read a field in the first 5 headers:
624
625        >>> [x[25] for x in header[:5]]
626        [1, 2, 3, 4]
627
628        Read a field in every other header:
629
630        >>> [x[37] for x in header[::2]]
631        [1, 3, 1, 3, 1, 3]
632        """
633        try:
634            i = self.wrapindex(i)
635            return Field.trace(traceno = i, segy = self.segy)
636
637        except TypeError:
638            try:
639                indices = i.indices(len(self))
640            except AttributeError:
641                msg = 'trace indices must be integers or slices, not {}'
642                raise TypeError(msg.format(type(i).__name__))
643
644            def gen():
645                # double-buffer the header. when iterating over a range, we
646                # want to make sure the visible change happens as late as
647                # possible, and that in the case of exception the last valid
648                # header was untouched. this allows for some fancy control
649                # flow, and more importantly helps debugging because you can
650                # fully inspect and interact with the last good value.
651                x = Field.trace(None, self.segy)
652                buf = bytearray(x.buf)
653                for j in range(*indices):
654                    # skip re-invoking __getitem__, just update the buffer
655                    # directly with fetch, and save some initialisation work
656                    buf = x.fetch(buf, j)
657                    x.buf[:] = buf
658                    x.traceno = j
659                    yield x
660
661            return gen()
662
663    def __setitem__(self, i, val):
664        """header[i] = val
665
666        Write the ith header of the file, starting at 0. Unlike data traces
667        (which return numpy.ndarrays), changes to returned headers being
668        iterated over *will* be reflected on disk.
669
670        Parameters
671        ----------
672        i   : int or slice
673        val : Field or array_like of dict_like
674
675        Notes
676        -----
677        .. versionadded:: 1.1
678
679        Behaves like [] for lists
680
681        Examples
682        --------
683        Copy a header to a different trace:
684
685        >>> header[28] = header[29]
686
687        Write multiple fields in a trace:
688
689        >>> header[10] = { 37: 5, TraceField.INLINE_3D: 2484 }
690
691        Set a fixed set of values in all headers:
692
693        >>> for x in header[:]:
694        ...     x[37] = 1
695        ...     x.update({ TraceField.offset: 1, 2484: 10 })
696
697        Write a field in multiple headers
698
699        >>> for x in header[:10]:
700        ...     x.update({ TraceField.offset : 2 })
701
702        Write a field in every other header:
703
704        >>> for x in header[::2]:
705        ...     x.update({ TraceField.offset : 2 })
706        """
707
708        x = self[i]
709
710        try:
711            x.update(val)
712        except AttributeError:
713            if isinstance(val, Field) or isinstance(val, dict):
714                val = itertools.repeat(val)
715
716            for h, v in zip(x, val):
717                h.update(v)
718
719    @property
720    def iline(self):
721        """
722        Headers, accessed by inline
723
724        Returns
725        -------
726        line : HeaderLine
727        """
728        return HeaderLine(self, self.segy.iline, 'inline')
729
730    @iline.setter
731    def iline(self, value):
732        """Write iterables to lines
733
734        Examples:
735            Supports writing to *all* crosslines via assignment, regardless of
736            data source and format. Will respect the sample size and structure
737            of the file being assigned to, so if the argument traces are longer
738            than that of the file being written to the surplus data will be
739            ignored. Uses same rules for writing as `f.iline[i] = x`.
740        """
741        for i, src in zip(self.segy.ilines, value):
742            self.iline[i] = src
743
744    @property
745    def xline(self):
746        """
747        Headers, accessed by crossline
748
749        Returns
750        -------
751        line : HeaderLine
752        """
753        return HeaderLine(self, self.segy.xline, 'crossline')
754
755    @xline.setter
756    def xline(self, value):
757        """Write iterables to lines
758
759        Examples:
760            Supports writing to *all* crosslines via assignment, regardless of
761            data source and format. Will respect the sample size and structure
762            of the file being assigned to, so if the argument traces are longer
763            than that of the file being written to the surplus data will be
764            ignored. Uses same rules for writing as `f.xline[i] = x`.
765        """
766
767        for i, src in zip(self.segy.xlines, value):
768            self.xline[i] = src
769
770class Attributes(Sequence):
771    """File-wide attribute (header word) reading
772
773    Lazily read a single header word for every trace in the file. The
774    Attributes implement the array interface, and will behave as expected when
775    indexed and sliced.
776
777    Notes
778    -----
779    .. versionadded:: 1.1
780    """
781
782    def __init__(self, field, filehandle, tracecount):
783        super(Attributes, self).__init__(tracecount)
784        self.field = field
785        self.filehandle = filehandle
786        self.tracecount = tracecount
787        self.dtype = np.intc
788
789    def __iter__(self):
790        # attributes requires a custom iter, because self[:] returns a numpy
791        # array, which in itself is iterable, but not an iterator
792        return iter(self[:])
793
794    def __getitem__(self, i):
795        """attributes[:]
796
797        Parameters
798        ----------
799        i : int or slice or array_like
800
801        Returns
802        -------
803        attributes : array_like of dtype
804
805        Examples
806        --------
807        Read all unique sweep frequency end:
808
809        >>> end = segyio.TraceField.SweepFrequencyEnd
810        >>> sfe = np.unique(f.attributes( end )[:])
811
812        Discover the first traces of each unique sweep frequency end:
813
814        >>> end = segyio.TraceField.SweepFrequencyEnd
815        >>> attrs = f.attributes(end)
816        >>> sfe, tracenos = np.unique(attrs[:], return_index = True)
817
818        Scatter plot group x/y-coordinates with SFEs (using matplotlib):
819
820        >>> end = segyio.TraceField.SweepFrequencyEnd
821        >>> attrs = f.attributes(end)
822        >>> _, tracenos = np.unique(attrs[:], return_index = True)
823        >>> gx = f.attributes(segyio.TraceField.GroupX)[tracenos]
824        >>> gy = f.attributes(segyio.TraceField.GroupY)[tracenos]
825        >>> scatter(gx, gy)
826        """
827        try:
828            xs = np.asarray(i, dtype = self.dtype)
829            xs = xs.astype(dtype = self.dtype, order = 'C', copy = False)
830            attrs = np.empty(len(xs), dtype = self.dtype)
831            return self.filehandle.field_foreach(attrs, xs, self.field)
832
833        except TypeError:
834            try:
835                i = slice(i, i + 1, 1)
836            except TypeError:
837                pass
838
839            traces = self.tracecount
840            filehandle = self.filehandle
841            field = self.field
842
843            start, stop, step = i.indices(traces)
844            indices = range(start, stop, step)
845            attrs = np.empty(len(indices), dtype = self.dtype)
846            return filehandle.field_forall(attrs, start, stop, step, field)
847
848class Text(Sequence):
849    """Interact with segy in text mode
850
851    This mode gives access to reading and writing functionality for textual
852    headers.
853
854    The primary data type is the python string. Reading textual headers is done
855    with [], and writing is done via assignment. No additional structure is
856    built around the textual header, so everything is treated as one long
857    string without line breaks.
858
859    Notes
860    -----
861    .. versionchanged:: 1.7
862        common list operations (Sequence)
863
864    """
865
866    def __init__(self, filehandle, textcount):
867        super(Text, self).__init__(textcount)
868        self.filehandle = filehandle
869
870    def __getitem__(self, i):
871        """text[i]
872
873        Read the text header at i. 0 is the mandatory, main
874
875        Examples
876        --------
877        Print the textual header:
878
879        >>> print(f.text[0])
880
881        Print the first extended textual header:
882
883        >>> print(f.text[1])
884
885        Print a textual header line-by-line:
886
887        >>> # using zip, from the zip documentation
888        >>> text = str(f.text[0])
889        >>> lines = map(''.join, zip( *[iter(text)] * 80))
890        >>> for line in lines:
891        ...     print(line)
892        ...
893        """
894        try:
895            i = self.wrapindex(i)
896            return self.filehandle.gettext(i)
897
898        except TypeError:
899            try:
900                indices = i.indices(len(self))
901            except AttributeError:
902                msg = 'trace indices must be integers or slices, not {}'
903                raise TypeError(msg.format(type(i).__name__))
904
905            def gen():
906                for j in range(*indices):
907                    yield self.filehandle.gettext(j)
908            return gen()
909
910    def __setitem__(self, i, val):
911        """text[i] = val
912
913        Write the ith text header of the file, starting at 0.
914        If val is instance of Text or iterable of Text,
915        value is set to be the first element of every Text
916
917        Parameters
918        ----------
919        i : int or slice
920        val : str, Text or iterable if i is slice
921
922        Examples
923        --------
924        Write a new textual header:
925
926        >>> f.text[0] = make_new_header()
927        >>> f.text[1:3] = ["new_header1", "new_header_2"]
928
929        Copy a textual header:
930
931        >>> f.text[1] = g.text[0]
932
933        Write a textual header based on Text:
934
935        >>> f.text[1] = g.text
936        >>> assert f.text[1] == g.text[0]
937
938        >>> f.text[1:3] = [g1.text, g2.text]
939        >>> assert f.text[1] == g1.text[0]
940        >>> assert f.text[2] == g2.text[0]
941
942        """
943        if isinstance(val, Text):
944            self[i] = val[0]
945            return
946
947        try:
948            i = self.wrapindex(i)
949            self.filehandle.puttext(i, val)
950
951        except TypeError:
952            try:
953                indices = i.indices(len(self))
954            except AttributeError:
955                msg = 'trace indices must be integers or slices, not {}'
956                raise TypeError(msg.format(type(i).__name__))
957
958            for i, text in zip(range(*indices), val):
959                if isinstance(text, Text):
960                    text = text[0]
961                self.filehandle.puttext(i, text)
962
963
964    def __str__(self):
965        msg = 'str(text) is deprecated, use explicit format instead'
966        warnings.warn(msg, DeprecationWarning)
967        return '\n'.join(map(''.join, zip(*[iter(str(self[0]))] * 80)))
968