drltools.py 13.3 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
import prasopes.graphtools as gt
Yan's avatar
Yan committed
9
10
11
12
13
import sys
import matplotlib
import numpy as np
matplotlib.use("Qt5Agg")

Yan's avatar
Yan committed
14

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


23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def add_parent(parent_widget, mass_selector, spectrum, ds, parenttable):
    """add parent ion to the table"""
    ap_dial = QtWidgets.QDialog(parent_widget, windowTitle='Add Ion')
    parent_widget.finished.connect(ap_dial.close)

    ion = dict(x=[0], y=[0], line=None, name="", xlabel="m/z",
                ylabel="ion count", annotation=[])

    ion_graph = Figure(figsize=(3, 1.5), dpi=100, facecolor="None")
    ionspect = ion_graph.add_subplot(111, facecolor=(1, 1, 1, 0.8))
    graph_canvas = FigureCanvas(ion_graph)
    ion_graph.tight_layout()
    graph_canvas.setStyleSheet("background-color:transparent;")
    graph_canvas.setAutoFillBackground(False)
    graph_canvas.setFocusPolicy(QtCore.Qt.ClickFocus)
    gt.pop_plot(ionspect, ion)

40
41
    def add_ion(start_mz, end_mz, figure, canvas, table, dialog):
        """extracts provided widgets and put them into table"""
42
43
44
45
46
47
48
        newrow = table.rowCount()
        table.setRowCount(newrow+1)
        table.setItem(newrow,0,QtWidgets.QTableWidgetItem())
        table.horizontalHeader().setSectionResizeMode(
            1, QtWidgets.QHeaderView.ResizeToContents)
        table.horizontalHeader().setSectionResizeMode(
            2, QtWidgets.QHeaderView.ResizeToContents)
49
        figure.subplots_adjust(-0.01,0,1,1,0,0)
50
51
        table.setCellWidget(newrow,1,start_mz)
        table.setCellWidget(newrow,2,end_mz)
52
        table.setCellWidget(newrow,3,canvas)
53
54
        dialog.close()

55
56
57
58
59
60
61
62
63
    def end_bindings(bindings, axis):
        fig = axis.get_figure()
        for i in bindings:
            axis.canvas.mpl_disconnect(i)
        return

    def update_parent(start_mz, end_mz, spectrum, ion_set, dataset):
        start = start_mz.value()
        end = end_mz.value()
64

65
66
67
68
69
70
        if start > end:
            start = end_mz.value()
            end = start_mz.value()
            start_mz.setValue(start)
            end_mz.setValue(end)

71
72
73
74
        #Dont do anything to graph when the spectrum is not populated
        if (start == 0 and start == end) or type(dataset['masses']) == type(None):
            return

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
        #TODO: make the function more economical both in CPU load and in linecount
        start_mass = None
        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:
            end_mass = len(dataset['masses'])
        if start_mass is None:
            start_mass = len(dataset['masses'])-1
        if start_mass == end_mass:
            if start_mass == 0:
                end_mass = 1
            else:
                start_mass -= 1
                end_mass = start_mass + 2
        spectrum.clear()
        gt.pop_plot(spectrum, ion_set)
        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.1)
        spectrum.figure.canvas.draw()

108
109
110
111
    def update_fields(x_min, x_max, startw, endw, spectrum, ion_set, dataset):
        endw.setValue(x_max)
        startw.setValue(x_min)
        update_parent(startw, endw, spectrum, ion_set, dataset)
112
113
114
115
116
117

    def activate_altselector(event, mass_selector, picker):
        if event.key == 'shift':
            mass_selector.active = False
            picker.active = True

118
119
120
    def deactivate_altselector(mass_selector, picker):
        mass_selector.active = True
        picker.active = False
121

122
123
124
    def deactivate_keyrelease(event, mass_selector, picker):
        if event.key == 'shift':
            deactivate_altselector(mass_selector, picker)
125
126
127
128
129
130

    ax = spectrum.get_figure()
    bindings = []
    bindings.append(ax.canvas.mpl_connect('key_press_event', lambda
        event: activate_altselector(event, mass_selector, picker)))
    bindings.append(ax.canvas.mpl_connect('key_release_event', lambda
131
        event: deactivate_keyrelease(event, mass_selector, picker)))
132
133
134
    # TODO: somehow manage if user enters with shift pressed, but
    # figure_enter_event cannot handle this :( and with QT it is PIA
    bindings.append(ax.canvas.mpl_connect('figure_leave_event', lambda
135
        event: deactivate_altselector(mass_selector, picker)))
136
137
138
139

    ap_dial.finished.connect(lambda: end_bindings(bindings, ax))

    start_mz = QtWidgets.QDoubleSpinBox(maximum=999999)
140
    start_mz.editingFinished.connect(lambda: update_parent(
141
142
        start_mz, end_mz, ionspect, ion, ds))
    end_mz = QtWidgets.QDoubleSpinBox(maximum=999999)
143
    end_mz.editingFinished.connect(lambda: update_parent(
144
145
146
        start_mz, end_mz, ionspect, ion, ds))

    submit = QtWidgets.QPushButton("&Submit")
147
    submit.clicked.connect(lambda: add_ion(start_mz, end_mz, ion_graph, graph_canvas, parenttable, ap_dial))
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
    cancel = QtWidgets.QPushButton("Cancel")
    cancel.clicked.connect(ap_dial.close)

    main_layout = QtWidgets.QVBoxLayout(ap_dial)
    data_layout = QtWidgets.QHBoxLayout()
    butt_layout = QtWidgets.QHBoxLayout()

    butt_layout.addStretch(1)
    butt_layout.addWidget(submit)
    butt_layout.addWidget(cancel)

    data_layout.addWidget(QtWidgets.QLabel("start:"))
    data_layout.addWidget(start_mz)
    data_layout.addWidget(QtWidgets.QLabel("m/z"))
    data_layout.addSpacing(20)
    data_layout.addWidget(QtWidgets.QLabel("end:"))
    data_layout.addWidget(end_mz)
    data_layout.addWidget(QtWidgets.QLabel("m/z"))
    data_layout.addStretch(0)


    main_layout.addWidget(QtWidgets.QLabel("Press shift in "
        "the chromatogram window and \n"
        "select the ion range by the right mouse button\n"
        "or fill the columns manually"))
    main_layout.addWidget(graph_canvas, 1)
    main_layout.addLayout(data_layout)
    main_layout.addLayout(butt_layout)

    picker = SpanSelector(spectrum, lambda x_min, x_max:
178
179
        update_fields(x_min, x_max, start_mz, end_mz,
            ionspect, ion, ds),
180
181
182
183
184
185
186
        'horizontal', useblit=True, rectprops=dict(
        alpha=0.15, facecolor='orange'), button=3)
    picker.active = False

    ap_dial.show()


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
def add_daughter(parent_widget, p_table, d_table):
    ad_dial = QtWidgets.QDialog(parent_widget, windowTitle='Add line')
    parent_widget.finished.connect(ad_dial.close)

    if p_table.rowCount() is 0:
        QtWidgets.QMessageBox.warning(parent_widget,
                'Add line', "The Raw ions table is empty, "
                "cancelling request")
        return

    def add_ion(parent_widget, parent, correction, cf, ptable, dtable):
        pn = parent.currentIndex()
        corr = correction.currentIndex()-1
        cf = cf.value()
        print(pn,corr,cf)
        print(ptable.cellWidget(pn,1).value())
        start_mz = QtWidgets.QDoubleSpinBox(maximum=999999)
        start_mz.setValue(ptable.cellWidget(pn,1).value())
        end_mz = QtWidgets.QDoubleSpinBox(maximum=999999)
        end_mz.setValue(ptable.cellWidget(pn,2).value())

        newrow = dtable.rowCount()
        dtable.setRowCount(newrow+1)
        dtable.setItem(newrow,0,QtWidgets.QTableWidgetItem())
        dtable.item(newrow,0).setText(ptable.item(pn,0).text())
        dtable.horizontalHeader().setSectionResizeMode(
            1, QtWidgets.QHeaderView.ResizeToContents)
        dtable.horizontalHeader().setSectionResizeMode(
            2, QtWidgets.QHeaderView.ResizeToContents)
        dtable.setCellWidget(newrow,1,start_mz)
        dtable.setCellWidget(newrow,2,end_mz)

    ion_list = []
    for i in range(p_table.rowCount()):
        if type(p_table.item(i,0)) is not type(None):
            name = p_table.item(i,0).text()
        else:
            name = ""
        text = "{} ({}-{})".format(
            name, p_table.cellWidget(i,1).value(),
            p_table.cellWidget(i,2).value())
        ion_list.append(text)

    p_sel = QtWidgets.QComboBox()
    p_sel.addItems(ion_list)
    c_sel = QtWidgets.QComboBox()
    c_sel.addItem(None)
    c_sel.addItems(ion_list)
    corr_factor = QtWidgets.QDoubleSpinBox(maximum=1337)

    submit = QtWidgets.QPushButton("&Submit")
    cancel = QtWidgets.QPushButton("Cancel")
    cancel.clicked.connect(ad_dial.close)

    main_layout = QtWidgets.QVBoxLayout(ad_dial)
    data_layout = QtWidgets.QHBoxLayout()
    butt_layout = QtWidgets.QHBoxLayout()

    butt_layout.addStretch(1)
    butt_layout.addWidget(submit)
    submit.clicked.connect(lambda: add_ion(
        ad_dial, p_sel, c_sel, corr_factor, p_table, d_table))
    butt_layout.addWidget(cancel)

    data_layout.addWidget(QtWidgets.QLabel("parent ion:"))
    data_layout.addWidget(p_sel)
    data_layout.addSpacing(20)
    data_layout.addWidget(QtWidgets.QLabel("Correct to:"))
    data_layout.addWidget(c_sel)
    data_layout.addWidget(QtWidgets.QLabel("mutliplied by:"))
    data_layout.addWidget(corr_factor)
    data_layout.addWidget(QtWidgets.QLabel("%"))

    main_layout.addLayout(data_layout)
    main_layout.addLayout(butt_layout)

    ad_dial.show()


266
267
def iontable(labels, exp_col):
    """creates a table for ions"""
268
269
270
271
272
273
274
    def remove_rows(table):
        # TODO: maybe better selection in future, but this works for now
        rows = reversed(list(set(
            map(lambda index: index.row(), table.selectedIndexes()))))
        for row in rows:
            table.removeRow(row)

275
    table = QtWidgets.QTableWidget(columnCount=len(labels))
276
    table.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
277
278
                        QtWidgets.QSizePolicy.Expanding)
    table.setHorizontalHeaderLabels(labels)
279
280
281
282
283
284
285
286
    table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)

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

    """tx = table.verticalHeader().width() \
287
288
289
        + table.verticalScrollBar().height()
    for n in range(len(labels)):
        tx = tx + table.columnWidth(n)
290
291
292
    table.setMinimumSize(tx, 0)"""
    """table.horizontalHeader().setSectionResizeMode(
            exp_col, QtWidgets.QHeaderView.ResizeToContents)"""
293
294
    table_add = QtWidgets.QPushButton("Add")
    table_rem = QtWidgets.QPushButton("Remove")
295
    table_rem.clicked.connect(lambda: remove_rows(table))
296
297
298
299
300
301
302
    table_butlayout = QtWidgets.QHBoxLayout()
    table_butlayout.addWidget(table_add)
    table_butlayout.addWidget(table_rem)
    table_butlayout.addStretch(0)
    return table, table_butlayout, table_add, table_rem


303
def dialog(parent, ds, filename, mass_selector, spectrum):
304
    """constructs a dialog window"""
305

Yan's avatar
Yan committed
306
307
    dial_widget = QtWidgets.QDialog(
        parent, windowTitle='Delayed reactant labelling')
Yan's avatar
Yan committed
308

Yan's avatar
Yan committed
309
310
    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
311
312
313
314
    graph_canvas = FigureCanvas(dial_graph)
    graph_canvas.setStyleSheet("background-color:transparent;")
    graph_canvas.setAutoFillBackground(False)

315
316
317
318
    drl_load = QtWidgets.QPushButton("&Load")
    drl_save = QtWidgets.QPushButton("&Save")
    drl_export = QtWidgets.QPushButton("&Export")
    close = QtWidgets.QPushButton("&Close")
319
320
321
322
323
    close.clicked.connect(dial_widget.close)

    # pt = parenttable
    pt, pt_butlayout, pt_add, pt_rem = iontable(
        ["Name", "start (m/z)", "end (m/z)", "profile"], 2)
324
325
326
327
    pt_add.setText("&Add")
    pt_add.clicked.connect(lambda: add_parent(
        dial_widget, mass_selector, spectrum, ds, pt))

328
329
    # dt = daughtertable
    dt, dt_butlayout, dt_add, dt_rem = iontable(
330
        ["Name", "start (m/z)", "end (m/z)", "corr to", "factor"], 2)
331
332
    add_corr = QtWidgets.QPushButton("Add correction")
    dt_butlayout.addWidget(add_corr)
333
334
    dt_add.clicked.connect(lambda: add_daughter(
        dial_widget, pt, dt))
335

Yan's avatar
Yan committed
336
337
338
339
    main_layout = QtWidgets.QVBoxLayout(dial_widget)
    sub_layout = QtWidgets.QHBoxLayout()
    tablelayout = QtWidgets.QVBoxLayout()
    main_butlayout = QtWidgets.QHBoxLayout()
Yan's avatar
Yan committed
340

341
342
    main_layout.addLayout(sub_layout)
    main_layout.addWidget(HBar())
Yan's avatar
Yan committed
343
    main_layout.addLayout(main_butlayout)
344

Yan's avatar
Yan committed
345
346
347
348
    main_butlayout.addWidget(drl_load)
    main_butlayout.addWidget(drl_save)
    main_butlayout.addStretch(1)
    main_butlayout.addWidget(drl_export)
349
    main_butlayout.addWidget(close)
Yan's avatar
Yan committed
350

351
    sub_layout.addWidget(graph_canvas, stretch=1)
Yan's avatar
Yan committed
352
    sub_layout.addLayout(tablelayout)
353

Yan's avatar
Yan committed
354
355
356
357
358
359
360
    tablelayout.addWidget(QtWidgets.QLabel("Raw ions table:"))
    tablelayout.addWidget(pt)
    tablelayout.addLayout(pt_butlayout)
    tablelayout.addWidget(HBar())
    tablelayout.addWidget(QtWidgets.QLabel("Corrected ions table:"))
    tablelayout.addWidget(dt)
    tablelayout.addLayout(dt_butlayout)
361

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