From e94858fef000e40a86f4cd90a3abadbdafd1fe00 Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Fri, 11 Aug 2023 12:15:39 +0200 Subject: [PATCH] Allow PCA widget to be interrupted during preprocessing Code stolen from Learner and the Outliers widget --- Orange/projection/base.py | 34 +++++++++++++++++++++++----- Orange/widgets/unsupervised/owpca.py | 12 ++++++++-- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/Orange/projection/base.py b/Orange/projection/base.py index b07f0cb39e1..1dae7f7b21b 100644 --- a/Orange/projection/base.py +++ b/Orange/projection/base.py @@ -1,3 +1,5 @@ +import warnings + import copy import inspect import threading @@ -9,6 +11,7 @@ from Orange.data.util import SharedComputeValue, get_unique_names from Orange.misc.wrapper_meta import WrapperMeta from Orange.preprocess import RemoveNaNRows +from Orange.util import dummy_callback, wrap_callback, OrangeDeprecationWarning import Orange.preprocess __all__ = ["LinearCombinationSql", "Projector", "Projection", "SklProjector", @@ -44,17 +47,36 @@ def fit(self, X, Y=None): raise NotImplementedError( "Classes derived from Projector must overload method fit") - def __call__(self, data): - data = self.preprocess(data) + def __call__(self, data, progress_callback=None): + if progress_callback is None: + progress_callback = dummy_callback + progress_callback(0, "Preprocessing...") + try: + cb = wrap_callback(progress_callback, end=0.1) + data = self.preprocess(data, progress_callback=cb) + except TypeError: + data = self.preprocess(data) + warnings.warn("A keyword argument 'progress_callback' has been " + "added to the preprocess() signature. Implementing " + "the method without the argument is deprecated and " + "will result in an error in the future.", + OrangeDeprecationWarning, stacklevel=2) self.domain = data.domain + progress_callback(0.1, "Fitting...") clf = self.fit(data.X, data.Y) clf.pre_domain = data.domain clf.name = self.name + progress_callback(1) return clf - def preprocess(self, data): - for pp in self.preprocessors: + def preprocess(self, data, progress_callback=None): + if progress_callback is None: + progress_callback = dummy_callback + n_pps = len(self.preprocessors) + for i, pp in enumerate(self.preprocessors): + progress_callback(i / n_pps) data = pp(data) + progress_callback(1) return data # Projectors implemented using `fit` access the `domain` through the @@ -208,8 +230,8 @@ def _get_sklparams(self, values): raise TypeError("Wrapper does not define '__wraps__'") return params - def preprocess(self, data): - data = super().preprocess(data) + def preprocess(self, data, progress_callback=None): + data = super().preprocess(data, progress_callback) if any(v.is_discrete and len(v.values) > 2 for v in data.domain.attributes): raise ValueError("Wrapped scikit-learn methods do not support " diff --git a/Orange/widgets/unsupervised/owpca.py b/Orange/widgets/unsupervised/owpca.py index 18b96d14284..29611146fb2 100644 --- a/Orange/widgets/unsupervised/owpca.py +++ b/Orange/widgets/unsupervised/owpca.py @@ -158,8 +158,16 @@ def fit(self): self.start(self._call_projector, data, projector) @staticmethod - def _call_projector(data: Table, projector, _): - return projector(data) + def _call_projector(data: Table, projector, state): + + def callback(i: float, status=""): + state.set_progress_value(i * 100) + if status: + state.set_status(status) + if state.is_interruption_requested(): + raise Exception # pylint: disable=broad-exception-raised + + return projector(data, progress_callback=callback) def on_done(self, result): pca = result