Browse Source

Initial commit
This is the version of the GUI for the 'new Skip-0 DMA'

Timo Dritschler 9 years ago
commit
e9998d1aa0
7 changed files with 2979 additions and 0 deletions
  1. 41 0
      bin/board.cnfg
  2. 2036 0
      bin/heb
  3. 98 0
      heb/__init__.py
  4. 429 0
      heb/board.py
  5. 98 0
      heb/io.py
  6. 259 0
      heb/plot.py
  7. 18 0
      setup.py

+ 41 - 0
bin/board.cnfg

@@ -0,0 +1,41 @@
+#
+#     HEB   (Hot Electron Bolometer) Configuration file
+#   
+#  (c) Karlsruhe Institute of Technology, 2014
+#  All rights reserved.
+#
+#  Applicable Firmware Version(s): 
+#
+
+
+[Config]
+
+fpga_delay_max = 15
+fpga_delay = 0
+fpga_delay_factor = 150
+
+chip_delay_max = 31
+chip_1_delay = 16
+chip_2_delay = 16
+chip_3_delay = 16
+chip_4_delay = 16
+chip_delay_factor = 3
+
+th_delay_max = 15
+th_delay = 12
+th_delay_factor = 150
+
+adc_delay_max = 15
+adc_1_delay = 4
+adc_2_delay = 4
+adc_4_delay = 4
+adc_3_delay = 4
+adc_delay_factor = 150
+
+th_to_adc_cycles = 7
+
+# -1 = 'ADC 1 delay is the same as th_to_adc_cycles',  0 .. 16 = 'Use this offset instead'
+adc_1_delay_individual = -1
+
+orbits_observe = 32
+orbits_skip = 15

+ 2036 - 0
bin/heb

@@ -0,0 +1,2036 @@
+#! python
+
+import sys
+import os
+import argparse
+import logging
+import time
+from PyQt4 import QtGui, QtCore
+from matplotlib.figure import Figure
+from matplotlib.backends.backend_qt4agg import (
+    FigureCanvasQTAgg as FigureCanvas,
+    NavigationToolbar2QTAgg as NavigationToolbar)
+from heb.board import write_pci
+import heb.io
+import heb.plot
+import heb.board
+import numpy as np
+from array import array as pArr
+import struct
+import matplotlib.cm as cm
+import matplotlib.colors as colors
+
+
+OPT_3D_MAP = "3D Map"
+OPT_TRAIN = "Train"
+OPT_COMBINED = "Combined"
+OPT_FFT = "Fourier transform"
+# OPT_RECONSTRUCTED = "Reconstructed"
+OPT_ADC_1 = "ADC 1"
+OPT_ADC_2 = "ADC 2"
+OPT_ADC_3 = "ADC 3"
+OPT_ADC_4 = "ADC 4"
+
+
+def enable_wait_cursor():
+    QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
+
+
+def disable_wait_cursor():
+    QtGui.QApplication.restoreOverrideCursor()
+
+
+class Spinbox(QtGui.QSpinBox):
+
+    def __init__(self, minimum, maximum, default):
+        QtGui.QSpinBox.__init__(self)
+        self.setMinimum(minimum)
+        self.setMaximum(maximum)
+        self.setValue(default)
+
+    def setValueSilent(self, value):
+        self.blockSignals(True)
+        self.setValue(value)
+        self.blockSignals(False)
+
+
+class GraphicalLED(QtGui.QWidget):
+
+    def __init__(self, parent=None, colorRGB=(255, 0, 0), height=20, width=20):
+        QtGui.QWidget.__init__(self, parent)
+        self.width = width
+        self.height = height
+        self.color = QtGui.QColor(colorRGB[0], colorRGB[1], colorRGB[2])
+        self.center = QtCore.QPoint(width, height)
+        self.setMinimumSize(2 * width, 2 * height)
+        self.setMaximumSize(2 * width, 2 * height)
+
+    def paintEvent(self, event):
+        paint = QtGui.QPainter()
+        paint.begin(self)
+        paint.setRenderHint(QtGui.QPainter.Antialiasing)
+
+        # draw a grey 'socket' for the LED
+        paint.setPen(QtGui.QColor(160, 160, 160))
+        paint.setBrush(QtGui.QColor(180, 180, 180))
+        paint.drawEllipse(self.center, self.width, self.height)
+
+        # draw the body of the LED
+        paint.setBrush(self.color)
+        paint.drawEllipse(self.center, self.width*0.85, self.height*0.85)
+
+        # Some versions of Qt4 differ in the way their QLinearGradients
+        # work... But i don't want to remove the functionality :)
+        if False:
+            self.draw_light_reflex(paint)
+
+    def draw_light_reflex(self, paint):
+        # set up a light reflex ellipse on the LED
+        paint.translate(self.center)
+        paint.rotate(-45.0)
+        paint.setPen(QtGui.QColor(255, 255, 255, 0))
+        highlight_center = QtCore.QPoint(0, self.width*(-0.34))
+
+        # light reflex is pure white and fades to transparent at top of LED
+        gradient = QtGui.QLinearGradient(highlight_center*1.5, QtCore.QPoint(0, 0))
+        gradient.setColorAt(0.0, QtGui.QColor(255, 255, 255, 120))
+        gradient.setColorAt(1.0, QtGui.QColor(255, 255, 255, 0))
+
+        # draw the light reflex using the gradient brush
+        paint.setBrush(gradient)
+        paint.drawEllipse(highlight_center, self.width*0.63, self.width*0.42)
+
+        paint.end()
+
+    def set_color(self, colorRGB=(255, 0, 0)):
+        self.color = QtGui.QColor(colorRGB[0], colorRGB[1], colorRGB[2])
+        self.update()
+
+
+class BitsDisplayTable(QtGui.QTableWidget):
+
+        def __init__(self, value, parent=None, optimalSize=True):
+            QtGui.QTableWidget.__init__(self, parent)
+            self.numbers = str(value)
+            if len(self.numbers) == 0:
+                raise ValueError("Cant create a table for a value of length 0.")
+            self.length = len(self.numbers)
+            self.do_style()
+            if optimalSize is True:
+                self.do_optimal_size()
+
+        def do_style(self):
+            self.horizontalHeader().setDefaultSectionSize(35)
+            self.horizontalHeader().setResizeMode(QtGui.QHeaderView.Fixed)
+            self.horizontalHeader().setVisible(True)
+            self.verticalHeader().setDefaultSectionSize(17)
+            self.verticalHeader().setResizeMode(QtGui.QHeaderView.Fixed)
+            self.verticalHeader().setVisible(False)
+            self.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
+            self.setRowCount(1)
+            self.setColumnCount(self.length)
+
+            # If self.length would be 5, this line would generate ('4', '3',
+            # '2', '1', '0')
+            headers = tuple([str(i) for i in reversed(range(0, self.length))])
+            self.setHorizontalHeaderLabels(headers)
+
+            for i in range(len(self.numbers)):
+                item = QtGui.QTableWidgetItem(self.numbers[i])
+                item.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
+                self.setItem(0, i, item)
+
+        def width(self):
+            width = 6
+            if self.verticalHeader().isHidden() is False:
+                width = self.verticalHeader().width() + 6
+
+            for i in range(self.columnCount()):
+                width = width + self.columnWidth(i)
+
+            return width
+
+        def height(self):
+            height = 6
+            if self.horizontalHeader().isHidden() is False:
+                height = self.horizontalHeader().height() + 6
+            for i in range(self.rowCount()):
+                height = height + self.rowHeight(i)
+
+            return height
+
+        def do_optimal_size(self):
+            size = QtCore.QSize(self.width(), self.height())
+            self.setMaximumSize(size)
+            self.setMinimumSize(size)
+
+        def stretch_to_width(self, width_in):
+            width = self.width()
+            if width >= width_in:
+                return
+
+            factor = width_in/float(width)
+            error = 0
+            for i in range(self.length):
+                current_cell_size = self.columnWidth(i)
+                new_cell_size = int(current_cell_size * factor)
+                error += new_cell_size - (current_cell_size * factor)
+                if (error >= 1.0) or (error <= -1.0):
+                    new_cell_size -= int(error)
+                    error -= int(error)
+                self.horizontalHeader().resizeSection(i, new_cell_size)
+
+            self.do_optimal_size()
+
+        def set_item(self, row, col, value):
+            item = QtGui.QTableWidgetItem(str(value))
+            item.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
+            width = self._get_table_item_width(QtGui.QTableWidgetItem(value))
+            if width > self.columnWidth(col):
+                self.horizontalHeader().resizeSection(col, width)
+            self.setItem(row, col, item)
+
+        def set_numbers(self, value):
+            new_numbers = str(value)
+            if len(new_numbers) == 0:
+                raise ValueError("Cant create a table for a value of length 0.")
+            if len(new_numbers) != len(self.numbers):
+                raise ValueError("New Values for table don't match size."
+                                 "Expected size %i but got %i" % (len(self.numbers), len(new_numbers)))
+            self.numbers = new_numbers
+            for i in range(len(self.numbers)):
+                item = self.item(0, i)
+                item.setText(self.numbers[i])
+
+        def set_label(self, start, end, label, color=None):
+            if (start < 0) or (end > self.columnCount()-1) or (start > end):
+                raise ValueError("Invalid Start and End positions for Label: %s" % label)
+            if self.rowCount() < 2:
+                self.insertRow(1)
+                for i in range(self.length):
+                    self.setItem(1, i, QtGui.QTableWidgetItem(''))
+
+            span = (end-start)+1
+            if span > 1:
+                self.setSpan(1, start, 1, span)
+            item = QtGui.QTableWidgetItem(label)
+            item.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
+            if color:
+                item.setBackground(color)
+            self.setItem(1, start, item)
+
+            # Check if the label is larger then then cells it spans and resize the cells
+            # accordingly, if the label ends up larger then the cells.
+            label_width = self._get_table_item_width(QtGui.QTableWidgetItem(label))
+            cells_width = 0
+            for i in range(start, end+1):
+                cells_width = cells_width + self.columnWidth(i)
+
+            if label_width > cells_width:
+                new_cell_size = label_width/span
+                for i in range(start, end+1):
+                    self.horizontalHeader().resizeSection(i, new_cell_size)
+
+            self.do_optimal_size()
+
+        def grey_out_column(self, column):
+            if (column < 0) or (column > self.length):
+                raise ValueError("Supplied column is out of range for this table")
+            for i in range(self.rowCount()):
+                self.item(i, column).setForeground(QtGui.QColor(120, 120, 120))
+                self.item(i, column).setBackground(QtGui.QColor(200, 200, 200))
+
+        def undo_grey_out_column(self, column):
+            if (column < 0) or (column > self.length):
+                raise ValueError("Supplied column is out of range for this table")
+            for i in range(self.rowCount()):
+                self.item(i, column).setForeground(QtGui.QColor(0, 0, 0))
+                self.item(i, column).setBackground(QtGui.QColor(255, 255, 255))
+
+        # Create a table just to insert our item and show how large its width ends up...
+        # We have to use this stupid workaround since QTableWidgetItem has no width() property...
+        def _get_table_item_width(self, item):
+            table = QtGui.QTableWidget()
+            table.horizontalHeader().setResizeMode(QtGui.QHeaderView.ResizeToContents)
+            table.setRowCount(1)
+            table.setColumnCount(1)
+            table.setItem(0, 0, item)
+            width = table.columnWidth(0)
+            table.deleteLater()
+            return width
+
+
+class BitsEditTable(BitsDisplayTable):
+
+    def __init__(self, value, parent=None, optimalSize=True):
+        BitsDisplayTable.__init__(self, value, parent, optimalSize)
+        self.checkboxes = []
+        self.populate_checkboxes()
+        self.verticalHeader().setDefaultSectionSize(35)
+        self.do_optimal_size()
+
+    def populate_checkboxes(self):
+        for i in range(self.length):
+            widget = QtGui.QWidget()
+            self.checkboxes += [QtGui.QCheckBox()]
+            layout = QtGui.QHBoxLayout(widget)
+            layout.addWidget(self.checkboxes[i])
+            layout.setAlignment(QtCore.Qt.AlignCenter)
+            layout.setContentsMargins(0, 0, 0, 0)
+            widget.setLayout(layout)
+            self.setCellWidget(0, i, widget)
+
+    def set_numbers(self, value):
+            new_numbers = str(value)
+            if len(new_numbers) == 0:
+                raise ValueError("Cant create a table for a value of length 0.")
+            if len(new_numbers) != len(self.numbers):
+                raise ValueError("New Values for table dont match size."
+                                 "Expected size %i but got %i" % (len(self.numbers), len(new_numbers)))
+            self.numbers = new_numbers
+            for i in range(len(self.numbers)):
+                if self.numbers[i] == '1':
+                    self.checkboxes[i].setChecked(True)
+                else:
+                    self.checkboxes[i].setChecked(False)
+
+    def get_bits(self):
+        bits = ''
+        for i in range(self.length):
+            if self.checkboxes[i].isChecked():
+                bits += '1'
+            else:
+                bits += '0'
+        return bits
+
+    def get_bit(self, bit):
+        if bit > self.length:
+            return None
+        return self.checkboxes[(self.length - bit) - 1].isChecked()
+
+    def clear_all_bits(self):
+        for i in range(self.length):
+            self.checkboxes[i].setChecked(False)
+
+
+class PlotCanvas(FigureCanvas):
+    def __init__(self, parent=None, width=5, height=4, dpi=100):
+        self.figure = Figure(figsize=(width, height), dpi=dpi)
+
+        FigureCanvas.__init__(self, self.figure)
+        self.setParent(parent)
+
+        FigureCanvas.setSizePolicy(self,
+                                   QtGui.QSizePolicy.Expanding,
+                                   QtGui.QSizePolicy.Expanding)
+        FigureCanvas.updateGeometry(self)
+
+    def update_figure(self, opt_widgets, data, frm, to, label=None):
+        adcs = [w for w in (('ADC {}'.format(i), i) for i in range(1, 5))
+                if opt_widgets[w[0]].isChecked()]
+
+        def is_active(mode):
+            return opt_widgets[mode].isChecked() and adcs
+
+        if not data or frm == to:
+            return
+
+        self.figure.clear()
+        self.figure.subplots_adjust(right=0.9)
+        self.figure.suptitle(os.path.basename(data.filename))
+
+        if is_active(OPT_3D_MAP):
+            self.plot_heatmaps(adcs, data, frm, to)
+
+        if is_active(OPT_TRAIN):
+            args = {'label': label} if label else {}
+            self.plot_train(adcs, data, frm, to, **args)
+
+        if opt_widgets[OPT_COMBINED].isChecked() and adcs:
+            self.plot_combined(adcs, data, frm, to, False)
+
+        if is_active(OPT_FFT):
+            self.plot_fft(adcs, data, frm, to)
+
+        self.draw()
+
+    def plot_heatmaps(self, adcs, data, frm, to):
+        n_adcs = len(adcs)
+        n_rows = 2 if n_adcs > 2 else 1
+        n_cols = 2 if n_adcs > 1 else 1
+
+        for i, (name, adc) in enumerate(adcs):
+            axis = self.figure.add_subplot(n_rows, n_cols, i+1)
+            axis.set_title(name)
+            heatmap = data.heatmap(adc, frm, to)
+            image = heb.plot.heatmap(heatmap, axis)
+
+        bar_axis = self.figure.add_axes([0.85, 0.15, 0.05, 0.7])
+        self.figure.subplots_adjust(right=0.8)
+        self.figure.colorbar(image, cax=bar_axis)
+
+    def plot_train(self, adcs, data, frm, to, **kwargs):
+        n_adcs = len(adcs)
+        n_rows = 2 if n_adcs > 2 else 1
+        n_cols = 2 if n_adcs > 1 else 1
+
+        for i, (name, adc) in enumerate(adcs):
+            axis = self.figure.add_subplot(n_rows, n_cols, i+1)
+            heb.plot.train(data, axis, adc, frm, to, **kwargs)
+
+    def plot_combined(self, adcs, data, frm, to, show_reconstructed):
+        axis = self.figure.add_subplot(111)
+        heb.plot.combined(data, axis, adcs, frm, to, show_reconstructed)
+
+    def plot_fft(self, adcs, data, frm, to):
+        n_adcs = len(adcs)
+        n_rows = 2 if n_adcs > 2 else 1
+        n_cols = 2 if n_adcs > 1 else 1
+
+        for i, (name, adc) in enumerate(adcs):
+            axis = self.figure.add_subplot(n_rows, n_cols, i+1)
+            axis.set_title(name)
+            image = heb.plot.fft(data, axis, adc, frm, to)  # <-- This needs to also pass the 'skipped' value!
+
+        bar_axis = self.figure.add_axes([0.85, 0.15, 0.05, 0.7])
+        self.figure.subplots_adjust(right=0.8)
+        self.figure.colorbar(image, cax=bar_axis)
+
+
+class SimpleStatusDisplay(QtGui.QWidget):
+    def __init__(self, parent=None):
+        QtGui.QWidget.__init__(self, parent)
+        self.do_layout()
+
+    def do_layout(self):
+        self.setWindowTitle("Board Status")
+        status_displays = QtGui.QGridLayout()
+
+        status_displays.addWidget(QtGui.QLabel("Board Status Readouts"))
+
+        # First Label + LED Group
+        self.data_flow_pipeline_led = GraphicalLED(self, (128, 128, 128), 10, 10)
+        led_group_1_layout = QtGui.QGridLayout(self)
+        label_group_1 = QtGui.QLabel("DataFlow Pipeline")
+        label_group_1.setMinimumWidth(100)
+        led_group_1_layout.addWidget(label_group_1, 0, 1)
+        led_group_1_layout.addWidget(self.data_flow_pipeline_led, 0, 0)
+        led_group_1_layout.setColumnStretch(2, 1)
+        led_group_1_widget = QtGui.QWidget(self)
+        led_group_1_widget.setLayout(led_group_1_layout)
+        status_displays.addWidget(led_group_1_widget, 1, 0)
+
+        # Second Label + LED Group
+        self.master_ctrl_led = GraphicalLED(self, (128, 128, 128), 10, 10)
+        led_group_2_layout = QtGui.QGridLayout(self)
+        label_group_2 = QtGui.QLabel("Master Control")
+        label_group_2.setMinimumWidth(100)
+        led_group_2_layout.addWidget(label_group_2, 0, 1)
+        led_group_2_layout.addWidget(self.master_ctrl_led, 0, 0)
+        led_group_2_layout.setColumnStretch(2, 1)
+        led_group_2_widget = QtGui.QWidget(self)
+        led_group_2_widget.setLayout(led_group_2_layout)
+        status_displays.addWidget(led_group_2_widget, 2, 0)
+
+        # Third Label + LED Group
+        self.data_check_led = GraphicalLED(self, (128, 128, 128), 10, 10)
+        led_group_3_layout = QtGui.QGridLayout(self)
+        label_group_3 = QtGui.QLabel("Data Check")
+        label_group_3.setMinimumWidth(100)
+        led_group_3_layout.addWidget(label_group_3, 0, 1)
+        led_group_3_layout.addWidget(self.data_check_led, 0, 0)
+        led_group_3_layout.setColumnStretch(2, 1)
+        led_group_3_widget = QtGui.QWidget(self)
+        led_group_3_widget.setLayout(led_group_3_layout)
+        status_displays.addWidget(led_group_3_widget, 3, 0)
+
+        # Fourth Label + LED Group
+        self.pll_ld_led = GraphicalLED(self, (128, 128, 128), 10, 10)
+        led_group_4_layout = QtGui.QGridLayout(self)
+        label_group_4 = QtGui.QLabel("PLL_LD")
+        label_group_4.setMinimumWidth(100)
+        led_group_4_layout.addWidget(label_group_4, 0, 1)
+        led_group_4_layout.addWidget(self.pll_ld_led, 0, 0)
+        led_group_4_layout.setColumnStretch(2, 1)
+        led_group_4_widget = QtGui.QWidget(self)
+        led_group_4_widget.setLayout(led_group_4_layout)
+        status_displays.addWidget(led_group_4_widget, 4, 0)
+
+        status_displays.setColumnStretch(5, 2)
+        status_displays.setRowStretch(5, 1)
+        self.setLayout(status_displays)
+
+    def update_status(self, registers):
+        try:
+            bits = []
+            bits += ['{0:032b}'.format(registers[0])]
+            bits += ['{0:032b}'.format(registers[1])]
+            bits += ['{0:032b}'.format(registers[2])]
+
+            s1 = heb.board.get_dec_from_bits(bits[0], 2, 0)
+            if s1 == 0:
+                # Pipeline in reset mode
+                self.data_flow_pipeline_led.set_color((255, 255, 0))
+            elif s1 == 1:
+                # Pipeline is idle
+                self.data_flow_pipeline_led.set_color((0, 190, 0))
+            elif s1 == 6:
+                # Pipeline in error state
+                self.data_flow_pipeline_led.set_color((255, 0, 0))
+            else:
+                # Should not happen!
+                self.data_flow_pipeline_led.set_color((128, 128, 128))
+
+            s2 = heb.board.get_dec_from_bits(bits[0], 29, 26)
+            if s2 == 0:
+                # Master Control in reset mode
+                self.master_ctrl_led.set_color((255, 255, 0))
+            elif s2 == 1:
+                # Master Control is idle
+                self.master_ctrl_led.set_color((0, 190, 0))
+            elif s2 == 8:
+                # Master Control in error state
+                self.master_ctrl_led.set_color((255, 0, 0))
+            else:
+                # Should not happen!
+                self.master_ctrl_led.set_color((128, 128, 128))
+
+            s3 = heb.board.get_dec_from_bits(bits[2], 15, 12)
+            if s3 == 15:
+                # Data Check Idle
+                self.data_check_led.set_color((0, 190, 0))
+            else:
+                # Data Check Error
+                self.data_check_led.set_color((255, 255, 0))
+
+            s4 = int(bits[0][7])
+            if s4 == 0:
+                # PLL_LD not active
+                self.pll_ld_led.set_color((255, 255, 0))
+            elif s4 == 1:
+                # PLL_LD is active
+                self.pll_ld_led.set_color((0, 190, 0))
+
+        except IndexError:
+            QtGui.QMessageBox.critical(self, "Status Update Error", "Not enough registers give for status update.")
+            return
+
+
+class AdvancedBoardInterface(QtGui.QWidget):
+
+    def __init__(self, parent=None):
+        QtGui.QWidget.__init__(self, parent)
+        self.parent = parent
+        self.do_layout()
+        self.data_flow_pipeline_status = None
+
+    def do_layout(self):
+
+        table_grid = QtGui.QGridLayout()
+
+        table_grid.addWidget(QtGui.QLabel("Status1 Register 0x9050 (Readonly)"), 0, 0)
+        self.status1_table = BitsDisplayTable(32*'0', self)
+        self.do_status1_table_layout(self.status1_table)
+        table_grid.addWidget(self.status1_table, 1, 0)
+
+        table_grid.addWidget(QtGui.QLabel("Status2 Register 0x9054 (Readonly)"), 2, 0)
+        self.status2_table = BitsDisplayTable(32*'0', self)
+        self.do_status2_table_layout(self.status2_table)
+        table_grid.addWidget(self.status2_table, 3, 0)
+
+        table_grid.addWidget(QtGui.QLabel("Status3 Register 0x9058 (Readonly)"), 4, 0)
+        self.status3_table = BitsDisplayTable(32*'0', self)
+        self.do_status3_table_layout(self.status3_table)
+        table_grid.addWidget(self.status3_table, 5, 0)
+
+        table_grid.addItem(QtGui.QSpacerItem(1, 20), 6, 0)
+        table_grid.addWidget(QtGui.QLabel("Control Register 0x9040"), 7, 0)
+
+        buttons_box = QtGui.QHBoxLayout()
+        buttons_box.setAlignment(QtCore.Qt.AlignLeft)
+        self.write_control_button = QtGui.QPushButton("Write Values to board")
+        self.write_control_button.setMaximumWidth(200)
+        buttons_box.addWidget(self.write_control_button)
+        self.clear_control_button = QtGui.QPushButton("Clear Input")
+        self.clear_control_button.setMaximumWidth(200)
+        buttons_box.addWidget(self.clear_control_button)
+        self.check_status_control_button = QtGui.QPushButton("Check Status")
+        self.check_status_control_button.setMaximumWidth(200)
+        buttons_box.addWidget(self.check_status_control_button)
+        buttons_widget = QtGui.QWidget()
+        buttons_widget.setLayout(buttons_box)
+        table_grid.addWidget(buttons_widget, 8, 0)
+
+        self.control_table = BitsEditTable(32*'0', self)
+        self.do_control_table_layout(self.control_table)
+        table_grid.addWidget(self.control_table, 9, 0)
+
+        self.write_control_button.clicked.connect(self.send_control_to_board)
+        self.clear_control_button.clicked.connect(self.control_table.clear_all_bits)
+        self.check_status_control_button.clicked.connect(self.parent.do_status_readout)
+
+        width1 = self.status1_table.width()
+        width2 = self.status2_table.width()
+        width3 = self.status3_table.width()
+        width4 = self.control_table.width()
+        max_width = max(width1, max(width2, max(width3, width4)))
+        self.status1_table.stretch_to_width(max_width)
+        self.status2_table.stretch_to_width(max_width)
+        self.status3_table.stretch_to_width(max_width)
+        self.control_table.stretch_to_width(max_width)
+
+        self.setLayout(table_grid)
+
+    def send_control_to_board(self):
+        if not self.parent._check_for_no_continuous_read():
+            logging.log("Cant write to board while continuous readout is active")
+            return
+
+        bits = self.control_table.get_bits()
+        dec_val_bits = int(bits, 2)
+        self.parent.text_area.write("Writing to board Register 0x9040: %s" % ('0x{0:08x}'.format(dec_val_bits)))
+        try:
+            heb.board.write_pci(hex(dec_val_bits), '0x9040')
+        except heb.board.BoardError as e:
+            QtGui.QMessageBox.critical(self, "Board communication",
+                                       "Was unable to write value to board!\nReason: "+str(e))
+
+        self.parent.do_status_readout()
+
+    def do_status1_table_layout(self, table):
+        # from right to left
+        table.set_label(29, 31, "FSM_Data_Pipeline_Status", QtCore.Qt.green)
+        table.grey_out_column(28)
+        table.set_label(27, 27, "FULL", QtCore.Qt.green)
+        table.set_label(26, 26, "EMPTY", QtCore.Qt.green)
+        table.grey_out_column(25)
+        table.grey_out_column(24)
+        table.set_label(14, 23, "RD_data_Counts", QtCore.Qt.green)
+        table.set_label(13, 13, "OVR_ADC", QtGui.QColor(210, 210, 0))
+        table.grey_out_column(12)
+        table.grey_out_column(11)
+        table.grey_out_column(10)
+        table.grey_out_column(9)
+        table.grey_out_column(8)
+        table.set_label(7, 7, "PLL_LD", QtGui.QColor(210, 210, 0))
+        table.grey_out_column(6)
+        table.set_label(2, 5, "Master Control", QtCore.Qt.green)
+        table.grey_out_column(1)
+        table.set_label(0, 0, "1")
+
+    def do_status2_table_layout(self, table):
+        #from right to left
+        table.set_label(31, 31, "FIFO 128 255 empty", QtCore.Qt.green)
+        table.set_label(30, 30, "FIFO 128 255 full", QtCore.Qt.green)
+        table.grey_out_column(29)
+        table.grey_out_column(28)
+        table.set_label(17, 27, "wr data count 128 255", QtGui.QColor(210, 210, 0))
+        table.grey_out_column(16)
+        table.set_label(15, 15, "FIFO 255 64 empty", QtCore.Qt.green)
+        table.set_label(14, 14, "FIFO 255 64 full", QtCore.Qt.green)
+        table.grey_out_column(13)
+        table.grey_out_column(12)
+        table.set_label(2, 11, "rd data count 255 64", QtGui.QColor(210, 210, 0))
+        table.grey_out_column(1)
+        table.grey_out_column(0)
+
+    def do_status3_table_layout(self, table):
+        #from right to left
+        table.set_label(29, 31, "FSM_ARBITER_DDR3", QtCore.Qt.green)
+        table.grey_out_column(28)
+        table.set_label(25, 27, "FSM_WR_DDR3", QtCore.Qt.green)
+        table.grey_out_column(24)
+        table.set_label(21, 23, "FSM_R_DDR3", QtCore.Qt.green)
+        table.grey_out_column(20)
+        table.set_label(16, 19, "BC_ERROR", QtGui.QColor(210, 210, 0))
+        table.set_label(1, 15, "Number of wrong BC", QtGui.QColor(255, 255, 0))
+        table.grey_out_column(0)
+
+    def do_control_table_layout(self, table):
+        #from right to left
+        table.set_label(31, 31, "reset", QtGui.QColor(0, 255, 255))
+        table.grey_out_column(30)
+        table.grey_out_column(29)
+        table.grey_out_column(28)
+        table.set_label(27, 27, "ADC_1(A+D)", QtGui.QColor(0, 255, 255))
+        table.set_label(26, 26, "ADC_2(A+D)2", QtGui.QColor(0, 255, 255))
+        table.set_label(25, 25, "T/H_1", QtGui.QColor(210, 210, 0))
+        table.set_label(24, 24, "T/H_2", QtGui.QColor(210, 210, 0))
+        table.set_label(23, 23, "T/H_3", QtGui.QColor(210, 210, 0))
+        table.set_label(22, 22, "T/H_4", QtGui.QColor(210, 210, 0))
+        table.set_label(21, 21, "EN_data_Trans", QtCore.Qt.yellow)
+        table.set_label(20, 20, "EN_readout", QtCore.Qt.yellow)
+        table.set_label(18, 19, "ADC_1", QtCore.Qt.yellow)
+        table.set_label(16, 17, "ADC_2", QtCore.Qt.yellow)
+        table.set_label(14, 15, "ADC_3", QtCore.Qt.yellow)
+        table.set_label(12, 13, "ADC_4", QtCore.Qt.yellow)
+        table.grey_out_column(11)
+        table.grey_out_column(10)
+        table.grey_out_column(9)
+        table.grey_out_column(8)
+        table.grey_out_column(7)
+        table.grey_out_column(6)
+        table.grey_out_column(5)
+        table.grey_out_column(4)
+        table.set_label(3, 3, "Header", QtCore.Qt.green)
+        table.grey_out_column(2)
+        table.set_label(1, 1, "Pilot Bunch by FPGA", QtGui.QColor(0, 255, 255))
+        table.set_label(0, 0, "FPGA Temp monitor Reset", QtGui.QColor(0, 255, 255))
+
+    def update_status(self, registers):
+        try:
+            self.status1_table.set_numbers('{0:032b}'.format(registers[0]))
+            self.status2_table.set_numbers('{0:032b}'.format(registers[1]))
+            self.status3_table.set_numbers('{0:032b}'.format(registers[2]))
+        except:
+            return
+
+
+class PopupDialog(QtGui.QDialog):
+    def __init__(self, text, parent=None):
+        QtGui.QDialog.__init__(self, parent)
+        self.text = text
+        self.setWindowTitle("User action required")
+        self.do_layout()
+        self.return_value = False
+
+    def do_layout(self):
+        size = QtCore.QSize(200, 200)
+        #self.setMaximumSize(size)
+        self.setMinimumSize(size)
+        box = QtGui.QVBoxLayout()
+        self.text_label = QtGui.QLabel(self.text)
+        self.text_label.setAlignment(QtCore.Qt.AlignCenter)
+        box.addWidget(self.text_label)
+        self.okay_btn = QtGui.QPushButton("Okay")
+        self.okay_btn.setStyleSheet("padding: 15px;")
+        self.okay_btn.clicked.connect(self.on_okay)
+        box.addWidget(self.okay_btn)
+        box.addSpacerItem(QtGui.QSpacerItem(1, 20))
+        self.cancel_btn = QtGui.QPushButton("Cancel")
+        self.cancel_btn.setStyleSheet("padding: 15px;")
+        self.cancel_btn.clicked.connect(self.on_cancel)
+        box.addWidget(self.cancel_btn)
+        self.setLayout(box)
+
+    def on_okay(self):
+        self.return_value = True
+        self.close()
+
+    def on_cancel(self):
+        self.close()
+
+    def get_return_value(self):
+        return self.return_value
+
+
+class RegisterBitsDialog(QtGui.QDialog):
+    def __init__(self, n_bits, parent=None):
+        QtGui.QDialog.__init__(self, parent)
+        self.n_bits = n_bits
+        self.setWindowTitle("Register Bit Input Helper")
+        self.do_layout()
+        self.return_bits = None
+
+    def do_layout(self):
+        size = QtCore.QSize(200, 200)
+        #self.setMaximumSize(size)
+        self.setMinimumSize(size)
+        box = QtGui.QVBoxLayout()
+        self.table = BitsEditTable("0"*self.n_bits)
+        box.addWidget(self.table)
+        self.okay_btn = QtGui.QPushButton("Okay")
+        self.okay_btn.setStyleSheet("padding: 15px;")
+        self.okay_btn.clicked.connect(self.on_okay)
+        box.addWidget(self.okay_btn)
+        box.addSpacerItem(QtGui.QSpacerItem(1, 20))
+        self.cancel_btn = QtGui.QPushButton("Cancel")
+        self.cancel_btn.setStyleSheet("padding: 15px;")
+        self.cancel_btn.clicked.connect(self.on_cancel)
+        box.addWidget(self.cancel_btn)
+        self.setLayout(box)
+
+    def on_okay(self):
+        self.return_bits = self.table.get_bits()
+        self.close()
+
+    def on_cancel(self):
+        self.close()
+
+    def get_return_value(self):
+        return self.return_bits
+
+
+class FileLikeQTextEdit(QtGui.QTextEdit):
+    def __init__(self, parent=None):
+        QtGui.QTextEdit.__init__(self, parent)
+
+    def write(self, str_in):
+        self.append(str_in)
+
+    def flush(self):
+        pass
+
+    def close(self):
+        pass
+
+
+class LogHandler(logging.Handler):
+    def __init__(self, text_area):
+        logging.Handler.__init__(self)
+        self.text_area = text_area
+
+    def emit(self, record):
+        self.text_area.write(self.format(record))
+
+
+class ApplicationWindow(QtGui.QMainWindow):
+    def __init__(self, args):
+        QtGui.QMainWindow.__init__(self)
+        self.args = args
+        self.board_config = heb.board.BoardConfiguration(self.args.config if self.args.config else None)
+        self.opt_widgets = {}
+        self.data = None
+        self.continuous_read = False
+
+        self.do_layout()
+        self.register_observers()
+
+        if args.input:
+            self.read_and_update_data_from_file(args.input)
+
+    def new_checkbox(self, name, func):
+        checkbox = QtGui.QCheckBox(name)
+        checkbox.clicked.connect(func)
+        self.opt_widgets[name] = checkbox
+        return checkbox
+
+    def new_radiobutton(self, name, func):
+        button = QtGui.QRadioButton(name)
+        button.clicked.connect(func)
+        self.opt_widgets[name] = button
+        return button
+
+    def new_datawidget(self):
+        splitter = QtGui.QSplitter(self)
+
+        self.canvas = PlotCanvas(splitter)
+        self.mpl_toolbar = NavigationToolbar(self.canvas, splitter)
+
+        canvas_box = QtGui.QVBoxLayout()
+        canvas_box.addWidget(self.canvas)
+        canvas_box.addWidget(self.mpl_toolbar)
+        canvas_widget = QtGui.QWidget()
+        canvas_widget.setLayout(canvas_box)
+        sidebar = QtGui.QGridLayout()
+
+        data_view_groupbox = QtGui.QGroupBox()
+        data_view_groupbox.setTitle('Data View Settings')
+        data_view_layout = QtGui.QGridLayout()
+        data_view_layout.setVerticalSpacing(5)
+        data_view_layout.addWidget(self.new_radiobutton(OPT_3D_MAP, self.on_option_change), 1, 0)
+        data_view_layout.addWidget(self.new_radiobutton(OPT_TRAIN, self.on_option_change), 2, 0)
+        data_view_layout.addWidget(self.new_radiobutton(OPT_FFT, self.on_option_change), 3, 0)
+        data_view_layout.addWidget(self.new_radiobutton(OPT_COMBINED, self.on_option_change), 4, 0)
+        # sidebar.addWidget(self.new_checkbox(OPT_RECONSTRUCTED, self.on_option_change))
+        data_view_layout.addWidget(self.new_checkbox(OPT_ADC_1, self.on_option_change), 1, 1)
+        data_view_layout.addWidget(self.new_checkbox(OPT_ADC_2, self.on_option_change), 2, 1)
+        data_view_layout.addWidget(self.new_checkbox(OPT_ADC_3, self.on_option_change), 3, 1)
+        data_view_layout.addWidget(self.new_checkbox(OPT_ADC_4, self.on_option_change), 4, 1)
+        self.from_spinbox = Spinbox(0, 999, 0)
+        self.from_spinbox.valueChanged.connect(self.on_option_change)
+        self.to_spinbox = Spinbox(-1, 10000000, 1000)
+        self.to_spinbox.valueChanged.connect(self.on_to_changed)
+        data_view_layout.addWidget(QtGui.QLabel("Data range"), 5, 0)
+        data_view_layout.addWidget(self.from_spinbox, 6, 0)
+        data_view_layout.addWidget(self.to_spinbox, 6, 1)
+        self.opt_widgets[OPT_ADC_1].setChecked(True)
+        data_view_groupbox.setLayout(data_view_layout)
+        sidebar.addWidget(data_view_groupbox, 0, 0, 1, 2)  # Span both columns
+
+        adc_settings_groupbox = QtGui.QGroupBox()
+        adc_settings_groupbox.setTitle('Timing Settings')
+        adc_delay_grid = QtGui.QGridLayout()
+        adc_delay_grid.setVerticalSpacing(5)
+        adc_delay_grid.addWidget(QtGui.QLabel("T/H Delay"), 0, 1, 1, 2)
+        self.th_delay_spinbox = Spinbox(0, 15, 0)
+        self.th_delay_spinbox.setMaximumWidth(80)
+        self.th_delay_spinbox.valueChanged.connect(self.on_adc_delay_changed)
+        adc_delay_grid.addWidget(self.th_delay_spinbox, 1, 1)
+        adc_delay_grid.addWidget(QtGui.QLabel("Coarse Delay"), 1, 0)
+        adc_delay_grid.addWidget(QtGui.QLabel("ADC_1"), 2, 1)
+        adc_delay_grid.addWidget(QtGui.QLabel("ADC_2"), 2, 2)
+        adc_delay_grid.addWidget(QtGui.QLabel("ADC_3"), 2, 3)
+        adc_delay_grid.addWidget(QtGui.QLabel("ADC_4"), 2, 4)
+        self.adc_1_delay_spinbox = Spinbox(0, 31, 0)
+        self.adc_1_delay_spinbox.setMaximumWidth(80)
+        self.adc_1_delay_spinbox.valueChanged.connect(self.on_adc_delay_changed)
+        self.adc_2_delay_spinbox = Spinbox(0, 31, 0)
+        self.adc_2_delay_spinbox.setMaximumWidth(80)
+        self.adc_2_delay_spinbox.valueChanged.connect(self.on_adc_delay_changed)
+        self.adc_3_delay_spinbox = Spinbox(0, 31, 0)
+        self.adc_3_delay_spinbox.setMaximumWidth(80)
+        self.adc_3_delay_spinbox.valueChanged.connect(self.on_adc_delay_changed)
+        self.adc_4_delay_spinbox = Spinbox(0, 31, 0)
+        self.adc_4_delay_spinbox.setMaximumWidth(80)
+        self.adc_4_delay_spinbox.valueChanged.connect(self.on_adc_delay_changed)
+        adc_delay_grid.addWidget(QtGui.QLabel("Fine Delay"), 3, 0)
+        adc_delay_grid.addWidget(self.adc_1_delay_spinbox, 3, 1)
+        adc_delay_grid.addWidget(self.adc_2_delay_spinbox, 3, 2)
+        adc_delay_grid.addWidget(self.adc_3_delay_spinbox, 3, 3)
+        adc_delay_grid.addWidget(self.adc_4_delay_spinbox, 3, 4)
+        self.adc_1_delay_text = QtGui.QLineEdit()
+        self.adc_1_delay_text.setReadOnly(True)
+        self.adc_1_delay_text.setMaximumWidth(80)
+        self.adc_2_delay_text = QtGui.QLineEdit()
+        self.adc_2_delay_text.setReadOnly(True)
+        self.adc_2_delay_text.setMaximumWidth(80)
+        self.adc_3_delay_text = QtGui.QLineEdit()
+        self.adc_3_delay_text.setReadOnly(True)
+        self.adc_3_delay_text.setMaximumWidth(80)
+        self.adc_4_delay_text = QtGui.QLineEdit()
+        self.adc_4_delay_text.setReadOnly(True)
+        self.adc_4_delay_text.setMaximumWidth(80)
+        adc_delay_grid.addWidget(QtGui.QLabel("Total Delay"), 4, 0)
+        adc_delay_grid.addWidget(self.adc_1_delay_text, 4, 1)
+        adc_delay_grid.addWidget(self.adc_2_delay_text, 4, 2)
+        adc_delay_grid.addWidget(self.adc_3_delay_text, 4, 3)
+        adc_delay_grid.addWidget(self.adc_4_delay_text, 4, 4)
+        adc_settings_groupbox.setLayout(adc_delay_grid)
+        sidebar.addWidget(adc_settings_groupbox, 1, 0, 1, 2)  # Span both columns
+        self.set_adc_delay_spinboxes_active(False)
+
+        aquisition_settings_groupbox = QtGui.QGroupBox()
+        aquisition_settings_groupbox.setTitle('Acquisition Settings')
+        aquisition_settings_layout = QtGui.QGridLayout()
+        aquisition_settings_layout.setVerticalSpacing(5)
+        self.simulate_checkbox = QtGui.QCheckBox("Simulate Pilot Bunch")
+        aquisition_settings_layout.addWidget(self.simulate_checkbox, 0, 0)
+        self.number_of_orbits_spinbox = Spinbox(1, 10000000, 0)
+        self.number_of_orbits_spinbox.valueChanged.connect(self.on_number_of_orbits_changed)
+        aquisition_settings_layout.addWidget(self.number_of_orbits_spinbox, 1, 0)
+        aquisition_settings_layout.addWidget(QtGui.QLabel("Number of orbits to observe"), 1, 1)
+        self.number_of_skipped_orbits_spinbox = Spinbox(0, 100, 0)
+        self.number_of_skipped_orbits_spinbox.valueChanged.connect(self.on_number_of_skipped_orbits_changed)
+        aquisition_settings_layout.addWidget(self.number_of_skipped_orbits_spinbox, 2, 0)
+        aquisition_settings_layout.addWidget(QtGui.QLabel("Number of orbits to skip"), 2, 1)
+        aquisition_settings_groupbox.setLayout(aquisition_settings_layout)
+        sidebar.addWidget(aquisition_settings_groupbox, 2, 0, 1, 2)
+
+        data_consistency_groupbox = QtGui.QGroupBox()
+        data_consistency_groupbox.setTitle('Data Consistency')
+        data_consistency_layout = QtGui.QHBoxLayout()
+        self.data_consistency_led = GraphicalLED(self, (128, 128, 128), 10, 10)
+        data_consistency_layout.addWidget(self.data_consistency_led)
+        data_consistency_layout.addWidget(QtGui.QLabel("Consistency Indicator"))
+        data_consistency_groupbox.setLayout(data_consistency_layout)
+        sidebar.addWidget(data_consistency_groupbox, 3, 0, 1, 2)
+
+
+        read_data_toolbox = QtGui.QToolBox()
+
+        single_and_timed_read_layout = QtGui.QGridLayout()
+        single_and_timed_read_layout.setVerticalSpacing(10)
+        single_and_timed_read_layout.addWidget(QtGui.QLabel("Read every n-Milliseconds"), 0, 0)
+        self.continuous_read_interval_spinbox = Spinbox(0, 10000, 100)
+        self.continuous_read_checkbox = self.new_checkbox("Continuous Readout", self.on_continuous_read)
+        single_and_timed_read_layout.addWidget(self.continuous_read_interval_spinbox, 1, 0)
+        single_and_timed_read_layout.addWidget(self.continuous_read_checkbox, 1, 1)
+        self.start_button = QtGui.QPushButton("Single Read")
+        self.start_button.clicked.connect(self.on_single_read)
+        single_and_timed_read_layout.addWidget(self.start_button, 2, 0)
+        single_and_timed_read_layout.setRowStretch(2, 1)  # Otherwise, the label in row 0 will take all the space...
+        single_and_timed_read_wdgt = QtGui.QWidget()
+        single_and_timed_read_wdgt.setLayout(single_and_timed_read_layout)
+        read_data_toolbox.addItem(single_and_timed_read_wdgt, 'Single and Continuous Readout')
+
+        acquisition_layout = QtGui.QGridLayout()
+        acquisition_layout.setVerticalSpacing(10)
+        self.acquisitions_spinbox = Spinbox(1, 10000000, 10)
+        acquisition_layout.addWidget(self.acquisitions_spinbox, 0, 0)
+        acquisition_layout.addWidget(QtGui.QLabel("Count"), 0, 1)
+        self.wait_time_spinbox = Spinbox(1, 60, 15)
+        acquisition_layout.addWidget(self.wait_time_spinbox, 1, 0)
+        acquisition_layout.addWidget(QtGui.QLabel("Wait (s)"), 1, 1)
+        self.spectrogram_checkbox = QtGui.QCheckBox("Build Spectrograms")
+        acquisition_layout.addWidget(self.spectrogram_checkbox, 2, 0)
+        self.acquire_button = QtGui.QPushButton("Acquire")
+        self.acquire_button.clicked.connect(self.on_acquire)
+        acquisition_layout.addWidget(self.acquire_button, 3, 0)
+        self.stop_button = QtGui.QPushButton("Stop")
+        self.stop_button.setEnabled(False)
+        self.stop_button.clicked.connect(self.on_stop)
+        acquisition_layout.addWidget(self.stop_button, 3, 1)
+        self.acquisition_progressbar = QtGui.QProgressBar()
+        acquisition_layout.addWidget(self.acquisition_progressbar, 4, 0, 1, 2)
+        acquisition_wdgt = QtGui.QWidget()
+        acquisition_wdgt.setLayout(acquisition_layout)
+        read_data_toolbox.addItem(acquisition_wdgt, 'Acquisition')
+
+        timescan_layout = QtGui.QGridLayout()
+        timescan_layout.setVerticalSpacing(10)
+        self.scan_button = QtGui.QPushButton("Time scan")
+        self.scan_button.clicked.connect(self.on_scan)
+        timescan_layout.addWidget(self.scan_button, 0, 0)
+        self.scan_stop_button = QtGui.QPushButton("Stop scan")
+        self.scan_stop_button.setEnabled(False)
+        timescan_layout.addWidget(self.scan_stop_button, 0, 1)
+        self.timescan_progressbar = QtGui.QProgressBar()
+        timescan_layout.addWidget(self.timescan_progressbar, 1, 0, 1, 2)
+        timescan_layout.addWidget(QtGui.QLabel('Coarse Scan Range'), 2, 0, 1, 2)
+        self.time_scan_coarse_from = Spinbox(0, self.board_config.get('th_delay_max'), 0)
+        timescan_layout.addWidget(self.time_scan_coarse_from, 3, 0)
+        self.time_scan_coarse_to = Spinbox(0, self.board_config.get('th_delay_max'), self.board_config.get('th_delay_max'))
+        timescan_layout.addWidget(self.time_scan_coarse_to, 3, 1)
+        timescan_layout.addWidget(QtGui.QLabel('Fine Scan Range'), 4, 0, 1, 2)
+        self.time_scan_fine_from = Spinbox(0, self.board_config.get('chip_delay_max'), 0)
+        timescan_layout.addWidget(self.time_scan_fine_from, 5, 0)
+        self.time_scan_fine_to = Spinbox(0, self.board_config.get('chip_delay_max'), self.board_config.get('chip_delay_max'))
+        timescan_layout.addWidget(self.time_scan_fine_to, 5, 1)
+
+        timescan_wdgt = QtGui.QWidget()
+        timescan_wdgt.setLayout(timescan_layout)
+        read_data_toolbox.addItem(timescan_wdgt, 'Time Scan')
+
+        read_data_toolbox.setMinimumHeight(240)
+
+        sidebar.addWidget(read_data_toolbox, 4, 0, 1, 2)
+        sidebar.setVerticalSpacing(10)
+        sidebar.setRowStretch(5, 1)
+
+        sidebar_group = QtGui.QGroupBox("sidebar")
+        sidebar_group.setLayout(sidebar)
+        sidebar_group.setFlat(True)
+
+        splitter.addWidget(canvas_widget)
+        splitter.addWidget(sidebar_group)
+        splitter.setStretchFactor(0, 4)
+        splitter.setStretchFactor(1, 2)
+        return splitter
+
+    def do_layout(self):
+        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
+        self.setWindowTitle("HEB analysis")
+
+        self.file_menu = QtGui.QMenu('&File', self)
+        self.file_menu.addAction('&Open Datafile', self.on_open,
+                                 QtCore.Qt.CTRL + QtCore.Qt.Key_O)
+        self.file_menu.addAction('O&pen Config', self.on_open_config,
+                                 QtCore.Qt.CTRL + QtCore.Qt.Key_P)
+        self.file_menu.addAction('&Save Config', self.on_save_config,
+                                 QtCore.Qt.CTRL + QtCore.Qt.Key_S)
+        self.file_menu.addAction('&Quit', self.on_close,
+                                 QtCore.Qt.CTRL + QtCore.Qt.Key_Q)
+        self.menuBar().addMenu(self.file_menu)
+
+        self.tab_views = QtGui.QTabWidget(self)
+        self.setCentralWidget(self.tab_views)
+        # Group the board control buttons together
+        control_buttons = QtGui.QGridLayout()
+        control_buttons.addWidget(QtGui.QLabel("Board Control"), 0, 0)
+
+        self.start_board_button = QtGui.QPushButton("Start Board")
+        self.start_board_button.clicked.connect(self.on_start_board)
+        control_buttons.addWidget(self.start_board_button, 1, 0)
+
+        self.calibrate_board_button = QtGui.QPushButton("Calibrate")
+        self.calibrate_board_button.clicked.connect(self.on_calibrate_board)
+        self.calibrate_board_button.setEnabled(False)
+        control_buttons.addWidget(self.calibrate_board_button, 2, 0)
+
+        self.sync_board_button = QtGui.QPushButton("Synchronize")
+        self.sync_board_button.clicked.connect(self.on_sync_board)
+        self.sync_board_button.setEnabled(False)
+        control_buttons.addWidget(self.sync_board_button, 3, 0)
+
+        self.set_defaults_button = QtGui.QPushButton("Set Defaults")
+        self.set_defaults_button.clicked.connect(self.on_set_defaults)
+        self.set_defaults_button.setEnabled(False)
+        control_buttons.addWidget(self.set_defaults_button, 4, 0)
+
+        self.soft_reset_button = QtGui.QPushButton("Soft Reset")
+        self.soft_reset_button.clicked.connect(self.on_soft_reset)
+        self.soft_reset_button.setEnabled(False)
+        control_buttons.addWidget(self.soft_reset_button, 5, 0)
+
+        self.stop_board_button = QtGui.QPushButton("Board Off")
+        self.stop_board_button.clicked.connect(self.on_stop_board)
+        control_buttons.addWidget(self.stop_board_button, 6, 0)
+
+        #self.show_advanced_tick = QtGui.QCheckBox("Show advanced Interface")
+        #self.show_advanced_tick.setEnabled(True)
+        #self.show_advanced_tick.clicked.connect(self.do_show_advanced_interface)
+        #control_buttons.addWidget(self.show_advanced_tick, 6, 0)
+
+        control_buttons.setRowStretch(7, 1)
+        buttons_widget = QtGui.QWidget()
+        buttons_widget.setLayout(control_buttons)
+        buttons_widget.setMinimumWidth(200)
+        buttons_widget.setMaximumWidth(200)
+
+        # Group the Simple status display together with the "Check Status" button
+        status_box = QtGui.QGridLayout()
+        self.simple_status_display = SimpleStatusDisplay()
+        status_box.addWidget(self.simple_status_display, 0, 0)
+        self.get_status_button = QtGui.QPushButton("Check Status")
+        self.get_status_button.setEnabled(True)
+        self.get_status_button.clicked.connect(self.do_status_readout)
+        self.get_status_button.setMinimumWidth(150)
+        self.get_status_button.setMaximumWidth(150)
+        status_box.addWidget(self.get_status_button, 1, 0)
+        status_box.setRowStretch(2, 1)
+        simple_status_widget = QtGui.QWidget()
+        simple_status_widget.setLayout(status_box)
+
+        #Text Area for logging of inconsistent readouts
+        self.error_text_area = FileLikeQTextEdit(self)
+        self.error_text_area.setFont(QtGui.QFont('monospace', 9))
+        self.error_text_area.setTextColor(QtCore.Qt.red)
+        self.error_text_area.setMaximumWidth(300)
+        self.error_text_area.setReadOnly(True)
+        clear_error_text_btn = QtGui.QPushButton("Clear List")
+        clear_error_text_btn.clicked.connect(self.error_text_area.clear)
+        error_text_box = QtGui.QVBoxLayout()
+        error_text_box.addWidget(QtGui.QLabel("Inconsistent Files List"))
+        error_text_box.addWidget(self.error_text_area)
+        error_text_box.addWidget(clear_error_text_btn)
+        error_text_wdgt = QtGui.QWidget()
+        error_text_wdgt.setLayout(error_text_box)
+
+        #Now bundle the Board Control buttons and the Status LEDS together
+        top_box = QtGui.QHBoxLayout()
+        top_box.addWidget(buttons_widget)
+        top_box.addWidget(simple_status_widget)
+        top_box.addWidget(error_text_wdgt)
+        top_box.insertStretch(-1, 1)
+        top_box_w = QtGui.QWidget()
+        top_box_w.setLayout(top_box)
+
+        #Create the Timing Indication Block
+        self.fpga_delay_table = BitsDisplayTable('0', self)
+        self.fpga_delay_table.set_label(0, 0, "FPGA Delay")
+        self.th_delay_table = BitsDisplayTable('0', self)
+        self.th_delay_table.set_label(0, 0, "T/H Delay")
+        self.fpga_temperature = QtGui.QLineEdit()
+        self.fpga_temperature.setMaximumWidth(60)
+        self.fpga_temperature.setReadOnly(True)
+        self.adc_delay_table = BitsDisplayTable(4*'0', self)
+        self.adc_delay_table.set_label(0, 0, "ADC 1")
+        self.adc_delay_table.set_label(1, 1, "ADC 2")
+        self.adc_delay_table.set_label(2, 2, "ADC 3")
+        self.adc_delay_table.set_label(3, 3, "ADC 4")
+
+        timing_line_1 = QtGui.QHBoxLayout()
+        timing_line_1.setAlignment(QtCore.Qt.AlignLeft)
+        timing_line_1.addWidget(self.fpga_delay_table)
+        timing_line_1.addWidget(self.th_delay_table)
+        timing_line_1.addWidget(self.adc_delay_table)
+        timing_line_1_wdgt = QtGui.QWidget()
+        timing_line_1_wdgt.setLayout(timing_line_1)
+        timing_line_2 = QtGui.QGridLayout()
+        timing_line_2.addWidget(QtGui.QLabel("FPGA Temperature"), 0, 0)
+        timing_line_2.addWidget(self.fpga_temperature, 0, 1)
+        timing_line_2.setColumnStretch(2, 1)
+        timing_line_2_wdgt = QtGui.QWidget()
+        timing_line_2_wdgt.setLayout(timing_line_2)
+
+        timing_box = QtGui.QVBoxLayout()
+        timing_box.addWidget(QtGui.QLabel("   Timings in Pico Seconds"), 0, QtCore.Qt.AlignLeft)
+        timing_box.addWidget(timing_line_1_wdgt, 0, QtCore.Qt.AlignLeft)
+        timing_box.addWidget(timing_line_2_wdgt, 0, QtCore.Qt.AlignLeft)
+        timing_box_wdgt = QtGui.QWidget()
+        timing_box_wdgt.setLayout(timing_box)
+
+        #Create the the "Write Registers" block
+        sub_box = QtGui.QGridLayout()
+        sub_box.addWidget(QtGui.QLabel("Register Address"), 0, 0)
+        sub_box.addWidget(QtGui.QLabel("Register Value"), 0, 1)
+        self.adv_register_address = QtGui.QLineEdit()
+        self.adv_register_address.setText("0x9040")
+        self.adv_register_address.setInputMask(r"\0\xHhhhhhhh")
+        sub_box.addWidget(self.adv_register_address, 1, 0)
+        self.adv_register_value = QtGui.QLineEdit()
+        self.adv_register_value.setText("0x0")
+        self.adv_register_value.setInputMask(r"\0\xHhhhhhhh")
+        sub_box.addWidget(self.adv_register_value, 1, 1)
+        helper_btn = QtGui.QPushButton("Open Input Help")
+        helper_btn.clicked.connect(self.show_input_helper)
+        sub_box.addWidget(helper_btn, 2, 0)
+        adv_write_reg_val_btn = QtGui.QPushButton("Write Value")
+        adv_write_reg_val_btn.clicked.connect(self.write_register_adv)
+        sub_box.addWidget(adv_write_reg_val_btn, 2, 1)
+        sub_box.setRowStretch(3, 1)
+        self.sub_box_w = QtGui.QWidget()
+        self.sub_box_w.setLayout(sub_box)
+        self.sub_box_w.setVisible(False)
+
+        #Pack Board Control / Status LEDs / Timing Indication / "Write Register" together
+        control_block = QtGui.QGridLayout()
+        control_block.addWidget(top_box_w, 0, 0)
+        control_block.addWidget(timing_box_wdgt, 1, 0)
+        control_block.addWidget(self.sub_box_w, 3, 0)
+        control_block.setRowStretch(2, 1)
+        control_block_wdgt = QtGui.QWidget()
+        control_block_wdgt.setSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum)
+        control_block_wdgt.setLayout(control_block)
+
+        #Split the area vertically and add a text area in the bottom half
+        splitter = QtGui.QSplitter()
+        splitter.setOrientation(QtCore.Qt.Vertical)
+
+        splitter.addWidget(control_block_wdgt)
+        self.text_area = FileLikeQTextEdit(self)
+        self.text_area.setFont(QtGui.QFont('monospace', 9))
+        self.text_area.setReadOnly(True)
+        splitter.addWidget(self.text_area)
+
+        # Let log handler write to text area
+        log_handler = LogHandler(self.text_area)
+        log_handler.setFormatter(logging.Formatter('%(asctime)s - %(funcName)s(): %(message)s'))
+        logger = logging.getLogger()
+        logger.addHandler(log_handler)
+        logger.setLevel(logging.INFO)
+        self.board_config.logger = logger
+
+        # Create a widget for the Advanced Board Interface
+        self.advanced_interface = AdvancedBoardInterface(self)
+        self.advanced_interface.setVisible(False)
+
+        #set up tabs for the main window
+        self.tab_views.addTab(splitter, "Control")
+        self.tab_views.addTab(self.advanced_interface, "Registers")
+        self.tab_views.addTab(self.new_datawidget(), "Data")
+        self.tab_views.setCurrentIndex(2)
+
+    def do_show_advanced_interface(self):
+        show = self.show_advanced_tick.isChecked()
+        self.advanced_interface.setVisible(show)
+        self.sub_box_w.setVisible(show)
+
+    def register_observers(self):
+        sbo = self.board_config.observe
+        sbo(self.adc_1_delay_spinbox, self.adc_1_delay_spinbox.setValueSilent, 'chip_1_delay')
+        sbo(self.adc_2_delay_spinbox, self.adc_2_delay_spinbox.setValueSilent, 'chip_2_delay')
+        sbo(self.adc_3_delay_spinbox, self.adc_3_delay_spinbox.setValueSilent, 'chip_3_delay')
+        sbo(self.adc_4_delay_spinbox, self.adc_4_delay_spinbox.setValueSilent, 'chip_4_delay')
+        sbo(self.adc_1_delay_text, lambda x: self.adc_1_delay_text.setText('%i + %i' % (self.board_config.get('th_delay')*self.board_config.get('th_delay_factor'),x*self.board_config.get('chip_delay_factor'))), 'chip_1_delay')
+        sbo(self.adc_2_delay_text, lambda x: self.adc_2_delay_text.setText('%i + %i'%(self.board_config.get('th_delay')*self.board_config.get('th_delay_factor'),x*self.board_config.get('chip_delay_factor'))), 'chip_2_delay')
+        sbo(self.adc_3_delay_text, lambda x: self.adc_3_delay_text.setText('%i + %i'%(self.board_config.get('th_delay')*self.board_config.get('th_delay_factor'),x*self.board_config.get('chip_delay_factor'))), 'chip_3_delay')
+        sbo(self.adc_4_delay_text, lambda x: self.adc_4_delay_text.setText('%i + %i'%(self.board_config.get('th_delay')*self.board_config.get('th_delay_factor'),x*self.board_config.get('chip_delay_factor'))), 'chip_4_delay')
+        sbo(self.fpga_delay_table, lambda x: self.fpga_delay_table.set_item(0, 0, x*self.board_config.get('fpga_delay_factor')), 'fpga_delay')
+        sbo(self.th_delay_table, lambda x: self.th_delay_table.set_item(0, 0, x*self.board_config.get('th_delay_factor')), 'th_delay')
+        sbo(self.th_delay_spinbox, self.th_delay_spinbox.setValueSilent, 'th_delay')
+        sbo(self.number_of_orbits_spinbox, self.number_of_orbits_spinbox.setValueSilent, 'orbits_observe')
+        sbo(self.number_of_skipped_orbits_spinbox, self.number_of_skipped_orbits_spinbox.setValueSilent, 'orbits_skip')
+        sbo(self.adc_delay_table, lambda x: self.adc_delay_table.set_item(0, 0, x * self.board_config.get('adc_delay_factor')), 'adc_1_delay')
+        sbo(self.adc_delay_table, lambda x: self.adc_delay_table.set_item(0, 1, x * self.board_config.get('adc_delay_factor')), 'adc_2_delay')
+        sbo(self.adc_delay_table, lambda x: self.adc_delay_table.set_item(0, 2, x * self.board_config.get('adc_delay_factor')), 'adc_3_delay')
+        sbo(self.adc_delay_table, lambda x: self.adc_delay_table.set_item(0, 3, x * self.board_config.get('adc_delay_factor')), 'adc_4_delay')
+
+    def do_status_readout(self):
+        try:
+            registers = heb.board.read_pci(3, '0x9050', decimal=True)
+            self.simple_status_display.update_status(registers)
+            self.advanced_interface.update_status(registers)
+            control = heb.board.read_pci(1, '0x9040')[0]
+            control_bits = '{0:032b}'.format(int(control, 16))
+            self.advanced_interface.control_table.set_numbers(control_bits)
+            if control_bits[22:26] == "1111":
+                self.start_board_button.setEnabled(False)
+                self.soft_reset_button.setEnabled(True)
+                self.calibrate_board_button.setEnabled(True)
+            else:
+                self.start_board_button.setEnabled(True)
+                self.soft_reset_button.setEnabled(False)
+                self.calibrate_board_button.setEnabled(False)
+            fpga_temp_raw_hex = heb.board.read_pci(1,'0x9110')[0]
+            fpga_temp_raw_hex = fpga_temp_raw_hex[-3:]
+            fpga_temp_raw_bin = '{0:012b}'.format(int(fpga_temp_raw_hex, 16))
+            fpga_temp_encoded = heb.board.get_dec_from_bits(fpga_temp_raw_bin, 9, 0)
+            fpga_temp_celsius = '{0:2.2f}'.format(((fpga_temp_encoded*503.975)/1024)-273.15)
+            self.fpga_temperature.setText(fpga_temp_celsius + " C")
+        except heb.board.BoardError as e:
+            logging.error("Reading board status failed: {}".format(str(e)))
+
+    def write_register_adv(self):
+
+        # First cast both texts to integers (base 16) and let python check
+        # if the entered values are valid hex-numbers
+        try:
+            addr = int("%s"%self.adv_register_address.text(), 16)
+        except ValueError as e:
+            logging.error("Register address is invalid!")
+            self.adv_register_address.setStyleSheet("QLineEdit { background-color : #FF6666; }")
+            return
+
+        try:
+            value = int("%s"%self.adv_register_value.text(), 16)
+        except ValueError as e:
+            logging.error("Register value is invalid!")
+            self.adv_register_value.setStyleSheet("QLineEdit { background-color : #FF6666; }")
+            return
+
+        # Now use the numbers
+        addr = "%s"%self.adv_register_address.text()
+        value = "%s"%self.adv_register_value.text()
+        self.adv_register_address.setStyleSheet("QLineEdit { background-color : white; }")
+        self.adv_register_value.setStyleSheet("QLineEdit { background-color : white; }")
+
+        try:
+            heb.board.write_pci(value, addr)
+        except heb.board.BoardError as e:
+            QtGui.QMessageBox.critical(self, "Board communication", "'pci' " + str(e))
+            logging.error("Could not write values to board due to an error!")
+            return
+
+        # Intercept writes to the "Timing" Registers to catch the input and put it into their
+        # respective displays
+        if addr == "0x9060":
+            if value[:8] == "0x000501":
+                #We are writing a timing value. Decide which one
+                if value[-1:] == "0":
+                    #FPGA
+                    self.board_config.update('fpga_delay', int(value[8:9], 16))
+                elif value[-1:] == "3":
+                    #T/H
+                    self.board_config.update('th_delay', int(value[8:9], 16))
+                elif value[-1:] == "4":
+                    #ADC_1
+                    self.board_config.update('adc_1_delay', int(value[8:9], 16))
+                elif value[-1:] == "5":
+                    #ADC_2
+                    self.board_config.update('adc_1_delay', int(value[8:9], 16))
+                elif value[-1:] == "6":
+                    #ADC_3
+                    self.board_config.update('adc_1_delay', int(value[8:9], 16))
+                elif value[-1:] == "7":
+                    #ADC_4
+                    self.board_config.update('adc_1_delay', int(value[8:9], 16))
+        
+        self.do_status_readout()   
+        logging.info("Written '%s' to register '%s'." % (value, addr))
+
+    def show_input_helper(self):
+        dialog = RegisterBitsDialog(32)
+        dialog.exec_()
+        dialog.deleteLater()
+        bits = dialog.get_return_value()
+        if bits:
+            hex_str = hex(int(bits, 2))
+            self.adv_register_value.setText(hex_str)
+
+    def _check_for_no_continuous_read(self):
+        if self.continuous_read is True:
+            dialog = PopupDialog("Continuous Read is currently active!\n"
+                                 "Stop continuous read and still perform this action?")
+            dialog.exec_()
+            dialog.deleteLater()
+            ret = dialog.get_return_value()
+            if ret is False:
+                return False
+            else:
+                self._set_continuous_read_inactive()
+                self.continuous_read_checkbox.setChecked(False)
+                return True
+        else:
+            return True
+
+    def on_start_board(self):
+        enable_wait_cursor()
+
+        if not self._check_for_no_continuous_read():
+            disable_wait_cursor()
+            return
+
+        try:
+            if heb.board.is_active():
+                disable_wait_cursor()
+                self.do_status_readout()
+                return
+
+            logging.info("##### Activating Board #####")
+            heb.board.start_dma()
+            #cmd = ['pci', '--list-dma-engines']
+            #logging.info(heb.board.safe_call(cmd))
+            heb.board.write_pci('0x20001000', '0x9100')
+            heb.board.write_pci('0x05', '0x9040')
+            time.sleep(1.0)
+            dialog1 = PopupDialog("Switch On the power supply --> FIRST <--")
+            dialog1.exec_()
+            dialog1.deleteLater()
+
+            if not dialog1.get_return_value():
+                logging.error("Starting procedure canceled")
+                disable_wait_cursor()
+                return
+
+            logging.info("Switch ON T/Hs")
+            heb.board.write_pci('0x3C1', '0x9040')
+            logging.info("9040:  " + str(heb.board.read_pci(1, '0x9040')[0]))
+            time.sleep(0.1)
+            dialog2 = PopupDialog("Switch On the power supply --> SECOND <--")
+            dialog2.exec_()
+            dialog2.deleteLater()
+
+            if not dialog2.get_return_value():
+                self.test_area.write("Starting procedure canceled")
+                disable_wait_cursor()
+                return
+
+            logging.info("Switch ON ADCs")
+            heb.board.write_pci('0x3F1', '0x9040')
+            logging.info("9040:  " + str(heb.board.read_pci(1, '0x9040')[0]))
+            time.sleep(0.1)
+
+            heb.board.write_pci('0x3F0', '0x9040')
+            logging.info("9040:  " + str(heb.board.read_pci(1, '0x9040')[0]))
+            time.sleep(1.0)
+
+            logging.info("Board started successfully!")
+        except heb.board.BoardError as e:
+            logging.error("Starting board failed: {}".format(str(e)))
+
+        disable_wait_cursor()
+        self.do_status_readout()
+
+
+    def on_calibrate_board(self):
+        enable_wait_cursor()
+        if not self._check_for_no_continuous_read():
+            disable_wait_cursor()
+            return
+        try:
+            logging.info("##### Calibrating Board #####")
+            logging.info("Removing Board Reset")
+            heb.board.write_pci('0x2000003f0', '0x9040')
+            time.sleep(0.5)
+
+            logging.info("SPI Fanout Programming...")
+            heb.board.write_pci('0x083', '0x9068')
+            time.sleep(0.5)
+            heb.board.write_pci('0x6a2', '0x9068')
+            time.sleep(0.5)
+
+            logging.info("PLL calibration start...")
+            logging.info("PLL Reset...")
+            heb.board.write_pci('0x80000000', '0x9060')
+            time.sleep(0.5)
+            logging.info("Set CH_0 (FPGA) clock FPGA ...")
+            heb.board.write_pci('0x00050000', '0x9060')
+            time.sleep(0.5)
+
+            logging.info("Set CH_3 clock fanout ...")
+            heb.board.write_pci('0x00050003', '0x9060')
+            time.sleep(0.5)
+
+            logging.info("Set CH_4 clock ADC 1 ...")
+            heb.board.write_pci('0x00050004', '0x9060')
+            time.sleep(0.5)
+
+            logging.info("Set CH_5 clock ADC 2 ...")
+            heb.board.write_pci('0x00050005', '0x9060')
+            time.sleep(0.5)
+
+            logging.info("Set CH_6 clock ADC 3 ...")
+            heb.board.write_pci('0x00050006', '0x9060')
+            time.sleep(0.5)
+
+            logging.info("Set CH_7 clock ADC 4 ...")
+            heb.board.write_pci('0x00050007', '0x9060')
+            time.sleep(0.5)
+
+            logging.info("Set R8 ...")
+            heb.board.write_pci('0x10000908', '0x9060')
+            time.sleep(0.5)
+
+            logging.info("Set R11 ...")
+            heb.board.write_pci('0x0082800B', '0x9060')
+            time.sleep(0.5)
+
+            logging.info("Set R13 ...")
+            heb.board.write_pci('0x029F400D', '0x9060')
+            time.sleep(0.5)
+
+            logging.info("Set R14 (F_out and Global_EN => ON) ...")
+            heb.board.write_pci('0x0830040E', '0x9060')
+            time.sleep(0.5)
+
+            logging.info("Set R15 ...")
+            heb.board.write_pci('0xD000100F', '0x9060')
+            time.sleep(0.5)
+
+        except heb.board.BoardError as e:
+            logging.error("Calibration failed: {}".format(str(e)))
+            disable_wait_cursor()
+            self.do_status_readout()
+            return
+
+        logging.info("Board Calibration successful!")
+        self.sync_board_button.setEnabled(True)
+        disable_wait_cursor()
+        self.do_status_readout()
+
+    def on_sync_board(self):
+        enable_wait_cursor()
+        if not self._check_for_no_continuous_read():
+            disable_wait_cursor()
+            return
+        try:
+            logging.info("##### Synchronize PLLs #####")
+            logging.info("Send the PLL sync signals ...")
+            heb.board.write_pci('0x1003f0', '0x9040')
+            time.sleep(1.0)
+            logging.info("Done!")
+            heb.board.write_pci('0x0003f0', '0x9040')
+        except heb.board.BoardError as e:
+            logging.error("Synchronization failed: {}".format(str(e)))
+            disable_wait_cursor()
+            self.do_status_readout()
+            return
+
+        logging.info("Board synchronization successful!")
+        self.set_defaults_button.setEnabled(True)
+        disable_wait_cursor()
+        self.do_status_readout()
+
+    def on_set_defaults(self):
+        enable_wait_cursor()
+        if not self._check_for_no_continuous_read():
+            disable_wait_cursor()
+            return
+        try:
+            logging.info("##### Setting default Values #####")
+            logging.info("Set Defaults delay value in the board...")
+            #Set_FPGA_clock_delay.sh 0
+            self.board_config.set_fpga_delay(self.board_config.get('fpga_delay'))
+            time.sleep(0.1)
+            #Set_Delay_chip.sh 16 16 16 16
+            factors = [self.board_config.get('chip_1_delay'), self.board_config.get('chip_2_delay'),
+                       self.board_config.get('chip_3_delay'), self.board_config.get('chip_4_delay')]
+            self.board_config.set_chip_delay([0, 1, 2, 3], factors)
+            time.sleep(0.6)
+            #Set_TH_Delay.sh 12
+            self.board_config.set_th_delay(self.board_config.get('th_delay'))
+            time.sleep(0.1)
+            #Set_ADC_1_Delay.sh 4
+            self.board_config.set_adc_delay(0, self.board_config.get('adc_1_delay'))
+            time.sleep(0.1)
+            #Set_ADC_2_Delay.sh 4
+            self.board_config.set_adc_delay(1, self.board_config.get('adc_2_delay'))
+            time.sleep(0.1)
+            #Set_ADC_3_Delay.sh 4
+            self.board_config.set_adc_delay(2, self.board_config.get('adc_3_delay'))
+            time.sleep(0.1)
+            #Set_ADC_4_Delay.sh 4
+            self.board_config.set_adc_delay(3, self.board_config.get('adc_4_delay'))
+            time.sleep(0.1)
+
+            heb.board.write_pci('{0:08x}'.format(self.board_config.get('orbits_observe')), '0x9020')
+            self.number_of_orbits_spinbox.setValueSilent(self.board_config.get('orbits_observe'))
+
+            heb.board.write_pci('{0:08x}'.format(self.board_config.get('orbits_skip')), '0x9028')
+            self.number_of_skipped_orbits_spinbox.setValueSilent(self.board_config.get('orbits_skip'))
+
+        except heb.board.BoardError as e:
+            logging.error("Setting defaults failed: {}".format(str(e)))
+            disable_wait_cursor()
+            self.do_status_readout()
+            return
+
+        logging.info("Default values set successfully!")
+        self.set_adc_delay_spinboxes_active(True)
+        disable_wait_cursor()
+        self.do_status_readout()
+
+
+    def on_stop_board(self):
+        enable_wait_cursor()
+        if not self._check_for_no_continuous_read():
+            disable_wait_cursor()
+            return
+        try:
+            logging.info("##### Switching Off Board #####")
+            heb.board.write_pci('0x01', '0x9040')
+            heb.board.stop_dma()
+            cmd = ['pci', '--list-dma-engines']
+            logging.info(heb.board.safe_call(cmd))
+            time.sleep(0.5)
+        except heb.board.BoardError as e:
+            logging.error("Sequence failed: {}".format(str(e)))
+            disable_wait_cursor()
+            self.do_status_readout()
+            return
+
+        logging.info("Board switched off successfully!")
+        self.start_board_button.setEnabled(True)
+        self.calibrate_board_button.setEnabled(False)
+        self.sync_board_button.setEnabled(False)
+        self.set_defaults_button.setEnabled(False)
+        self.set_adc_delay_spinboxes_active(False)
+        disable_wait_cursor()
+        self.do_status_readout()
+
+    def on_soft_reset(self):
+        enable_wait_cursor()
+        if not self._check_for_no_continuous_read():
+            disable_wait_cursor()
+            return
+        try:
+            logging.info("Soft-Resetting Board ...")
+            heb.board.write_pci('0x1', '0x9040', hex_mask='0x1')
+            time.sleep(1)
+            heb.board.write_pci('0x0', '0x9040', hex_mask='0x1')
+        except heb.board.BoardError as e:
+            logging.error("Sequence failed: {}".format(str(e)))
+            disable_wait_cursor()
+            self.do_status_readout()
+            return
+
+        logging.info("Soft-Reset successful.")
+        disable_wait_cursor()
+        self.do_status_readout()
+        return
+
+    def disable_acquisition_input(self):
+        self.start_button.setEnabled(False)
+        self.acquire_button.setEnabled(False)
+
+    def enable_acquisition_input(self):
+        self.start_button.setEnabled(True)
+        self.acquire_button.setEnabled(True)
+
+    def set_adc_delay_spinboxes_active(self, value=True):
+        self.th_delay_spinbox.setEnabled(value)
+        self.adc_1_delay_spinbox.setEnabled(value)
+        self.adc_2_delay_spinbox.setEnabled(value)
+        self.adc_3_delay_spinbox.setEnabled(value)
+        self.adc_4_delay_spinbox.setEnabled(value)
+
+    def on_single_read(self):
+        self.continuous_read_checkbox.setEnabled(False)
+        self.disable_acquisition_input()
+        self.read_data_and_safe()
+        self.enable_acquisition_input()
+        self.continuous_read_checkbox.setEnabled(True)
+
+    def _set_continuous_read_active(self):
+        self.start_button.setEnabled(False)
+        self.acquire_button.setEnabled(False)
+        self.scan_button.setEnabled(False)
+        self.continuous_read = True
+
+    def _set_continuous_read_inactive(self):
+        if self.continuous_read:
+            self.continuous_read = False
+            self.timer.stop()
+        self.start_button.setEnabled(True)
+        self.acquire_button.setEnabled(True)
+        self.scan_button.setEnabled(True)
+
+    def on_continuous_read(self):
+        if self.continuous_read_checkbox.isChecked():
+            self._set_continuous_read_active()
+            self.do_continuous_read()
+        else:
+            self._set_continuous_read_inactive()
+
+    def do_continuous_read(self):
+        self.timer = QtCore.QTimer()
+        logging.info("Start continuous read")
+
+        def continuous_read_step():
+            if self.continuous_read:
+                self.read_data()
+                self.timer.start(self.continuous_read_interval_spinbox.value())
+            else:
+                self.timer.stop()
+
+        self.timer.timeout.connect(continuous_read_step)
+        self.timer.start(self.continuous_read_interval_spinbox.value())
+
+    def read_and_update(self, read_func, *args):
+        enable_wait_cursor()
+
+        # Remove all references as soon as possible
+        if self.data:
+            del self.data
+            self.data = None
+
+        header = self.advanced_interface.control_table.get_bit(28)
+        self.data = read_func(*args, force=self.args.force_read, header=header, cache=self.args.cache_data)
+
+        if heb.io.is_data_consistent(self.data):
+            self.data_consistency_led.set_color((0, 190, 0))
+        else:
+            self.data_consistency_led.set_color((255, 0, 0))
+            if read_func is heb.io.read_from_file:
+                self.error_text_area.write(str(args[0]))
+
+        disable_wait_cursor()
+        frm = self.from_spinbox.value()
+        to = self.to_spinbox.value()
+        self.canvas.update_figure(self.opt_widgets, self.data, frm, to)
+
+    def read_and_update_data_from_file(self, filename):
+        self.read_and_update(heb.io.read_from_file, str(filename))
+
+    def read_and_update_data_from_string(self, raw_data):
+        self.read_and_update(heb.io.read_from_string, raw_data)
+
+    def read_data_and_safe(self):
+        now = time.time()
+        filename = '{:0.3f}.out'.format(now)
+
+        try:
+            simulate = self.simulate_checkbox.isChecked()
+            heb.board.acquire_data(filename, simulate=simulate, duration=2.0)
+            #heb.board.run_status('/home/heb/StatusComparison.sh')
+            self.read_and_update_data_from_file(filename)
+        except heb.board.BoardError as e:
+            logging.error("Reading failed: {}".format(str(e)))
+
+    def read_data(self):
+        try:
+            if self.simulate_checkbox.isChecked():
+                heb.board.start_pilot_bunch_emulator()
+
+            heb.board.start_acquisition()
+            heb.board.wait_for_revolutions()
+            heb.board.stop_acquisition()
+            heb.board.enable_transfer()
+            cmd = ['pci', '-r', 'dma0', '-o', '/dev/stdout', '--multipacket']
+            data_raw = heb.board.safe_call(cmd)
+            heb.board.flush_dma()
+            # Sadly, the PCI software also outputs the Information, that it has
+            # written data to the desired file.  This is recorded by our
+            # software and needs to be removed, so we split the returned string
+            # at the 'Writting data to file blah, blah" Btw: YES this
+            # 'Writting' is actually correct. No, i don't know why it is
+            # grammatically wrong in the first place :D
+            data_split = data_raw.split('Writting')[0]
+            self.read_and_update_data_from_string(data_split)
+        except heb.board.BoardError as e:
+            logging.error("Reading failed: {}".format(str(e)))
+
+        # return data
+
+    def iterate_spectrograms(self, path):
+        if not os.path.isdir("./"+str(path)):
+            return
+
+        transform = self.data.fft(1, frm=0, to=-1)
+	for i in range(heb.BUNCHES_PER_TURN-1):
+            filename = os.path.join(".", str(path), "%i.hsp" % i)
+            write_header = False
+            if not os.path.isfile(filename):
+                write_header = True
+            f = open(filename, 'ab')
+            if write_header:
+                f.write("#hsp\n")  # heb spectrogram magic number
+                #f.write(b'\x40')  # 64bit
+                #f.write('64')  # 64bit
+                #f.write(sytruct.pack('>I', self.number_of_skipped_orbits_spinbox.value()))
+                f.write("#"+str(self.number_of_skipped_orbits_spinbox.value()))
+                f.write("\n")
+            line = transform[i,:]
+            # f.write(struct.pack('>I', len(line)))
+            #f.write(str(len(line)))
+            f.write('{:0.3f} '.format(time.time()))
+            #data = pArr('d', line)
+            #data.tofile(f)
+            for e in line:
+                f.write("%s "%np.absolute(e))
+            f.write("\n")
+            f.close()
+
+    def stop_acquisition(self):
+        self.timer.stop()
+        self.enable_acquisition_input()
+        self.continuous_read_checkbox.setEnabled(True)
+        self.acquisition_progressbar.reset()
+
+    def on_acquire(self):
+        self.disable_acquisition_input()
+        self.continuous_read_checkbox.setEnabled(False)
+        self.stop_button.setEnabled(True)
+
+        self.timer = QtCore.QTimer()
+        num_acquisitions = self.acquisitions_spinbox.value()
+        self.acquisition_progressbar.setRange(1, num_acquisitions)
+
+        spectrogram_dir = "./spectrograms_{:0.3f}".format(time.time())
+        if self.spectrogram_checkbox.isChecked():
+            os.mkdir(spectrogram_dir)
+
+        # We increase already once because we do a single acquisition before the
+        # timer is started, otherwise we have to wait until the timer fires the
+        # first time.
+        self.current_acquisition = 1
+        self.read_data_and_safe()
+        if self.spectrogram_checkbox.isChecked():
+            self.iterate_spectrograms(spectrogram_dir)
+
+        def on_timeout():
+            if self.current_acquisition < num_acquisitions:
+                self.current_acquisition += 1
+                self.acquisition_progressbar.setValue(self.current_acquisition)
+                self.read_data_and_safe()
+                self.iterate_spectrograms(spectrogram_dir)
+            else:
+                self.stop_acquisition()
+
+        self.timer.timeout.connect(on_timeout)
+        self.timer.start(self.wait_time_spinbox.value() * 1000)
+
+    def on_stop(self):
+        self.stop_acquisition()
+
+    def on_scan(self):
+        c_frm = self.time_scan_coarse_from.value()
+        c_to = self.time_scan_coarse_to.value()
+        if c_frm > c_to:
+            logging.info('Coarse Scan Interval is invalid: (%i > %i)' % (c_frm, c_to))
+            return
+        f_frm = self.time_scan_fine_from.value()
+        f_to = self.time_scan_fine_to.value()
+        if f_frm > f_to:
+            logging.info('Fine Scan Interval is invalid: (%i > %i)' % (f_frm, f_to))
+            return
+
+        self.disable_acquisition_input()
+        self.continuous_read_checkbox.setEnabled(False)
+        self.scan_button.setEnabled(False)
+        self.set_adc_delay_spinboxes_active(False)
+
+        self.canvas.figure.clear()
+
+        th_old = self.board_config.get('th_delay')
+        adc_1_old = self.board_config.get('adc_1_delay')
+        adc_2_old = self.board_config.get('adc_2_delay')
+        adc_3_old = self.board_config.get('adc_3_delay')
+        adc_4_old = self.board_config.get('adc_4_delay')
+
+        minimum = [None, None, None, None]
+        maximum = np.zeros((4, 3))
+        heatmap = np.zeros((4, (f_to - f_frm + 1), (c_to - c_frm + 1)))
+        self.timescan_progressbar.setRange(1, ((f_to - f_frm) + 1) * ((c_to - c_frm) + 1))
+        self.stop_requested = False
+
+        def time_scan_stop():
+            self.stop_requested = True
+            self.board_config.set_delay(th_old)
+            self.board_config.set_chip_delay([0, 1, 2, 3], [adc_1_old, adc_2_old, adc_3_old, adc_4_old])
+            self.enable_acquisition_input()
+            self.continuous_read_checkbox.setEnabled(True)
+            self.scan_button.setEnabled(True)
+            self.scan_stop_button.setEnabled(False)
+            self.set_adc_delay_spinboxes_active(True)
+            self.timescan_progressbar.reset()
+
+        self.scan_stop_button.clicked.connect(time_scan_stop)
+        self.scan_stop_button.setEnabled(True)
+
+        c_step = 0
+        for coarse in range(c_frm, c_to + 1):
+            try:
+                self.board_config.set_delay(coarse)
+            except heb.board.BoardError as e:
+                QtGui.QMessageBox.critical(self, "Board communication", str(e))
+                time_scan_stop()
+                return
+            
+            f_step = 0
+            for fine in range(f_frm, f_to + 1):
+                self.board_config.set_chip_delay([0, 1, 2, 3], [fine, fine, fine, fine])
+
+                try:
+                    if self.simulate_checkbox.isChecked() is True:
+                        heb.board.start_pilot_bunch_emulator()
+
+                    heb.board.start_acquisition()
+                    heb.board.wait_for_revolutions()
+                    heb.board.stop_acquisition()
+                    heb.board.enable_transfer()
+                    cmd = ['pci', '-r', 'dma0', '-o', '/dev/stdout', '--multipacket']
+                    data_raw = heb.board.safe_call(cmd)
+                    heb.board.flush_dma()
+                    #Sadly, the PCI software also outputs the Information, that it has written data to the desired file.
+                    #This is recorded by our software and needs to be removed, so we split the returned string at the
+                    # "Writting data to file blah, blah" string.
+                    #Btw: YES this 'Writting' is actually correct. Its a typo in the 'pci' driver...
+                    data_split = data_raw.split('Writting')[0]
+                    data = heb.io.read_from_string(data_split, force=True, cache=False)
+                except heb.board.BoardError as e:
+                    QtGui.QMessageBox.critical(self, "Board communication", str(e))
+                    time_scan_stop()
+                    return
+
+                for adc in range(4):
+                    buckets = data.array[:, adc:adc+1].reshape(-1)
+                    heatmap[adc, f_step, c_step] = float(buckets.sum())/buckets.shape[0]
+                    if heatmap[adc, f_step, c_step] > maximum[adc, 0]:
+                        maximum[adc, 0] = heatmap[adc, f_step, c_step]
+                        maximum[adc, 1] = coarse
+                        maximum[adc, 2] = fine
+                    if minimum[adc] is None or minimum[adc] > heatmap[adc, f_step, c_step]:
+                        minimum[adc] = heatmap[adc, f_step, c_step]
+
+                self.timescan_progressbar.setValue(((c_step * (f_to - f_frm + 1)) + f_step) + 1)
+                #GUI is blocked in our tight loop. Give it an opportunity to handle events
+                QtGui.QApplication.processEvents()
+                if self.stop_requested is True:
+                    # Time Scan Stop is already performed by button press. Nothing else to do but leave
+                    return
+                f_step += 1
+            c_step += 1
+
+        self.canvas.figure.clear()
+        self.canvas.figure.suptitle("Timing Analysis")
+
+        if True:
+            now = time.time()
+            filename = 'timescan_{:0.3f}.out'.format(now)
+            f = open(filename, 'wr')
+
+        for adc in range(4):
+            if True:
+                f.write("#ADC_%s\n" % adc)
+                for coarse, curr_cor in enumerate(np.transpose(heatmap[adc])):
+                    for fine, value in enumerate(curr_cor):
+                        f.write("%i;%i;%f\n" % ((coarse+c_frm), (fine+f_frm), value))
+                f.write('\n')
+
+            axis = self.canvas.figure.add_subplot(2, 2, adc+1)
+            image = axis.imshow(heatmap[adc, :, :], interpolation='nearest', aspect='auto')
+            axis.set_xticks(range((c_to - c_frm) + 1))
+            axis.set_xticklabels(range(c_frm, c_to + 1))
+            axis.set_yticks(range((f_to - f_frm) + 1))
+            axis.set_yticklabels(range(f_frm, f_to + 1))
+            axis.invert_yaxis()
+            if adc > 1:
+                axis.set_xlabel('Coarse Delay')
+            if adc is 0 or adc is 2:
+                axis.set_ylabel('Fine Delay')
+            axis.set_title("ADC_%i, C:%i, F:%i" % (adc+1, maximum[adc, 1], maximum[adc, 2]))
+
+        if True:
+            f.close()
+            f = open(filename+'.gnuplot', 'wr')
+            f.write('set datafile separator ";"\n')
+            f.write('set multiplot layout 2,2\n')
+            f.write('unset key\n')
+            for i in range(4):
+                f.write('set label 1 "ADC_%i" at graph 0.7,0.95 font ",8"\n'%(i+1))
+                f.write('plot "%s" every :::%i::%i using 3 with lines\n'%(filename, i, i))
+            f.write('unset multiplot\n')
+            f.close() 
+
+
+        bar_axis = self.canvas.figure.add_axes([0.85, 0.15, 0.05, 0.7])
+        self.canvas.figure.subplots_adjust(right=0.8)
+        self.canvas.figure.colorbar(image, cax=bar_axis)
+        self.canvas.draw()
+        time_scan_stop()
+
+    def on_to_changed(self, value):
+        if value > 0:
+            self.from_spinbox.setMaximum(value - 1)
+
+        self.on_option_change()
+
+    def on_option_change(self):
+        frm = self.from_spinbox.value()
+        to = self.to_spinbox.value()
+
+        enable_wait_cursor()
+        self.canvas.update_figure(self.opt_widgets, self.data, frm, to)
+        disable_wait_cursor()
+
+    def on_adc_delay_changed(self):
+        try:
+            self.board_config.set_delay(self.th_delay_spinbox.value())
+            factors = [self.adc_1_delay_spinbox.value(), self.adc_2_delay_spinbox.value(),
+                       self.adc_3_delay_spinbox.value(), self.adc_4_delay_spinbox.value()]
+            self.board_config.set_chip_delay([0, 1, 2, 3], factors)
+        except heb.board.BoardError as e:
+            logging.error("ADC fine delay failed: {}".format(str(e)))
+            self.do_status_readout()
+            return
+
+    def on_number_of_orbits_changed(self):
+        number = self.number_of_orbits_spinbox.value()
+        hex_str = hex(number)
+        try:
+            heb.board.write_pci(hex_str, '0x9020')
+        except heb.board.BoardError as e:
+            logging.error("Setting orbits failed: {}".format(str(e)))
+            self.do_status_readout()
+            return
+
+    def on_number_of_skipped_orbits_changed(self):
+        number = self.number_of_skipped_orbits_spinbox.value()
+        hex_str = hex(number)
+        try:
+            heb.board.write_pci(hex_str, '0x9028')
+        except heb.board.BoardError as e:
+            logging.error("Could not set ob skipped orbits due to an error!")
+            self.do_status_readout()
+            return
+
+    def on_open(self):
+        filename = QtGui.QFileDialog.getOpenFileName(self, 'Open Datafile', '')
+        if filename:
+            self.read_and_update_data_from_file(filename)
+
+    def on_open_config(self):
+        filename = QtGui.QFileDialog.getOpenFileName(self, 'Open Configuration', '')
+        if filename:
+            self.board_config.load_config(filename)
+            self.on_set_defaults()
+
+    def on_save_config(self):
+        filename = QtGui.QFileDialog.getSaveFileName(self, 'Save Configuration', '')
+        if filename:
+            self.board_config.save_config(filename)
+
+    def on_close(self):
+        self.close()
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('-i', '--input', metavar='FILE', type=str,
+                        help="HEB file")
+    parser.add_argument('-c', '--config', metavar='FILE', type=str,
+                        help="Board Configuration File")
+    parser.add_argument('--force-read', action='store_true',
+                        help="Ignore cache and force re-computation.")
+    parser.add_argument('--check', action='store_true',
+                        help="Check contents of file before proceeding")
+    parser.add_argument('--cache-data', action='store_true',
+                        help="Save already processed NumPy arrays")
+    parser.add_argument('-v', '--verbose', action='store_true',
+                        help="Increase verbosity.")
+
+    args = parser.parse_args()
+
+    if args.verbose:
+        logging.basicConfig(level=logging.DEBUG)
+    else:
+        logging.basicConfig(level=logging.INFO)
+
+    app = QtGui.QApplication(sys.argv)
+
+    window = ApplicationWindow(args)
+    window.show()
+    try:
+        window.do_status_readout()
+    except heb.board.BoardError as e:
+        QtGui.QMessageBox.critical(window, "Board Communication Error",
+                                   "Could not communicate with the Board!\nError was: 'pci' " + str(e)
+                                   + "\nMake sure the HEB Board is connected correctly and run 'Check Status'.")
+    sys.exit(app.exec_())
+
+
+if __name__ == '__main__':
+    main()

+ 98 - 0
heb/__init__.py

@@ -0,0 +1,98 @@
+import logging
+import threading
+import numpy as np
+import time
+
+
+def _pad_array(array):
+    height, width = array.shape
+
+    # Miriam uses floor hence the padding is actually a cutting. Will wait for
+    # response if this is desired ...
+    pwidth = 2**np.floor(np.log2(width))
+    padded = np.zeros((height, pwidth))
+    padded[:, :width] = array[:, :pwidth]
+    return padded
+
+try:
+    import reikna.cluda
+    import reikna.fft
+
+    _plans = {}
+    _in_buffers = {}
+    _out_buffers = {}
+
+    _api = reikna.cluda.ocl_api()
+    _thr = _api.Thread.create()
+
+    def _fft(array):
+        start = time.time()
+        padded = _pad_array(array).astype(np.complex64)
+        height, width = padded.shape
+
+        if width in _plans:
+            fft = _plans[width]
+            in_dev = _in_buffers[width]
+            out_dev = _out_buffers[width]
+        else:
+            fft = reikna.fft.FFT(padded, axes=(1,)).compile(_thr)
+            in_dev = _thr.to_device(padded)
+            out_dev = _thr.empty_like(in_dev)
+            _plans[width] = fft
+            _in_buffers[width] = in_dev
+            _out_buffers[width] = out_dev
+
+        fft(out_dev, in_dev)
+        logging.info("GPU fft: {} s".format(time.time() - start))
+        return out_dev.get()[:, :width / 2 + 1]
+
+except ImportError:
+    logging.info("Failed to import reikna package. Falling back to Numpy FFT.")
+    def _fft(array):
+        start = time.time()
+        freqs = np.fft.rfft(_pad_array(array))
+        logging.info("np fft: {} s".format(time.time() - start))
+        return freqs
+
+
+BUNCHES_PER_TURN = 184
+HEADER_SIZE_BYTES = 32
+
+
+class DataSet(object):
+    def __init__(self, array, filename):
+        self.filename = filename
+        self.array = array
+        self._heatmaps = {}
+        self._ffts = {}
+
+    def bunch(self, number):
+        return self.array[self.array[:, 4] == number]
+
+    def num_bunches(self):
+        return self.array.shape[0]
+
+    def num_turns(self):
+        return self.num_bunches() / BUNCHES_PER_TURN
+
+    def heatmap(self, adc=1, frm=0, to=-1, bunch_frm=0, bunch_to=-1):
+        if not 1 <= adc <= 4:
+            raise ValueError('adc must be in [1,4]')
+
+        if not adc in self._heatmaps:
+            heatmap = self.array[:,adc-1].reshape(-1, BUNCHES_PER_TURN).transpose()
+            self._heatmaps[adc] = heatmap
+
+        return self._heatmaps[adc][bunch_frm:bunch_to, frm:to]
+
+    def fft(self, adc=1, frm=0, to=-1):
+        if not 1 <= adc <= 4:
+            raise ValueError('adc must be in [1,4]')
+
+        # if not adc in self._ffts:
+        #     heatmap = self.heatmap(adc, frm, to)
+        #     self._ffts[adc] = np.fft.fft2(heatmap, axes=[1])
+
+        # return self._ffts[adc]
+        heatmap = self.heatmap(adc, frm, to)
+        return _fft(heatmap)

+ 429 - 0
heb/board.py

@@ -0,0 +1,429 @@
+import re
+import time
+import logging
+import subprocess
+import ConfigParser
+import numpy as np
+
+log = logging.getLogger(__name__)
+
+
+class BoardError(Exception):
+    pass
+
+
+class ObserverError(Exception):
+    pass
+
+
+class BoardConfiguration():
+
+    def __init__(self, config_file=None, logger=None):
+        self._config = {}
+        self._observers = {}
+        self.logger = logger
+        self._set_defaults()
+
+        if config_file:
+            self.load_config(config_file)
+
+    def _set_defaults(self):
+        c = self._config
+        c['fpga_delay_max']       = 15
+        c['fpga_delay']           = 0
+        c['fpga_delay_factor']    = 150
+        
+        c['chip_delay_max']       = 31
+        c['chip_1_delay']         = 4
+        c['chip_2_delay']         = 4
+        c['chip_3_delay']         = 4
+        c['chip_4_delay']         = 4
+        c['chip_delay_factor']    = 3
+
+        c['th_delay_max']         = 15
+        c['th_delay']             = 3
+        c['th_delay_factor']      = 150
+        
+        c['adc_delay_max']        = 15
+        c['adc_1_delay']          = 4
+        c['adc_2_delay']          = 4
+        c['adc_3_delay']          = 4
+        c['adc_4_delay']          = 4
+        c['adc_delay_factor']     = 150
+        
+        c['th_to_adc_cycles']     = 7
+        c['adc_1_delay_individual'] = -1  #      -1 = 'ADC 1 delay is the same as th_to_adc_cycles'
+                                          # 0 .. 16 = 'Use this offset instead'
+        
+        c['orbits_observe']       = 100
+        c['orbits_skip']          = 2
+
+    def load_config(self, filename):
+        if filename:
+            config = ConfigParser.RawConfigParser()
+            if not config.read(str(filename)):
+                return
+
+            for key in self._config.keys():
+                try:
+                    self._config[key] = config.getint('Config', key)
+                    if self.logger:
+                        self.logger.info("Read '%s' for '%s' from '%s'"%(str(self._config[key]), key, str(filename)))
+                except ConfigParser.NoOptionError as e:
+                    pass
+                except ConfigParser.NoSectionError as e:
+                    pass
+
+    def save_config(self, filename):
+        if filename:
+            with open(str(filename), 'w') as f:
+                cp = ConfigParser.RawConfigParser()
+                cp.add_section('Config')
+                for key in self._config.keys():
+                    cp.set('Config', key, self._config[key])
+                f.write('#\n'
+                        '#     HEB   (Hot Electron Bolometer) Configuration file\n'
+                        '#\n'
+                        '#  (c) Karlsruhe Institute of Technology, 2014\n'
+                        '#  All rights reserved.\n'
+                        '#\n'
+                        '#  Applicable Firmware Version(s):\n'
+                        '#\n\n')
+                cp.write(f)
+
+    def get(self, key):
+        return self._config.get(key, None)
+
+    def update(self, key, value):
+        self._config[key] = value
+        self._notify_observers(key, value)
+
+    def get(self, key):
+        return self._config.get(key, None)
+
+    def update(self, key, value):
+        self._config[key] = value
+        self._notify_observers(key, value)
+
+    def observe(self, who, callback, key):
+        if key not in self._config.keys():
+            raise ObserverError(str("Key '%s' is unknown."%key))
+        
+        if self._observers.get(key, None) is None:
+            self._observers[key] = []
+        
+        self._observers[key].append([who, callback])
+        
+    def unobserve(self, who, key=None):
+        if key is not None:
+            observers = np.array(self._observers.get(key, None))
+            if observers is None:
+                return
+            if who not in observers[:, 0]:
+                return               
+            for i, _obs in enumerate(self._observers[key]):
+                if _obs[0] is who:
+                    del self._observers[key][i]
+                    if not self._observers[key]:
+                        del self._observers[key]
+            return
+        
+        for _key in self._observers.keys():
+            for i, _obs in enumerate(self._observers[_key]):
+                if _obs[0] is who:
+                    del self._observers[_key][i]
+                    if not self._observers[_key]:
+                        del self._observers[_key]
+
+    def _notify_observers(self, key, value):
+        observers = self._observers.get(key, None)
+        if observers is None:
+            return
+        for (who, callback) in observers:
+            callback(value)
+        
+    def make_uint(self, value, maximum, name=None):
+        if value is None:
+            raise ValueError(str("%s Value is invalid (None)" % name))
+        
+        val = None
+        try:
+            val = int(value)
+        except:
+            raise ValueError(str("%s Value is not a valid number" % name))
+
+        if maximum is not None:    
+            if val > maximum:
+                raise ValueError(str("%s Value is too large (>%i)" % (name, maximum)))
+                
+        if val < 0:
+            raise ValueError(str("%s Values below 0 are not allowed" % name))
+        
+        return val
+
+    def set_fpga_delay(self, value):
+        time_factor = self.make_uint(value, self.get('fpga_delay_max'), 'FPGA_Delay')
+        reg_value = "0x000501" + '{0:01x}'.format(time_factor) + "0"
+        write_pci(reg_value, '0x9060')
+        if self.logger:
+            self.logger.info("Set FPGA clock delay %i * %i --> %i picoseconds" % (time_factor, self.get('fpga_delay_factor'), time_factor*self.get('fpga_delay_factor')))
+        self.update('fpga_delay', value)
+
+    def set_chip_delay(self, adcs, values):
+        if not adcs or not values:
+            if self.logger:
+                self.logger.info("Nothing to do for chip delay.")
+            return
+
+        _adcs = []
+        for adc in adcs:
+            _adcs.append(self.make_uint(adc, 3, 'ADC_'))
+            
+        _values = []
+        for value in values:
+            _values.append(self.make_uint(value, self.get('chip_delay_max'), 'ADC_Value'))
+
+        a_v = zip(_adcs, _values) 
+            
+        factors = [None, None, None, None]
+        for (adc, value) in a_v:
+            factors[adc] = value
+            
+        reg_value = ''
+        mask = ''
+        # Chip Delays are stored as 'ADC_4 ADC_3 ADC_2 ADC_1' in the register.
+        # Therefore, we need to traverse the factors array in reverse order
+        for value in reversed(factors):
+            if value is not None:
+                reg_value += '{0:02x}'.format(value)
+                mask += 'ff'
+            else:
+                reg_value += '00'
+                mask += '00'
+               
+        write_pci(reg_value, '0x9080', hex_mask=mask)
+        if self.logger:
+            s = "Setting ADC Delays:"
+            for (adc, value) in a_v:
+                s += ' ADC_%i Fine Delay: %i,' % (adc, value)
+            s = s[:-1]  # cut away the last dangling ','
+            self.logger.info(s)
+        for (adc, value) in a_v:
+            s = 'chip_%i_delay'%(adc+1)
+            self.update(s, value)
+
+    def set_th_delay(self, value):
+        time_factor = self.make_uint(value, self.get('th_delay_max'), 'TH_Delay')
+        reg_value = "0x000501" + '{0:01x}'.format(time_factor) + "3"
+        write_pci(reg_value, '0x9060')
+        if self.logger:
+            self.logger.info("Set T/H Signal delay %i * %i --> %i picoseconds" % (time_factor, self.get('th_delay_factor'), time_factor*self.get('th_delay_factor')))
+        self.update('th_delay', value)
+
+    def set_adc_delay(self, adc, value):
+        if adc is None or value is None:
+            if self.logger:
+                self.logger.info("Nothing to do for ADC delay.")
+            return
+        _adc = self.make_uint(adc, 3, 'ADC Number')    
+        _val = self.make_uint(value, self.get('adc_delay_max'), 'ADC Delay')
+        reg_value = "0x000501" + '{0:01x}'.format(_val) + "%i" % (_adc+4)
+        write_pci(reg_value, '0x9060')
+        if self.logger:
+            s = "Setting ADC_%i delay %i * %i --> %i picoseconds" % ((_adc+1), _val, self.get('adc_delay_factor'), _val*self.get('adc_delay_factor'))
+            self.logger.info(s)
+        adc_s = 'adc_%i_delay'%(_adc+1)
+        self.update(adc_s, _val)
+
+    def set_delay(self, n):
+        def write_delay(value, channel):
+            cmd = '00501' + '%01x' % value + str(channel)
+            write_pci(cmd, reg='0x9060')
+            time.sleep(0.005)
+
+        write_delay(n, 3)
+        self.update('th_delay', n)
+
+        delay = n + self.get('th_to_adc_cycles')
+
+        if delay > self.get('adc_delay_max'):
+            delay -= self.get('adc_delay_max') + 1
+
+        write_delay(delay, 5)
+        self.update('adc_2_delay', delay)
+        write_delay(delay, 6)
+        self.update('adc_3_delay', delay)
+        write_delay(delay, 7)
+        self.update('adc_4_delay', delay)
+
+        #ADC 1 might have an individual delay
+        try:
+            delay = n + self.make_uint(self.get('adc_1_delay_individual'), 16, 'ADC 1 individual delay')
+        except ValueError:
+            if self.logger:
+                self.logger.info(r"'adc_1_delay_individual' not set or inactive. Using default.")
+
+        if delay > self.get('adc_delay_max'):
+            delay -= self.get('adc_delay_max') + 1
+        write_delay(delay, 4)
+        self.update('adc_1_delay', delay)
+
+
+def safe_call(cmd):
+    try:
+        return subprocess.check_output(cmd)
+    except subprocess.CalledProcessError as e:
+        raise BoardError(e.output)
+    except OSError as e:
+        raise BoardError('{}: {}'.format(' '.join(cmd), str(e)))
+
+
+def write_pci(value, reg='0x9040', opts=[], hex_mask='FFFFFFFF'):
+
+    if hex_mask != 'FFFFFFFF':
+        if len(hex_mask) > 8:
+            hex_mask = hex_mask[-8:]
+        prev_val = read_pci(1, reg)[0]
+        prev_bits = '{0:032b}'.format(int(prev_val,16))
+        mask_bits = '{0:032b}'.format(int(hex_mask,16))
+        value_bits = '{0:032b}'.format(int(value,16))
+        new_bits = list(prev_bits)
+        
+        for i, bit in enumerate(mask_bits):
+            if bit == '1':
+                new_bits[i] = value_bits[i]
+                
+        value = hex(int("".join(new_bits),2))
+
+    cmd = ['pci', '-w', reg, value]
+    cmd.extend(opts)
+    safe_call(cmd)
+    log.debug('Written %str to register %s'%(value, reg))
+    
+
+def read_pci(amount=1, reg='0x9000', opts=[], decimal=False):
+    cmd = ['pci', '-r', reg, "-s%i"%amount]
+    cmd.extend(opts)
+    output = safe_call(cmd).split("\n")
+    lines = []
+    for line in output:
+        if line and line != "\n":
+            lines.append(line.split(":  ")[1])
+    formated = []
+    for line in lines:
+        if line and line != "\n":
+            formated += re.findall(r'[\da-fA-F]{8}', line) 
+    if decimal:
+        return [int(x,16) for x in formated]
+    else:
+        return formated
+
+    
+def get_dec_from_bits(bits, msb=-1, lsb=-1):
+    rtrnValue = 0
+    
+    if (msb < 0 or lsb < 0):
+        rtrnValue = int(bits)
+    elif (msb < lsb) or (msb > 31) or (lsb > 31):
+        log.info("Bit range for msb and lsb of get_dec_from_bits was wrong. Not truncating")
+        rtrnValue = int(bits, 2)
+    else:
+        chunk = ('{0:032b}'.format(int(bits, 2)))[31-msb:32-lsb]
+        rtrnValue = int(chunk, 2)
+        
+    return rtrnValue
+
+
+def start_dma(dma='dma0r'):
+    log.info('Start DMA')
+    cmd = ['pci', '--start-dma', dma]
+    safe_call(cmd)
+    time.sleep(0.05)
+    
+
+def stop_dma(dma='dma0r'):
+    log.info('Stop DMA')
+    cmd = ['pci', '--stop-dma', dma]
+    safe_call(cmd)
+    time.sleep(0.05)
+
+
+def data_reset():
+    log.info('Data reset')
+    write_pci('000003f1', hex_mask='1')
+    time.sleep(0.05)
+    write_pci('000003f0', hex_mask='1')
+    time.sleep(0.05)
+
+
+def flush_dma(dma='dma0'):
+    log.info('Flushing DMA Pipeline')
+    write_pci('03f0', hex_mask='CF0')
+    cmd = ['pci', '-r', dma, '-o', '/dev/null', '--multipacket']
+    safe_call(cmd)
+
+
+def is_active():
+    control = read_pci(1, '0x9040')[0]
+    control_bits = '{0:032b}'.format(int(control, 16))
+    return control_bits[22:26] == "1111"
+
+
+def start_pilot_bunch_emulator():
+    log.info('Start pilot bunch emulator')
+    write_pci('400003f0', hex_mask='400003F0')
+    time.sleep(0.05)
+    write_pci('03f0', hex_mask='CF0')
+
+
+def start_acquisition():
+    log.info('Start acquisition')
+    write_pci('1', '4', hex_mask='1')
+    write_pci('00bf0', hex_mask='CF0')
+
+
+def stop_acquisition():
+    log.info('Stop acquisition')
+    write_pci('003f0', hex_mask='CF0')
+    time.sleep(0.05)
+
+
+def enable_transfer():
+    log.info('Enable data transfer')
+    write_pci('007f0', hex_mask='CF0')
+    time.sleep(0.05)
+
+
+def write_data(filename, dma='dma0'):
+    log.info('Write data to {}'.format(filename))
+    cmd = ['pci', '-r', dma, '-o', filename, '--multipacket']
+    safe_call(cmd)
+    write_pci('003f0', hex_mask='CF0')
+
+
+def run_status(script_path):
+    cmd = ['bash', script_path]
+    output = safe_call(cmd)
+    log.info(output)
+
+
+def acquire_data(filename, duration=1.0, simulate=False):
+    #start_dma()  #Dont start dma when it is already active! (Causes data corruption in the driver)
+    #data_reset() #No Longer needed
+
+    if simulate:
+        start_pilot_bunch_emulator()
+
+    start_acquisition()
+    time.sleep(duration)
+    stop_acquisition()
+    enable_transfer()
+    write_data(filename)
+    flush_dma()
+
+
+def wait_for_revolutions():
+    n = read_pci(1, '0x9020', decimal=True)[0]  # Get the ammount of orbits to observe
+    spin_time_ns = 368 * n
+    wait_time_s = spin_time_ns / 1000.0 / 1000.0 / 1000.0   # Milli, Micro, Nano
+    time.sleep(wait_time_s * 1.2)  # 20% Safety margin

+ 98 - 0
heb/io.py

@@ -0,0 +1,98 @@
+import os
+import math
+import logging
+import numpy as np
+
+from heb import DataSet, BUNCHES_PER_TURN, HEADER_SIZE_BYTES
+
+
+def is_data_consistent(dataset):
+    bunch_numbers = dataset.array[:, -1]
+    expected = np.tile(np.arange(0, BUNCHES_PER_TURN), bunch_numbers.shape[0] / BUNCHES_PER_TURN)
+    wrong_indices = np.argwhere(bunch_numbers != expected)
+    if wrong_indices.shape[0] > 0:
+        filling = bunch_numbers[wrong_indices[0][0]:]
+        expected_filling = np.tile([0xdead], filling.shape[0])
+        wrong_filling_indices = np.argwhere(filling != expected_filling)
+        if wrong_filling_indices.shape[0] > 2:  # Some times filling does not start immediately... Why? I have NO IDEA!
+            return False
+        else:
+            return True
+    else:
+        return True    
+
+
+def _cached_exist(filename):
+    return os.path.exists(os.path.abspath('{}.npy'.format(filename)))
+
+
+def decode_data(data):
+        data = data[np.where(data != 0x01234567)]
+
+        # Make sure we read multiple of fours
+        data = data[:4 * (math.floor(data.size / 4))]
+
+        bunch_low = data & 0xfff
+        bunch_high = np.right_shift(data, 12) & 0xfff
+        bunch_number = np.right_shift(data, 24) & 0xfff
+
+        bunch_low = bunch_low.reshape(-1, 4)
+        bunch_high = bunch_high.reshape(-1, 4)
+
+        result = np.empty((bunch_low.shape[0] + bunch_high.shape[0], 5), dtype=np.float)
+        result[0::2,:4] = bunch_low
+        result[1::2,:4] = bunch_high
+        result[0::2, 4] = bunch_number[::4]
+        result[1::2, 4] = bunch_number[::4] + 1
+
+        result = result[:184 * (math.floor(result.shape[0] / 184)), :]
+        return result
+
+
+def read_from_file(filename, force=False, header=False, cache=False):
+    if _cached_exist(filename) and not force:
+        cached_filename = '{}.npy'.format(filename)
+        logging.info("Read pre-computed data from {}".format(cached_filename))
+        return DataSet(np.load(cached_filename), filename)
+
+    with open(filename, 'rb') as f:
+        logging.info("Read data from {}".format(filename))
+
+        data = np.fromfile(f, dtype=np.uint32)
+        #If header is sent with the data, truncate it
+        if header:
+            # We read words of 4 bytes each
+            splice_words = HEADER_SIZE_BYTES / 4
+            data = data[splice_words:]
+        result = decode_data(data)
+        dataset = DataSet(result, filename)
+
+        if cache:
+            logging.info('Saving pre-computed data')
+            np.save('{}.npy'.format(filename), result)
+
+        return dataset
+        
+        
+def read_from_string(raw_data, force=False, header=False, cache=False, cache_filename="_heb_data_cache"):
+    if _cached_exist(cache_filename) and not force:
+        cache_file = '{}.npy'.format(cache_filename)
+        logging.info("Read pre-computed data from {}".format(cache_file))
+        return DataSet(np.load(cache_file), cache_filename)
+
+    logging.info("Read data directly from device.")
+    logging.info("Read %i bytes of data" % len(raw_data))
+    data = np.fromstring(raw_data, dtype=np.uint32)
+    #If header is sent with the data, truncate it
+    if header:
+            # We read words of 4 bytes each
+            splice_words = HEADER_SIZE_BYTES / 4
+            data = data[splice_words:]
+    result = decode_data(data)
+    dataset = DataSet(result, "HEB Live Data")
+
+    if cache:
+        logging.info('Saving pre-computed data')
+        np.save('{}.npy'.format(cache_filename), result)
+
+    return dataset

+ 259 - 0
heb/plot.py

@@ -0,0 +1,259 @@
+import numpy as np
+import matplotlib.pyplot as plt
+import scipy.constants
+
+
+try:
+    from numpy.fft import rfftfreq
+except ImportError:
+    # This is a backport of the rfftfreq function present as of 2012 but not
+    # included in Ubuntu 12.04.
+    #
+    # Licensed under BSD-new.
+    def rfftfreq(n, d=1.0):
+        """
+        Return the Discrete Fourier Transform sample frequencies
+        (for usage with rfft, irfft).
+
+        The returned float array `f` contains the frequency bin centers in cycles
+        per unit of the sample spacing (with zero at the start).  For instance, if
+        the sample spacing is in seconds, then the frequency unit is cycles/second.
+
+        Given a window length `n` and a sample spacing `d`::
+
+          f = [0, 1, ...,     n/2-1,     n/2] / (d*n)   if n is even
+          f = [0, 1, ..., (n-1)/2-1, (n-1)/2] / (d*n)   if n is odd
+
+        Unlike `fftfreq` (but like `scipy.fftpack.rfftfreq`)
+        the Nyquist frequency component is considered to be positive.
+
+        Parameters
+        ----------
+        n : int
+            Window length.
+        d : scalar, optional
+            Sample spacing (inverse of the sampling rate). Defaults to 1.
+
+        Returns
+        -------
+        f : ndarray
+            Array of length ``n//2 + 1`` containing the sample frequencies.
+
+        Examples
+        --------
+        >>> signal = np.array([-2, 8, 6, 4, 1, 0, 3, 5, -3, 4], dtype=float)
+        >>> fourier = np.fft.rfft(signal)
+        >>> n = signal.size
+        >>> sample_rate = 100
+        >>> freq = np.fft.fftfreq(n, d=1./sample_rate)
+        >>> freq
+        array([  0.,  10.,  20.,  30.,  40., -50., -40., -30., -20., -10.])
+        >>> freq = np.fft.rfftfreq(n, d=1./sample_rate)
+        >>> freq
+        array([  0.,  10.,  20.,  30.,  40.,  50.])
+
+        """
+        val = 1.0/(n*d)
+        N = n//2 + 1
+        results = np.arange(0, N, dtype=int)
+        return results * val
+
+
+H = 184
+FREV = scipy.constants.c / 110.4    # lightspeed divided by circumference
+TREV = 1 / FREV
+
+
+# This is a backport of the polyval function present as of 2011 but not
+# included in Ubuntu 12.04.
+#
+# Licensed under BSD-new.
+def polyval(x, c, tensor=True):
+    """
+    Evaluate a polynomial at points x.
+
+    If `c` is of length `n + 1`, this function returns the value
+
+    .. math:: p(x) = c_0 + c_1 * x + ... + c_n * x^n
+
+    The parameter `x` is converted to an array only if it is a tuple or a
+    list, otherwise it is treated as a scalar. In either case, either `x`
+    or its elements must support multiplication and addition both with
+    themselves and with the elements of `c`.
+
+    If `c` is a 1-D array, then `p(x)` will have the same shape as `x`.  If
+    `c` is multidimensional, then the shape of the result depends on the
+    value of `tensor`. If `tensor` is true the shape will be c.shape[1:] +
+    x.shape. If `tensor` is false the shape will be c.shape[1:]. Note that
+    scalars have shape (,).
+
+    Trailing zeros in the coefficients will be used in the evaluation, so
+    they should be avoided if efficiency is a concern.
+
+    Parameters
+    ----------
+    x : array_like, compatible object
+        If `x` is a list or tuple, it is converted to an ndarray, otherwise
+        it is left unchanged and treated as a scalar. In either case, `x`
+        or its elements must support addition and multiplication with
+        with themselves and with the elements of `c`.
+    c : array_like
+        Array of coefficients ordered so that the coefficients for terms of
+        degree n are contained in c[n]. If `c` is multidimensional the
+        remaining indices enumerate multiple polynomials. In the two
+        dimensional case the coefficients may be thought of as stored in
+        the columns of `c`.
+    tensor : boolean, optional
+        If True, the shape of the coefficient array is extended with ones
+        on the right, one for each dimension of `x`. Scalars have dimension 0
+        for this action. The result is that every column of coefficients in
+        `c` is evaluated for every element of `x`. If False, `x` is broadcast
+        over the columns of `c` for the evaluation.  This keyword is useful
+        when `c` is multidimensional. The default value is True.
+
+        .. versionadded:: 1.7.0
+
+    Returns
+    -------
+    values : ndarray, compatible object
+        The shape of the returned array is described above.
+
+    See Also
+    --------
+    polyval2d, polygrid2d, polyval3d, polygrid3d
+
+    Notes
+    -----
+    The evaluation uses Horner's method.
+
+    Examples
+    --------
+    >>> from numpy.polynomial.polynomial import polyval
+    >>> polyval(1, [1,2,3])
+    6.0
+    >>> a = np.arange(4).reshape(2,2)
+    >>> a
+    array([[0, 1],
+           [2, 3]])
+    >>> polyval(a, [1,2,3])
+    array([[  1.,   6.],
+           [ 17.,  34.]])
+    >>> coef = np.arange(4).reshape(2,2) # multidimensional coefficients
+    >>> coef
+    array([[0, 1],
+           [2, 3]])
+    >>> polyval([1,2], coef, tensor=True)
+    array([[ 2.,  4.],
+           [ 4.,  7.]])
+    >>> polyval([1,2], coef, tensor=False)
+    array([ 2.,  7.])
+
+    """
+    c = np.array(c, ndmin=1, copy=0)
+    if c.dtype.char in '?bBhHiIlLqQpP':
+        # astype fails with NA
+        c = c + 0.0
+    if isinstance(x, (tuple, list)):
+        x = np.asarray(x)
+    if isinstance(x, np.ndarray) and tensor:
+        c = c.reshape(c.shape + (1,)*x.ndim)
+
+    c0 = c[-1] + x*0
+    for i in range(2, len(c) + 1) :
+        c0 = c[-i] + c0*x
+    return c0
+
+
+def bunch(data, number, adc=None, frm=0, to=-1, **kwargs):
+    bdata = data.bunch(number)[frm:to,1:]
+    xs = np.arange(frm, max(to, bdata.shape[0]))
+
+    if adc is None:
+        plt.plot(xs, np.mean(bdata, axis=1),
+                 label='Averaged ADC count, bunch {}'.format(number),
+                 **kwargs)
+    else:
+        plt.plot(xs, bdata[:,adc-1],
+                 label='ADC {}, bunch {}'.format(adc, number),
+                 **kwargs)
+
+
+def train(data, axis, adc=None, frm=0, to=-1, **kwargs):
+    pdata = data.array[frm:to, :-1]
+
+    if adc is None:
+        axis.plot(np.mean(pdata, axis=1), **kwargs)
+    else:
+        axis.plot(pdata[:,adc-1], **kwargs)
+
+    axis.set_xlabel('Sample point')
+
+    if adc:
+        axis.set_title('ADC {}'.format(adc))
+
+
+def combined(data, axis, adcs, frm=0, to=-1, show_reconstructed=True):
+    array = data.array[frm:to, :]
+
+    N_s = 16
+    N_p = array.shape[0]
+
+    # Remove bunch number and flatten array
+    array = array[:,:4].reshape((-1, 1))
+
+    # plot original data
+    orig_xs = np.arange(0, array.shape[0])
+    axis.plot(orig_xs, array, '.')
+
+    # 4 rows for each ADC
+    ys = array.reshape((4, -1), order='F')
+
+    # multi-fit for each column of 4 ADCs, unfortunately, xs' are always the
+    # same, so we have to offset them later when computing the value
+    xs = [1, 2, 3, 4]
+    c = np.polynomial.polynomial.polyfit(xs, ys, 6)
+
+    smooth_xs = np.linspace(1, 4, N_s)
+    fitted_ys = polyval(smooth_xs, c, tensor=True)
+
+    if True:
+        # Output maxima
+        ys = np.max(fitted_ys, axis=1)
+        xs = 4 * ((np.argmax(fitted_ys, axis=1) + 1) / float(N_s)) + orig_xs[::4] - .5
+
+        axis.plot(xs, ys, '.', color="red")
+
+    if True:
+        # Debug output
+        ys = fitted_ys.reshape((-1, 1))
+        xs = np.repeat(np.arange(0, 4 * N_p, 4), N_s) + np.tile(np.linspace(0, 3, N_s), N_p)
+        axis.plot(xs, ys, '.', alpha=0.3)
+
+
+def heatmap(heatmap, axis):
+    image = axis.imshow(heatmap, interpolation='nearest', aspect='auto')
+    axis.invert_yaxis()
+    axis.set_xlabel('Turn')
+    axis.set_ylabel('Bunch position')
+    return image
+
+
+def fft(data, axis, adc, frm=0, to=-1, skipped=0):
+    transform = data.fft(adc, frm=frm, to=to)
+    freqs = rfftfreq(transform.shape[1], TREV * (skipped + 1))
+
+    transform = transform[:, 1:]  # Throw away column 1
+    # Due to the nature of the signal being divided into 'buckets', frequency '1' has an excessively large value and
+    # skews the proportions for the other signals. It does not hold any useful information either...
+    # Therefore, we remove it from the results.
+
+    magnitude = np.log(np.abs(transform))
+    image = axis.imshow(magnitude,
+                        interpolation='none',
+                        aspect='auto',
+                        extent=[freqs[1], freqs[-1], 0, H],  # Start from freq[1] since we removed freq[0] from the data
+                        origin='lower')
+
+    axis.set_xlabel('Frequency')
+    axis.set_ylabel('Bunch position')
+    return image

+ 18 - 0
setup.py

@@ -0,0 +1,18 @@
+import os
+from setuptools import setup, find_packages
+
+DESCRIPTION = "Analysis for the HEB experiment"
+
+setup(
+    name='heb',
+    version='0.1',
+    author='Matthias Vogelgesang',
+    author_email='matthias.vogelgesang@kit.edu',
+    license='BSD',
+    scripts=['bin/heb'],
+    description=DESCRIPTION,
+    long_description=DESCRIPTION,
+    packages=find_packages(),
+    install_requires=['numpy',
+                      'matplotlib'],
+)