from .fit_ui import Ui_page
from .page import Page
from . import page
from .. import tasks
from ... import parameters
from PySide6.QtPdfWidgets import QPdfView
from PySide6.QtPdf import QPdfDocument
from PySide6.QtWidgets import QWidget
from PySide6.QtCore import QCoreApplication
from PySide6.QtCore import Qt
from matplotlib.figure import Figure
from pathlib import Path
from tempfile import NamedTemporaryFile
from shutil import copy2 as copy
[docs]
class Fit(page.ConfirmClose, page.NavRedoExit, Page):
"""Performs the curve fitting."""
def __init__(self, parent: QWidget = None):
# Load user interface design created in Qt Designer.
super().__init__(parent)
self.ui = Ui_page()
self.ui.setupUi(self)
# Make sure hiding buttons does not change layout.
for button in (self.ui.redo, self.ui.exit):
size_policy = button.sizePolicy()
size_policy.setRetainSizeWhenHidden(True)
button.setSizePolicy(size_policy)
# Configure PDF viewer properties not accessible in Designer.
self.ui.viewer.setPageMode(QPdfView.PageMode.SinglePage)
self.ui.viewer.setZoomMode(QPdfView.ZoomMode.FitInView)
self.ui.viewer.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.ui.viewer.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.document = QPdfDocument()
self.ui.viewer.setDocument(self.document)
# Connect signals to slots.
self.ui.back.clicked.connect(self.go_back.emit)
self.ui.redo.clicked.connect(self.on_redo)
self.ui.exit.clicked.connect(QCoreApplication.quit)
self.ui.bins.valueChanged.connect(self.navigation_update)
# Initialize further instance attributes.
self.task = None
self.temp_file = None # Used in `invalidate()`.
[docs]
def parameters_load(self):
"""Loads parameter values from disk."""
folder = Path(self.parent().select.ui.folder_display.text())
parameters.init(folder/'analysis')
section = self.ui.title.text().lower().replace(' ', '_')
self.ui.bins.setValue(parameters.get(section, 'bins'))
[docs]
def parameters_save(self):
"""Saves parameter values to disk."""
if self.parameters_different():
section = self.ui.title.text().lower().replace(' ', '_')
parameters.set(section, 'bins', self.ui.bins.value())
parameters.save()
[docs]
def parameters_different(self):
"""Returns if parameter values are different in UI than on disk."""
section = self.ui.title.text().lower().replace(' ', '_')
bins_now = self.ui.bins.value()
bins_old = parameters.get(section, 'bins')
return (bins_now != bins_old)
[docs]
def on_enter(self):
"""Called when entering the page."""
self.mark_busy()
self.parameters_load()
self.fit_curve()
[docs]
def on_redo(self):
"""Called when the user pressed the redo button."""
self.mark_busy()
self.status('Invalidating previous results…')
self.parent().invalidate(self)
self.fit_curve()
[docs]
def fit_curve(self):
"""Fits the cell density to a curve."""
self.task = tasks.FitCurve()
self.task.finished.connect(self.tasks_done)
self.task.status_update.connect(self.status_update)
self.task.status_clear.connect(self.status_clear)
folder = Path(self.parent().select.ui.folder_display.text())
figure = Figure()
cells = folder/'analysis'/'cells.tsv'
target = folder/'analysis'/'fit.pdf'
n_bins = self.ui.bins.value()
self.task.start(figure, cells, target, n_bins)
[docs]
def tasks_done(self):
"""Finishes up when all background tasks are done."""
self.task = None
folder = Path(self.parent().select.ui.folder_display.text())
file = folder/'analysis'/'fit.pdf'
self.document.load(str(file))
self.status_clear.emit()
self.parameters_save()
# Possibly remove temporary file create in `invalidate()`.
if self.temp_file and self.temp_file.exists():
self.temp_file.unlink()
self.mark_ready()
[docs]
def invalidate(self):
"""Invalidates results from this analysis step."""
folder = Path(self.parent().select.ui.folder_display.text())
file = folder/'analysis'/'fit.pdf'
if file.exists():
self.document.close()
# Even after closing the document, the file we loaded it from is
# still locked, so we cannot delete it. This may be a bug in Qt.
# As a work-around, we copy the currently displayed file to a
# temporary file, display that instead, and remove the temporary
# file in `ready()` after the new document has been displayed.
temp_file = NamedTemporaryFile(
prefix='fit_', suffix='.pdf', delete=False
)
temp_file.close()
self.temp_file = Path(temp_file.name)
copy(file, self.temp_file)
self.document.load(str(self.temp_file))
self.flush()
tasks.delete( (
folder/'analysis'/'fit.pdf',
folder/'analysis'/'fit.png',
folder/'analysis'/'fit.svg',
) )