msonmobtools.py 7.7 KB
Newer Older
Yan's avatar
Yan committed
1
2
3
4
5
6
7
8
#!/usr/bin/env python3
from matplotlib.backends.backend_qt5agg import\
        FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from PyQt5 import QtWidgets
from PyQt5 import QtGui
import matplotlib
import numpy as np
Yan's avatar
Yan committed
9
import prasopes.datasets as ds
Yan's avatar
Yan committed
10
11
12
13
14
15
16
17
18
19
20
import prasopes.datatools as dt
import prasopes.graphtools as gt
import prasopes.filetools as ft
import prasopes.config as cf
import prasopes.drltools as drl
import prasopes.imagetools as imgt
import os.path
import logging
matplotlib.use("Qt5Agg")


Yan's avatar
Yan committed
21
logger = logging.getLogger('msonmobLogger')
Yan's avatar
Yan committed
22
23
settings = cf.settings()

Yan's avatar
Yan committed
24
25
26
27
28
29
30
31
32
33
def get_massargs(tab, row, masses):
    startm = drl.floatize(tab, row, 3) - (drl.floatize(tab, row, 4) / 2)
    endm = drl.floatize(tab, row, 3) + (drl.floatize(tab, row, 4) / 2)
    massargs = dt.argsubselect(masses, startm, endm)
    return massargs

def update_profile(table, row, dataset):
    """parent table profile spectrum updating procedure"""
    logger.debug("updating parent table row {} profile".format(row))
    # Dont do anything to graph when the spectrum is not populated
Yan's avatar
Yan committed
34
    if not dataset or isinstance(dataset, ds.ThermoRawDataset):
Yan's avatar
Yan committed
35
36
37
38
        return
    spectrum = table.cellWidget(row, 5).figure.get_axes()[0]
    spectrum.clear()
    limits = []
Yan's avatar
Yan committed
39
    spectra = dataset.get_mobspectra(*[drl.floatize(table, row, i)
Yan's avatar
Yan committed
40
41
                                    for i in (1, 2)])
    for i, spectxy in enumerate(spectra):
Yan's avatar
Yan committed
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
        massargs = get_massargs(table, row, spectxy[0])
        spectrum.plot(spectxy[0], spectxy[1], ':', color='gray')
        spectrum.plot(spectxy[0][massargs], spectxy[1][massargs],
                      color=gt.colors[i % len(gt.colors)]/255)
        limits.append((spectxy[0][massargs[[0, -1]]],
                       max(spectxy[1][massargs])))
    widest = np.argmax([abs(lim[0][1]-lim[0][0]) for lim in limits])
    xmin, xmax = limits[widest][0]
    xex = max((xmax-xmin)*0.25, 0.02)
    spectrum.set_xlim(xmin-xex, xmax+xex)
    ymax = max(*[lim[1] for lim in limits], 1)
    spectrum.set_ylim(ymax*-0.1, ymax*1.2)
    spectrum.figure.canvas.draw()


def ionstable_changed(row, col, ds, table):
Yan's avatar
Yan committed
58
    if col in (1, 2, 3, 4):
Yan's avatar
Yan committed
59
        update_profile(table, row, ds)
Yan's avatar
Yan committed
60
61
62
63
64
65
66
67
68
69
70
    table.blockSignals(True)
    table.item(row, 0).setBackground(QtGui.QBrush(
        QtGui.QColor(*gt.colors[row % len(gt.colors)], alpha=50)))
    table.blockSignals(False)


def pop_dial(augCanvas, dialspect, ionstable, labels):
    ds = augCanvas.ds
    dialspect.clear()
    gt.pop_plot([], [], dialspect, labels)
    for row in range(ionstable.rowCount()):
Yan's avatar
Yan committed
71
        name = ionstable.item(row, 0).text()
Yan's avatar
Yan committed
72
        startmob = drl.floatize(ionstable, row, 3)\
Yan's avatar
Yan committed
73
            - (drl.floatize(ionstable, row, 4) / 2)
Yan's avatar
Yan committed
74
        endmob = drl.floatize(ionstable, row, 3)\
Yan's avatar
Yan committed
75
76
            + (drl.floatize(ionstable, row, 4) / 2)
        [tstart, tend] = [drl.floatize(ionstable, row, i) for i in (1, 2)]
Yan's avatar
Yan committed
77
        spectrum = ds.get_ms_onmob(startmob, endmob, tstart, tend)
Yan's avatar
Yan committed
78
79
        dialspect.plot(spectrum[0], spectrum[1], label=name,
                       color=(gt.colors[row % len(gt.colors)] / 255))
Yan's avatar
Yan committed
80
81
82
83
    if ionstable.rowCount():
        dialspect.autoscale(True)
        dialspect.legend(loc=2)
    dialspect.figure.canvas.draw()
Yan's avatar
Yan committed
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
    return


def add_row(ds, dialspect, ionstable):
    """add parent ion to the table"""
    logger.debug("adding line")
    newrow = ionstable.rowCount()

    ionstable.blockSignals(True)

    ionstable.setRowCount(newrow + 1)
    for i in range(5):
        ionstable.setItem(newrow, i, QtWidgets.QTableWidgetItem())
        if newrow != 0:
            val = drl.floatize(ionstable, newrow-1, i)
Yan's avatar
Yan committed
99
            if i not in (1, 2, 4):
Yan's avatar
Yan committed
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
                val = val+1
            ionstable.item(newrow, i).setText(str(val))

    ion_graph = Figure(figsize=(3, 1.5), dpi=100, facecolor="None")
    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)
    ionstable.setCellWidget(newrow, 5, graph_canvas)

    ionstable.blockSignals(False)
    ionstable_changed(newrow, 1, ds, ionstable)
    return


def remove_rows(ionstable, rows=None):
    logger.info("remowing rows")
    if not rows:
        rows = reversed(list(set(
            map(lambda x: x.row(), ionstable.selectedIndexes()))))
    [ionstable.removeRow(row) for row in rows]
    return


Yan's avatar
Yan committed
125
126
127
128
129
130
131
132
133
134
def export_dial(augCanvas, grph):
    """exports the reactivity into the .dat file format"""
    if not augCanvas.ds or isinstance(augCanvas.ds, ds.ThermoRawDataset):
        QtWidgets.QMessageBox.warning(
            None, "Export spectrum",
            "Nothing to export, cancelling request")
        return
    exp_f_name = ft.get_save_filename(
        "Export spectrum", "dat table (*.dat)", "dat", None)
    if exp_f_name != '':
Yan's avatar
Yan committed
135
136
        names = ["mass", "intensity"]
        units = ["m/z", ""]
Yan's avatar
Yan committed
137
        description = os.path.basename(augCanvas.ds.filename) + " " +\
Yan's avatar
Yan committed
138
            " -- ".join([line._label for line in grph.get_lines()])
Yan's avatar
Yan committed
139
140
141
142
143
        expf = open(exp_f_name, 'w')
        expf.write(dt.specttostr(grph, " ", names, units, description))
        expf.close


Yan's avatar
Yan committed
144
def main_window(parent, augCanvas, update_signal):
Yan's avatar
Yan committed
145
    reactlabels = dict(name="", xlabel="$m/z\ \\it→$",
Yan's avatar
Yan committed
146
                       ylabel="$Intensity\ \\it→$")
Yan's avatar
Yan committed
147
    cache = augCanvas.mobontofcache
Yan's avatar
Yan committed
148
149
150
151
152
153
154
155

    def onclose(widget, event, ionstable, canvas, cache, update_fnc):
        logger.debug("custom close routine called")
        cache[0], cache[1] = ionstable, canvas
        update_signal.signal.disconnect(update_fnc)
        QtWidgets.QDialog.closeEvent(widget, event)

    def update_fnc():
Yan's avatar
Yan committed
156
        pop_dial(augCanvas, dialspect, ionstable, reactlabels)
Yan's avatar
Yan committed
157
158

    dial_widget = QtWidgets.QDialog(
Yan's avatar
Yan committed
159
            parent, windowTitle='m/z - mobility')
Yan's avatar
Yan committed
160
    dial_widget.closeEvent = lambda event: onclose(
Yan's avatar
Yan committed
161
        dial_widget, event, ionstable, graph_canvas, cache, update_fnc)
Yan's avatar
Yan committed
162
163
164
165
166
167
168
169
170
    update_signal.signal.connect(update_fnc)

    if cache == [None, None]:
        dial_graph = Figure(figsize=(5, 2), dpi=100, facecolor="None",
                            constrained_layout=True)
        dialspect = dial_graph.add_subplot(111, facecolor=(1, 1, 1, 0.8))
        graph_canvas = FigureCanvas(dial_graph)
        graph_canvas.setStyleSheet("background-color:transparent;")
        graph_canvas.setAutoFillBackground(False)
Yan's avatar
Yan committed
171

Yan's avatar
Yan committed
172
173
        gt.zoom_factory(dialspect, 1.15, reactlabels)
        gt.pan_factory(dialspect, reactlabels)
Yan's avatar
Yan committed
174

Yan's avatar
Yan committed
175
        ionstable = dt.table(["Name", "Start time (min)", "End time (min)",
Yan's avatar
Yan committed
176
                              "mobility", "Peak width", "Profile"])
Yan's avatar
Yan committed
177
178
    else:
        ionstable = cache[0]
Yan's avatar
Yan committed
179
        graph_canvas = cache[1]
Yan's avatar
Yan committed
180
181
        dialspect = graph_canvas.figure.axes[0]

Yan's avatar
Yan committed
182
183
184
185
186
187
188
189
190
191
192
    ionstable.itemChanged.connect(lambda item: ionstable_changed(
        item.row(), item.column(), augCanvas.ds, ionstable))

    updatebtn = QtWidgets.QPushButton("Update")
    updatebtn.clicked.connect(lambda: update_fnc())

    addbtn = QtWidgets.QPushButton("Add")
    addbtn.clicked.connect(lambda: add_row(
        augCanvas.ds, dialspect, ionstable))
    rmbtn = QtWidgets.QPushButton("Remove")
    rmbtn.clicked.connect(lambda: remove_rows(ionstable))
Yan's avatar
Yan committed
193
194

    expbtn = QtWidgets.QPushButton("Export")
Yan's avatar
Yan committed
195
196
    expbtn.clicked.connect(lambda: export_dial(
        augCanvas, dialspect))
Yan's avatar
Yan committed
197
198

    buttlayout = QtWidgets.QHBoxLayout()
Yan's avatar
Yan committed
199
200
201
202
    buttlayout.addWidget(updatebtn)
    buttlayout.addStretch()
    buttlayout.addWidget(addbtn)
    buttlayout.addWidget(rmbtn)
Yan's avatar
Yan committed
203
204
205
206
207
208
209
210
211
    buttlayout.addStretch()
    buttlayout.addWidget(expbtn)

    dial_layout = QtWidgets.QVBoxLayout(dial_widget)
    dial_layout.addWidget(graph_canvas, stretch=1)
    dial_layout.addWidget(ionstable)
    dial_layout.addLayout(buttlayout)
    dial_widget.setFocus()
    dial_widget.show()
Yan's avatar
Yan committed
212
    update_fnc()