File: _Diagram.py

package info (click to toggle)
python-biopython 1.64%2Bdfsg-5
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 44,416 kB
  • ctags: 12,472
  • sloc: python: 153,759; xml: 67,286; ansic: 9,003; sql: 1,488; makefile: 144; sh: 59
file content (471 lines) | stat: -rw-r--r-- 19,075 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
# Copyright 2003-2008 by Leighton Pritchard.  All rights reserved.
# This code is part of the Biopython distribution and governed by its
# license.  Please see the LICENSE file that should have been included
# as part of this package.
#
# Contact:       Leighton Pritchard, Scottish Crop Research Institute,
#                Invergowrie, Dundee, Scotland, DD2 5DA, UK
#                L.Pritchard@scri.ac.uk
################################################################################

""" Diagram module

    Provides:

    o Diagram -   Container for information concerning the tracks to be
                    drawn in a diagram, and the interface for defining the
                    diagram (possibly split these functions in later version?)

    For drawing capabilities, this module uses reportlab to draw and write
    the diagram:

    http://www.reportlab.com

    For dealing with biological information, the package expects BioPython
    objects - namely SeqRecord ojbects containing SeqFeature objects.
"""

#------------------------------------------------------------------------------
# IMPORTS

# ReportLab
try:
    from reportlab.graphics import renderPM
except ImportError:
    #This is an optional part of ReportLab, so may not be installed.
    renderPM=None

# GenomeDiagram
from ._LinearDrawer import LinearDrawer
from ._CircularDrawer import CircularDrawer
from ._Track import Track

from Bio.Graphics import _write

#------------------------------------------------------------------------------
# CLASSES

#------------------------------------------------------------
# Diagram


class Diagram(object):
    """ Diagram

        Provides:

        Attributes:

        o name         String, identifier for the diagram

        o tracks       List of Track objects comprising the diagram

        o format       String, format of the diagram (circular/linear)

        o pagesize     String, the pagesize of output

        o orientation  String, the page orientation (landscape/portrait)

        o x            Float, the proportion of the page to take up with even
                              X margins

        o y            Float, the proportion of the page to take up with even
                              Y margins

        o xl           Float, the proportion of the page to take up with the
                              left X margin

        o xr           Float, the proportion of the page to take up with the
                              right X margin

        o yt           Float, the proportion of the page to take up with the
                              top Y margin

        o yb           Float, the proportion of the page to take up with the
                              bottom Y margin

        o circle_core  Float, the proportion of the available radius to leave
                       empty at the center of a circular diagram (0 to 1).

        o start        Int, the base/aa position to start the diagram at

        o end          Int, the base/aa position to end the diagram at

        o tracklines   Boolean, True if track guidelines are to be drawn

        o fragments    Int, for a linear diagram, the number of equal divisions
                                into which the sequence is divided

        o fragment_size Float, the proportion of the space available to each
                                   fragment that should be used in drawing

        o track_size   Float, the proportion of the space available to each
                                  track that should be used in drawing

        o circular     Boolean, True if the genome/sequence to be drawn is, in
                                reality, circular.

        Methods:

        o __init__(self, name=None) Called on instantiation

        o draw(self, format='circular', ...) Instructs the package to draw
            the diagram

        o write(self, filename='test1.ps', output='PS') Writes the drawn
            diagram to a specified file, in a specified format.

        o add_track(self, track, track_level) Adds a Track object to the
            diagram, with instructions to place it at a particular level on
            the diagram

        o del_track(self, track_level) Removes the track that is to be drawn
            at a particular level on the diagram

        o get_tracks(self) Returns the list of Track objects to be drawn
            contained in the diagram

        o renumber_tracks(self, low=1) Renumbers all tracks consecutively,
            optionally from a passed lowest number

        o get_levels(self) Returns a list of levels currently occupied by
            Track objects

        o get_drawn_levels(self) Returns a list of levels currently occupied
            by Track objects that will be shown in the drawn diagram (i.e.
            are not hidden)

        o range(self) Returns the lowest- and highest-numbered positions
            contained within features in all tracks on the diagram as a tuple.

        o __getitem__(self, key) Returns the track contained at the level of
            the passed key

        o __str__(self) Returns a formatted string describing the diagram

    """
    def __init__(self, name=None, format='circular', pagesize='A3',
         orientation='landscape', x=0.05, y=0.05, xl=None,
         xr=None, yt=None, yb=None, start=None, end=None,
         tracklines=False, fragments=10, fragment_size=0.9,
         track_size=0.75, circular=True, circle_core=0.0):
        """ __init__(self, name=None)

            o name  String describing the diagram

            o format    String: 'circular' or 'linear', depending on the sort of
                        diagram required

            o pagesize  String describing the ISO size of the image, or a tuple
                        of pixels

            o orientation   String describing the required orientation of the
                            final drawing ('landscape' or 'portrait')

            o x         Float (0->1) describing the relative size of the X
                        margins to the page

            o y         Float (0->1) describing the relative size of the Y
                        margins to the page

            o xl        Float (0->1) describing the relative size of the left X
                        margin to the page (overrides x)

            o xl        Float (0->1) describing the relative size of the left X
                        margin to the page (overrides x)

            o xr        Float (0->1) describing the relative size of the right X
                        margin to the page (overrides x)

            o yt        Float (0->1) describing the relative size of the top Y
                        margin to the page (overrides y)

            o yb        Float (0->1) describing the relative size of the lower Y
                        margin to the page (overrides y)

            o start     Int, the position to begin drawing the diagram at


            o end       Int, the position to stop drawing the diagram at

            o tracklines    Boolean flag to show (or not) lines delineating
                        tracks on the diagram

            o fragments Int, for linear diagrams, the number of sections into
                        which to break the sequence being drawn

            o fragment_size     Float (0->1), for linear diagrams, describing
                                the proportion of space in a fragment to take
                                up with tracks

            o track_size        Float (0->1) describing the proportion of space
                                in a track to take up with sigils

            o circular  Boolean flag to indicate whether the sequence being
                        drawn is circular

        """
        self.tracks = {}   # Holds all Track objects, keyed by level
        self.name = name    # Description of the diagram
        # Diagram page setup attributes
        self.format = format
        self.pagesize = pagesize
        self.orientation = orientation
        self.x = x
        self.y = y
        self.xl = xl
        self.xr = xr
        self.yt = yt
        self.yb = yb
        self.start = start
        self.end = end
        self.tracklines = tracklines
        self.fragments = fragments
        self.fragment_size = fragment_size
        self.track_size = track_size
        self.circular = circular
        self.circle_core = circle_core
        self.cross_track_links = []

    def set_all_tracks(self, attr, value):
        """ set_all_tracks(self, attr, value)

            o attr      An attribute of the Track class

            o value     The value to set that attribute

            Set the passed attribute of all tracks in the set to the
            passed value
        """
        for track in self.tracks.values():
            if hasattr(track, attr):          # If the feature has the attribute
                if getattr(track, attr) != value:
                    setattr(track, attr, value)   # set it to the passed value

    def draw(self, format=None, pagesize=None, orientation=None,
             x=None, y=None, xl=None, xr=None, yt=None, yb=None,
             start=None, end=None, tracklines=None, fragments=None,
             fragment_size=None, track_size=None, circular=None,
             circle_core=None, cross_track_links=None):
        """Draw the diagram, with passed parameters overriding existing attributes.
        """
        # Pass the parameters to the drawer objects that will build the
        # diagrams.  At the moment, we detect overrides with an or in the
        # Instantiation arguments, but I suspect there's a neater way to do
        # this.
        if format == 'linear':
            drawer = LinearDrawer(self, pagesize or self.pagesize,
                                  orientation or self.orientation,
                                  x or self.x, y or self.y, xl or self.xl,
                                  xr or self.xr, yt or self.yt,
                                  yb or self.yb, start or self.start,
                                  end or self.end,
                                  tracklines or self.tracklines,
                                  fragments or self.fragments,
                                  fragment_size or self.fragment_size,
                                  track_size or self.track_size,
                                  cross_track_links or self.cross_track_links)
        else:
            drawer = CircularDrawer(self, pagesize or self.pagesize,
                                    orientation or self.orientation,
                                    x or self.x, y or self.y, xl or self.xl,
                                    xr or self.xr, yt or self.yt,
                                    yb or self.yb, start or self.start,
                                    end or self.end,
                                    tracklines or self.tracklines,
                                    track_size or self.track_size,
                                    circular or self.circular,
                                    circle_core or self.circle_core,
                                    cross_track_links or self.cross_track_links)
        drawer.draw()   # Tell the drawer to complete the drawing
        self.drawing = drawer.drawing  # Get the completed drawing

    def write(self, filename='test1.ps', output='PS', dpi=72):
        """ write(self, filename='test1.ps', output='PS', dpi=72)

            o filename      String indicating the name of the output file,
                            or a handle to write to.

            o output        String indicating output format, one of PS, PDF,
                            SVG, or provided the ReportLab renderPM module is
                            installed, one of the bitmap formats JPG, BMP,
                            GIF, PNG, TIFF or TIFF.  The format can be given
                            in upper or lower case.

            o dpi           Resolution (dots per inch) for bitmap formats.

            Write the completed drawing out to a file in a prescribed format

            No return value.
        """
        return _write(self.drawing, filename, output, dpi=dpi)

    def write_to_string(self, output='PS', dpi=72):
        """Returns a byte string containing the diagram in the requested format.

            o output        String indicating output format, one of PS, PDF,
                            SVG, JPG, BMP, GIF, PNG, TIFF or TIFF (as
                            specified for the write method).

            o dpi           Resolution (dots per inch) for bitmap formats.

            Return the completed drawing as a bytes string in a prescribed format
        """
        #The ReportLab drawToString method, which this function used to call,
        #just used a cStringIO or StringIO handle with the drawToFile method.
        #In order to put all our complicated file format specific code in one
        #place we just used a StringIO handle here, later a BytesIO handle
        #for Python 3 compatibility.
        #
        #TODO - Rename this method to include keyword bytes?
        from io import BytesIO
        handle = BytesIO()
        self.write(handle, output, dpi)
        return handle.getvalue()

    def add_track(self, track, track_level):
        """ add_track(self, track, track_level)

            o track         Track object to draw

            o track_level   Int, the level at which the track will be drawn
                            (above an arbitrary baseline)

            Add a pre-existing Track to the diagram at a given level
        """
        if track is None:
            raise ValueError("Must specify track")
        if track_level not in self.tracks:     # No track at that level
            self.tracks[track_level] = track   # so just add it
        else:       # Already a track there, so shunt all higher tracks up one
            occupied_levels = sorted(self.get_levels()) # Get list of occupied levels...
            occupied_levels.reverse()           # ...reverse it (highest first)
            for val in occupied_levels:
                # If track value >= that to be added
                if val >= track.track_level:
                    self.tracks[val+1] = self.tracks[val] # ...increment by 1
            self.tracks[track_level] = track   # And put the new track in
        self.tracks[track_level].track_level = track_level

    def new_track(self, track_level, **args):
        """ new_track(self, track_level) -> Track

            o track_level   Int, the level at which the track will be drawn
                            (above an arbitrary baseline)

            Add a new Track to the diagram at a given level and returns it for
            further user manipulation.
        """
        newtrack = Track()
        for key in args:
            setattr(newtrack, key, args[key])
        if track_level not in self.tracks:        # No track at that level
            self.tracks[track_level] = newtrack   # so just add it
        else:       # Already a track there, so shunt all higher tracks up one
            occupied_levels = sorted(self.get_levels()) # Get list of occupied levels...
            occupied_levels.reverse()           # ...reverse (highest first)...
            for val in occupied_levels:
                if val >= track_level:        # Track value >= that to be added
                    self.tracks[val+1] = self.tracks[val] # ..increment by 1
            self.tracks[track_level] = newtrack   # And put the new track in
        self.tracks[track_level].track_level = track_level
        return newtrack

    def del_track(self, track_level):
        """ del_track(self, track_level)

            o track_level   Int, the level of the track on the diagram to delete

            Remove the track at the passed level from the diagram
        """
        del self.tracks[track_level]

    def get_tracks(self):
        """ get_tracks(self) -> list

            Returns a list of the tracks contained in the diagram
        """
        return list(self.tracks.values())

    def move_track(self, from_level, to_level):
        """ move_track(self, from_level, to_level)

            o from_level    Int, the level at which the track to be moved is
                            found

            o to_level      Int, the level to move the track to

            Moves a track from one level on the diagram to another
        """
        aux = self.tracks[from_level]
        del self.tracks[from_level]
        self.add_track(aux, to_level)

    def renumber_tracks(self, low=1, step=1):
        """ renumber_tracks(self, low=1, step=1)

            o low       Int, the track number to start from

            o step      Int, the track interval for separation of tracks

            Reassigns all the tracks to run consecutively from the lowest
            value (low)
        """
        track = low                 # Start numbering from here
        levels = self.get_levels()

        conversion = {}             # Holds new set of levels
        for level in levels:        # Starting at low...
            conversion[track] = self.tracks[level]  # Add old tracks to new set
            conversion[track].track_level = track
            track += step                           # step interval
        self.tracks = conversion   # Replace old set of levels with new set

    def get_levels(self):
        """ get_levels(self) -> [int, int, ...]

            Return a sorted list of levels occupied by tracks in the diagram
        """
        return sorted(self.tracks)

    def get_drawn_levels(self):
        """ get_drawn_levels(self) -> [int, int, ...]

            Return a sorted list of levels occupied by tracks that are not
            explicitly hidden
        """
        return sorted(key for key in self.tracks if not self.tracks[key].hide)

    def range(self):
        """ range(self) -> (int, int)

            Returns the lowest and highest base (or mark) numbers containd in
            track features as a tuple
        """
        lows, highs = [], []
        for track in self.tracks.values(): # Get ranges for each track
            low, high = track.range()
            lows.append(low)
            highs.append(high)
        return (min(lows), max(highs))      # Return extremes from all tracks

    def __getitem__(self, key):
        """ __getitem__(self, key) -> Track

            o key       The id of a track in the diagram

            Return the Track object with the passed id
        """
        return self.tracks[key]

    def __str__(self):
        """ __str__(self) -> ""

            Returns a formatted string with information about the diagram
        """
        outstr = ["\n<%s: %s>" % (self.__class__, self.name)]
        outstr.append("%d tracks" % len(self.tracks))
        for level in self.get_levels():
            outstr.append("Track %d: %s\n" % (level, self.tracks[level]))
        outstr = '\n'.join(outstr)
        return outstr