__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
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:
Yan's avatar
Yan committed
218
219
            imgt.clip_spect_img(augCanvas.ms, augCanvas.spectplot,
                                augCanvas.filename)
220
221
        if event.modifiers().__int__() == QtCore.Qt.ControlModifier+\
                                          QtCore.Qt.ShiftModifier:
222
            dt.clip_spectstr(augCanvas)
Yan's avatar
Yan committed
223
    if event.key() in (QtCore.Qt.Key_Left, QtCore.Qt.Key_Right):
224
        gt.shift_times(event, augCanvas)
Yan's avatar
Yan committed
225
226


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


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

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

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

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

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

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

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

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

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

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

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

362
    main_window.setCentralWidget(augCanvas)
Yan's avatar
Yan committed
363

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

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

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

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

    main_window.setFocus()

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

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

406

Yan's avatar
Yan committed
407
408
if __name__ == "__main__":
    main()