# -------------------------------------------------------------------------
# Copyright (C) 2005-2013 Martin Strohalm
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# Complete text of GNU GPL can be found in the file LICENSE.TXT in the
# main directory of the program.
# -------------------------------------------------------------------------
# load libs
import wx
import numpy
import copy
# load modules
import mod_signal
import calculations
# MAIN PLOT OBJECTS
# -----------------
class container:
"""Container to hold plot objects."""
def __init__(self, objects):
self.objects = objects
# ----
def __additem__(self, obj):
self.objects.append(obj)
# ----
def __delitem__(self, index):
del self.objects[index]
# ----
def __setitem__(self, index, obj):
self.objects[index] = obj
# ----
def __getitem__(self, index):
return self.objects[index]
# ----
def __len__(self):
return len(self.objects)
# ----
def getBoundingBox(self, minX=None, maxX=None, absolute=False):
"""Get bounding box coverring all visible objects."""
# init values if no data in objects
rect = [numpy.array([0, 0]), numpy.array([1, 1])]
# get bouding boxes from objects
have = False
for obj in self.objects:
if obj.properties['visible']:
oRect = obj.getBoundingBox(minX, maxX, absolute)
if not oRect or not numpy.all(numpy.isfinite(oRect)):
continue
elif have and oRect:
rect[0] = numpy.minimum(rect[0], oRect[0])
rect[1] = numpy.maximum(rect[1], oRect[1])
elif oRect:
rect = oRect
have = True
# check scale
if rect[0][0] == rect[1][0]:
rect[0][0] -= 0.5
rect[1][0] += 0.5
if rect[0][1] == rect[1][1]:
rect[1][1] += 0.5
return rect
# ----
def getLegend(self):
"""Get a list of legend names."""
# get names
names = []
for obj in self.objects:
if obj.properties['visible']:
legend = obj.getLegend()
if legend and legend[0] != '':
names.append(obj.getLegend())
return names
# ----
def getPoint(self, obj, xPos, coord='screen'):
"""Get interpolated Y position for given X."""
return self.objects[obj].getPoint(xPos, coord)
# ----
def countGels(self):
"""Get number of visible gels."""
count = 0
for obj in self.objects:
if obj.properties['visible'] and obj.properties['showInGel']:
count += 1
return max(count,1)
# ----
def cropPoints(self, minX, maxX):
"""Crop points in all visible objects to selected X range."""
for obj in self.objects:
if obj.properties['visible']:
obj.cropPoints(minX, maxX)
# ----
def scaleAndShift(self, scale, shift):
"""Scale and shift all visible objects."""
for obj in self.objects:
if obj.properties['visible']:
obj.scaleAndShift(scale, shift)
# ----
def filterPoints(self, filterSize):
"""Filter points in all visible objects."""
for obj in self.objects:
if obj.properties['visible']:
obj.filterPoints(filterSize)
# ----
def draw(self, dc, printerScale, overlapLabels, reverse):
"""Draw all visible objects."""
# draw in reverse order
if reverse:
self.objects.reverse()
# draw objects
for obj in self.objects:
if obj.properties['visible']:
obj.draw(dc, printerScale)
# draw object's labels
self.drawLabels(dc, printerScale, overlapLabels)
# reverse back order
if reverse:
self.objects.reverse()
# ----
def drawLabels(self, dc, printerScale, overlapLabels):
"""Draw labels for all visible objects."""
# get labels from objects
annots = []
labels = []
for obj in self.objects:
if obj.properties['visible'] and isinstance(obj, annotations):
annots += obj.makeLabels(dc, printerScale)
elif obj.properties['visible']:
labels += obj.makeLabels(dc, printerScale)
# check labels
if not annots and not labels:
return
# sort labels
annots.sort()
annots.reverse()
labels.sort()
labels.reverse()
labels = annots + labels
# preset font by first label
font = labels[0][3]['labelFont']
colour = labels[0][3]['labelColour']
bgr = labels[0][3]['labelBgr']
bgrColour = labels[0][3]['labelBgrColour']
dc.SetFont(_scaleFont(font, printerScale['fonts']))
dc.SetTextForeground(colour)
dc.SetTextBackground(bgrColour)
if bgr:
dc.SetBackgroundMode(wx.SOLID)
# draw labels
occupied = []
for label in labels:
text = label[1]
textCoords = label[2]
properties = label[3]
# check limits
if abs(textCoords[1]) > 10000000:
continue
# check free space and draw label
if overlapLabels or self._checkFreeSpace(textCoords, occupied):
# check pen
if properties['labelFont'] != font:
font = properties['labelFont']
dc.SetFont(_scaleFont(font, printerScale['fonts']))
if properties['labelColour'] != colour:
colour = properties['labelColour']
dc.SetTextForeground(colour)
#if properties['labelBgrColour'] != bgrColour:
# bgrColour = properties['labelBgrColour']
# dc.SetTextBackground(bgrColour)
if properties['labelBgr'] != bgr:
bgr = properties['labelBgr']
if bgr:
dc.SetBackgroundMode(wx.SOLID)
else:
dc.SetBackgroundMode(wx.TRANSPARENT)
# set angle
angle = properties['labelAngle']
if angle == 90 and properties['flipped']:
angle = -90
# draw label
dc.DrawRotatedText(text, textCoords[0], textCoords[1], angle)
occupied.append(textCoords)
dc.SetBackgroundMode(wx.TRANSPARENT)
# ----
def drawGel(self, dc, gelCoords, gelHeight, printerScale):
"""Draw gel for all allowed objects."""
# draw objects
for obj in self.objects:
if obj.properties['visible'] and obj.properties['showInGel']:
obj.drawGel(dc, gelCoords, gelHeight, printerScale)
gelCoords[0] += gelHeight
# ----
def append(self, obj):
self.objects.append(obj)
# ----
def empty(self):
del self.objects[:]
# ----
def _checkFreeSpace(self, coords, occupied):
"""Check free space for label."""
curX1, curY1, curX2, curY2 = coords
# check occupied space
for occX1, occY1, occX2, occY2 in occupied:
if (curX1 < curX2) and ((occX1 <= curX1 <= occX2) or (occX1 <= curX2 <= occX2) or (curX1 <= occX1 and curX2 >= occX2)):
if (occY2 <= curY1 <= occY1) or (occY2 <= curY2 <= occY1) or (curY1 >= occY1 and curY2 <= occY2):
return False
elif (curX1 > curX2) and ((occX2 <= curX1 <= occX1) or (occX2 <= curX2 <= occX1) or (curX1 <= occX2 and curX2 >= occX1)):
if (occY1 <= curY1 <= occY2) or (occY1 <= curY2 <= occY2) or (curY1 >= occY2 and curY2 <= occY1):
return False
return True
# ----
class annotations:
"""Base class for annotations drawing."""
def __init__(self, points, **attr):
# set default params
self.properties = {
'visible': True,
'flipped': False,
'xOffset': 0,
'yOffset': 0,
'normalized': False,
'showInGel': False,
'exactFit': False,
'showPoints': True,
'showLabels': True,
'showXPos': True,
'pointColour': (0, 0, 255),
'pointSize': 3,
'labelAngle': 90,
'labelBgr': True,
'labelColour': (0, 0, 0),
'labelBgrColour': (255, 255, 255),
'labelFont': wx.Font(10, wx.SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, 0),
'labelMaxLength': 20,
'xPosDigits': 2,
}
self.currentScale = (1., 1.)
self.currentShift = (0., 0.)
self.normalization = 1.0
# get new attributes
for name, value in attr.items():
self.properties[name] = value
# convert points to array
self.points = numpy.array([[p[0], p[1]] for p in points])
self.pointsCropped = self.points
self.pointsScaled = self.pointsCropped
if len(self.points):
self.pointsBox = (numpy.minimum.reduce(self.points), numpy.maximum.reduce(self.points))
# get labels
self.labels = ['']*len(points)
for x, point in enumerate(points):
if len(point) > 2:
self.labels[x] = point[2]
self.labelsCropped = self.labels
# calculate normalization
self._normalization()
# ----
def setProperties(self, **attr):
"""Set object properties."""
for name, value in attr.items():
self.properties[name] = value
# ----
def setNormalization(self, value):
"""Force specified normalization to be used insted of calculated one."""
value = float(value)
if value == 0.0:
value = 1.0
self.normalization = value
# ----
def getBoundingBox(self, minX=None, maxX=None, absolute=False):
"""Get bounding box for whole data or X selection"""
# use relevant data
if minX != None and maxX != None:
self.cropPoints(minX, maxX)
data = self.pointsCropped
else:
data = self.points
# check data
if not len(data):
return False
# get range
if minX != None and maxX != None:
minXY = numpy.minimum.reduce(data)
maxXY = numpy.maximum.reduce(data)
else:
minXY = [self.pointsBox[0][0], self.pointsBox[0][1]]
maxXY = [self.pointsBox[1][0], self.pointsBox[1][1]]
# extend values slightly to fit data
if not absolute and not self.properties['exactFit']:
xExtend = (maxXY[0] - minXY[0]) * 0.05
yExtend = (maxXY[1] - minXY[1]) * 0.05
minXY[0] -= xExtend
maxXY[0] += xExtend
minXY[1] -= yExtend
maxXY[1] += yExtend
# extend values to fit labels
elif not absolute:
if self.properties['showLabels'] and self.properties['labelAngle']==0:
maxXY[1] += (maxXY[1] - minXY[1]) * 0.2
elif self.properties['showLabels'] and self.properties['labelAngle']==90:
maxXY[1] += (maxXY[1] - minXY[1]) * 0.4
else:
maxXY[1] += (maxXY[1] - minXY[1]) * 0.05
# apply normalization
if self.properties['normalized']:
minXY[1] = minXY[1] / self.normalization
maxXY[1] = maxXY[1] / self.normalization
# apply offset
minXY[0] += self.properties['xOffset']
minXY[1] += self.properties['yOffset']
maxXY[0] += self.properties['xOffset']
maxXY[1] += self.properties['yOffset']
# apply flipping
if self.properties['flipped']:
minY = -1 * maxXY[1]
maxY = -1 * minXY[1]
minXY[1] = minY
maxXY[1] = maxY
return [minXY, maxXY]
# ----
def getLegend(self):
"""Get legend."""
return None
# ----
def cropPoints(self, minX, maxX):
"""Crop points to selected X range."""
# apply offset
minX -= self.properties['xOffset']
maxX -= self.properties['xOffset']
# get indexes of points in selection
i1 = mod_signal.locate(self.points, minX)
i2 = mod_signal.locate(self.points, maxX)
# crop data
self.pointsCropped = self.points[i1:i2]
self.labelsCropped = self.labels[i1:i2]
# ----
def scaleAndShift(self, scale, shift):
"""Scale and shift points to screen coordinations."""
self.pointsScaled = self.pointsCropped
xScale = scale[0]
yScale = scale[1]
xShift = shift[0]
yShift = shift[1]
# apply flipping
if self.properties['flipped']:
yScale *= -1
# apply normalization
if self.properties['normalized']:
yScale /= self.normalization
# apply offset
xShift += self.properties['xOffset'] * xScale
yShift += self.properties['yOffset'] * yScale
# recalculate data
self.pointsScaled = _scaleAndShift(self.pointsCropped, xScale, yScale, xShift, yShift)
self.currentScale = scale
self.currentShift = shift
# ----
def filterPoints(self, filterSize):
"""Filter points for printing and exporting"""
pass
# ----
def draw(self, dc, printerScale):
"""Draw object."""
# check data
if not len(self.pointsScaled):
return
# draw points
if self.properties['showPoints']:
pencolour = [max(x-70,0) for x in self.properties['pointColour']]
pen = wx.Pen(pencolour, 1*printerScale['drawings'], wx.SOLID)
brush = wx.Brush(self.properties['pointColour'], wx.SOLID)
dc.SetPen(pen)
dc.SetBrush(brush)
for point in self.pointsScaled:
dc.DrawCircle(point[0], point[1], self.properties['pointSize']*printerScale['drawings'])
# ----
def drawGel(self, dc, gelCoords, gelHeight, printerScale):
"""Draw gel."""
pass
# ----
def makeLabels(self, dc, printerScale):
"""Get object labels."""
# check labels
if not self.properties['showLabels'] or not self.labelsCropped:
return []
# set font
dc.SetFont(_scaleFont(self.properties['labelFont'], printerScale['fonts']))
# prepare labels
labels = []
format = '%0.'+`self.properties['xPosDigits']`+'f - '
for x, label in enumerate(self.labelsCropped):
# check max length
if len(label) > self.properties['labelMaxLength']:
label = label[:self.properties['labelMaxLength']] + '...'
# add X position
if self.properties['showXPos']:
label = (format % self.pointsCropped[x][0]) + label
# get position
xPos = self.pointsScaled[x][0]
yPos = self.pointsScaled[x][1]
# get text position
textSize = dc.GetTextExtent(label)
if self.properties['labelAngle'] == 90:
if self.properties['flipped']:
textXPos = xPos + textSize[1]*0.5
textYPos = yPos + 5*printerScale['drawings']
textCoords = (textXPos, textYPos, textXPos-textSize[1], textYPos+textSize[0])
else:
textXPos = xPos - textSize[1]*0.5
textYPos = yPos - 5*printerScale['drawings']
textCoords = (textXPos, textYPos, textXPos+textSize[1], textYPos-textSize[0])
elif self.properties['labelAngle'] == 0:
if self.properties['flipped']:
textXPos = xPos - textSize[0]*0.5
textYPos = yPos + 4*printerScale['drawings']
textCoords = (textXPos, textYPos, textXPos+textSize[0], textYPos-textSize[1])
else:
textXPos = xPos - textSize[0]*0.5
textYPos = yPos - textSize[1] - 4*printerScale['drawings']
textCoords = (textXPos, textYPos, textXPos+textSize[0], textYPos-textSize[1])
# add label and sort by intensity
labels.append((self.pointsCropped[x][1], label, textCoords, self.properties))
return labels
# ----
def _normalization(self):
"""Calculate normalization constants."""
normalization = 1.0
# calc normalization
if len(self.points):
normalization = self.pointsBox[1][1] / 100.
# check value
if normalization == 0.0:
normalization = 1.0
# set value
self.normalization = normalization
# ----
class points:
"""Base class for polypoints and polylines drawing."""
def __init__(self, points, **attr):
# set default params
self.properties = {
'legend': '',
'visible': True,
'flipped': False,
'xOffset': 0,
'yOffset': 0,
'normalized': False,
'showInGel': False,
'exactFit': False,
'showPoints': True,
'pointColour': (0, 0, 255),
'pointSize': 3,
'fillPoints': True,
'showLines': True,
'lineColour': (0, 0, 255),
'lineWidth': 1,
'lineStyle': wx.SOLID,
'xOffsetDigits': 2,
'yOffsetDigits': 0,
}
self.currentScale = (1., 1.)
self.currentShift = (0., 0.)
self.normalization = 1.0
# get new attributes
for name, value in attr.items():
self.properties[name] = value
# convert points to array
self.points = numpy.array(points)
self.cropped = self.points
self.scaled = self.cropped
if len(self.points):
self.pointsBox = (numpy.minimum.reduce(self.points), numpy.maximum.reduce(self.points))
# calculate normalization
self._normalization()
# ----
def setProperties(self, **attr):
"""Set object properties."""
for name, value in attr.items():
self.properties[name] = value
# ----
def setNormalization(self, value):
"""Force specified normalization to be used insted of calculated one."""
value = float(value)
if value == 0.0:
value = 1.0
self.normalization = value
# ----
def getBoundingBox(self, minX=None, maxX=None, absolute=False):
"""Get bounding box for whole data or X selection"""
# use relevant data
if minX != None and maxX != None:
self.cropPoints(minX, maxX)
data = self.cropped
else:
data = self.points
# check data
if not len(data):
return False
# get range
if minX != None and maxX != None:
minXY = numpy.minimum.reduce(data)
maxXY = numpy.maximum.reduce(data)
else:
minXY = [self.pointsBox[0][0], self.pointsBox[0][1]]
maxXY = [self.pointsBox[1][0], self.pointsBox[1][1]]
# extend values slightly to fit data
if not absolute and not self.properties['exactFit']:
xExtend = (maxXY[0] - minXY[0]) * 0.05
yExtend = (maxXY[1] - minXY[1]) * 0.05
minXY[0] -= xExtend
maxXY[0] += xExtend
minXY[1] -= yExtend
maxXY[1] += yExtend
# apply normalization
if self.properties['normalized']:
minXY[1] = minXY[1] / self.normalization
maxXY[1] = maxXY[1] / self.normalization
# apply offset
minXY[0] += self.properties['xOffset']
minXY[1] += self.properties['yOffset']
maxXY[0] += self.properties['xOffset']
maxXY[1] += self.properties['yOffset']
# apply flipping
if self.properties['flipped']:
minY = -1 * maxXY[1]
maxY = -1 * minXY[1]
minXY[1] = minY
maxXY[1] = maxY
return [minXY, maxXY]
# ----
def getLegend(self):
"""Get legend."""
# get legend
legend = self.properties['legend']
offset = ''
# add current offset
if not self.properties['normalized']:
if self.properties['xOffset']:
format = ' X%0.'+`self.properties['xOffsetDigits']`+'f'
offset += format % self.properties['xOffset']
if self.properties['yOffset']:
format = ' Y%0.'+`self.properties['yOffsetDigits']`+'f'
offset += format % self.properties['yOffset']
if legend and offset:
legend += ' (Offset%s)' % offset
# set colour
if self.properties['showPoints']:
return (legend, self.properties['pointColour'])
else:
return (legend, self.properties['lineColour'])
# ----
def cropPoints(self, minX, maxX):
"""Crop points to selected X range."""
# apply offset
minX -= self.properties['xOffset']
maxX -= self.properties['xOffset']
# crop line
if self.properties['showLines']:
self.cropped = mod_signal.crop(self.points, minX, maxX)
# crop points
else:
i1 = mod_signal.locate(self.points, minX)
i2 = mod_signal.locate(self.points, maxX)
self.cropped = self.points[i1:i2]
# ----
def scaleAndShift(self, scale, shift):
"""Scale and shift points to screen coordinations."""
self.scaled = self.cropped
xScale = scale[0]
yScale = scale[1]
xShift = shift[0]
yShift = shift[1]
# apply flipping
if self.properties['flipped']:
yScale *= -1
# apply normalization
if self.properties['normalized']:
yScale /= self.normalization
# apply offset
xShift += self.properties['xOffset'] * xScale
yShift += self.properties['yOffset'] * yScale
# recalculate data
if len(self.cropped):
self.scaled = _scaleAndShift(self.cropped, xScale, yScale, xShift, yShift)
self.currentScale = scale
self.currentShift = shift
# ----
def filterPoints(self, filterSize):
"""Filter points for printing and exporting"""
# filter data
if len(self.scaled) and self.properties['showLines']:
self.scaled = _filterPoints(self.scaled, filterSize)
# ----
def draw(self, dc, printerScale):
"""Draw object."""
# check data
if not len(self.scaled):
return
# draw lines
if self.properties['showLines'] and len(self.scaled) > 1:
pen = wx.Pen(self.properties['lineColour'], self.properties['lineWidth']*printerScale['drawings'], self.properties['lineStyle'])
brush = wx.Brush(self.properties['lineColour'], wx.SOLID)
dc.SetPen(pen)
dc.SetBrush(brush)
dc.DrawLines(self.scaled)
# draw points
if self.properties['showPoints']:
if self.properties['fillPoints']:
pencolour = [max(x-70,0) for x in self.properties['pointColour']]
pen = wx.Pen(pencolour, self.properties['lineWidth']*printerScale['drawings'], wx.SOLID)
brush = wx.Brush(self.properties['pointColour'], wx.SOLID)
else:
pencolour = self.properties['pointColour']
pen = wx.Pen(pencolour, self.properties['lineWidth']*printerScale['drawings'], wx.SOLID)
brush = wx.TRANSPARENT_BRUSH
dc.SetPen(pen)
dc.SetBrush(brush)
for point in self.scaled:
dc.DrawCircle(point[0], point[1], self.properties['pointSize']*printerScale['drawings'])
# ----
def drawGel(self, dc, gelCoords, gelHeight, printerScale):
"""Draw gel."""
pass
# ----
def makeLabels(self, dc, printerScale):
"""Get object labels."""
return []
# ----
def _normalization(self):
"""Calculate normalization constants."""
normalization = 1.0
# calc normalization
if len(self.points):
normalization = self.pointsBox[1][1] / 100.
# check value
if normalization == 0.0:
normalization = 1.0
# set value
self.normalization = normalization
# ----
class spectrum:
"""Base class for spectrum drawing."""
def __init__(self, scan, **attr):
# set default params
self.properties = {
'legend': '',
'visible': True,
'flipped': False,
'xOffset': 0,
'yOffset': 0,
'normalized': False,
'showInGel': True,
'showSpectrum': True,
'showPoints': True,
'showLabels': True,
'showIsotopicLabels': True,
'showTicks': True,
'showGelLegend': True,
'spectrumColour': (0, 0, 255),
'spectrumWidth': 1,
'spectrumStyle': wx.SOLID,
'labelAngle': 90,
'labelDigits': 2,
'labelCharge': False,
'labelGroup': False,
'labelBgr': True,
'labelColour': (0, 0, 0),
'labelBgrColour': (255, 255, 255),
'labelFont': wx.Font(10, wx.SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, 0),
'tickColour': (200, 200, 200),
'isotopeColour': None,
'msmsColour': None,
'tickWidth': 1,
'tickStyle': wx.SOLID,
'xOffsetDigits': 2,
'yOffsetDigits': 0,
}
self.currentScale = (1., 1.)
self.currentShift = (0., 0.)
self.normalization = 1.0
# get new attributes
for name, value in attr.items():
self.properties[name] = value
# convert spectrum points to array
self.spectrumPoints = numpy.array(scan.profile)
self.spectrumCropped = self.spectrumPoints
self.spectrumScaled = self.spectrumCropped
if len(self.spectrumPoints):
self.spectrumBox = (numpy.minimum.reduce(self.spectrumPoints), numpy.maximum.reduce(self.spectrumPoints))
# convert peaklist points to array
self.peaklist = copy.deepcopy(scan.peaklist)
self.peaklistPoints = numpy.array([[peak.mz, peak.ai, peak.base] for peak in scan.peaklist])
self.peaklistCropped = self.peaklistPoints
self.peaklistScaled = self.peaklistCropped
self.peaklistCroppedPeaks = self.peaklist[:]
if len(self.peaklistPoints):
self.peaklistBox = (numpy.minimum.reduce(self.peaklistPoints), numpy.maximum.reduce(self.peaklistPoints))
# calculate normalization
self._normalization()
# ----
def setProperties(self, **attr):
"""Set object properties."""
for name, value in attr.items():
self.properties[name] = value
# ----
def setNormalization(self, value):
"""Force specified normalization to be used insted of calculated one."""
value = float(value)
if value == 0.0:
value = 1.0
self.normalization = value
# ----
def getBoundingBox(self, minX=None, maxX=None, absolute=False):
"""Get bounding box for whole data or X selection."""
spectrumBox = None
peaklistBox = None
# use relevant data
if minX != None and maxX != None:
self.cropPoints(minX, maxX)
spectrumData = self.spectrumCropped
peaklistData = self.peaklistCropped
else:
spectrumData = self.spectrumPoints
peaklistData = self.peaklistPoints
# calculate bounding box for spectrum
if len(spectrumData) and self.properties['showSpectrum']:
if minX != None and maxX != None:
minXY = numpy.minimum.reduce(spectrumData)
maxXY = numpy.maximum.reduce(spectrumData)
else:
minXY = [self.spectrumBox[0][0], self.spectrumBox[0][1]]
maxXY = [self.spectrumBox[1][0], self.spectrumBox[1][1]]
if not absolute:
maxXY[1] += (maxXY[1] - minXY[1]) * 0.05
spectrumBox = [minXY, maxXY]
# calculate bounding box for peaklist
if len(peaklistData) and (self.properties['showSpectrum'] or self.properties['showLabels'] or self.properties['showTicks']):
if minX != None and maxX != None:
minXY = numpy.minimum.reduce(peaklistData)
maxXY = numpy.maximum.reduce(peaklistData)
else:
minXY = [self.peaklistBox[0][0], self.peaklistBox[0][1], self.peaklistBox[0][2]]
maxXY = [self.peaklistBox[1][0], self.peaklistBox[1][1], self.peaklistBox[1][2]]
minXY = [minXY[0], min(minXY[1:])]
maxXY = [maxXY[0], max(maxXY[1:])]
# extend values to fit labels
if not absolute:
xExtend = (maxXY[0] - minXY[0]) * 0.02
minXY[0] -= xExtend
maxXY[0] += xExtend
if self.properties['showLabels'] and self.properties['labelAngle']==0:
maxXY[1] += (maxXY[1] - minXY[1]) * 0.2
elif self.properties['showLabels'] and self.properties['labelAngle']==90:
maxXY[1] += (maxXY[1] - minXY[1]) * 0.4
else:
maxXY[1] += (maxXY[1] - minXY[1]) * 0.05
peaklistBox = [minXY, maxXY]
# use both
if spectrumBox and peaklistBox:
minXY, maxXY = [numpy.minimum(spectrumBox[0], peaklistBox[0]), numpy.maximum(spectrumBox[1], peaklistBox[1])]
elif spectrumBox:
minXY, maxXY = spectrumBox
elif peaklistBox:
minXY, maxXY = peaklistBox
else:
return False
# apply normalization
if self.properties['normalized']:
minXY[1] = minXY[1] / self.normalization
maxXY[1] = maxXY[1] / self.normalization
# apply offset
if not self.properties['normalized']:
minXY[0] += self.properties['xOffset']
minXY[1] += self.properties['yOffset']
maxXY[0] += self.properties['xOffset']
maxXY[1] += self.properties['yOffset']
# apply flipping
if self.properties['flipped']:
minY = -1 * maxXY[1]
maxY = -1 * minXY[1]
minXY[1] = minY
maxXY[1] = maxY
return [minXY, maxXY]
# ----
def getLegend(self):
"""Get legend."""
# get legend
legend = self.properties['legend']
offset = ''
# add current offset
if not self.properties['normalized']:
if self.properties['xOffset']:
format = ' X%0.'+`self.properties['xOffsetDigits']`+'f'
offset += format % self.properties['xOffset']
if self.properties['yOffset']:
format = ' Y%0.'+`self.properties['yOffsetDigits']`+'f'
offset += format % self.properties['yOffset']
if legend and offset:
legend += ' (Offset%s)' % offset
# set colour
if len(self.spectrumPoints) and self.properties['showSpectrum']:
return (legend, self.properties['spectrumColour'])
else:
return (legend, self.properties['tickColour'])
# ----
def getPoint(self, xPos, coord='screen'):
"""Get interpolated Y position for given X."""
# get relevant data
if coord == 'user':
points = self.spectrumCropped
else:
points = self.spectrumScaled
# check data
if not len(points):
return None
# get xPos index
index = mod_signal.locate(points, xPos)
if index == 0 or index == len(points):
return None
# get yPos
yPos = mod_signal.interpolate(points[index-1], points[index], x=xPos)
return [xPos, yPos]
# ----
def cropPoints(self, minX, maxX):
"""Crop points to selected X range."""
# apply offset
minX -= self.properties['xOffset']
maxX -= self.properties['xOffset']
# crop spectrum data
if self.properties['showSpectrum']:
self.spectrumCropped = mod_signal.crop(self.spectrumPoints, minX, maxX)
# crop peaklist data
if self.properties['showSpectrum'] or self.properties['showLabels'] or self.properties['showTicks']:
i1 = mod_signal.locate(self.peaklistPoints, minX)
i2 = mod_signal.locate(self.peaklistPoints, maxX)
self.peaklistCropped = self.peaklistPoints[i1:i2]
self.peaklistCroppedPeaks = self.peaklist[i1:i2]
# ----
def scaleAndShift(self, scale, shift):
"""Scale and shift points to screen coordinations."""
self.spectrumScaled = self.spectrumCropped
self.peaklistScaled = self.peaklistCropped
xScale = scale[0]
yScale = scale[1]
xShift = shift[0]
yShift = shift[1]
# apply flipping
if self.properties['flipped']:
yScale *= -1
# apply normalization
if self.properties['normalized']:
yScale /= self.normalization
# apply offset
if not self.properties['normalized']:
xShift += self.properties['xOffset'] * xScale
yShift += self.properties['yOffset'] * yScale
# scale and shift spectrum data
if len(self.spectrumCropped):
self.spectrumScaled = _scaleAndShift(self.spectrumCropped, xScale, yScale, xShift, yShift)
# scale and shift peaklist data
if len(self.peaklistCropped):
self.peaklistScaled = numpy.array((xScale, yScale, yScale)) * self.peaklistCropped + numpy.array((xShift, yShift, yShift))
self.currentScale = scale
self.currentShift = shift
# ----
def filterPoints(self, filterSize):
"""Filter spectrum points invisible in current resolution."""
# filter spectrum data
if len(self.spectrumScaled) and self.properties['showSpectrum']:
self.spectrumScaled = _filterPoints(self.spectrumScaled, filterSize)
# ----
def draw(self, dc, printerScale):
"""Draw object."""
# draw line spectrum
if len(self.spectrumScaled) > 2 and self.properties['showSpectrum']:
self._drawSpectrum(dc, printerScale)
# draw peaklist ticks
if len(self.peaklistScaled) and (self.properties['showTicks'] or not len(self.spectrumPoints)):
self._drawPeaklist(dc, printerScale)
# ----
def drawGel(self, dc, gelCoords, gelHeight, printerScale):
"""Draw gel."""
# draw line spectrum gel
if len(self.spectrumScaled) > 2 and self.properties['showSpectrum']:
self._drawSpectrumGel(dc, gelCoords, gelHeight, printerScale)
# draw peaklist gel
elif len(self.peaklistScaled) and (self.properties['showSpectrum'] or self.properties['showLabels'] or self.properties['showTicks']):
self._drawPeaklistGel(dc, gelCoords, gelHeight, printerScale)
# draw gel legend
self._drawGelLegend(dc, gelCoords, gelHeight, printerScale)
# ----
def makeLabels(self, dc, printerScale):
"""Get object labels."""
# check labels
if not self.properties['showLabels'] or not len(self.peaklistScaled):
return []
# set font
dc.SetFont(_scaleFont(self.properties['labelFont'], printerScale['fonts']))
# prepare labels
labels = []
format = '%0.'+`self.properties['labelDigits']`+'f'
for x, peak in enumerate(self.peaklistScaled):
# skip isotopes
if not self.properties['showIsotopicLabels'] and self.peaklistCroppedPeaks[x].isotope != 0:
continue
# get position
xPos = peak[0]
yPos = peak[1]
# get label
label = format % self.peaklistCroppedPeaks[x].mz
# add charge to label
if self.properties['labelCharge'] and self.peaklistCroppedPeaks[x].charge != None:
label += ' (%d)' % self.peaklistCroppedPeaks[x].charge
# add group to label
if self.properties['labelGroup'] and self.peaklistCroppedPeaks[x].group:
label += ' [%s]' % self.peaklistCroppedPeaks[x].group
# get text position
textSize = dc.GetTextExtent(label)
if self.properties['labelAngle'] == 90:
if self.properties['flipped']:
textXPos = xPos + textSize[1]*0.5
textYPos = yPos + 5*printerScale['drawings']
textCoords = (textXPos, textYPos, textXPos-textSize[1], textYPos+textSize[0])
else:
textXPos = xPos - textSize[1]*0.5
textYPos = yPos - 5*printerScale['drawings']
textCoords = (textXPos, textYPos, textXPos+textSize[1], textYPos-textSize[0])
elif self.properties['labelAngle'] == 0:
if self.properties['flipped']:
textXPos = xPos - textSize[0]*0.5
textYPos = yPos + 4*printerScale['drawings']
textCoords = (textXPos, textYPos, textXPos+textSize[0], textYPos-textSize[1])
else:
textXPos = xPos - textSize[0]*0.5
textYPos = yPos - textSize[1] - 4*printerScale['drawings']
textCoords = (textXPos, textYPos, textXPos+textSize[0], textYPos-textSize[1])
# add label and sort by intensity
labels.append((self.peaklistCroppedPeaks[x].ai, label, textCoords, self.properties))
return labels
# ----
def _drawSpectrum(self, dc, printerScale):
"""Draw spectrum lines."""
# set pen and brush
pen = wx.Pen(self.properties['spectrumColour'], self.properties['spectrumWidth']*printerScale['drawings'], self.properties['spectrumStyle'])
brush = wx.Brush(self.properties['spectrumColour'], wx.SOLID)
dc.SetPen(pen)
dc.SetBrush(brush)
# draw lines
dc.DrawLines(self.spectrumScaled)
# set pen for points
pen = wx.Pen(self.properties['spectrumColour'], self.properties['spectrumWidth']*printerScale['drawings'], wx.SOLID)
dc.SetPen(pen)
# draw points if it makes sense
count = len(self.spectrumScaled)
if self.properties['showPoints'] \
and count > 2 \
and (self.spectrumScaled[2][0] - self.spectrumScaled[1][0]) > (6*printerScale['drawings']) \
and ((self.spectrumScaled[-1][0] - self.spectrumScaled[0][0]) / count) > (6*printerScale['drawings']):
for point in self.spectrumScaled:
try: dc.DrawCircle(point[0], point[1], 2*printerScale['drawings'])
except OverflowError: pass
# ----
def _drawSpectrumGel(self, dc, gelCoords, gelHeight, printerScale):
"""Draw spectrum gel."""
# get plot coordinates
gelY1, plotX1, plotY1, plotX2, plotY2, zeroY = gelCoords
# correct zero
if self.properties['flipped'] and (plotY1 < zeroY < plotY2):
plotY1 = zeroY
shift = zeroY
elif (plotY1 < zeroY < plotY2):
plotY2 = zeroY
shift = plotY1
else:
shift = plotY1
# set color step
step = (plotY2 - plotY1) / 255
if step == 0:
return False
# init brush
dc.SetPen(wx.TRANSPARENT_PEN)
brush = wx.Brush((255,255,255), wx.SOLID)
dc.SetBrush(brush)
# get first point and color
lastX = round(self.spectrumScaled[0][0])
lastY = 255
previousX = lastX
previousY = lastY
maxY = 255
# draw gel
for point in self.spectrumScaled:
# get point
xPos = round(point[0])
intens = round((point[1] - shift)/step)
intens = min(intens, 255)
intens = max(intens, 0)
# reverse color for flipped spectra
if self.properties['flipped']:
intens = 255 - intens
# filter points
if xPos-lastX >= printerScale['drawings']:
# set color if different
if lastY != maxY:
brush.SetColour((maxY, maxY, maxY))
dc.SetBrush(brush)
# draw point rectangle
try: dc.DrawRectangle(lastX, gelY1, xPos-lastX, gelHeight)
except: pass
# empty space
if maxY < previousY and xPos-previousX > printerScale['drawings']:
maxY = previousY
brush.SetColour((maxY, maxY, maxY))
dc.SetBrush(brush)
try: dc.DrawRectangle(lastX+printerScale['drawings'], gelY1, xPos-(lastX+printerScale['drawings']), gelHeight)
except: pass
# save last
lastX = xPos
lastY = maxY
maxY = intens
# remember previous
previousX = xPos
previousY = intens
# get highest intensity
maxY = min(intens, maxY)
# ----
def _drawPeaklist(self, dc, printerScale):
"""Draw peaklist ticks."""
# set pen params
peakPen = wx.Pen(self.properties['tickColour'], self.properties['tickWidth']*printerScale['drawings'], self.properties['tickStyle'])
isotopePen = wx.Pen(self.properties['tickColour'], self.properties['tickWidth']*printerScale['drawings'], self.properties['tickStyle'])
peakBrush = wx.Brush(self.properties['tickColour'], wx.SOLID)
msmsBrush = wx.Brush(self.properties['tickColour'], wx.SOLID)
if self.properties['isotopeColour']:
isotopePen.SetColour(self.properties['isotopeColour'])
if self.properties['msmsColour']:
msmsBrush.SetColour(self.properties['msmsColour'])
# draw isotopes
dc.SetPen(isotopePen)
for x, peak in enumerate(self.peaklistScaled):
if self.peaklistCroppedPeaks[x].isotope != 0:
try:
dc.DrawLine(peak[0], peak[2], peak[0], peak[1])
dc.DrawLine(peak[0]-3*printerScale['drawings'], peak[2], peak[0]+3*printerScale['drawings'], peak[2])
except OverflowError:
pass
# draw peaks
dc.SetPen(peakPen)
dc.SetBrush(peakBrush)
for x, peak in enumerate(self.peaklistScaled):
if self.peaklistCroppedPeaks[x].isotope == 0:
try:
dc.DrawLine(peak[0], peak[2], peak[0], peak[1])
dc.DrawLine(peak[0]-3*printerScale['drawings'], peak[2], peak[0]+3*printerScale['drawings'], peak[2])
dc.DrawRectangle(peak[0]-1*printerScale['drawings'], peak[1]-1*printerScale['drawings'], 3*printerScale['drawings'], 3*printerScale['drawings'])
except OverflowError:
pass
# draw fragmentation mark
dc.SetPen(wx.TRANSPARENT_PEN)
dc.SetBrush(msmsBrush)
for x, peak in enumerate(self.peaklistScaled):
if self.peaklistCroppedPeaks[x].childScanNumber != None:
try: dc.DrawCircle(peak[0], peak[1], 3*printerScale['drawings'])
except OverflowError: pass
# ----
def _drawPeaklistGel(self, dc, gelCoords, gelHeight, printerScale):
"""Draw peaklist gel."""
# get plot coordinates
gelY1, plotX1, plotY1, plotX2, plotY2, zeroY = gelCoords
# correct zero
if self.properties['flipped'] and (plotY1 < zeroY < plotY2):
plotY1 = zeroY
shift = zeroY
elif (plotY1 < zeroY < plotY2):
plotY2 = zeroY
shift = plotY1
else:
shift = plotY1
# set color step
step = (plotY2 - plotY1) / 255
if step == 0:
return False
# init brush
dc.SetPen(wx.TRANSPARENT_PEN)
brush = wx.Brush((255,255,255), wx.SOLID)
dc.SetBrush(brush)
# get first point and color
lastX = round(self.peaklistScaled[0][0])
lastY = 255
maxY = 255
# draw rectangles
last = len(self.peaklistScaled)-1
for x, point in enumerate(self.peaklistScaled):
# get intensity colour
xPos = round(point[0])
intens = round((point[1] - shift)/step)
intens = min(intens, 255)
intens = max(intens, 0)
# reverse color for flipped spectra
if self.properties['flipped']:
intens = 255 - intens
# draw first
if x==0:
brush.SetColour((intens, intens, intens))
dc.SetBrush(brush)
try: dc.DrawRectangle(xPos, gelY1, printerScale['drawings'], gelHeight)
except: pass
lastY = maxY
maxY = intens
# filter points
if xPos-lastX >= printerScale['drawings']:
# set color if different
if lastY != maxY:
brush.SetColour((maxY, maxY, maxY))
dc.SetBrush(brush)
# draw peak line
try: dc.DrawRectangle(lastX, gelY1, printerScale['drawings'], gelHeight)
except: pass
# save last
lastX = xPos
lastY = maxY
maxY = intens
# draw last
if x==last:
brush.SetColour((maxY, maxY, maxY))
dc.SetBrush(brush)
try: dc.DrawRectangle(xPos, gelY1, printerScale['drawings'], gelHeight)
except: pass
continue
# get highest intensity
maxY = min(intens, maxY)
# ----
def _drawGelLegend(self, dc, gelCoords, gelHeight, printerScale):
"""docstring for _drawGelLegend"""
# get plot coordinates
gelY1, plotX1, plotY1, plotX2, plotY2, zeroY = gelCoords
# get colour
if len(self.spectrumPoints) and self.properties['showSpectrum']:
colour = self.properties['spectrumColour']
else:
colour = self.properties['tickColour']
# set dc
pencolour = [max(i-70,0) for i in colour]
pen = wx.Pen(pencolour, 1*printerScale['drawings'], wx.SOLID)
dc.SetPen(pen)
dc.SetTextForeground(colour)
dc.SetBrush(wx.Brush(colour, wx.SOLID))
# draw legend circle
x = plotX2 - 9 * printerScale['drawings']
y = gelY1 + (gelHeight)/2
dc.DrawCircle(x, y, 3*printerScale['drawings'])
# draw legend text
if self.properties['showGelLegend'] and self.properties['legend']:
textSize = dc.GetTextExtent(self.properties['legend'])
x = plotX2 - textSize[0] - 17*printerScale['drawings']
y = gelY1 + gelHeight/2 - textSize[1]/2
dc.DrawText(self.properties['legend'], x, y)
# ----
def _normalization(self):
"""Calculate normalization constants."""
normalization = 0.0
# get range from points and peaklist
if len(self.spectrumPoints) and len(self.peaklistPoints):
spectrumMinXY, spectrumMaxXY = self.spectrumBox
peaklistMinXY, peaklistMaxXY = self.peaklistBox
normalization = max(spectrumMaxXY[1], peaklistMaxXY[1]) / 100.
# get range from points only
elif len(self.spectrumPoints):
minXY, maxXY = self.spectrumBox
normalization = maxXY[1] / 100.
# get range from peaklist only
elif len(self.peaklistPoints):
minXY, maxXY = self.peaklistBox
normalization = maxXY[1] / 100.
# check value
if normalization == 0.0:
normalization = 1.0
# set value
self.normalization = normalization
# ----
# HELPERS
# -------
def _scaleFont(font, scale):
"""Scale font."""
# check printerScale
if scale == 1:
return font
# get font
pointSize = font.GetPointSize()
family = font.GetFamily()
style = font.GetStyle()
weight = font.GetWeight()
underline = font.GetUnderlined()
faceName = font.GetFaceName()
encoding = font.GetDefaultEncoding()
# scale pointSize
pointSize = pointSize * scale * 1.3
# make print font
printerFont = wx.Font(pointSize, family, style, weight, underline, faceName, encoding)
return printerFont
# ----
def _scaleAndShift(points, scaleX, scaleY, shiftX, shiftY):
"""Scale and shift signal points used by plot. New array is returned.
points (numpy array) - data points
scaleX (float) - x-axis scale
scaleY (float) - y-axis scale
shiftX (float) - x-axis shift
shiftY (float) - y-axis shift
"""
# check signal type
if not isinstance(points, numpy.ndarray):
raise TypeError, "Signal points must be NumPy array!"
if points.dtype.name != 'float64':
raise TypeError, "Signal points must be float64!"
# check signal data
if len(points) == 0:
return numpy.array([])
# scale and shift signal
return calculations.signal_rescale(points, float(scaleX), float(scaleY), float(shiftX), float(shiftY))
# ----
def _filterPoints(points, resolution):
"""Filter signal points according to resolution. New array is returned.
points (numpy array) - data points
resolution (float) - resolution point size
"""
# check signal type
if not isinstance(points, numpy.ndarray):
raise TypeError, "Signal points must be NumPy array!"
if points.dtype.name != 'float64':
raise TypeError, "Signal points must be float64!"
# check signal data
if len(points) == 0:
return numpy.array([])
# filter signal
return calculations.signal_filter(points, float(resolution))
# ----