From 5e275d57e55c395238020be3d51ad05af3e40e7a Mon Sep 17 00:00:00 2001
From: Alexander Minges <alexander.minges@gmail.com>
Date: Thu, 9 Jul 2015 00:49:25 +0200
Subject: [PATCH] Move Instrument specific code to class (WIP)

---
 instruments/analytikJenaqTower2.py | 31 ++++++++++++++++
 pydsf.py                           | 57 ++++++++++++------------------
 ui/mainwindow.py                   | 20 +++++++----
 ui/mplwidget.py                    | 19 ++++++----
 4 files changed, 79 insertions(+), 48 deletions(-)
 create mode 100644 instruments/analytikJenaqTower2.py

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()