diff --git a/instruments/analytikJenaqTower2.py b/instruments/analytikJenaqTower2.py new file mode 100644 index 0000000..44146e3 --- /dev/null +++ b/instruments/analytikJenaqTower2.py @@ -0,0 +1,31 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- + +import csv +import numpy as np + + +class AnalytikJenaqTower2: + + def __init__(self): + self.name = "Analytik Jena qTower 2.0/2.2" + self.providesTempRange = False + self.providesDeltaT = False + + def loadData(self, filename, reads, wells): + with open(filename, 'r') as f: + reader = csv.reader(f, delimiter=';', quoting=csv.QUOTE_NONE) + i = 0 + for row in reader: + temp = np.zeros(reads, dtype=float) + for read in range(reads + 1): + if read > 0: + try: + temp[read - 1] = row[read] + except (IndexError, ValueError): + temp[read - 1] = 0.0 + elif read == 0: + wells[i].name = row[read] + wells[i].raw = temp + i += 1 + return wells diff --git a/pydsf.py b/pydsf.py index 62fe33a..4a6a4f3 100644 --- a/pydsf.py +++ b/pydsf.py @@ -34,13 +34,20 @@ except ImportError: try: from PyQt5.QtCore import QCoreApplication -except: +except ImportError: raise ImportError('----- PyQt5 must be installed -----') +# Import available instruments +try: + from instruments.analytikJenaqTower2 import AnalytikJenaqTower2 +except ImportError as err: + raise ImportError('Error while loading instrument plugins:', err) + _translate = QCoreApplication.translate class Well: + """ Represents a well in a microtiter plate. Owned by an object of type 'Plate'. @@ -137,23 +144,10 @@ class Well: # Run peak finding; return NaN in case of error try: peak_indexes = peakutils.indexes(y, thres=0.3) + peaks_x = peakutils.interpolate(x, y, width=3, ind=peak_indexes) - # loop over results to find maximum value for peak candidates - max_y = None # current maximum - max_i = None # index of current maximum - for peak in peak_indexes: - # if current peak is larger than old maximum and its - # second derivative is positive, replace maximum with - # current peak - if (not max_y or y[peak] > max_y) and y[peak] > 0: - max_y = y[peak] - max_i = peak - - # If a global maximum is identified, return use its x value as - # melting temperature - if max_y and max_i: - tm = x[max_i] - # if no maximum is found, return NaN + if len(peaks_x) > 0: + tm = max(peaks_x) else: return np.NaN @@ -163,9 +157,6 @@ class Well: try: if (tm and tm >= self.owner.tm_cutoff_low and tm <= self.owner.tm_cutoff_high): - tm = round(peakutils.interpolate(x, y, - width=3, - ind=[max_i])[0], 2) self.owner.denatured_wells.remove(self) # If everything is fine, remove the denatured flag return tm # and return the Tm @@ -246,8 +237,8 @@ class Well: class Experiment: + def __init__(self, exp_type, - gui=None, files=None, replicates=None, t1=25, @@ -275,7 +266,6 @@ class Experiment: self.max_tm = None self.min_tm = None self.replicates = None - self.gui = gui self.signal_threshold = signal_threshold self.avg_plate = None self.baseline_correction = baseline_correction @@ -337,7 +327,7 @@ class Experiment: Triggers analyzation of plates. """ for plate in self.plates: - plate.analyze(gui=self.gui) + plate.analyze() # if more than one plate is present, calculate average values for the # merged average plate if len(self.plates) > 1: @@ -364,6 +354,7 @@ class Experiment: class Plate: + def __init__(self, plate_type, owner, plate_id=None, filename=None, @@ -445,7 +436,7 @@ class Plate: self.wells[i].raw = temp i += 1 - def analyze(self, gui=None): + def analyze(self): try: # Try to access data file in the given path with open(self.filename) as f: @@ -455,17 +446,16 @@ class Plate: print('Error accessing file: {}'.format(e)) if self.type == 'Analytik Jena qTOWER 2.0/2.2': - self.analytikJena() - if gui: - update_progress_bar(gui.pb, 1) + instrument = AnalytikJenaqTower2() + else: # Raise exception, if the instrument's name is unknown raise NameError('Unknown instrument type: {}'.format(self.type)) + self.wells = instrument.loadData(self.filename, self.reads, self.wells) + for well in self.wells: well.analyze() - if gui: - update_progress_bar(gui.pb, 15) self.tms.append(well.tm) @@ -511,7 +501,9 @@ class Plate: elif dataType == 'derivative': d = well.derivatives[1][i] else: - raise ValueError('Valid dataTypes are "raw", "filtered", and "derivative"! dataType provided was:{}'.format(dataType)) + raise ValueError("Valid dataTypes are raw," + "filtered, and derivative! dataType" + "provided was:{}".format(dataType)) d_rounded = float(np.round(d, decimals=3)) row.append(d_rounded) writer.writerow(row) @@ -524,11 +516,8 @@ class Plate: raise NotImplementedError -def update_progress_bar(bar, value): - bar.setValue(value) - - class PlotResults(): + def plot_tm_heatmap_single(self, plate, widget): """ Plot Tm heatmap (Fig. 1) diff --git a/ui/mainwindow.py b/ui/mainwindow.py index 72bbbf3..69e503c 100644 --- a/ui/mainwindow.py +++ b/ui/mainwindow.py @@ -72,6 +72,7 @@ class TaskSignals(QObject): class Tasks(QObject): + def __init__(self): super(Tasks, self).__init__() @@ -100,6 +101,7 @@ class Tasks(QObject): class MainWindow(QMainWindow, Ui_MainWindow): + """ Class documentation goes here. """ @@ -150,7 +152,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): @pyqtSlot("QAbstractButton*") def on_buttonBox_output_clicked(self, button): if button == self.buttonBox_output.button(QDialogButtonBox.Open): - path = QFileDialog.getExistingDirectory(parent=self, caption=_translate('MainWindow', 'Choose output path'), options=QFileDialog.ShowDirsOnly) + path = QFileDialog.getExistingDirectory(parent=self, caption=_translate( + 'MainWindow', 'Choose output path'), options=QFileDialog.ShowDirsOnly) self.lineEdit_output.setText(path.strip()) @pyqtSlot("QString") @@ -186,29 +189,32 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.tabWidget.addTab(tab, title + str(plate.id)) plotter.plot_tm_heatmap_single(plate, tab) if self.checkBox_saveplots.isChecked(): - tab.canvas.save("{}/heatmap_{}.svg".format(self.outputPath, plate.id)) + tab.canvas.save( + "{}/heatmap_{}.svg".format(self.outputPath, plate.id)) tab = self.generate_plot_tab("tab_raw_{}".format(plate.id)) title = _translate("MainWindow", "Raw Data #") self.tabWidget.addTab(tab, title + str(plate.id)) plotter.plot_raw(plate, tab) if self.checkBox_saveplots.isChecked(): - tab.canvas.save("{}/raw_{}.svg".format(self.outputPath, plate.id)) + tab.canvas.save( + "{}/raw_{}.svg".format(self.outputPath, plate.id)) tab = self.generate_plot_tab("tab_derivative_{}".format(plate.id)) title = _translate("MainWindow", "Derivatives #") self.tabWidget.addTab(tab, title + str(plate.id)) plotter.plot_derivative(plate, tab) if self.checkBox_saveplots.isChecked(): - tab.canvas.save("{}/derivatives_{}.svg".format(self.outputPath, plate.id)) + tab.canvas.save( + "{}/derivatives_{}.svg".format(self.outputPath, plate.id)) else: tab = self.generate_plot_tab("tab_heatmap_{}".format(plate.id)) title = _translate("MainWindow", "Heatmap ") self.tabWidget.addTab(tab, title + str(plate.id)) plotter.plot_tm_heatmap_single(plate, tab) if self.checkBox_saveplots.isChecked(): - tab.canvas.save("{}/heatmap_{}.svg".format(self.outputPath, plate.id)) - + tab.canvas.save( + "{}/heatmap_{}.svg".format(self.outputPath, plate.id)) @pyqtSlot() def on_buttonBox_process_accepted(self): @@ -279,7 +285,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): '{}/plate_{}_dI_dT.csv'.format(self.outputPath, str(plate.id)), dataType='derivative') plate.write_data_table( '{}/plate_{}_filtered_data.csv'.format(self.outputPath, - str(plate.id)), dataType='filtered') + str(plate.id)), dataType='filtered') plate.write_data_table('{}/plate_{}_raw_data.csv'.format( self.outputPath, str(plate.id))) diff --git a/ui/mplwidget.py b/ui/mplwidget.py index b90694f..1620186 100644 --- a/ui/mplwidget.py +++ b/ui/mplwidget.py @@ -9,6 +9,7 @@ _translate = QCoreApplication.translate class MplCanvas(FigureCanvas): + def __init__(self, parent=None, width=4, height=5, dpi=100): self.fig = Figure(figsize=(width, height), dpi=dpi) self.ax = self.fig.add_subplot(111) @@ -34,20 +35,23 @@ class MplCanvas(FigureCanvas): except IOError: QtWidgets.QMessageBox.critical( self, _translate("MainWindow", "Error"), - _translate("MainWindow", "Error saving figure! Please check permissions/free space of target path!"), + _translate("MainWindow", "Error saving figure! Please check " + "permissions/free space of target path!"), QtWidgets.QMessageBox.Close, QtWidgets.QMessageBox.Close) class CustomNavigationToolbar(NavigationToolbar): + toolitems = ( - (_translate("CustomNavigationToolbar", 'Save'), + (_translate("CustomNavigationToolbar", "Save"), _translate("CustomNavigationToolbar", - 'Save the figure'), 'filesave', - 'save_figure'), - (_translate("CustomNavigationToolbar", 'Subplots'), + "Save the figure"), "filesave", + "save_figure"), + (_translate("CustomNavigationToolbar", "Subplots"), _translate("CustomNavigationToolbar", - 'Configure subplots'), 'subplots', - 'configure_subplots'), (None, None, None, None), ) + "Configure subplots"), "subplots", + "configure_subplots"), + (None, None, None, None), ) def __init__(self, canvas, parent, coordinates=True): NavigationToolbar.__init__(self, canvas, parent, @@ -55,6 +59,7 @@ class CustomNavigationToolbar(NavigationToolbar): class MplWidget(QtWidgets.QGraphicsView): + def __init__(self, parent=None): QtWidgets.QGraphicsView.__init__(self, parent) self.canvas = MplCanvas()