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

3
4
from matplotlib.backends.backend_qt5agg import\
    FigureCanvasQTAgg as FigureCanvas
Yan's avatar
Yan committed
5
6
7
8
from matplotlib.figure import Figure
from matplotlib.widgets import SpanSelector
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
try:
    from rawautoparams import load_params
    autoparams = True
17
except ImportError:
Yan's avatar
Yan committed
18
    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
class AugFigureCanvas(FigureCanvas):
53
    # TODO: move the widget into graphtools when I'm done
54
55
    def __init__(self):
        self.figure = Figure(figsize=(5, 4), dpi=100, facecolor="None",
56
                             constrained_layout=True)
57
58
59
60
61
        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=[])
62
63
64
65
        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=[])
66
67
68
        self.filename = None
        self.drlcache = [None, None]
        grid = self.figure.add_gridspec(2, 1)
69
70
71
72
        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))
73
74
        self.setStyleSheet("background-color:transparent;")
        self.setAutoFillBackground(False)
75
        self.paramstable = dt.table(["", "name", "value"], 100)
76
77
78
79
80
81
        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)
82
83
84
85
86
        # 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), 'horizontal', useblit=True,
                rectprops=dict(alpha=0.15, facecolor='purple'), button=3)
87
88
89


def load_file(parent, augCanvas, update, settings, loadthread, filename=None):
Yan's avatar
Yan committed
90
    """populates dataset and plots it"""
91
92
93
    directory = augCanvas.filename or settings.value("open_folder")
    filename = filename or QtWidgets.QFileDialog.getOpenFileName(
            caption="Open spectrum", directory=directory,
3Yan's avatar
3Yan committed
94
            filter="Finnigan RAW files (*.raw, *.RAW)")[0]
95
96
    if filename != '' and os.path.isfile(filename)\
            and not os.path.isdir(filename):
Yan's avatar
Yan committed
97
98
        error = update_signal()
        errormsg = []
99

100
        def runfnc():
Yan's avatar
Yan committed
101
            try:
Yan's avatar
Yan committed
102
                [i.clear() for i in (
103
104
                    augCanvas.ds, augCanvas.chrom['timesarg'],
                    augCanvas.ms['params'], augCanvas.ms['headers'])]
105
106
                [augCanvas.ds.append(dict(
                    chrom_dat=i[0], masses=i[1], matrix=i[2]))
107
                 for i in load_raw(filename, settings.value("tmp_location"))]
108
            except rawprasslib.ParsingException as pex:
Yan's avatar
Yan committed
109
110
                errormsg.append("Opening of the file has failed!")
                errormsg.append(
111
112
                    "File is incompatible with the rawprasslib, "
                    "canceling request!\n\n"
Yan's avatar
Yan committed
113
                    "Error message:\n{}".format(pex.args[0]))
Yan's avatar
Yan committed
114
                error.signal.emit()
115
                return
116
            if autoparams:
117
                try:
118
119
                    (augCanvas.ms['params'], rawheaders,
                        augCanvas.chrom['machtype']) = load_params(filename)
120
                    segments = [len(subset['chrom_dat'][0])
121
                                for subset in augCanvas.ds]
122
                    indicies = [sum(segments[:i+1])
123
                                for i in range(len(segments))]
124
125
                    augCanvas.ms['headers'] = np.split(
                            rawheaders, indicies)[:-1]
126
                except Exception as pex:
127
128
                    errormsg.append(
                            "File is incompatible with the rawautoparams,")
Yan's avatar
Yan committed
129
130
131
132
                    errormsg.append(
                            "no parameters loaded!\n\n"
                            "Error message:\n{}".format(pex.args[0]))
                    error.signal.emit()
133
134
            gt.populate(augCanvas)
            augCanvas.filename = filename
135
136
137
            parent.setWindowTitle("Prasopes - {}".format(
                os.path.basename(filename)))
            update.signal.emit()
138
139
        error.signal.connect(lambda: QtWidgets.QMessageBox.critical(
            parent, errormsg[0], errormsg[1]))
140
141
        loadthread.run = runfnc
        loadthread.start()
3Yan's avatar
3Yan committed
142

Yan's avatar
Yan committed
143

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


175
176
177
178
179
180
181
182
183
184
185
186
def update_spectrum(augCanvas, config):
    if augCanvas.filename:
        slims = [augCanvas.spectplot.get_xlim(),
                 augCanvas.spectplot.get_ylim()]
        augCanvas.ds.clear()
        [augCanvas.ds.append(dict(chrom_dat=i[0], masses=i[1], matrix=i[2]))
         for i in load_raw(augCanvas.filename, config.value("tmp_location"))]
        gt.populate(augCanvas)
        augCanvas.spectplot.set_xlim(slims[0])
        augCanvas.spectplot.set_ylim(slims[1])
        gt.ann_spec(augCanvas.spectplot, augCanvas.ms)
        augCanvas.draw()
Yan's avatar
Yan committed
187
188


189
def dropped(event, parent, augCanvas, update, config, loadthread):
190
    dropurl = event.mimeData().urls()[0].toLocalFile()
191
    load_file(parent, augCanvas, update, config, loadthread, filename=dropurl)
Yan's avatar
Yan committed
192
193
194
195


def drag_entered(event):
    if event.mimeData().hasUrls() and event.mimeData().urls()[0]\
196
            .toLocalFile().lower().endswith('.raw'):
Yan's avatar
Yan committed
197
198
199
        event.accept()


200
def predictmz(form, augCanvas):
Yan's avatar
Yan committed
201
202
    text = form.text()
    if text == "":
203
        augCanvas.ms["predict"] = None
Yan's avatar
Yan committed
204
        return
205
206
207
208
209
210
211
    slims = [augCanvas.spectplot.get_xlim(),
             augCanvas.spectplot.get_ylim()]
    augCanvas.ms["predict"] = getmzpattern(text)
    gt.populate(augCanvas)
    augCanvas.spectplot.set_xlim(slims[0])
    augCanvas.spectplot.set_ylim(slims[1])
    augCanvas.draw()
Yan's avatar
Yan committed
212
213


214
def oddeven_changed(augCanvas, config, oddevenact):
3Yan's avatar
3Yan committed
215
    config.setValue("view/oddeven", oddevenact.isChecked())
216
    update_spectrum(augCanvas, config)
3Yan's avatar
3Yan committed
217
218


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


Yan's avatar
Yan committed
236
237
238
239
240
def about(parent):
    """constructs window with "about" info"""
    QtWidgets.QMessageBox.information(
            parent, "About Prasopes",
            "Prasopes Finnigan raw file viewer\n\n"
241
            "Version: 0.0.12 (alpha)")
Yan's avatar
Yan committed
242
243


Yan's avatar
Yan committed
244
def main():
245
    app = QtWidgets.QApplication(sys.argv)
246
    loadthread = QtCore.QThread()
247

248
    augCanvas = AugFigureCanvas()
249
    update = update_signal()
Yan's avatar
Yan committed
250

Yan's avatar
Yan committed
251
252
    config = cf.settings()

253
    barHandler = QStatusBarLogger()
254
255
    barHandler.trigger.signal.connect(
        lambda: barHandler.statusBar.showMessage(barHandler.msg))
256

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

Yan's avatar
Yan committed
275
    main_window = QtWidgets.QMainWindow(windowTitle="Prasopes")
Yan's avatar
Yan committed
276

277
    if QtGui.QIcon.themeName() == "":
Yan's avatar
Yan committed
278
279
        QtGui.QIcon.setThemeName("TangoMFK")

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

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

Yan's avatar
Yan committed
343
344
    predictform = QtWidgets.QLineEdit(maximumWidth=150)
    predictform.editingFinished.connect(lambda: predictmz(
345
        predictform, augCanvas))
Yan's avatar
Yan committed
346

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

373
    main_window.setCentralWidget(augCanvas)
Yan's avatar
Yan committed
374

Yan's avatar
Yan committed
375
376
377
378
379
    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
380

Yan's avatar
Yan committed
381
382
383
    toolBar.addAction(openact)
    toolBar.addAction(exportact)
    toolBar.addSeparator()
Yan's avatar
Yan committed
384
385
386
    toolBar.addWidget(QtWidgets.QLabel("Predict Formula:"))
    toolBar.addWidget(predictform)
    toolBar.addSeparator()
Yan's avatar
Yan committed
387
388
    toolBar.addAction(zceact)
    toolBar.addAction(drlact)
Yan's avatar
Yan committed
389
390
    toolBar.addSeparator()
    toolBar.addAction(autozoomy)
Yan's avatar
Yan committed
391

Yan's avatar
Yan committed
392
393
    main_window.dragEnterEvent = lambda event: drag_entered(event)
    main_window.dropEvent = lambda event: dropped(
394
        event, main_window, augCanvas, update, config, loadthread)
Yan's avatar
Yan committed
395
    main_window.setAcceptDrops(True)
396
397
    main_window.keyPressEvent = lambda event: key_pressed(
            event, augCanvas, config)
Yan's avatar
Yan committed
398

399
400
    main_window.addToolBar(QtCore.Qt.TopToolBarArea, toolBar)
    main_window.addDockWidget(QtCore.Qt.LeftDockWidgetArea, treedock)
Yan's avatar
Yan committed
401
    main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, paramsdock)
Yan's avatar
Yan committed
402
    main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, consoledock)
403
    main_window.setStatusBar(barHandler.statusBar)
404
405
406

    main_window.setFocus()

Yan's avatar
Yan committed
407
    if len(sys.argv) == 2:
408
        load_file(main_window, augCanvas, update, config, loadthread,
409
                  filename=sys.argv[1])
Yan's avatar
Yan committed
410
    else:
411
412
        gt.pop_plot(0, 0, augCanvas.spectplot, augCanvas.ms)
        gt.pop_plot(0, 0, augCanvas.chromplot, augCanvas.chrom)
Yan's avatar
Yan committed
413
414
415

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

417

Yan's avatar
Yan committed
418
419
if __name__ == "__main__":
    main()