__main__.py 16.3 KB
Newer Older
Yan's avatar
Yan committed
1
2
3
4
5
#!/usr/bin/env python3

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.widgets import SpanSelector
Yan's avatar
Yan committed
6
from matplotlib.gridspec import GridSpec
Yan's avatar
Yan committed
7
8
from PyQt5 import QtCore
from PyQt5 import QtWidgets
Yan's avatar
Yan committed
9
from PyQt5 import QtGui
10
from PyQt5 import QtPrintSupport
Yan's avatar
Yan committed
11
from rawprasslib import load_raw
Yan's avatar
Yan committed
12
from rawprasslib import rawprasslib
Yan's avatar
Yan committed
13
from prasopes.predictmz import predict as getmzpattern
Yan's avatar
Yan committed
14
15
16
17
18
try:
    from rawautoparams import load_params
    autoparams = True
except:
    autoparams = False
Yan's avatar
Yan committed
19
import numpy as np
Yan's avatar
Yan committed
20
import prasopes.config as cf
21
import prasopes.datatools as dt
Yan's avatar
Yan committed
22
import prasopes.drltools as drl
Yan's avatar
Yan committed
23
import prasopes.filetools as ft
Yan's avatar
Yan committed
24
25
import prasopes.graphtools as gt
import prasopes.imagetools as imgt
Yan's avatar
Yan committed
26
import prasopes.zcetools as zce
Yan's avatar
Yan committed
27
import prasopes.docks as docks
Yan's avatar
Yan committed
28
import prasopes.tangoicons
Yan's avatar
Yan committed
29
30
31
import sys
import matplotlib
import logging
3Yan's avatar
3Yan committed
32
import os.path
Yan's avatar
Yan committed
33
34
35
matplotlib.use("Qt5Agg")


36
37
38
39
class update_signal(QtCore.QObject):
    signal = QtCore.pyqtSignal()


40
41
42
43
class QStatusBarLogger(logging.Handler):
    def __init__(self, parent=None):
        super().__init__()
        self.statusBar = QtWidgets.QStatusBar(parent)
Yan's avatar
Yan committed
44
45
        self.trigger = update_signal()
        self.msg = str("")
46
47

    def emit(self, record):
Yan's avatar
Yan committed
48
49
        self.msg = self.format(record)
        self.trigger.signal.emit()
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
class AugFigureCanvas(FigureCanvas):
    #TODO: move the widget into graphtools when I'm done
    def __init__(self):
        self.figure = Figure(figsize=(5, 4), dpi=100, facecolor="None",
                constrained_layout=True)
        super().__init__(self.figure)
        self.ds = []
        self.ms = dict(annotation=[], name="Spectrum", xlabel="m/z",
                       ylabel="ion count", xtics=20, predict=None,
                       params=[], headers=[], texts=[])
        self.chrom = dict(x=[0], y=[0], t_start=None, t_end=None, machtype=None,
                          name="Chromatogram", xlabel="time(min)",
                          ylabel="total ion count", timesarg=[])
        self.filename = None
        self.drlcache = [None, None]
        grid = self.figure.add_gridspec(2, 1)
        self.chromplot = self.figure.add_subplot(grid[0,0], facecolor=(1, 1, 1, 0.8))
        self.spectplot = self.figure.add_subplot(grid[1,0], facecolor=(1, 1, 1, 0.8))
        self.setStyleSheet("background-color:transparent;")
        self.setAutoFillBackground(False)
        self.paramstable = dt.table(["","name","value"], 100)
        gt.pan_factory(self.chromplot)
        gt.zoom_factory(self.chromplot, 1.15)
        gt.pan_factory(self.spectplot, self.ms)
        gt.zoom_factory(self.spectplot, 1.15, self.ms)
        gt.textedit_factory(self.spectplot, self.ms)
        self.mass_selector = gt.AugSpanSelector(self.spectplot, self.ms)
        #TODO: rewrite timeSelector to better shape when I'm done
        self.time_selector = SpanSelector(self.chromplot, lambda x_min, x_max:
81
82
83
                gt.pick_times(x_min, x_max, self), 'horizontal',
                useblit=True, rectprops=dict(alpha=0.15, facecolor='purple'),
                button=3)
84
85
86


def load_file(parent, augCanvas, update, settings, loadthread, filename=None):
Yan's avatar
Yan committed
87
    """populates dataset and plots it"""
88
89
90
    directory = augCanvas.filename or settings.value("open_folder")
    filename = filename or QtWidgets.QFileDialog.getOpenFileName(
            caption="Open spectrum", directory=directory,
3Yan's avatar
3Yan committed
91
            filter="Finnigan RAW files (*.raw, *.RAW)")[0]
92
93
    if filename is not '' and os.path.isfile(filename)\
                          and not os.path.isdir(filename):
Yan's avatar
Yan committed
94
95
        error = update_signal()
        errormsg = []
96
        def runfnc():
Yan's avatar
Yan committed
97
            try:
Yan's avatar
Yan committed
98
                [i.clear() for i in (
99
100
101
102
                    augCanvas.ds, augCanvas.chrom['timesarg'], 
                    augCanvas.ms['params'],augCanvas.ms['headers'])]
                [augCanvas.ds.append(dict(
                    chrom_dat=i[0], masses=i[1], matrix=i[2]))
103
104
                    for i in load_raw(filename, settings.value("tmp_location"))]
            except rawprasslib.ParsingException as pex:
Yan's avatar
Yan committed
105
106
                errormsg.append("Opening of the file has failed!")
                errormsg.append(
107
108
                    "File is incompatible with the rawprasslib, "
                    "canceling request!\n\n"
Yan's avatar
Yan committed
109
                    "Error message:\n{}".format(pex.args[0]))
Yan's avatar
Yan committed
110
                error.signal.emit()
111
112
113
                return
            if autoparams == True:
                try:
114
115
116
117
118
119
120
121
                    (augCanvas.ms['params'], rawheaders
                        , augCanvas.chrom['machtype']) = load_params(filename)
                    segments = [len(subset['chrom_dat'][0])
                            for subset in augCanvas.ds]
                    indicies = [sum(segments[:i+1])
                            for i in range(len(segments))]
                    augCanvas.ms['headers'] = np.split(
                            rawheaders, indicies)[:-1]
122
                except Exception as pex:
123
124
                    errormsg.append(
                            "File is incompatible with the rawautoparams,")
Yan's avatar
Yan committed
125
126
127
128
                    errormsg.append(
                            "no parameters loaded!\n\n"
                            "Error message:\n{}".format(pex.args[0]))
                    error.signal.emit()
129
130
            gt.populate(augCanvas)
            augCanvas.filename = filename
131
132
133
            parent.setWindowTitle("Prasopes - {}".format(
                os.path.basename(filename)))
            update.signal.emit()
Yan's avatar
Yan committed
134
135
        error.signal.connect(lambda:
            QtWidgets.QMessageBox.critical(parent, errormsg[0], errormsg[1]))
136
137
        loadthread.run = runfnc
        loadthread.start()
3Yan's avatar
3Yan committed
138

Yan's avatar
Yan committed
139

Yan's avatar
Yan committed
140
def print_graph(data_set, mass_spec, chrom_spec, spect, fn, table):
Yan's avatar
Yan committed
141
    def printimage(printdevice, img):
Yan's avatar
Yan committed
142
        printer.setResolution(600)
Yan's avatar
Yan committed
143
        painter = QtGui.QPainter(printdevice)
Yan's avatar
Yan committed
144
145
146
147
        font = painter.font()
        linesize = printer.resolution()/15
        font.setPixelSize(linesize)
        painter.setFont(font)
Yan's avatar
Yan committed
148
        painter.drawImage(0,0,img)
Yan's avatar
Yan committed
149
150
151
152
153
154
155
156
        offset = img.size().height()
        line = 1
        spacing = 1.5
        for row in range(table.rowCount()):
            if table.cellWidget(row,0).checkState() == 2:
                text = table.item(row,1).text() + table.item(row,2).text()
                painter.drawText(300,int(offset+line*linesize*spacing), text)
                line += 1
Yan's avatar
Yan committed
157
158
        painter.end()
    #TODO: substitute the QPrintPreviewDialog with QPrintPreviewWidget
159
160
    printPreview = QtPrintSupport.QPrintPreviewDialog()
    printer = printPreview.printer()
Yan's avatar
Yan committed
161
162
    printer.setPageSize(printer.A5)
    printer.setDuplex(printer.DuplexNone)
Yan's avatar
Yan committed
163
    image = imgt.paint_image(mass_spec, spect, printer)
Yan's avatar
Yan committed
164
    printPreview.paintRequested.connect(lambda:
Yan's avatar
Yan committed
165
                                        printimage(printer, image))
166
167
168
    printPreview.exec()


Yan's avatar
Yan committed
169
170
def update_spectrum(chromatogram, spect, ds, ms, fn, chrom, config):
    if fn[0] is not None:
Yan's avatar
Yan committed
171
        slims = [spect.get_xlim(), spect.get_ylim()]
3Yan's avatar
3Yan committed
172
173
174
        ds.clear()
        [ds.append(dict(chrom_dat=i[0], masses=i[1], matrix=i[2])) for i
                in load_raw(fn[0], config.value("tmp_location"))]
Yan's avatar
Yan committed
175
176
177
        gt.populate(chromatogram, spect, ds, ms, chrom)
        spect.set_xlim(slims[0])
        spect.set_ylim(slims[1])
Yan's avatar
Yan committed
178
        gt.ann_spec(spect, ms)
Yan's avatar
Yan committed
179
180
181
        spect.get_figure().canvas.draw()


182
def dropped(event, parent, chromatogram, spectrum, ds, ms, filename,
183
            chrom, update, config, loadthread):
184
    dropurl = event.mimeData().urls()[0].toLocalFile()
185
    load_file(parent, chromatogram, spectrum, ds, ms, filename,
186
              chrom, update, config, loadthread, filename=dropurl)
Yan's avatar
Yan committed
187
188
189
190


def drag_entered(event):
    if event.mimeData().hasUrls() and event.mimeData().urls()[0]\
191
        .toLocalFile().lower().endswith('.raw'):
Yan's avatar
Yan committed
192
193
194
        event.accept()


Yan's avatar
Yan committed
195
196
197
198
199
200
201
202
203
204
205
206
207
def predictmz(form, chromatogram, spect, ds, ms, chrom):
    text = form.text()
    if text == "":
        ms["predict"] = None
        return
    slims = [spect.get_xlim(), spect.get_ylim()]
    ms["predict"] = getmzpattern(text)
    gt.populate(chromatogram, spect, ds, ms, chrom)
    spect.set_xlim(slims[0])
    spect.set_ylim(slims[1])
    spect.get_figure().canvas.draw()


3Yan's avatar
3Yan committed
208
209
210
211
212
213
def oddeven_changed(chromatogram, spectrum, ds, ms, filename, chrom, config,
        oddevenact):
    config.setValue("view/oddeven", oddevenact.isChecked())
    update_spectrum(chromatogram, spectrum, ds, ms, filename, chrom, config)


214
def key_pressed(event, augCanvas, config):
Yan's avatar
Yan committed
215
    if event.key() == QtCore.Qt.Key_F5:
216
        update_spectrum(augCanvas, config)
217
218
    if event.key() == QtCore.Qt.Key_C:
        if event.modifiers().__int__() == QtCore.Qt.ControlModifier:
219
220
221
222
223
            if augCanvas.paramstable.underMouse():
                dt.clip_tablestr(augCanvas)
            else:
                imgt.clip_spect_img(augCanvas.ms, augCanvas.spectplot,
                                    augCanvas.filename)
224
225
        if event.modifiers().__int__() == QtCore.Qt.ControlModifier+\
                                          QtCore.Qt.ShiftModifier:
226
            dt.clip_spectstr(augCanvas)
Yan's avatar
Yan committed
227
    if event.key() in (QtCore.Qt.Key_Left, QtCore.Qt.Key_Right):
228
        gt.shift_times(event, augCanvas)
Yan's avatar
Yan committed
229
230


Yan's avatar
Yan committed
231
232
233
234
235
def about(parent):
    """constructs window with "about" info"""
    QtWidgets.QMessageBox.information(
            parent, "About Prasopes",
            "Prasopes Finnigan raw file viewer\n\n"
236
            "Version: 0.0.12 (alpha)")
Yan's avatar
Yan committed
237
238


Yan's avatar
Yan committed
239
def main():
240
    app = QtWidgets.QApplication(sys.argv)
241
    loadthread = QtCore.QThread()
242

243
    augCanvas = AugFigureCanvas()
244
    update = update_signal()
Yan's avatar
Yan committed
245

Yan's avatar
Yan committed
246
247
    config = cf.settings()

248
    barHandler = QStatusBarLogger()
Yan's avatar
Yan committed
249
250
    barHandler.trigger.signal.connect(lambda:
            barHandler.statusBar.showMessage(barHandler.msg))
251

Yan's avatar
Yan committed
252
    p_logger = logging.getLogger('parseLogger')
253
    params_logger = logging.getLogger('acqLogLogger')
Yan's avatar
Yan committed
254
    drl_logger = logging.getLogger('drlLogger')
255
    zce_logger = logging.getLogger('zceLogger')
Yan's avatar
Yan committed
256
257
    #mpl_logger = logging.getLogger('matplotlib')
    #mpl_logger.setLevel("DEBUG")
Yan's avatar
Yan committed
258
    logging.basicConfig()
Yan's avatar
Yan committed
259
    #p_logger.setLevel("WARN")
Yan's avatar
Yan committed
260
    #p_logger.setLevel("DEBUG")
Yan's avatar
Yan committed
261
    #drl_logger.setLevel("INFO")
Yan's avatar
Yan committed
262
    drl_logger.setLevel("DEBUG")
263
    zce_logger.setLevel("DEBUG")
Yan's avatar
Yan committed
264
    #params_logger.setLevel("DEBUG")
265
266
    p_logger.addHandler(barHandler)
    zce_logger.addHandler(barHandler)
267
268
    params_logger.addHandler(barHandler)
    barHandler.setLevel("DEBUG")
Yan's avatar
Yan committed
269

Yan's avatar
Yan committed
270
    main_window = QtWidgets.QMainWindow(windowTitle="Prasopes")
Yan's avatar
Yan committed
271

Yan's avatar
Yan committed
272
273
274
    if QtGui.QIcon.themeName() is "":
        QtGui.QIcon.setThemeName("TangoMFK")

Yan's avatar
Yan committed
275
276
277
    consoledock = docks.consoleDockWidget(
            locals(), "&Console", "view/consolevisible")
    treedock = docks.treeDockWidget(
278
279
            "&File browser", "view/filebrowservisible", update, load_file, main_window,
            augCanvas, config, loadthread)
Yan's avatar
Yan committed
280
281
    paramsdock = docks.augDock("Acquisition parameters", "&Acq parameters",
            "view/acqparvisible")
282
283
    update.signal.connect(lambda: gt.update_paramstable(augCanvas))
    paramsdock.setWidget(augCanvas.paramstable)
Yan's avatar
Yan committed
284

Yan's avatar
Yan committed
285
    openact = QtWidgets.QAction(QtGui.QIcon.fromTheme(
Yan's avatar
Yan committed
286
        "document-open"), "&Open...", None)
Yan's avatar
Yan committed
287
288
    openact.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_O)
    openact.triggered.connect(lambda: load_file(
289
        main_window, augCanvas, update, config, loadthread))
Yan's avatar
Yan committed
290
    exportact = QtWidgets.QAction(QtGui.QIcon.fromTheme(
Yan's avatar
Yan committed
291
        "document-save-as"), "&Export...", None)
Yan's avatar
Yan committed
292
293
    exportact.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_E)
    exportact.triggered.connect(lambda: ft.export_dial(
294
        augCanvas, main_window))
295
296
297
    printact = QtWidgets.QAction(QtGui.QIcon.fromTheme(
        "document-print"), "&Print", None)
    printact.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_P)
298
    printact.triggered.connect(lambda: print_graph(augCanvas))
Yan's avatar
Yan committed
299
    settingsact = QtWidgets.QAction(QtGui.QIcon.fromTheme(
Yan's avatar
Yan committed
300
        "preferences-system"), "&Settings...", None)
Yan's avatar
Yan committed
301
302
    settingsact.triggered.connect(lambda: cf.dial(main_window))
    quitact = QtWidgets.QAction(QtGui.QIcon.fromTheme(
Yan's avatar
Yan committed
303
        "application-exit"), "&Quit", None)
Yan's avatar
Yan committed
304
305
306
    quitact.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_Q)
    quitact.triggered.connect(main_window.close)
    zceact = QtWidgets.QAction(QtGui.QIcon.fromTheme(
Yan's avatar
Yan committed
307
        "applications-utilities"), "&TSQ zce...", None)
Yan's avatar
Yan committed
308
309
    zceact.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_T)
    zceact.triggered.connect(lambda: zce.dialog(
310
        main_window, augCanvas, update))
Yan's avatar
Yan committed
311
    drlact = QtWidgets.QAction(QtGui.QIcon.fromTheme(
Yan's avatar
Yan committed
312
        "applications-utilities"), "&DRL...", None)
Yan's avatar
Yan committed
313
314
    drlact.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_D)
    drlact.triggered.connect(lambda: drl.main_window(
315
        main_window, augCanvas, update))
Yan's avatar
Yan committed
316
317
    aboutact = QtWidgets.QAction("&About Prasopes", None)
    aboutact.triggered.connect(lambda: about(main_window))
Yan's avatar
Yan committed
318
    autozoomy = QtWidgets.QAction(QtGui.QIcon.fromTheme(
Yan's avatar
Yan committed
319
320
321
322
        "zoom-original"), "Auto Zoom Y", None, checkable=True,
        checked=config.value("view/autozoomy", type=bool))
    autozoomy.triggered.connect(lambda: config.setValue(
                                "view/autozoomy", autozoomy.isChecked()))
323
    autozoomy.triggered.connect(lambda: gt.autozoomy(augCanvas.spectrum))
324
325
326
327
328
    intensitiesact = QtWidgets.QAction("&Show intensities", None,
            checkable=True, checked=config.value("view/intensities",
                type=bool))
    intensitiesact.triggered.connect(lambda: config.setValue(
        "view/intensities", intensitiesact.isChecked()))
Yan's avatar
Yan committed
329
330
    intensitiesact.triggered.connect(lambda: gt.ann_spec(
        augCanvas.spectplot, augCanvas.ms))
331
    intensitiesact.triggered.connect(lambda: augCanvas.draw())
332
333
334
    oddevenact = QtWidgets.QAction("&Odd / even", None, checkable=True,
            checked=config.value("view/oddeven", type=bool))
    oddevenact.triggered.connect(lambda:
335
        oddeven_changed(augCanvas, config, oddevenact))
Yan's avatar
Yan committed
336

Yan's avatar
Yan committed
337
338
    predictform = QtWidgets.QLineEdit(maximumWidth=150)
    predictform.editingFinished.connect(lambda: predictmz(
339
        predictform, augCanvas))
Yan's avatar
Yan committed
340

Yan's avatar
Yan committed
341
342
    file_menu = QtWidgets.QMenu('&File', main_window)
    main_window.menuBar().addMenu(file_menu)
Yan's avatar
Yan committed
343
344
345
    file_menu.addAction(openact)
    file_menu.addAction(exportact)
    file_menu.addSeparator()
346
347
    file_menu.addAction(printact)
    file_menu.addSeparator()
Yan's avatar
Yan committed
348
349
350
    file_menu.addAction(settingsact)
    file_menu.addSeparator()
    file_menu.addAction(quitact)
Yan's avatar
Yan committed
351
352
    tools_menu = QtWidgets.QMenu('&Tools', main_window)
    main_window.menuBar().addMenu(tools_menu)
Yan's avatar
Yan committed
353
354
    tools_menu.addAction(zceact)
    tools_menu.addAction(drlact)
355
    tools_menu.addSeparator()
Yan's avatar
Yan committed
356
    view_menu = QtWidgets.QMenu('&View', main_window)
Yan's avatar
Yan committed
357
358
    [view_menu.addAction(i.action) for i in (treedock, paramsdock, consoledock)]
    [view_menu.addAction(i) for i in (autozoomy, intensitiesact)]
359
360
    view_menu.addSeparator()
    view_menu.addAction(oddevenact)
Yan's avatar
Yan committed
361
    main_window.menuBar().addMenu(view_menu)
Yan's avatar
Yan committed
362
363
364
    help_menu = QtWidgets.QMenu('&Help', main_window)
    main_window.menuBar().addMenu(help_menu)
    help_menu.addAction(aboutact)
Yan's avatar
Yan committed
365

366
    main_window.setCentralWidget(augCanvas)
Yan's avatar
Yan committed
367

Yan's avatar
Yan committed
368
369
370
371
372
    toolBar = QtWidgets.QToolBar(main_window)
    toolBar.setAllowedAreas(QtCore.Qt.TopToolBarArea)
    toolBar.setFloatable(False)
    toolBar.setMovable(False)
    toolBar.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
Yan's avatar
Yan committed
373

Yan's avatar
Yan committed
374
375
376
    toolBar.addAction(openact)
    toolBar.addAction(exportact)
    toolBar.addSeparator()
Yan's avatar
Yan committed
377
378
379
    toolBar.addWidget(QtWidgets.QLabel("Predict Formula:"))
    toolBar.addWidget(predictform)
    toolBar.addSeparator()
Yan's avatar
Yan committed
380
381
    toolBar.addAction(zceact)
    toolBar.addAction(drlact)
Yan's avatar
Yan committed
382
383
    toolBar.addSeparator()
    toolBar.addAction(autozoomy)
Yan's avatar
Yan committed
384

Yan's avatar
Yan committed
385
386
    main_window.dragEnterEvent = lambda event: drag_entered(event)
    main_window.dropEvent = lambda event: dropped(
387
        event, main_window, augCanvas, update, config)
Yan's avatar
Yan committed
388
    main_window.setAcceptDrops(True)
389
390
    main_window.keyPressEvent = lambda event: key_pressed(
            event, augCanvas, config)
Yan's avatar
Yan committed
391

392
393
    main_window.addToolBar(QtCore.Qt.TopToolBarArea, toolBar)
    main_window.addDockWidget(QtCore.Qt.LeftDockWidgetArea, treedock)
Yan's avatar
Yan committed
394
    main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, paramsdock)
Yan's avatar
Yan committed
395
    main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, consoledock)
396
    main_window.setStatusBar(barHandler.statusBar)
397
398
399

    main_window.setFocus()

Yan's avatar
Yan committed
400
    if len(sys.argv) == 2:
401
        load_file(main_window, augCanvas, update, config, loadthread,
402
                  filename=sys.argv[1])
Yan's avatar
Yan committed
403
    else:
404
405
        gt.pop_plot(0, 0, augCanvas.spectplot, augCanvas.ms)
        gt.pop_plot(0, 0, augCanvas.chromplot, augCanvas.chrom)
Yan's avatar
Yan committed
406
407
408

    main_window.show()
    sys.exit(app.exec_())
Yan's avatar
Yan committed
409

410

Yan's avatar
Yan committed
411
412
if __name__ == "__main__":
    main()