drltools.py 9.87 KB
Newer Older
Yan's avatar
Yan committed
1
2
3
4
5
6
7
#!/usr/bin/env python3

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.widgets import SpanSelector
from PyQt5 import QtCore
from PyQt5 import QtWidgets
8
from PyQt5 import QtGui
Yan's avatar
Yan committed
9
10
11
12
13
14
import sys
import matplotlib
import numpy as np
matplotlib.use("Qt5Agg")

class HBar(QtWidgets.QFrame):
15
    """horizontal bar class"""
Yan's avatar
Yan committed
16
17
18
19
20
21
    def __init__(self):
        super(HBar, self).__init__()
        self._main = QtWidgets.QWidget()
        self.setFrameShape(QtWidgets.QFrame.HLine)


22
23
24
25
def update_spectrum(start, end, spectrum, dataset):
    """spectrum updating procedure"""
    #Dont do anything to graph when the spectrum is not populated
    if (start == 0 and start == end) or type(dataset['masses']) == type(None):
26
27
        return

28
    #TODO: make the function more economical both in CPU load and in linecount
Yan's avatar
Yan committed
29
30
    mlen = len(dataset['masses'])
    start_mass = None 
31
32
33
34
35
36
37
    end_mass = None
    for i, mass in enumerate(dataset['masses'][0:]):
        if mass > start and start_mass is None:
            start_mass = i
        if mass > end and end_mass is None:
            end_mass = i
    if end_mass is None:
Yan's avatar
Yan committed
38
        end_mass = mlen
39
    if start_mass is None:
Yan's avatar
Yan committed
40
        start_mass = mlen-1
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
    if start_mass == end_mass:
        if start_mass == 0:
            end_mass = 1
        else:
            start_mass -= 1
            end_mass = start_mass + 2
    spectrum.clear()
    yshape = np.mean(dataset['matrix'], axis=0)
    dots_x = dataset['masses']
    dots_y = yshape
    full_x = dataset['masses'][start_mass:end_mass]
    full_y = yshape[start_mass:end_mass]
    spectrum.plot(dots_x, dots_y, ':', color='gray')
    spectrum.plot(full_x, full_y, 'r')
    xex = (dataset['masses'][end_mass-1]-dataset['masses'][start_mass])*0.25
    spectrum.set_xlim(dataset['masses'][start_mass]-xex,dataset['masses'][end_mass-1]+xex)
    ymax = max(yshape[start_mass:end_mass])
    spectrum.set_ylim(ymax*-0.1, ymax*1.2)
    spectrum.figure.canvas.draw()


def getTableItemList(ptable):
63
    ion_list = []
64
65
66
    for i in range(ptable.rowCount()):
        if type(ptable.cellWidget(i,0)) is not type(None):
            name = ptable.cellWidget(i,0).text()
67
68
69
        else:
            name = ""
        text = "{} ({}-{})".format(
70
71
            name, (ptable.cellWidget(i,1).text()),
            (ptable.cellWidget(i,2).text()))
72
        ion_list.append(text)
73
74
75
76
77
    return ion_list


def update_drl(start_mz, end_mz, spectrum, dataset, pname, dname,
        ptable, dtable):
Yan's avatar
Yan committed
78
79
80
81
82
83
    """cares that masses are in order and valid, then refresh tables"""
    for line in start_mz, end_mz:
        status = QtGui.QDoubleValidator().validate(line.text(),0)[0]
        if status in (0, 1):
            line.setText("0.00")

84
85
86
87
88
89
90
91
    start = float(start_mz.text())
    end = float(end_mz.text())

    if start > end:
        start, end = end, start
        start_mz.setText(str(start))
        end_mz.setText(str(end))

Yan's avatar
Yan committed
92
93
94
95
96
97
98
99
100
101
    dname.setText("{} ({:.2f}-{:.2f})".format(
        pname.text(),float(start_mz.text()),float(end_mz.text())))

    for i in (range(dtable.rowCount())):
        index = dtable.cellWidget(i,2).currentIndex()
        dtable.cellWidget(i,2).clear()
        dtable.cellWidget(i,2).addItem("")
        dtable.cellWidget(i,2).addItems(getTableItemList(ptable))
        dtable.cellWidget(i,2).setCurrentIndex(index)

102
103
104
105
    update_spectrum(start, end, spectrum, dataset)


def remove_rows(ptable, dtable):
Yan's avatar
Yan committed
106
    # TODO: maybe nicer selection in future, but this works for now
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
    rows = reversed(list(set(
        map(lambda index: index.row(), ptable.selectedIndexes()))))
    for row in rows:
        dtable.removeRow(row)
        ptable.removeRow(row)
        for i in range(dtable.rowCount()):
            index = dtable.cellWidget(i,2).currentIndex()
            dtable.cellWidget(i,2).clear()
            dtable.cellWidget(i,2).addItem("")
            dtable.cellWidget(i,2).addItems(getTableItemList(ptable))
            if index == row+1:
                dtable.cellWidget(i,2).setCurrentIndex(0)
            elif index > row+1:
                dtable.cellWidget(i,2).setCurrentIndex(index-1)


def setDoubleCell(table,row,column):
    """populate table cell with float-validated posititve text"""
    table.setCellWidget(row,column,QtWidgets.QLineEdit())
    validator = QtGui.QDoubleValidator()
    validator.setBottom(0)
    table.cellWidget(row,column).setValidator(validator)
    table.cellWidget(row,column).setFrame(False)
    table.cellWidget(row,column).setText("0")
    table.horizontalHeader().setSectionResizeMode(
            column, QtWidgets.QHeaderView.ResizeToContents)
    return table.cellWidget(row,column)


def add_line(parent_widget, mass_selector, spectrum, ds, parenttable, daughtertable):
    """add parent ion to the table"""
    ion_graph = Figure(figsize=(3, 1.5), dpi=100, facecolor="None")
    ionspect = ion_graph.add_subplot(111, facecolor=(1, 1, 1, 0.8),
            position=(-0.01,-0.01,1.02,1.02))
    graph_canvas = FigureCanvas(ion_graph)
    graph_canvas.setStyleSheet("background-color:transparent;")
    graph_canvas.setAutoFillBackground(False)

    newrow = parenttable.rowCount()
146

147
148
149
150
151
152
153
    parenttable.setRowCount(newrow + 1)
    pname = QtWidgets.QLineEdit()
    pname.setFrame(False)
    parenttable.setCellWidget(newrow, 0, pname)
    start_mz = setDoubleCell(parenttable, newrow, 1)
    end_mz = setDoubleCell(parenttable, newrow, 2)
    parenttable.setCellWidget(newrow, 3, graph_canvas)
154

155
    daughtertable.setRowCount(newrow + 1)
Yan's avatar
Yan committed
156
157

    #TODO: decide which box will be used in the final version
158
159
160
161
    daughtertable.setItem(newrow,0, QtWidgets.QTableWidgetItem())
    daughtertable.item(newrow,0).setFlags(daughtertable.item(newrow,0).flags()
            & ~QtCore.Qt.ItemIsSelectable)
    daughtertable.setCellWidget(newrow, 0, QtWidgets.QCheckBox())
162

163
164
    dname = QtWidgets.QTableWidgetItem()
    dname.setFlags(dname.flags() & ~QtCore.Qt.ItemIsEditable)
Yan's avatar
Yan committed
165
    dname.setTextAlignment(QtCore.Qt.AlignRight)
166
    daughtertable.setItem(newrow, 1, dname)
Yan's avatar
Yan committed
167
    daughtertable.setCellWidget(newrow, 1, QtWidgets.QCheckBox())
168

169
170
171
172
173
    corto = QtWidgets.QComboBox()
    corto.setFrame(False)
    corto.addItem("")
    corto.addItems(getTableItemList(parenttable))
    daughtertable.setCellWidget(newrow, 2, corto)
174

175
    corfact = setDoubleCell(daughtertable, newrow, 3)
176

177
178
    update_drl(start_mz, end_mz, ionspect, ds, pname, dname, 
            parenttable, daughtertable)
179

Yan's avatar
Yan committed
180
181
182
    pname.editingFinished.connect(lambda: update_drl(
        start_mz, end_mz, ionspect, ds, pname, dname, parenttable,
        daughtertable))
183
184
185
186
187
188
    start_mz.editingFinished.connect(lambda: update_drl(
        start_mz, end_mz, ionspect, ds, pname, dname, parenttable,
        daughtertable))
    end_mz.editingFinished.connect(lambda: update_drl(
        start_mz, end_mz, ionspect, ds, pname, dname, parenttable,
        daughtertable))
189
190


191
192
193
def iontable(labels, exp_col):
    """creates a table for ions"""
    table = QtWidgets.QTableWidget(columnCount=len(labels))
194
    table.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
195
196
                        QtWidgets.QSizePolicy.Expanding)
    table.setHorizontalHeaderLabels(labels)
197
198
199
200
201
202
203
    table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)

    for n in range(table.columnCount()):
        table.horizontalHeader().setSectionResizeMode(
            n, QtWidgets.QHeaderView.Stretch)
    table.setMinimumSize(600,0)

204
    return table
205
206


Yan's avatar
Yan committed
207
def dialog(parent, ds, filename, mass_selector, spectrum, cache):
208
    """constructs a dialog window"""
209

Yan's avatar
Yan committed
210
211
212
    def savecache(cache,pt,dt):
        cache[0], cache[1] = pt, dt

Yan's avatar
Yan committed
213
214
    dial_widget = QtWidgets.QDialog(
        parent, windowTitle='Delayed reactant labelling')
Yan's avatar
Yan committed
215

Yan's avatar
Yan committed
216
217
    dial_graph = Figure(figsize=(5, 2), dpi=100, facecolor="None")
    chromplot = dial_graph.add_subplot(111, facecolor=(1, 1, 1, 0.8))
Yan's avatar
Yan committed
218
219
220
221
    graph_canvas = FigureCanvas(dial_graph)
    graph_canvas.setStyleSheet("background-color:transparent;")
    graph_canvas.setAutoFillBackground(False)

222
223
224
225
    drl_load = QtWidgets.QPushButton("&Load")
    drl_save = QtWidgets.QPushButton("&Save")
    drl_export = QtWidgets.QPushButton("&Export")
    close = QtWidgets.QPushButton("&Close")
226
227
    close.clicked.connect(dial_widget.close)

Yan's avatar
Yan committed
228
229
230
    btn_add = QtWidgets.QPushButton("&Add")
    btn_rem = QtWidgets.QPushButton("Remove")

231
232
    # pt = parenttable
    # dt = daughtertable
Yan's avatar
Yan committed
233
234
235
    if cache == [None, None]:
        dt = iontable(["","Name", "corrected to", "factor"], 2)
        dt.horizontalHeader().setSectionResizeMode(
236
            0, QtWidgets.QHeaderView.ResizeToContents)
Yan's avatar
Yan committed
237
        dt.horizontalHeader().setSectionResizeMode(
238
239
            3, QtWidgets.QHeaderView.Fixed)

Yan's avatar
Yan committed
240
241
242
243
244
245
        pt = iontable(["Name", "start (m/z)", "end (m/z)", "profile"], 2)
        for i in range(5):
            add_line(dial_widget, mass_selector, spectrum, ds, pt, dt)
    else:
        pt = cache[0]
        dt = cache[1]
246
247
248
249

    btn_add.clicked.connect(lambda: add_line(
        dial_widget, mass_selector, spectrum, ds, pt, dt))
    btn_rem.clicked.connect(lambda: remove_rows(pt,dt))
Yan's avatar
Yan committed
250
251
    dial_widget.finished.connect(lambda: savecache(cache, pt, dt))

252

Yan's avatar
Yan committed
253
254
255
    main_layout = QtWidgets.QVBoxLayout(dial_widget)
    sub_layout = QtWidgets.QHBoxLayout()
    tablelayout = QtWidgets.QVBoxLayout()
Yan's avatar
Yan committed
256
    pt_butlayout = QtWidgets.QHBoxLayout()
Yan's avatar
Yan committed
257
    main_butlayout = QtWidgets.QHBoxLayout()
Yan's avatar
Yan committed
258

Yan's avatar
Yan committed
259
260
261
262
    pt_butlayout.addWidget(btn_add)
    pt_butlayout.addWidget(btn_rem)
    pt_butlayout.addStretch(0)

263
264
    main_layout.addLayout(sub_layout)
    main_layout.addWidget(HBar())
Yan's avatar
Yan committed
265
    main_layout.addLayout(main_butlayout)
266

Yan's avatar
Yan committed
267
268
269
270
    main_butlayout.addWidget(drl_load)
    main_butlayout.addWidget(drl_save)
    main_butlayout.addStretch(1)
    main_butlayout.addWidget(drl_export)
271
    main_butlayout.addWidget(close)
Yan's avatar
Yan committed
272

273
    sub_layout.addWidget(graph_canvas, stretch=1)
Yan's avatar
Yan committed
274
    sub_layout.addLayout(tablelayout)
275

Yan's avatar
Yan committed
276
277
278
279
280
    tablelayout.addWidget(QtWidgets.QLabel("Raw ions table:"))
    tablelayout.addWidget(pt)
    tablelayout.addLayout(pt_butlayout)
    tablelayout.addWidget(QtWidgets.QLabel("Corrected ions table:"))
    tablelayout.addWidget(dt)
281

Yan's avatar
Yan committed
282
    dial_widget.show()