drltools.py 9.6 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
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
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)

    def end_bindings(bindings, axis):
        fig = axis.get_figure()
        for i in bindings:
            axis.canvas.mpl_disconnect(i)
        return

    def update_fields(x_min, x_max, startw, endw):
        endw.setValue(x_max)
        startw.setValue(x_min)

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

        #TODO: maybe find a way how this should be called only once
        #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()


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

    def deactivate_altselector(event, mass_selector, picker):
        if event.name == 'motion_notify_event' \
            or (event.name == 'key_release_event'
                and event.key == 'shift'):
            mass_selector.active = True
            picker.active = False

    def add_ion(start_mz, end_mz, table, dialog):
        newrow = table.rowCount()
        table.setRowCount(newrow+1)
        col2 = QtWidgets.QTableWidgetItem()
        col2.setText("bagr")
        table.setCellWidget(newrow,1,start_mz)
        table.setCellWidget(newrow,2,end_mz)
        dialog.close()


    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
        event: deactivate_altselector(event, mass_selector, picker)))
    # 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
        event: deactivate_altselector(event, mass_selector, picker)))

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

    start_mz = QtWidgets.QDoubleSpinBox(maximum=999999)
    start_mz.valueChanged.connect(lambda: update_parent(
        start_mz, end_mz, ionspect, ion, ds))
    end_mz = QtWidgets.QDoubleSpinBox(maximum=999999)
    end_mz.valueChanged.connect(lambda: update_parent(
        start_mz, end_mz, ionspect, ion, ds))

    submit = QtWidgets.QPushButton("&Submit")
    submit.clicked.connect(lambda: add_ion(start_mz, end_mz, parenttable, ap_dial))
    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:
        update_fields(x_min, x_max, start_mz, end_mz),
        'horizontal', useblit=True, rectprops=dict(
        alpha=0.15, facecolor='orange'), button=3)
    picker.active = False

    ap_dial.show()


176
177
def iontable(labels, exp_col):
    """creates a table for ions"""
178
179
180
181
182
183
184
    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)

185
186
187
188
189
190
191
192
193
194
195
196
197
198
    table = QtWidgets.QTableWidget(columnCount=len(labels))
    table.setSizePolicy(QtWidgets.QSizePolicy.Fixed,
                        QtWidgets.QSizePolicy.Expanding)
    table.setHorizontalHeaderLabels(labels)
    tx = table.verticalHeader().width() \
        + table.verticalScrollBar().height()
    for n in range(len(labels)):
        tx = tx + table.columnWidth(n)
    table.setMinimumSize(tx, 0)
    table.horizontalHeader().setSectionResizeMode(
            exp_col, QtWidgets.QHeaderView.Stretch)
    table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
    table_add = QtWidgets.QPushButton("Add")
    table_rem = QtWidgets.QPushButton("Remove")
199
    table_rem.clicked.connect(lambda: remove_rows(table))
200
201
202
203
204
205
206
    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


207
def dialog(parent, ds, filename, mass_selector, spectrum):
208
    """constructs a dialog window"""
209

Yan's avatar
Yan committed
210
211
    dial_widget = QtWidgets.QDialog(
        parent, windowTitle='Delayed reactant labelling')
Yan's avatar
Yan committed
212

Yan's avatar
Yan committed
213
214
    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
215
216
217
218
    graph_canvas = FigureCanvas(dial_graph)
    graph_canvas.setStyleSheet("background-color:transparent;")
    graph_canvas.setAutoFillBackground(False)

219
220
221
222
    drl_load = QtWidgets.QPushButton("&Load")
    drl_save = QtWidgets.QPushButton("&Save")
    drl_export = QtWidgets.QPushButton("&Export")
    close = QtWidgets.QPushButton("&Close")
223
224
225
226
227
    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)
228
229
230
231
    pt_add.setText("&Add")
    pt_add.clicked.connect(lambda: add_parent(
        dial_widget, mass_selector, spectrum, ds, pt))

232
233
234
235
236
237
    # dt = daughtertable
    dt, dt_butlayout, dt_add, dt_rem = iontable(
        ["Name", "start (m/z)", "end (m/z)", "corr to"], 2)
    add_corr = QtWidgets.QPushButton("Add correction")
    dt_butlayout.addWidget(add_corr)

Yan's avatar
Yan committed
238
239
240
241
    main_layout = QtWidgets.QVBoxLayout(dial_widget)
    sub_layout = QtWidgets.QHBoxLayout()
    tablelayout = QtWidgets.QVBoxLayout()
    main_butlayout = QtWidgets.QHBoxLayout()
Yan's avatar
Yan committed
242

243
244
    main_layout.addLayout(sub_layout)
    main_layout.addWidget(HBar())
Yan's avatar
Yan committed
245
    main_layout.addLayout(main_butlayout)
246

Yan's avatar
Yan committed
247
248
249
250
    main_butlayout.addWidget(drl_load)
    main_butlayout.addWidget(drl_save)
    main_butlayout.addStretch(1)
    main_butlayout.addWidget(drl_export)
251
    main_butlayout.addWidget(close)
Yan's avatar
Yan committed
252

253
    sub_layout.addWidget(graph_canvas, stretch=1)
Yan's avatar
Yan committed
254
    sub_layout.addLayout(tablelayout)
255

Yan's avatar
Yan committed
256
257
258
259
260
261
262
    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)
263

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