示例#1
0
    def __init__(self):
        super().__init__()

        # Initialize UI states
        self.currName = None
        self.currRow = None
        self.currRoiSize = gvars.roi_draw_radius
        self.donor_first = self.getConfig(gvars.key_firstFrameIsDonor)
        self.donor_is_left = self.getConfig(gvars.key_donorLeft)
        self.bg_correction = self.getConfig(gvars.key_illuCorrect)

        self.batchLoaded = (
            False  # If videos have been batchloaded, disable some controls
        )

        # Initialize interface
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # Spot counter labels
        self.labels = (
            self.ui.labelColocGreenRedSpots,
            self.ui.labelGreenSpots,
            self.ui.labelRedSpots,
        )

        self.setupListView(use_layoutbox=False)
        self.setupFigureCanvas(ax_type="img", use_layoutbox=False)
        self.setupPlot()
        self.setupSplitter(layout=self.ui.LayoutBox)

        # Spinbox triggers
        self.spinBoxes = (self.ui.spotsGrnSpinBox, self.ui.spotsRedSpinBox)

        self.ui.spotsGrnSpinBox.valueChanged.connect(
            partial(self.displaySpotsSingle, "green")
        )
        self.ui.spotsRedSpinBox.valueChanged.connect(
            partial(self.displaySpotsSingle, "red")
        )

        # Contrast boxes
        self.contrastBoxesLo = (
            self.ui.contrastBoxLoGreen,
            self.ui.contrastBoxLoRed,
        )
        self.contrastBoxesHi = (
            self.ui.contrastBoxHiGreen,
            self.ui.contrastBoxHiRed,
        )
        for contrastBox in self.contrastBoxesLo + self.contrastBoxesHi:
            contrastBox.valueChanged.connect(self.refreshPlot)
        self.ui.contrastBoxHiGreen.setValue(
            self.getConfig(gvars.key_contrastBoxHiGrnVal)
        )
        self.ui.contrastBoxHiRed.setValue(
            self.getConfig(gvars.key_contrastBoxHiRedVal)
        )

        self.show()
示例#2
0
    def __init__(self):
        super().__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.inputs = Inputs()
        self.canvas = PlotCanvas()
        self.ui.mpl_LayoutBox.addWidget(self.canvas)

        self.connect_ui()

        self.traces = pd.DataFrame()
        self.values_from_gui()

        self.show()
class VideoWindow(BaseWindow):
    """
    Main UI for the application.
    """
    def __init__(self):
        super().__init__()

        # Initialize UI states
        self.currName = None
        self.currRow = None
        self.currRoiSize = gvars.roi_draw_radius
        self.donor_first = self.getConfig(gvars.key_firstFrameIsDonor)
        self.donor_is_left = self.getConfig(gvars.key_donorLeft)
        self.bg_correction = self.getConfig(gvars.key_illuCorrect)

        self.batchLoaded = (
            False  # If videos have been batchloaded, disable some controls
        )

        # Initialize interface
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # Spot counter labels
        self.labels = (
            self.ui.labelColocGreenRedSpots,
            self.ui.labelGreenSpots,
            self.ui.labelRedSpots,
        )

        self.setupListView(use_layoutbox=False)
        self.setupFigureCanvas(ax_type="img", use_layoutbox=False)
        self.setupPlot()
        self.setupSplitter(layout=self.ui.LayoutBox)

        # Spinbox triggers
        self.spinBoxes = (self.ui.spotsGrnSpinBox, self.ui.spotsRedSpinBox)

        self.ui.spotsGrnSpinBox.valueChanged.connect(
            partial(self.displaySpotsSingle, "green"))
        self.ui.spotsRedSpinBox.valueChanged.connect(
            partial(self.displaySpotsSingle, "red"))

        # Contrast boxes
        self.contrastBoxesLo = (
            self.ui.contrastBoxLoGreen,
            self.ui.contrastBoxLoRed,
        )
        self.contrastBoxesHi = (
            self.ui.contrastBoxHiGreen,
            self.ui.contrastBoxHiRed,
        )
        for contrastBox in self.contrastBoxesLo + self.contrastBoxesHi:
            contrastBox.valueChanged.connect(self.refreshPlot)
        self.ui.contrastBoxHiGreen.setValue(
            self.getConfig(gvars.key_contrastBoxHiGrnVal))
        self.ui.contrastBoxHiRed.setValue(
            self.getConfig(gvars.key_contrastBoxHiRedVal))

        self.show()

    def returnContainerInstance(self):
        """Returns appropriate data container for implemented windows"""
        return self.data.videos

    def enablePerWindow(self):
        """
        Disables specific commands that should be unavailable for certain
        window types. Export commands should be accessible from all windows
        (no implicit behavior).
        """
        self.ui: Ui_MenuBar

        menulist = (
            self.ui.actionRemove_File,
            self.ui.actionRemove_All_Files,
            self.ui.actionColocalize_All,
            self.ui.actionFind_Show_Traces,
            self.ui.actionClear_Traces,
            self.ui.actionClear_and_Rerun,
        )
        for menu in menulist:
            menu.setEnabled(True)

        self.ui.actionClose.setEnabled(False)

    def currentVideo(self) -> VideoContainer:
        """
        Quick interface to obtain file data (or names will be extremely long).
        Add type hinting in metadata to improve autocomplete.
        """
        return self.data.get(self.currName)

    def newTraceFromVideo(self, n) -> TraceContainer:
        """
        Shortcut to obtain trace, and create a new one if it doesn't exist.
        """
        tracename = self.currName + "_" + str(n)
        if tracename not in self.data.traces:
            self.data.traces[tracename] = TraceContainer(filename=tracename,
                                                         video=self.currName,
                                                         n=n)
        return self.data.traces[tracename]

    def trace_c(self, n, channel) -> TraceChannel:
        """
        Shortcut to obtain trace channel. See newTraceFromVideo().
        """
        if channel == "green":
            return self.newTraceFromVideo(n).grn
        elif channel == "red":
            return self.newTraceFromVideo(n).red
        elif channel == "acc":
            return self.newTraceFromVideo(n).acc
        else:
            raise ValueError("Invalid channel")

    def openFile(self):
        """
        Open file to load in.
        """
        if not self.getConfig(gvars.key_batchLoadingMode):
            directory = self.getLastOpenedDir()

            filenames, selectedFilter = QFileDialog.getOpenFileNames(
                self,
                caption="Open File",
                filter="Video files (*.tif *fits)",
                directory=directory,
            )

            if len(filenames) > 0:
                progressbar = ProgressBar(loop_len=len(filenames), parent=self)
                for i, full_filename in enumerate(filenames):
                    if progressbar.wasCanceled():
                        break

                    # Make sure name is unique
                    uniqueName = lib.utils.generate_unique_name(
                        full_filename=full_filename,
                        array=self.data.videos.keys(),
                    )

                    self.data.load_video_data(
                        path=full_filename,
                        name=uniqueName,
                        donor_is_first=self.donor_first,
                        donor_is_left=self.donor_is_left,
                        bg_correction=self.bg_correction,
                    )

                    item = QStandardItem(uniqueName)
                    item.setCheckable(False)

                    self.currName = uniqueName
                    self.currDir = os.path.dirname(full_filename)
                    self.listModel.appendRow(item)
                    # refresh listView
                    self.listView.repaint()
                    progressbar.increment()

                # Select first video if loading into an empty listView
                if self.currRow is None:
                    self.currRow = 0
                    self.selectListViewTopRow()
                    index = self.listModel.index(self.currRow, 0)
                    name = self.listModel.data(index)
                    self.currName = name

                # Write most recently opened directory to getConfig file,
                # but only if a file was selected
                if self.currDir is not None:
                    self.setConfig(gvars.key_lastOpenedDir, self.currDir)
        else:
            self.batchOpen()

    def batchOpen(self):
        """Loads one video at a time and extracts traces, then clears the
        video from memory afterwards"""
        directory = self.getLastOpenedDir()
        trace_window = self.windows[gvars.TraceWindow]  # type: TraceWindow

        filenames, selectedFilter = QFileDialog.getOpenFileNames(
            self,
            caption="Open File",
            filter="Video files (*.tif *.fits)",
            directory=directory,
        )

        if len(filenames) > 0:
            self.processEvents()
            progressbar = ProgressBar(loop_len=len(filenames), parent=self)
            for i, full_filename in enumerate(filenames):
                if progressbar.wasCanceled():
                    break
                else:
                    progressbar.increment()

                self.currName = os.path.basename(full_filename)
                if self.currName in self.data.videos:
                    continue

                self.data.load_video_data(
                    path=full_filename,
                    name=os.path.basename(full_filename),
                    donor_is_first=self.donor_first,
                    donor_is_left=self.donor_is_left,
                    bg_correction=self.bg_correction,
                )

                channels = ("green", "red")
                for c in channels:
                    if self.currentVideo().acc.exists:
                        self.colocalizeSpotsSingleVideo(channel=c,
                                                        find_npairs="auto")
                    else:
                        self.colocalizeSpotsSingleVideo(channel=c,
                                                        find_npairs="spinbox")
                self.getTracesSingleVideo()
                self.currentVideo().vid = None
                for c in self.currentVideo().channels + (
                        self.currentVideo().acc, ):
                    c.raw = None

                self.listModel.appendRow(QStandardItem(self.currName))
                self.listView.repaint()

                self.currDir = os.path.dirname(full_filename)
                if self.currDir is not None:
                    self.setConfig(gvars.key_lastOpenedDir, self.currDir)

        if len(self.data.traces) > 0:
            currently_loaded = trace_window.returnCurrentListViewNames()
            # Iterate over all filenames and add to list
            for name in self.data.traces.keys():
                # If name is already in list, skip it
                if name in currently_loaded:
                    continue
                item = QStandardItem(name)
                item.setCheckable(True)
                trace_window.listModel.appendRow(item)

            trace_window.selectListViewTopRow()
            trace_window.getCurrentListObject()
            trace_window.show()

            self.batchLoaded = True
            self.refreshInterface()
            self.selectListViewTopRow()
            self.refreshPlot()

    @timeit
    def colocalizeSpotsSingleVideo(self, channel, find_npairs="spinbox"):
        """
        Find and colocalize spots for a single currentVideo (not displayed).
        """
        vid = self.currentVideo()
        tolerance_type = self.getConfig(gvars.key_colocTolerance)
        tolerance_value = gvars.roi_coloc_tolerances[tolerance_type]

        if channel == "green":
            channel = vid.grn
            spinBox = self.ui.spotsGrnSpinBox
            pairs = ((vid.grn, vid.red), )
            colocs = (vid.coloc_grn_red, )

        elif channel == "red":
            channel = vid.red
            spinBox = self.ui.spotsRedSpinBox
            pairs = ((vid.grn, vid.red), )
            colocs = (vid.coloc_grn_red, )
        else:
            raise ValueError("Invalid color")

        if find_npairs == "spinbox":
            find_npairs = spinBox.value()
        elif find_npairs == "auto":
            find_npairs = self.getConfig(gvars.key_autoDetectPairs)
        else:
            raise ValueError("Select from 'spinbox' or 'auto'")

        if self.getConfig(gvars.key_fitSpots):
            if find_npairs > 0:
                # hardcoded value threshold until I come up with something
                # better
                spots = lib.imgdata.find_spots(channel.mean_nobg,
                                               value=20,
                                               method="laplacian_of_gaussian")

                # Sort spots based on intensity
                real_spots = []
                for spot in spots:
                    masks = lib.imgdata.circle_mask(yx=spot,
                                                    indices=vid.indices,
                                                    **gvars.cmask_p)
                    intensity, bg = lib.imgdata.tiff_stack_intensity(
                        channel.mean_nobg, *masks, raw=True)
                    if intensity > bg * 1.05:
                        real_spots.append(spot)

                channel.n_spots = len(real_spots)
                channel.spots = real_spots

        else:
            if find_npairs > 0:
                channel.spots = lib.imgdata.find_spots(
                    channel.mean_nobg,
                    value=find_npairs,
                    method="peak_local_max",
                )
                channel.n_spots = len(channel.spots)

        if channel == "red" and self.getConfig(gvars.key_unColocRed):
            vid.grn.spots = vid.red.spots
            vid.acc.spots = vid.red.spots
            vid.grn.n_spots = vid.red.n_spots
            vid.acc.n_spots = vid.red.n_spots

        for (c1, c2), coloc in zip(pairs, colocs):
            if all((c1.n_spots, c2.n_spots)) > 0:
                coloc.spots = lib.imgdata.colocalize_rois(
                    c1.spots,
                    c2.spots,
                    color1=coloc.color1,
                    color2=coloc.color2,
                    tolerance=tolerance_value,
                )
                coloc.n_spots = len(coloc.spots)

        vid.coloc_all.spots = vid.coloc_grn_red.spots
        vid.coloc_all.n_spots = vid.coloc_grn_red.n_spots

    def displaySpotsSingle(self, channel):
        """
        Displays colocalized spot for a single video.
        """
        self.getCurrentListObject()

        if self.currName is not None:
            self.colocalizeSpotsSingleVideo(channel)
            self.refreshPlot()

    def colocalizeSpotsAllVideos(self):
        """
        Colocalizes spots for all videos, with the same threshold. Use this
        method instead for progress bar.
        """
        progressbar = ProgressBar(loop_len=len(self.data.videos.keys()),
                                  parent=self)
        for name in self.data.videos.keys():
            self.currName = name
            for c in "green", "red":
                self.colocalizeSpotsSingleVideo(c)
            progressbar.increment()

        self.resetCurrentName()
        self.refreshPlot()

    def getTracesSingleVideo(self):
        """
        Gets traces from colocalized ROIs, for a single video.
        """
        if self.currName is None:
            return
        vid = self.currentVideo()

        # Clear all traces previously held traces whenever this is called
        vid.traces = {}

        if vid.coloc_grn_red.spots is None:
            for c in "green", "red":
                self.colocalizeSpotsSingleVideo(c)
        else:
            for n, *row in vid.coloc_grn_red.spots.itertuples():
                yx_grn, yx_red = lib.utils.pairwise(row)

                trace = self.newTraceFromVideo(n)

                # Green
                if vid.grn.exists and yx_grn is not None:
                    masks_grn = lib.imgdata.circle_mask(yx=yx_grn,
                                                        indices=vid.indices,
                                                        **gvars.cmask_p)
                    (
                        trace.grn.int,
                        trace.grn.bg,
                    ) = lib.imgdata.tiff_stack_intensity(vid.grn.raw,
                                                         *masks_grn,
                                                         raw=True)

                # Red
                masks_red = lib.imgdata.circle_mask(yx=yx_red,
                                                    indices=vid.indices,
                                                    **gvars.cmask_p)
                trace.red.int, trace.red.bg = lib.imgdata.tiff_stack_intensity(
                    vid.red.raw, *masks_red, raw=True)

                # Acceptor (if FRET)
                if vid.acc.exists:
                    (
                        trace.acc.int,
                        trace.acc.bg,
                    ) = lib.imgdata.tiff_stack_intensity(vid.acc.raw,
                                                         *masks_red,
                                                         raw=True)
                    trace.fret = lib.math.calc_E(trace.get_intensities())
                    trace.stoi = lib.math.calc_S(trace.get_intensities())

                trace.frames = np.arange(1, len(trace.red.int) + 1)
                trace.frames_max = max(trace.frames)

    def getTracesAllVideos(self):
        """
        Gets the traces from all videos that have colocalized ROIs.
        """
        for name in self.data.videos.keys():
            self.currName = name
            for c in "green", "red":
                self.colocalizeSpotsSingleVideo(c)

            self.getTracesSingleVideo()

        self.resetCurrentName()

    def savePlot(self):
        """
        Saves plot with colors suitable for export (e.g. white background).
        """
        self.setSavefigrcParams()

        if self.currName is not None:
            self.canvas.defaultImageName = self.currName
            self.canvas.defaultImageName = self.currName
        else:
            self.canvas.defaultImageName = "Blank"

        for ax in self.canvas.axes_all:
            ax.tick_params(axis="both", colors=gvars.color_hud_black)
            for spine in ax.spines.values():
                spine.set_edgecolor(gvars.color_hud_black)

        self.canvas.toolbar.save_figure()

        # Reset figure colors after plotting
        for ax in self.canvas.axes_all:
            ax.tick_params(axis="both", colors=gvars.color_hud_white)
            for spine in ax.spines.values():
                spine.set_edgecolor(gvars.color_hud_white)

        self.refreshPlot()

    def setupPlot(self):
        """
        Set up plot for MainWindow.
        """
        self.canvas.fig.set_facecolor(gvars.color_hud_black)
        self.canvas.fig.set_edgecolor(gvars.color_hud_white)

        for ax in self.canvas.axes_all:
            for spine in ax.spines.values():
                spine.set_edgecolor(gvars.color_hud_white)

    def refreshPlot(self):
        """
        Refreshes plot with selected list item.
        """
        for ax in self.canvas.axes_all:
            ax.tick_params(axis="both", colors=gvars.color_hud_white)
            ax.clear()

        if self.currName is not None:
            vid = self.currentVideo()
            roi_radius = vid.roi_radius

            contrast_lo = (
                self.ui.contrastBoxLoGreen,
                self.ui.contrastBoxLoRed,
            )
            keys = (
                gvars.key_contrastBoxHiGrnVal,
                gvars.key_contrastBoxHiRedVal,
            )
            contrast_hi = (
                self.ui.contrastBoxHiGreen,
                self.ui.contrastBoxHiRed,
            )
            # Rescale values according to UI first
            # might break image if too high
            sensitivity = 250

            for c, lo, hi in zip(
                    vid.channels, contrast_lo, contrast_hi
            ):  # type: ImageChannel, QDoubleSpinBox, QDoubleSpinBox
                clip_lo = float(lo.value() / sensitivity)
                clip_hi = float(hi.value() / sensitivity)
                c.rgba = lib.imgdata.rescale_intensity(c.mean,
                                                       range=(clip_lo,
                                                              clip_hi))

            # Save contrast settings
            for hi, cfg in zip(contrast_hi, keys):
                self.setConfig(cfg, hi.value())

            # Single channels
            for img, ax in zip(vid.channels, self.canvas.axes_single):
                if img.rgba is not None:
                    # Avoid imshow showing blank if clipped too much
                    if np.isnan(img.rgba).any():
                        img.rgba.fill(1)
                    ax.imshow(img.rgba, cmap=img.cmap, vmin=0)
                else:
                    lib.plotting.empty_imshow(ax)

            c1, c2 = vid.grn, vid.red
            if c1.rgba is not None and c2.rgba is not None:
                self.canvas.ax_grn_red.imshow(
                    lib.imgdata.light_blend(c1.rgba,
                                            c2.rgba,
                                            cmap1=c1.cmap,
                                            cmap2=c2.cmap),
                    vmin=0,
                )
            else:
                lib.plotting.empty_imshow(self.canvas.axes_blend.ax)

            for ax in self.canvas.axes_all:
                ax.set_xticks(())
                ax.set_yticks(())

            # Green spots
            if vid.grn.n_spots > 0:
                lib.plotting.plot_rois(
                    vid.grn.spots,
                    self.canvas.ax_grn,
                    color=gvars.color_white,
                    radius=roi_radius,
                )

            # Red spots
            if vid.red.n_spots > 0:
                lib.plotting.plot_rois(
                    vid.red.spots,
                    self.canvas.ax_red,
                    color=gvars.color_white,
                    radius=roi_radius,
                )

            # Colocalized spots
            if vid.coloc_grn_red.spots is not None:
                lib.plotting.plot_roi_coloc(
                    vid.coloc_grn_red.spots,
                    img_ax=self.canvas.ax_grn_red,
                    color1=gvars.color_green,
                    color2=gvars.color_red,
                    radius=roi_radius,
                )

        else:
            for ax in self.canvas.axes_all:
                lib.plotting.empty_imshow(ax)

        self.canvas.draw()
        self.refreshInterface()

    def refreshInterface(self):
        """
        Repaints UI labels to match the current plot shown.
        """
        if self.currName is not None:
            vid = self.currentVideo()

            channels = (
                vid.coloc_grn_red,
                vid.grn,
                vid.red,
            )

            for channel, label in zip(channels, self.labels):
                label.setText(str(channel.n_spots))

            self.ui.spotsGrnSpinBox.setDisabled(not vid.grn.exists)
            self.ui.spotsRedSpinBox.setDisabled(not vid.red.exists)

            if self.batchLoaded:
                self.disableSpinBoxes((
                    "green",
                    "red",
                ))

        else:
            for label in self.labels:
                label.setText(str("-"))

        # Repaint all labels
        for label in self.labels:
            label.repaint()

    def findTracesAndShow(self):
        """
        Gets the name of currently stored traces and puts them into the
        trace listView.
        """
        trace_window = self.windows[gvars.TraceWindow]

        if len(self.data.traces) == 0:
            # Load all traces into their respective videos, and generate a
            # list of traces
            self.getTracesAllVideos()
            currently_loaded = self.returnCurrentListViewNames()
            # Iterate over all filenames and add to list
            for (name, trace) in self.data.traces.items():
                if name in currently_loaded:
                    continue
                item = QStandardItem(trace.name)
                trace_window.listModel.appendRow(item)
                trace_window.currName = trace.name
                item.setCheckable(True)
            trace_window.selectListViewTopRow()
        else:
            trace_window.show()

        trace_window.refreshPlot()
        trace_window.show()

    def clearTraceAndRerun(self):
        """
        Clears everything and reruns on selected videos.
        Loads all traces into their respective videos, and generates a list
        of traces
        """
        trace_window = self.windows[gvars.TraceWindow]

        self.clearTraces()
        self.getTracesAllVideos()

        # Iterate over all filenames and add to list
        for (name, trace) in self.data.traces.items():
            item = QStandardItem(trace.name)
            trace_window.listModel.appendRow(item)
            trace_window.currName = trace.name
            item.setCheckable(True)

        if trace_window.isVisible():
            trace_window.selectListViewTopRow()
            trace_window.refreshPlot()

    def newTraceFromContainer(self, trace, n):
        """
        Creates an empty video object to load traces into by transplanting a
        list of loaded TraceContainers
        """
        tracename = self.currName + "_" + str(n)

        trace.currentVideo = self.currName
        trace.name = tracename
        trace.n = n

        if tracename not in self.data.videos[self.currName].traces:
            self.data.videos[self.currName].traces[tracename] = trace

    def disableSpinBoxes(self, channel):
        """
        Disables all spinboxes. Order must be as below, or a Qt bug will
        re-enable the boxes.
        """
        if "red" in channel:
            self.ui.spotsRedSpinBox.setDisabled(True)
            self.ui.spotsRedSpinBox.repaint()

        if "green" in channel:
            self.ui.spotsGrnSpinBox.setDisabled(True)
            self.ui.spotsGrnSpinBox.repaint()

    def _debug(self):
        """Debug for MainWindow."""
        pass
示例#4
0
class MainWindow(QMainWindow):
    """
    The main window that does everything
    """
    def __init__(self):
        super().__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.inputs = Inputs()
        self.canvas = PlotCanvas()
        self.ui.mpl_LayoutBox.addWidget(self.canvas)

        self.connect_ui()

        self.traces = pd.DataFrame()
        self.values_from_gui()

        self.show()

    def connect_ui(self):
        """Connectnumber interface"""
        # Connect all checkboxes dynamically
        [
            getattr(self.ui, c).clicked.connect(self.refresh_ui)
            for c in dir(self.ui) if c.startswith("checkBox")
        ]
        self.ui.pushButtonRefresh.clicked.connect(self.refresh_plots)
        self.ui.pushButtonExport.clicked.connect(self.export_traces_to_ascii)

    def refresh_ui(self):
        """Refreshes UI to e.g. disable some input boxes"""
        for inputBox, checkBox in (
            (self.ui.inputDonorMeanLifetime, self.ui.checkBoxDlifetime),
            (self.ui.inputAcceptorMeanLifetime, self.ui.checkBoxALifetime),
            (
                self.ui.inputTransitionProbabilityHi,
                self.ui.checkBoxTransitionProbability,
            ),
            (self.ui.inputFretStateMeans, self.ui.checkBoxRandomState),
            (self.ui.inputNoiseHi, self.ui.checkBoxNoise),
            (self.ui.inputMismatchHi, self.ui.checkBoxMismatch),
            (self.ui.inputScalerHi, self.ui.checkBoxScaler),
            (self.ui.inputBleedthroughHi, self.ui.checkBoxBleedthrough),
        ):
            inputBox.setDisabled(checkBox.isChecked())

        self.ui.inputMaxRandomStates.setEnabled(
            self.ui.checkBoxRandomState.isChecked())

    def values_from_gui(self):
        """
        Fetch values from GUI
        """
        # Number of examples
        self.inputs.n_examples = int(
            self.ui.examplesComboBox.currentText().split("x")[0])
        self.inputs.n_examples **= 2

        # Number of traces to export
        self.inputs.n_traces = int(self.ui.inputNumberOfTraces.value())

        # Trace length
        self.inputs.trace_len = int(self.ui.inputTraceLength.value())

        # Scramble probability
        self.inputs.scramble_prob = float(
            self.ui.inputScrambleProbability.value())

        # Aggregation probability
        self.inputs.aggregate_prob = float(
            self.ui.inputAggregateProbability.value())

        # Max aggregate size
        self.inputs.max_aggregate_size = int(
            self.ui.inputMaxAggregateSize.value())

        # FRET state means
        if self.ui.checkBoxRandomState.isChecked():
            self.inputs.fret_means = "random"
        else:
            self.inputs.fret_means = lib.utils.numstring_to_ls(
                self.ui.inputFretStateMeans.text())

        # Max number of random states
        self.inputs.max_random_states = int(
            self.ui.inputMaxRandomStates.value())

        # Donor mean lifetime
        if self.ui.checkBoxDlifetime.isChecked():
            self.inputs.donor_lifetime = None
        else:
            self.inputs.donor_lifetime = int(
                self.ui.inputDonorMeanLifetime.value())

        # Acceptor mean lifetime
        if self.ui.checkBoxALifetime.isChecked():
            self.inputs.acceptor_lifetime = None
        else:
            self.inputs.acceptor_lifetime = int(
                self.ui.inputAcceptorMeanLifetime.value())

        # Blinking probability
        self.inputs.blinking_prob = float(
            self.ui.inputBlinkingProbability.value())

        # Transition Probability
        if self.ui.checkBoxTransitionProbability.isChecked():
            self.inputs.transition_prob = float(
                self.ui.inputTransitionProbabilityLo.value())
        else:
            self.inputs.transition_prob = (
                float(self.ui.inputTransitionProbabilityLo.value()),
                float(self.ui.inputTransitionProbabilityHi.value()),
            )

        # Noise
        if self.ui.checkBoxNoise.isChecked():
            self.inputs.noise = float(self.ui.inputNoiseLo.value())
        else:
            self.inputs.noise = (
                float(self.ui.inputNoiseLo.value()),
                float(self.ui.inputNoiseHi.value()),
            )

        # Acceptor-only mismatch
        if self.ui.checkBoxMismatch.isChecked():
            self.inputs.aa_mismatch = float(self.ui.inputMismatchLo.value())
        else:
            self.inputs.aa_mismatch = (
                float(self.ui.inputMismatchLo.value()),
                float(self.ui.inputMismatchHi.value()),
            )

        # Donor Bleedthrough
        if self.ui.checkBoxBleedthrough.isChecked():
            self.inputs.bleed_through = float(
                self.ui.inputBleedthroughLo.value())
        else:
            self.inputs.bleed_through = (
                float(self.ui.inputBleedthroughLo.value()),
                float(self.ui.inputBleedthroughHi.value()),
            )

        # Scaler
        if self.ui.checkBoxScaler.isChecked():
            self.inputs.scaling_factor = float(self.ui.inputScalerLo.value())
        else:
            self.inputs.scaling_factor = (
                float(self.ui.inputScalerLo.value()),
                float(self.ui.inputScalerHi.value()),
            )

    def set_traces(self, n_traces):
        """Generate traces to show in the GUI or export"""
        if n_traces > 50:
            update_freq = 5
            progressbar = ProgressBar(parent=self,
                                      loop_len=n_traces / update_freq)
        else:
            update_freq = None
            progressbar = None

        self.traces = lib.algorithms.generate_traces(
            n_traces=n_traces,
            aa_mismatch=self.inputs.aa_mismatch,
            state_means=self.inputs.fret_means,
            random_k_states_max=self.inputs.max_random_states,
            max_aggregate_size=self.inputs.max_aggregate_size,
            aggregation_prob=self.inputs.aggregate_prob,
            scramble_prob=self.inputs.scramble_prob,
            trace_length=self.inputs.trace_len,
            trans_prob=self.inputs.transition_prob,
            blink_prob=self.inputs.blinking_prob,
            bleed_through=self.inputs.bleed_through,
            noise=self.inputs.noise,
            D_lifetime=self.inputs.donor_lifetime,
            A_lifetime=self.inputs.acceptor_lifetime,
            au_scaling_factor=self.inputs.scaling_factor,
            discard_unbleached=False,
            null_fret_value=-1,
            min_state_diff=0.2,
            acceptable_noise=0.25,
            progressbar_callback=progressbar,
            callback_every=update_freq,
        )

        if progressbar is not None:
            progressbar.close()

    def refresh_plots(self):
        """Refreshes preview plots"""
        self.values_from_gui()

        # generate at least enough traces to show required number of examples
        if self.inputs.n_traces < self.inputs.n_examples:
            self.inputs.n_traces = self.inputs.n_examples

        self.set_traces(self.inputs.n_examples)

        self.canvas.flush_events()
        self.canvas.fig.clear()

        n_subplots = self.inputs.n_examples
        nrows = int(self.inputs.n_examples**(1 / 2))
        ncols = nrows
        outer_grid = GridSpec(nrows, ncols, wspace=0.1, hspace=0.1)  # 2x2 grid

        for i in range(n_subplots):
            trace = self.traces[self.traces["name"] == i]
            inner_subplot = GridSpecFromSubplotSpec(
                nrows=5,
                ncols=1,
                subplot_spec=outer_grid[i],
                wspace=0,
                hspace=0,
                height_ratios=[3, 3, 3, 3, 1],
            )
            axes = [
                plt.Subplot(self.canvas.fig, inner_subplot[n])
                for n in range(5)
            ]
            ax_g_r, ax_red, ax_frt, ax_sto, ax_lbl = axes
            bleach = trace["_bleaches_at"].values[0]
            tmax = trace["frame"].max()
            fret_states = np.unique(trace["E_true"])
            fret_states = fret_states[fret_states != -1]

            ax_g_r.plot(trace["DD"], color="seagreen")
            ax_g_r.plot(trace["DA"], color="salmon")
            ax_red.plot(trace["AA"], color="red")
            ax_frt.plot(trace["E"], color="orange")
            ax_frt.plot(trace["E_true"], color="black", ls="-", alpha=0.3)

            for state in fret_states:
                ax_frt.plot([0, bleach], [state, state],
                            color="red",
                            alpha=0.2)

            ax_sto.plot(trace["S"], color="purple")

            lib.utils.plot_category(y=trace["label"], ax=ax_lbl, alpha=0.4)

            for ax in ax_frt, ax_sto:
                ax.set_ylim(-0.15, 1.15)

            for ax, s in zip((ax_g_r, ax_red), (trace["DD"], trace["AA"])):
                ax.set_ylim(s.max() * -0.15)
                ax.plot([0] * len(s), color="black", ls="--", alpha=0.5)

            for ax in axes:
                for spine in ax.spines.values():
                    spine.set_edgecolor("darkgrey")

                if bleach is not None:
                    ax.axvspan(bleach, tmax, color="black", alpha=0.1)

                ax.set_xticks(())
                ax.set_yticks(())
                ax.set_xlim(0, tmax)
                self.canvas.fig.add_subplot(ax)

        self.canvas.draw()

    def export_traces_to_ascii(self):
        """
        Opens a folder dialog to save traces to ASCII .txt files
        """
        self.set_traces(n_traces=int(self.ui.inputNumberOfTraces.value()))
        df = self.traces

        diag = ExportDialog(init_dir="~/Desktop/", accept_label="Export")

        if diag.exec():
            outdir = diag.selectedFiles()[0]
            appctxt.app.processEvents()
        else:
            outdir = None

        df.index = np.arange(0, len(df), 1) // int(
            self.ui.inputTraceLength.value())

        if outdir is not None:
            for idx, trace in df.groupby(df.index):
                bg = np.zeros(len(trace))
                path = os.path.join(
                    outdir,
                    "trace_{}_{}.txt".format(idx,
                                             time.strftime("%Y%m%d_%H%M")),
                )

                df = pd.DataFrame({
                    "D-Dexc-bg": bg,
                    "A-Dexc-bg": bg,
                    "A-Aexc-bg": bg,
                    "D-Dexc-rw": trace["DD"],
                    "A-Dexc-rw": trace["DA"],
                    "A-Aexc-rw": trace["AA"],
                    "S": trace["S"],
                    "E": trace["E"],
                }).round(4)

                date_txt = "Date: {}".format(time.strftime("%Y-%m-%d, %H:%M"))
                mov_txt = "Movie filename: {}".format(None)
                id_txt = "FRET pair #{}".format(idx)
                bl_txt = "Bleaches at {}".format(trace["fb"].values[0])

                with open(path, "w") as f:
                    exp_txt = "Simulated trace exported by Fiddler"
                    f.write("{0}\n"
                            "{1}\n"
                            "{2}\n"
                            "{3}\n"
                            "{4}\n\n"
                            "{5}".format(
                                exp_txt,
                                date_txt,
                                mov_txt,
                                id_txt,
                                bl_txt,
                                df.to_csv(index=False, sep="\t"),
                            ))