Esempio n. 1
0
    def __init__(self):
        super(CurveImage, self).__init__()
        log.debug("CurveImage creation")

        from linegrab.ui.linegrab_layout import Ui_MainWindow
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.setGeometry(450, 350, 1080, 600)

        # Make sure the system wide style sheet is applied before the
        # curve and image widgets style sheets overwrite
        self.qss_string = utils.load_style_sheet("qdarkstyle.css")
        self.setStyleSheet(self.qss_string)

        self.replace_widgets()

        # Align the image with the curve above it
        self.main_image_dialog.setContentsMargins(17, 0, 0, 0)

        self.add_manager_and_tools()

        # Timer to auto-close the application
        self.close_timer = QtCore.QTimer()
        self.close_timer.timeout.connect(self.closeEvent)

        self.set_app_defaults()

        self.show()
Esempio n. 2
0
class CurveImage(QtGui.QMainWindow):
    """ The main interface for the LineGrab application. Can be created
    from unittest or a main() for full test coverage.
    """
    def __init__(self):
        super(CurveImage, self).__init__()
        log.debug("CurveImage creation")

        from linegrab.ui.linegrab_layout import Ui_MainWindow
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.setGeometry(450, 350, 1080, 600)

        # Make sure the system wide style sheet is applied before the
        # curve and image widgets style sheets overwrite
        self.qss_string = utils.load_style_sheet("qdarkstyle.css")
        self.setStyleSheet(self.qss_string)

        self.replace_widgets()

        # Align the image with the curve above it
        self.main_image_dialog.setContentsMargins(17, 0, 0, 0)

        self.add_manager_and_tools()

        # Timer to auto-close the application
        self.close_timer = QtCore.QTimer()
        self.close_timer.timeout.connect(self.closeEvent)

        self.set_app_defaults()

        self.show()

    def replace_widgets(self):
        """ From: http://stackoverflow.com/questions/4625102/\
            how-to-replace-a-widget-with-another-using-qt
        Replace the current placeholders from qt designer with the
        custom widgets.
        """

        # Create the widget
        self.main_curve_dialog = visualize.CleanCurveDialog()

        # Remove the placeholder widget from the layout
        lcph = self.ui.labelCurvePlaceholder
        vlc = self.ui.verticalLayoutCurve
        vlc.removeWidget(lcph)
        lcph.close()

        # Add the new widget to the layout
        vlc.insertWidget(0, self.main_curve_dialog)
        vlc.update()

        # Create the widget
        self.main_image_dialog = visualize.CleanImageDialog()

        # Remove the placeholder widget from the layout
        liph = self.ui.labelImagePlaceholder
        vli = self.ui.verticalLayoutImage
        vli.removeWidget(liph)
        liph.close()

        # Add the new widget to the layout
        vli.insertWidget(0, self.main_image_dialog)
        vli.update()

    def add_manager_and_tools(self):
        """ Create the required plot manager to give access to the item
        list, graph tools, etc.
        """
        # Create a new plot manager, add plots to the plot manager.
        # There is already a plot manager associated with the
        # curvedialog, just create a new one for simplicity.
        manager = plot.PlotManager(self)
        manager.add_plot(self.main_curve_dialog.get_plot())

        # Add a panel to the plot manager - this apparently is not
        # required to enable the tool linkage. Was in here based on
        # example code. If you add this back in it creates an invisible
        # widget over at least the 'play' button.
        #manager.add_panel(plot.PlotItemList(self))

        # Associate the toolbar with the plot manager, this is created
        # along with the qmainwindow toolbars
        curve_toolbar = self.addToolBar("Curve tools")
        curve_toolbar.setIconSize(QtCore.QSize(36, 36))
        manager.add_toolbar(curve_toolbar, id(curve_toolbar))

        # If you do this, you get all of the other tools
        #manager.register_all_curve_tools()

        # Add the custom tool classes with wrapper signals
        self.select_tool = manager.add_tool(visualize.SelectSignalTool)
        self.zoom_tool = manager.add_tool(visualize.ZoomSignalTool)

        # Store a reference for use by the application
        self.curve_toolbar = curve_toolbar

    def set_parameters(self, args):
        """ Assign the startup environment parameters for this
        application run. Data source simulation/e2v, etc.
        """
        self.args = args

        if args.testing:
            self.delay_close()

        if args.source == "simulation":
            log.info("Create simulated spectra device")
            self.dev = simulation.SimulatedSpectraDevice()

        elif args.source == "sled":
            log.info("Create single sled cobra")
            self.dev = simulation.SimulatedCobraSLED()

        elif args.source == "cobra":
            log.info("Create DALSA cobra device")
            #self.dev = devices.DalsaCobraDevice()
            self.dev = DALSA.Cobra()

        elif args.source == "opto":
            log.info("Create OPTO sensor cobra device")
            self.dev = DALSA.OPTOCobra()

        elif args.source == "basler":
            log.info("Create DALSA basler device")
            #self.dev = devices.DalsaBaslerDevice()
            self.dev = DALSA.BaslerSprint4K()

        self.dev.setup_pipe()
        self.setup_pipe_timer()

    def delay_close(self):
        """ For testing purposes, create a qtimer that triggers the
        close event after a delay.
        """
        log.debug("Trigger delay close")
        self.close_timer.start(9000)

    def closeEvent(self, event=None):
        """ Cleanup the application on widget close
        close the pipes, stop all the timers. For some reason, if you
        don't specify event=None it will save the argument is not given.
        """
        log.debug("Cleanup close, %s", event)
        result = self.dev.close_pipe()
        log.info("Close pipe result: %s", result)
        self.data_timer.stop()
        self.close()

    def create_actions(self):
        """ Runtime generated toolbars to link guiqwt graph controls
        with the mainwindow level toolbars.
        """
        dgct = self.curve_toolbar.addAction

        agfe = dgct(QtGui.QIcon(":/greys/greys/full_extent.svg"),
                    "Full extent graph")
        self.action_graph_full_extent = agfe

        agr = dgct(QtGui.QIcon(":/greys/greys/reset.svg"),
                   "Reset graph parameters")
        self.action_graph_reset = agr

        spacer = QtGui.QWidget()
        spacer.setSizePolicy(QtGui.QSizePolicy.Expanding,
                             QtGui.QSizePolicy.Expanding)
        self.curve_toolbar.addWidget(spacer)

        act_name = "Instantaneous performance"
        self.action_fps_display = QtGui.QAction(act_name, self)
        self.curve_toolbar.addAction(self.action_fps_display)

        # Remove the placeholder toolbar
        self.ui.toolBar_GraphControls.setVisible(False)

    def setup_signals(self):
        """ Hook into graph emitted signals for controller level.
        """

        # Hook the play/pause buttons
        self.ui.actionContinue_Live_Updates.triggered.connect(self.on_live)
        self.ui.actionPause_Live_Updates.triggered.connect(self.on_pause)
        self.ui.actionSave.triggered.connect(self.on_save)

        # Custom graph buttons
        self.action_graph_reset.triggered.connect(self.reset_graph)
        self.action_graph_full_extent.triggered.connect(self.full_extent)

        # Custom tools generated in visualize that are not actions
        self.zoom_tool.wrap_sig.clicked.connect(self.process_zoom)
        self.select_tool.wrap_sig.clicked.connect(self.process_select)

    def on_save(self, action):
        """ Create a save as dialog, unless in testing mode where a
        filename is hardcoded.
        """

        # Freeze the display to make saving more robust
        self.ui.actionPause_Live_Updates.trigger()

        if not self.args.testing:
            file_name = QtGui.QFileDialog.getSaveFileName(
                None, "Save Tif", "", "TIFF (*.tif)", "",
                QtGui.QFileDialog.DontUseNativeDialog)
        else:
            file_name = "autosave"

        if file_name == "":
            self.ui.actionContinue_Live_Updates.trigger()
            return

        # Use the same inefficient yet understandable method of
        # transforming the image_data into a 2d numpy array, then use
        # pil to save to disk
        img_data = range(len(self.image_data))

        position = 0
        while position < len(img_data):
            img_data[position] = self.image_data[position]
            position += 1

        local_data = numpy.array(img_data).astype(float)

        log.info("Saving to: %s" % file_name)
        pil_image = Image.fromarray(local_data)
        pil_image.save(str("%s.tif" % file_name))

        # un-freeze the display now that the saving process isover
        self.ui.actionContinue_Live_Updates.trigger()

    def on_live(self, action):
        """ Live and pause buttons are the equivalent of toggle buttons.
        Only one can be enabled at a time.
        """
        log.info("Click live updates")
        if action == False:
            self.ui.actionContinue_Live_Updates.setChecked(True)

        self.ui.actionPause_Live_Updates.setChecked(False)
        self.live_updates = True

    def on_pause(self, action):
        """ Pause and live buttons are the equivalent of toggle buttons.
        Only one can be enabled at a time.
        """
        log.info("Pause live updates: %s", action)
        if action == False:
            self.ui.actionPause_Live_Updates.setChecked(True)

        self.ui.actionContinue_Live_Updates.setChecked(False)
        self.live_updates = False

    def full_extent(self):
        """ Set the x axis to the full data range (12-bit), and set auto
        scale off.
        """
        log.debug("Set full extent")
        self.auto_scale = False
        local_plot = self.main_curve_dialog.get_plot()
        local_plot.set_axis_limits(0, 0, 4096)
        local_plot.replot()

    def reset_graph(self):
        """ Reset curve, image visualizations to the default. Trigger a
        auto scale replot manually in case pause mode is active.
        """
        log.debug("reset graph")
        self.auto_scale = True
        self.select_tool.action.setChecked(True)

        dgplot = self.main_curve_dialog.get_plot()
        dgplot.do_autoscale()

        dgimage = self.main_curve_dialog.get_plot()
        dgimage.do_autoscale()

    def process_select(self, status):
        """ Provide a default tool for panning the graph and to get out
        of zoom mode.
        """
        log.debug("Select tool clicked %s", status)

    def process_zoom(self, status):
        """ Zoom clicked, turn off auto scaling. The linkage with the
        guiqwt control handles cursor updates and actual zoom
        functionality.
        """
        log.debug("Zoom tool clicked %s", status)
        if status == "True":
            self.auto_scale = False

    def set_app_defaults(self):
        """ Call the various application control setup functions.
        """
        self.curve_render = 0
        self.image_render = 0
        self.image_height = 200
        self.image_data = []
        self.auto_scale = True

        self.create_actions()
        self.setup_signals()
        self.reset_graph()

        self.fps = utils.SimpleFPS()

        # Click the live button
        self.ui.actionContinue_Live_Updates.trigger()

    def setup_pipe_timer(self):
        """ This is a non-threaded application that uses qtimers with
        zero length delays to continuously poll the devices for data,
        while staying responsive to user events.
        """
        self.data_timer = QtCore.QTimer()
        self.data_timer.timeout.connect(self.update_visuals)
        self.data_timer.start(0)

    def update_visuals(self):
        """ Attempt to read from the pipe, update the graph.
        """

        result, data = self.dev.grab_pipe()
        if not result:
            log.critical("Problem grabbing pipe")

        if self.live_updates == True:
            self.update_graph(data)
            self.curve_render += 1
            self.update_image(data)
            self.check_image(self.curve_render)

        self.update_fps()
        self.data_timer.start(0)

    def check_image(self, render_count):
        """ Provide post-data population and form showing alignment and
        rendering of the image area.
        """
        if render_count != 1:
            return

        # If it's the first render, autoscale to make sure it lines up
        # properly. See update_image for why this is necessary
        local_plot = self.main_image_dialog.get_plot()
        local_plot.do_autoscale()

        # divided by the width of the image 1.0 / 0.4 is a guessed
        # value that seems to provide appropriate balance between
        # startup looks and non-breaking functionality when the
        # image is clicked.
        ratio = 1.0 / 0.4
        local_plot.set_aspect_ratio(ratio, lock=False)

        # Change the plot axis to have 0 in the lower left corner
        local_plot.set_axis_limits(0, -85, self.image_height)

    def update_fps(self):
        """ Add tick, display the current rate. Include basic statistics
	on the current line of data.
        """
        self.fps.tick()

        range_str = ""
        gd = self.main_curve_dialog.curve.get_data()[1]
        range_str = "Max: %s, Min: %s, Avg: %0.5s          " \
             % (numpy.max(gd), numpy.min(gd), numpy.average(gd))

        fps_text = "%s Update: %s FPS" % (range_str, self.fps.rate())
        self.action_fps_display.setText(fps_text)

    def update_graph(self, data_list):
        """ Get the current line plot from the available line graph,
        change it's data and replot.
        """
        #log.debug("render graph")
        x_axis = range(len(data_list))

        mcd = self.main_curve_dialog
        mcd.curve.set_data(x_axis, data_list)

        if self.auto_scale:
            mcd.get_plot().do_autoscale()
        else:
            mcd.get_plot().replot()

    def update_image(self, data):
        """ Add the line of data to the image data, if it is greater
        than the desired display size, roll it.
        """
        self.image_data.append(data)
        if len(self.image_data) > self.image_height:
            self.image_data = self.image_data[1:]

        self.image_render += 1

        # A 200 pixel tall image squashed into the render view does not
        # appear unpleasantely jumpy when scrolled by 5
        if self.image_render % 5 != 0 and self.image_render != 1:
            return

        img_data = range(len(self.image_data))

        position = 0
        while position < len(img_data):
            img_data[position] = self.image_data[position]
            position += 1

        new_data = numpy.array(img_data).astype(float)

        mid = self.main_image_dialog
        mid.image.set_data(new_data)

        # If you do autoscale here, it tends to jump around in appearing
        # to stretch to the window and be in 'normal' size
        mid.get_plot().replot()