__main__.py 16.2 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
81
82
83
84
85
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:
                gt.pick_times(x_min, x_max, self.spectplot, self.ds, self.chromplot, self.ms, self.chrom, self.paramstable), 'horizontal', useblit=True, rectprops=dict(
                        alpha=0.15, facecolor='purple'), button=3)


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

Yan's avatar
Yan committed
138

Yan's avatar
Yan committed
139
def print_graph(data_set, mass_spec, chrom_spec, spect, fn, table):
Yan's avatar
Yan committed
140
    def printimage(printdevice, img):
Yan's avatar
Yan committed
141
        printer.setResolution(600)
Yan's avatar
Yan committed
142
        painter = QtGui.QPainter(printdevice)
Yan's avatar
Yan committed
143
144
145
146
        font = painter.font()
        linesize = printer.resolution()/15
        font.setPixelSize(linesize)
        painter.setFont(font)
Yan's avatar
Yan committed
147
        painter.drawImage(0,0,img)
Yan's avatar
Yan committed
148
149
150
151
152
153
154
155
        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
156
157
        painter.end()
    #TODO: substitute the QPrintPreviewDialog with QPrintPreviewWidget
158
159
    printPreview = QtPrintSupport.QPrintPreviewDialog()
    printer = printPreview.printer()
Yan's avatar
Yan committed
160
161
    printer.setPageSize(printer.A5)
    printer.setDuplex(printer.DuplexNone)
Yan's avatar
Yan committed
162
    image = imgt.paint_image(mass_spec, spect, printer)
Yan's avatar
Yan committed
163
    printPreview.paintRequested.connect(lambda:
Yan's avatar
Yan committed
164
                                        printimage(printer, image))
165
166
167
    printPreview.exec()


Yan's avatar
Yan committed
168
169
def update_spectrum(chromatogram, spect, ds, ms, fn, chrom, config):
    if fn[0] is not None:
Yan's avatar
Yan committed
170
        slims = [spect.get_xlim(), spect.get_ylim()]
3Yan's avatar
3Yan committed
171
172
173
        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
174
175
176
        gt.populate(chromatogram, spect, ds, ms, chrom)
        spect.set_xlim(slims[0])
        spect.set_ylim(slims[1])
Yan's avatar
Yan committed
177
        gt.ann_spec(spect, ms)
Yan's avatar
Yan committed
178
179
180
        spect.get_figure().canvas.draw()


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


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


Yan's avatar
Yan committed
194
195
196
197
198
199
200
201
202
203
204
205
206
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
207
208
209
210
211
212
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)


213
def key_pressed(event, augCanvas, config):
Yan's avatar
Yan committed
214
    if event.key() == QtCore.Qt.Key_F5:
215
        update_spectrum(augCanvas, config)
216
217
    if event.key() == QtCore.Qt.Key_C:
        if event.modifiers().__int__() == QtCore.Qt.ControlModifier:
218
            imgt.clip_spect_img(augCanvas)
219
220
        if event.modifiers().__int__() == QtCore.Qt.ControlModifier+\
                                          QtCore.Qt.ShiftModifier:
221
            dt.clip_spectstr(augCanvas)
Yan's avatar
Yan committed
222
    if event.key() in (QtCore.Qt.Key_Left, QtCore.Qt.Key_Right):
223
        gt.shift_times(event, augCanvas)
Yan's avatar
Yan committed
224
225


Yan's avatar
Yan committed
226
227
228
229
230
def about(parent):
    """constructs window with "about" info"""
    QtWidgets.QMessageBox.information(
            parent, "About Prasopes",
            "Prasopes Finnigan raw file viewer\n\n"
231
            "Version: 0.0.12 (alpha)")
Yan's avatar
Yan committed
232
233


Yan's avatar
Yan committed
234
def main():
235
    app = QtWidgets.QApplication(sys.argv)
236
    loadthread = QtCore.QThread()
237

238
    augCanvas = AugFigureCanvas()
239
    update = update_signal()
Yan's avatar
Yan committed
240

Yan's avatar
Yan committed
241
242
    config = cf.settings()

243
    barHandler = QStatusBarLogger()
Yan's avatar
Yan committed
244
245
    barHandler.trigger.signal.connect(lambda:
            barHandler.statusBar.showMessage(barHandler.msg))
246

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

Yan's avatar
Yan committed
265
    main_window = QtWidgets.QMainWindow(windowTitle="Prasopes")
Yan's avatar
Yan committed
266

Yan's avatar
Yan committed
267
268
269
    if QtGui.QIcon.themeName() is "":
        QtGui.QIcon.setThemeName("TangoMFK")

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

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

Yan's avatar
Yan committed
331
332
    predictform = QtWidgets.QLineEdit(maximumWidth=150)
    predictform.editingFinished.connect(lambda: predictmz(
333
        predictform, augCanvas))
Yan's avatar
Yan committed
334

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

360
    main_window.setCentralWidget(augCanvas)
Yan's avatar
Yan committed
361

Yan's avatar
Yan committed
362
363
364
365
366
    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
367

Yan's avatar
Yan committed
368
369
370
    toolBar.addAction(openact)
    toolBar.addAction(exportact)
    toolBar.addSeparator()
Yan's avatar
Yan committed
371
372
373
    toolBar.addWidget(QtWidgets.QLabel("Predict Formula:"))
    toolBar.addWidget(predictform)
    toolBar.addSeparator()
Yan's avatar
Yan committed
374
375
    toolBar.addAction(zceact)
    toolBar.addAction(drlact)
Yan's avatar
Yan committed
376
377
    toolBar.addSeparator()
    toolBar.addAction(autozoomy)
Yan's avatar
Yan committed
378

Yan's avatar
Yan committed
379
380
    main_window.dragEnterEvent = lambda event: drag_entered(event)
    main_window.dropEvent = lambda event: dropped(
381
        event, main_window, augCanvas, update, config)
Yan's avatar
Yan committed
382
    main_window.setAcceptDrops(True)
383
384
    main_window.keyPressEvent = lambda event: key_pressed(
            event, augCanvas, config)
Yan's avatar
Yan committed
385

386
387
    main_window.addToolBar(QtCore.Qt.TopToolBarArea, toolBar)
    main_window.addDockWidget(QtCore.Qt.LeftDockWidgetArea, treedock)
Yan's avatar
Yan committed
388
    main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, paramsdock)
Yan's avatar
Yan committed
389
    main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, consoledock)
390
    main_window.setStatusBar(barHandler.statusBar)
391
392
393

    main_window.setFocus()

Yan's avatar
Yan committed
394
    if len(sys.argv) == 2:
395
        load_file(main_window, augCanvas, update, config, loadthread,
396
                  filename=sys.argv[1])
Yan's avatar
Yan committed
397
    else:
398
399
        gt.pop_plot(0, 0, augCanvas.spectplot, augCanvas.ms)
        gt.pop_plot(0, 0, augCanvas.chromplot, augCanvas.chrom)
Yan's avatar
Yan committed
400
401
402

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

404

Yan's avatar
Yan committed
405
406
if __name__ == "__main__":
    main()