From 98053d7c8a0e7b6d2558c8bf2b647334a7f27907 Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Mon, 16 Sep 2024 12:25:50 +0200 Subject: [PATCH 1/6] Minimum subclass of spectral preprocess --- .../snom/widgets/owpreprocessimage.py | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 orangecontrib/snom/widgets/owpreprocessimage.py diff --git a/orangecontrib/snom/widgets/owpreprocessimage.py b/orangecontrib/snom/widgets/owpreprocessimage.py new file mode 100644 index 0000000..b570c1a --- /dev/null +++ b/orangecontrib/snom/widgets/owpreprocessimage.py @@ -0,0 +1,172 @@ +import time + +from AnyQt.QtWidgets import QFormLayout + +import Orange.data +from Orange import preprocess +from Orange.preprocess import Preprocess +from Orange.widgets.data.owpreprocess import ( + PreprocessAction, Description, icon_path +) +from Orange.widgets.widget import Output + +from orangecontrib.spectroscopy.preprocess import SelectColumn, \ + CommonDomain + +from orangecontrib.spectroscopy.widgets.owpreprocess import ( + SpectralPreprocess, create_preprocessor, InterruptException +) +from orangecontrib.spectroscopy.widgets.preprocessors.utils import BaseEditorOrange +from orangecontrib.spectroscopy.widgets.gui import lineEditFloatRange + + +class AddFeature(SelectColumn): + InheritEq = True + + +class _AddCommon(CommonDomain): + + def __init__(self, amount, domain): + super().__init__(domain) + self.amount = amount + + def transformed(self, data): + return data.X + self.amount + + +class AddConstant(Preprocess): + + def __init__(self, amount=0.): + self.amount = amount + + def __call__(self, data): + common = _AddCommon(self.amount, data.domain) + atts = [a.copy(compute_value=AddFeature(i, common)) + for i, a in enumerate(data.domain.attributes)] + domain = Orange.data.Domain(atts, data.domain.class_vars, + data.domain.metas) + return data.from_table(domain, data) + + +class AddEditor(BaseEditorOrange): + + name = "Add constant" + qualname = "orangecontrib.snom.add_constant_test" + + def __init__(self, parent=None, **kwargs): + super().__init__(parent, **kwargs) + + self.amount = 0. + + form = QFormLayout() + amounte = lineEditFloatRange(self, self, "amount", callback=self.edited.emit) + form.addRow("Addition", amounte) + self.controlArea.setLayout(form) + + def activateOptions(self): + pass # actions when user starts changing options + + def setParameters(self, params): + self.amount = params.get("amount", 0.) + + @classmethod + def createinstance(cls, params): + params = dict(params) + amount = float(params.get("amount", 0.)) + return AddConstant(amount=amount) + + def set_preview_data(self, data): + if data: + pass # TODO any settings + + +PREPROCESSORS = [ + PreprocessAction( + c.name, c.qualname, "Image", + Description(c.name, icon_path("Discretize.svg")), + c + ) for c in [ + AddEditor + ] +] + + +class OWPreprocessImage(SpectralPreprocess): + name = "Preprocess image" + id = "orangecontrib.snom.widgets.preprocessimage" + description = "Process image" + icon = "icons/preprocessimage.svg" + priority = 1010 + + settings_version = 2 + + PREPROCESSORS = PREPROCESSORS + BUTTON_ADD_LABEL = "Add integral..." + + class Outputs: + preprocessed_data = Output("Integrated Data", Orange.data.Table, default=True) + preprocessor = Output("Preprocessor", preprocess.preprocess.Preprocess) + + def __init__(self): + self.markings_list = [] + super().__init__() + + def show_preview(self, show_info_anyway=False): + # redraw integrals if number of preview curves was changed + super().show_preview(False) + + def create_outputs(self): + self._reference_compat_warning() + pp_def = [ + self.preprocessormodel.item(i) + for i in range(self.preprocessormodel.rowCount()) + ] + self.start( + self.run_task, + self.data, + self.reference_data, + pp_def, + self.process_reference, + ) + + @staticmethod + def run_task( + data: Orange.data.Table, + reference: Orange.data.Table, + pp_def, + process_reference, + state, + ): + def progress_interrupt(i: float): + state.set_progress_value(i) + if state.is_interruption_requested(): + raise InterruptException + + # Protects against running the task in succession many times, as would + # happen when adding a preprocessor (there, commit() is called twice). + # Wait 100 ms before processing - if a new task is started in meanwhile, + # allow that is easily` cancelled. + for _ in range(10): + time.sleep(0.010) + progress_interrupt(0) + + n = len(pp_def) + plist = [] + for i in range(n): + progress_interrupt(i / n * 100) + item = pp_def[i] + pp = create_preprocessor(item, reference) + plist.append(pp) + if data is not None: + data = pp(data) + progress_interrupt((i / n + 0.5 / n) * 100) + if process_reference and reference is not None and i != n - 1: + reference = pp(reference) + # if there are no preprocessors, return None instead of an empty list + preprocessor = preprocess.preprocess.PreprocessorList(plist) if plist else None + return data, preprocessor + + +if __name__ == "__main__": # pragma: no cover + from Orange.widgets.utils.widgetpreview import WidgetPreview + WidgetPreview(OWPreprocessImage).run(Orange.data.Table("collagen.csv")) From 54466077796ba846943aa3383c3c7cdd31b7f8e9 Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Mon, 16 Sep 2024 13:40:53 +0200 Subject: [PATCH 2/6] imageplots instead of curveplots --- .../snom/widgets/owpreprocessimage.py | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/orangecontrib/snom/widgets/owpreprocessimage.py b/orangecontrib/snom/widgets/owpreprocessimage.py index b570c1a..094b7b6 100644 --- a/orangecontrib/snom/widgets/owpreprocessimage.py +++ b/orangecontrib/snom/widgets/owpreprocessimage.py @@ -1,6 +1,7 @@ import time from AnyQt.QtWidgets import QFormLayout +from orangewidget.settings import SettingProvider import Orange.data from Orange import preprocess @@ -12,9 +13,12 @@ from orangecontrib.spectroscopy.preprocess import SelectColumn, \ CommonDomain +from orangecontrib.spectroscopy.widgets.owhyper import ImagePlot from orangecontrib.spectroscopy.widgets.owpreprocess import ( - SpectralPreprocess, create_preprocessor, InterruptException + GeneralPreprocess, + create_preprocessor, + InterruptException, ) from orangecontrib.spectroscopy.widgets.preprocessors.utils import BaseEditorOrange from orangecontrib.spectroscopy.widgets.gui import lineEditFloatRange @@ -91,7 +95,39 @@ def set_preview_data(self, data): ] -class OWPreprocessImage(SpectralPreprocess): +class AImagePlot(ImagePlot): + def clear_markings(self): + pass + + +class ImagePreviews: + curveplot = SettingProvider(AImagePlot) + curveplot_after = SettingProvider(AImagePlot) + + value_type = 1 + + def __init__(self): + # the name of curveplot is kept because GeneralPreprocess + # expects these names + self.curveplot = AImagePlot(self) + self.curveplot_after = AImagePlot(self) + + def shutdown(self): + self.curveplot.shutdown() + self.curveplot_after.shutdown() + + +class SpectralImagePreprocess(GeneralPreprocess, ImagePreviews, openclass=True): + def __init__(self): + ImagePreviews.__init__(self) + super().__init__() + + def onDeleteWidget(self): + super().onDeleteWidget() + ImagePreviews.shutdown(self) + + +class OWPreprocessImage(SpectralImagePreprocess): name = "Preprocess image" id = "orangecontrib.snom.widgets.preprocessimage" description = "Process image" @@ -169,4 +205,4 @@ def progress_interrupt(i: float): if __name__ == "__main__": # pragma: no cover from Orange.widgets.utils.widgetpreview import WidgetPreview - WidgetPreview(OWPreprocessImage).run(Orange.data.Table("collagen.csv")) + WidgetPreview(OWPreprocessImage).run(Orange.data.Table("iris.tab")) From fd26e96eaa85d9f3151eb010a201742cda24c32d Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Mon, 16 Sep 2024 14:17:14 +0200 Subject: [PATCH 3/6] use preprocess registry --- orangecontrib/snom/widgets/owpreprocessimage.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/orangecontrib/snom/widgets/owpreprocessimage.py b/orangecontrib/snom/widgets/owpreprocessimage.py index 094b7b6..6bbdb4e 100644 --- a/orangecontrib/snom/widgets/owpreprocessimage.py +++ b/orangecontrib/snom/widgets/owpreprocessimage.py @@ -6,9 +6,6 @@ import Orange.data from Orange import preprocess from Orange.preprocess import Preprocess -from Orange.widgets.data.owpreprocess import ( - PreprocessAction, Description, icon_path -) from Orange.widgets.widget import Output from orangecontrib.spectroscopy.preprocess import SelectColumn, \ @@ -20,6 +17,7 @@ create_preprocessor, InterruptException, ) +from orangecontrib.spectroscopy.widgets.preprocessors.registry import PreprocessorEditorRegistry from orangecontrib.spectroscopy.widgets.preprocessors.utils import BaseEditorOrange from orangecontrib.spectroscopy.widgets.gui import lineEditFloatRange @@ -84,15 +82,8 @@ def set_preview_data(self, data): pass # TODO any settings -PREPROCESSORS = [ - PreprocessAction( - c.name, c.qualname, "Image", - Description(c.name, icon_path("Discretize.svg")), - c - ) for c in [ - AddEditor - ] -] +preprocess_editors = PreprocessorEditorRegistry() +preprocess_editors.register(AddEditor, 100) class AImagePlot(ImagePlot): @@ -136,7 +127,7 @@ class OWPreprocessImage(SpectralImagePreprocess): settings_version = 2 - PREPROCESSORS = PREPROCESSORS + editor_registry = preprocess_editors BUTTON_ADD_LABEL = "Add integral..." class Outputs: From a3d115c50210862ba547705d207fe419a8929f7e Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Thu, 19 Sep 2024 11:14:24 +0200 Subject: [PATCH 4/6] Working (but not friendly) image previews The biggest problem is that settings for image previews are still context settings, and that is a problem because the matching them precisely should not be required. Now if user switches the data set to a one that has different column names, preprocessors are going to be forgotten. Because the actual preprocessing is more important than the previews, the settings here should not be context settings, or should be at least made optional. --- .../snom/widgets/owpreprocessimage.py | 89 ++++++++++++++++++- 1 file changed, 85 insertions(+), 4 deletions(-) diff --git a/orangecontrib/snom/widgets/owpreprocessimage.py b/orangecontrib/snom/widgets/owpreprocessimage.py index 6bbdb4e..ba2f7e9 100644 --- a/orangecontrib/snom/widgets/owpreprocessimage.py +++ b/orangecontrib/snom/widgets/owpreprocessimage.py @@ -1,7 +1,12 @@ import time from AnyQt.QtWidgets import QFormLayout -from orangewidget.settings import SettingProvider + +from Orange.data import Domain, DiscreteVariable, ContinuousVariable +from Orange.widgets.settings import DomainContextHandler +from Orange.widgets.utils.itemmodels import DomainModel +from orangewidget import gui +from orangewidget.settings import SettingProvider, ContextSetting, Setting import Orange.data from Orange import preprocess @@ -20,6 +25,7 @@ from orangecontrib.spectroscopy.widgets.preprocessors.registry import PreprocessorEditorRegistry from orangecontrib.spectroscopy.widgets.preprocessors.utils import BaseEditorOrange from orangecontrib.spectroscopy.widgets.gui import lineEditFloatRange +from orangewidget.widget import Msg class AddFeature(SelectColumn): @@ -127,19 +133,73 @@ class OWPreprocessImage(SpectralImagePreprocess): settings_version = 2 + settingsHandler = DomainContextHandler() + + _max_preview_spectra = 1000000 + preview_curves = Setting(10000) + editor_registry = preprocess_editors - BUTTON_ADD_LABEL = "Add integral..." + BUTTON_ADD_LABEL = "Add preprocessor..." + + attr_value = ContextSetting(None) class Outputs: preprocessed_data = Output("Integrated Data", Orange.data.Table, default=True) preprocessor = Output("Preprocessor", preprocess.preprocess.Preprocess) + class Warning(SpectralImagePreprocess.Warning): + threshold_error = Msg("Low slider should be less than High") + + class Error(SpectralImagePreprocess.Error): + image_too_big = Msg("Image for chosen features is too big ({} x {}).") + + class Information(SpectralImagePreprocess.Information): + not_shown = Msg("Undefined positions: {} data point(s) are not shown.") + + def image_values(self): + attr_value = self.attr_value.name if self.attr_value else None + return lambda data, attr=attr_value: \ + data.transform(Domain([data.domain[attr]])) + + def image_values_fixed_levels(self): + return None + def __init__(self): self.markings_list = [] super().__init__() + self.feature_value_model = DomainModel(DomainModel.SEPARATED, + valid_types=ContinuousVariable) + self.feature_value = gui.comboBox( + self.preview_settings_box, self, "attr_value", + label="Show feature", + contentsLength=12, searchable=True, + callback=self.update_feature_value, model=self.feature_value_model) + + self.contextAboutToBeOpened.connect(lambda x: self.init_interface_data(x[0])) + + self.preview_runner.preview_updated.connect(self.redraw_data) + + def update_feature_value(self): + self.redraw_data() + + def redraw_data(self): + self.curveplot.update_view() + self.curveplot_after.update_view() + + def init_interface_data(self, data): + self.init_attr_values(data) + self.curveplot.init_interface_data(data) + self.curveplot_after.init_interface_data(data) + + def init_attr_values(self, data): + domain = data.domain if data is not None else None + self.feature_value_model.set_domain(domain) + self.attr_value = ( + self.feature_value_model[0] if self.feature_value_model else None + ) + def show_preview(self, show_info_anyway=False): - # redraw integrals if number of preview curves was changed super().show_preview(False) def create_outputs(self): @@ -156,6 +216,27 @@ def create_outputs(self): self.process_reference, ) + def set_data(self, data): + super().set_data(data) + + self.closeContext() + + def valid_context(data): + if data is None: + return False + annotation_features = [v for v in data.domain.metas + data.domain.class_vars + if isinstance(v, (DiscreteVariable, ContinuousVariable))] + return len(annotation_features) >= 1 + + if valid_context(data): + self.openContext(data) + else: + # to generate valid interface even if context was not loaded + self.contextAboutToBeOpened.emit([data]) + + self.curveplot.update_view() + self.curveplot_after.update_view() + @staticmethod def run_task( data: Orange.data.Table, @@ -196,4 +277,4 @@ def progress_interrupt(i: float): if __name__ == "__main__": # pragma: no cover from Orange.widgets.utils.widgetpreview import WidgetPreview - WidgetPreview(OWPreprocessImage).run(Orange.data.Table("iris.tab")) + WidgetPreview(OWPreprocessImage).run(Orange.data.Table("whitelight.gsf")) From 16fb68e1e6329a48997bb626f52071844a931554 Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Thu, 19 Sep 2024 11:33:12 +0200 Subject: [PATCH 5/6] Move dummy Add preprocessor to the proper location --- .../snom/widgets/owpreprocessimage.py | 103 ++++-------------- .../snom/widgets/preprocessors/__init__.py | 1 + .../snom/widgets/preprocessors/multiply.py | 73 +++++++++++++ .../snom/widgets/preprocessors/registry.py | 5 + 4 files changed, 100 insertions(+), 82 deletions(-) create mode 100644 orangecontrib/snom/widgets/preprocessors/multiply.py create mode 100644 orangecontrib/snom/widgets/preprocessors/registry.py diff --git a/orangecontrib/snom/widgets/owpreprocessimage.py b/orangecontrib/snom/widgets/owpreprocessimage.py index ba2f7e9..70dc4a5 100644 --- a/orangecontrib/snom/widgets/owpreprocessimage.py +++ b/orangecontrib/snom/widgets/owpreprocessimage.py @@ -1,20 +1,16 @@ import time -from AnyQt.QtWidgets import QFormLayout - from Orange.data import Domain, DiscreteVariable, ContinuousVariable from Orange.widgets.settings import DomainContextHandler from Orange.widgets.utils.itemmodels import DomainModel +from orangecontrib.snom.widgets.preprocessors.registry import preprocess_image_editors from orangewidget import gui from orangewidget.settings import SettingProvider, ContextSetting, Setting import Orange.data from Orange import preprocess -from Orange.preprocess import Preprocess from Orange.widgets.widget import Output -from orangecontrib.spectroscopy.preprocess import SelectColumn, \ - CommonDomain from orangecontrib.spectroscopy.widgets.owhyper import ImagePlot from orangecontrib.spectroscopy.widgets.owpreprocess import ( @@ -22,74 +18,8 @@ create_preprocessor, InterruptException, ) -from orangecontrib.spectroscopy.widgets.preprocessors.registry import PreprocessorEditorRegistry -from orangecontrib.spectroscopy.widgets.preprocessors.utils import BaseEditorOrange -from orangecontrib.spectroscopy.widgets.gui import lineEditFloatRange -from orangewidget.widget import Msg - - -class AddFeature(SelectColumn): - InheritEq = True - - -class _AddCommon(CommonDomain): - - def __init__(self, amount, domain): - super().__init__(domain) - self.amount = amount - - def transformed(self, data): - return data.X + self.amount - - -class AddConstant(Preprocess): - - def __init__(self, amount=0.): - self.amount = amount - - def __call__(self, data): - common = _AddCommon(self.amount, data.domain) - atts = [a.copy(compute_value=AddFeature(i, common)) - for i, a in enumerate(data.domain.attributes)] - domain = Orange.data.Domain(atts, data.domain.class_vars, - data.domain.metas) - return data.from_table(domain, data) - - -class AddEditor(BaseEditorOrange): - name = "Add constant" - qualname = "orangecontrib.snom.add_constant_test" - - def __init__(self, parent=None, **kwargs): - super().__init__(parent, **kwargs) - - self.amount = 0. - - form = QFormLayout() - amounte = lineEditFloatRange(self, self, "amount", callback=self.edited.emit) - form.addRow("Addition", amounte) - self.controlArea.setLayout(form) - - def activateOptions(self): - pass # actions when user starts changing options - - def setParameters(self, params): - self.amount = params.get("amount", 0.) - - @classmethod - def createinstance(cls, params): - params = dict(params) - amount = float(params.get("amount", 0.)) - return AddConstant(amount=amount) - - def set_preview_data(self, data): - if data: - pass # TODO any settings - - -preprocess_editors = PreprocessorEditorRegistry() -preprocess_editors.register(AddEditor, 100) +from orangewidget.widget import Msg class AImagePlot(ImagePlot): @@ -138,7 +68,7 @@ class OWPreprocessImage(SpectralImagePreprocess): _max_preview_spectra = 1000000 preview_curves = Setting(10000) - editor_registry = preprocess_editors + editor_registry = preprocess_image_editors BUTTON_ADD_LABEL = "Add preprocessor..." attr_value = ContextSetting(None) @@ -158,8 +88,7 @@ class Information(SpectralImagePreprocess.Information): def image_values(self): attr_value = self.attr_value.name if self.attr_value else None - return lambda data, attr=attr_value: \ - data.transform(Domain([data.domain[attr]])) + return lambda data, attr=attr_value: data.transform(Domain([data.domain[attr]])) def image_values_fixed_levels(self): return None @@ -168,13 +97,19 @@ def __init__(self): self.markings_list = [] super().__init__() - self.feature_value_model = DomainModel(DomainModel.SEPARATED, - valid_types=ContinuousVariable) + self.feature_value_model = DomainModel( + DomainModel.SEPARATED, valid_types=ContinuousVariable + ) self.feature_value = gui.comboBox( - self.preview_settings_box, self, "attr_value", + self.preview_settings_box, + self, + "attr_value", label="Show feature", - contentsLength=12, searchable=True, - callback=self.update_feature_value, model=self.feature_value_model) + contentsLength=12, + searchable=True, + callback=self.update_feature_value, + model=self.feature_value_model, + ) self.contextAboutToBeOpened.connect(lambda x: self.init_interface_data(x[0])) @@ -224,8 +159,11 @@ def set_data(self, data): def valid_context(data): if data is None: return False - annotation_features = [v for v in data.domain.metas + data.domain.class_vars - if isinstance(v, (DiscreteVariable, ContinuousVariable))] + annotation_features = [ + v + for v in data.domain.metas + data.domain.class_vars + if isinstance(v, (DiscreteVariable, ContinuousVariable)) + ] return len(annotation_features) >= 1 if valid_context(data): @@ -277,4 +215,5 @@ def progress_interrupt(i: float): if __name__ == "__main__": # pragma: no cover from Orange.widgets.utils.widgetpreview import WidgetPreview + WidgetPreview(OWPreprocessImage).run(Orange.data.Table("whitelight.gsf")) diff --git a/orangecontrib/snom/widgets/preprocessors/__init__.py b/orangecontrib/snom/widgets/preprocessors/__init__.py index d78515b..83e3ef9 100644 --- a/orangecontrib/snom/widgets/preprocessors/__init__.py +++ b/orangecontrib/snom/widgets/preprocessors/__init__.py @@ -1,3 +1,4 @@ # load and register editors from . import registration_example # noqa: F401 from . import phase_unwrap # noqa: F401 +from . import multiply # noqa: F401 diff --git a/orangecontrib/snom/widgets/preprocessors/multiply.py b/orangecontrib/snom/widgets/preprocessors/multiply.py new file mode 100644 index 0000000..2135c75 --- /dev/null +++ b/orangecontrib/snom/widgets/preprocessors/multiply.py @@ -0,0 +1,73 @@ +# this is just an example of registration + +from AnyQt.QtWidgets import QFormLayout + +from Orange.data import Domain +from Orange.preprocess import Preprocess +from orangecontrib.snom.widgets.preprocessors.registry import preprocess_image_editors + +from orangecontrib.spectroscopy.preprocess import SelectColumn, CommonDomain + +from orangecontrib.spectroscopy.widgets.preprocessors.utils import BaseEditorOrange +from orangecontrib.spectroscopy.widgets.gui import lineEditFloatRange + + +class AddFeature(SelectColumn): + InheritEq = True + + +class _AddCommon(CommonDomain): + def __init__(self, amount, domain): + super().__init__(domain) + self.amount = amount + + def transformed(self, data): + return data.X + self.amount + + +class AddConstant(Preprocess): + def __init__(self, amount=0.0): + self.amount = amount + + def __call__(self, data): + common = _AddCommon(self.amount, data.domain) + atts = [ + a.copy(compute_value=AddFeature(i, common)) + for i, a in enumerate(data.domain.attributes) + ] + domain = Domain(atts, data.domain.class_vars, data.domain.metas) + return data.from_table(domain, data) + + +class AddEditor(BaseEditorOrange): + name = "Add constant" + qualname = "orangecontrib.snom.add_constant_test" + + def __init__(self, parent=None, **kwargs): + super().__init__(parent, **kwargs) + + self.amount = 0.0 + + form = QFormLayout() + amounte = lineEditFloatRange(self, self, "amount", callback=self.edited.emit) + form.addRow("Addition", amounte) + self.controlArea.setLayout(form) + + def activateOptions(self): + pass # actions when user starts changing options + + def setParameters(self, params): + self.amount = params.get("amount", 0.0) + + @classmethod + def createinstance(cls, params): + params = dict(params) + amount = float(params.get("amount", 0.0)) + return AddConstant(amount=amount) + + def set_preview_data(self, data): + if data: + pass # TODO any settings + + +preprocess_image_editors.register(AddEditor, 100) diff --git a/orangecontrib/snom/widgets/preprocessors/registry.py b/orangecontrib/snom/widgets/preprocessors/registry.py new file mode 100644 index 0000000..c14e97c --- /dev/null +++ b/orangecontrib/snom/widgets/preprocessors/registry.py @@ -0,0 +1,5 @@ +from orangecontrib.spectroscopy.widgets.preprocessors.registry import ( + PreprocessorEditorRegistry, +) + +preprocess_image_editors = PreprocessorEditorRegistry() From 421714999203ce59c37872a13719bfbb2da1a0d7 Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Thu, 19 Sep 2024 14:13:02 +0200 Subject: [PATCH 6/6] Add some tests (workflow saving does not work) --- .../widgets/tests/test_owpreprocessimage.py | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 orangecontrib/snom/widgets/tests/test_owpreprocessimage.py diff --git a/orangecontrib/snom/widgets/tests/test_owpreprocessimage.py b/orangecontrib/snom/widgets/tests/test_owpreprocessimage.py new file mode 100644 index 0000000..8dcde9d --- /dev/null +++ b/orangecontrib/snom/widgets/tests/test_owpreprocessimage.py @@ -0,0 +1,138 @@ +import numpy as np + +from orangewidget.tests.utils import excepthook_catch + +from Orange.data import Table, Domain +from Orange.widgets.tests.base import WidgetTest +from Orange.preprocess.preprocess import Preprocess + +from orangecontrib.spectroscopy.tests import spectral_preprocess +from orangecontrib.spectroscopy.tests.spectral_preprocess import ( + pack_editor, + wait_for_preview, +) +from orangecontrib.spectroscopy.widgets.preprocessors.misc import ( + CutEditor, + SavitzkyGolayFilteringEditor, +) + +from orangecontrib.snom.widgets.owpreprocessimage import OWPreprocessImage +from orangecontrib.snom.widgets.preprocessors.registry import preprocess_image_editors + + +PREPROCESSORS = list(map(pack_editor, preprocess_image_editors.sorted())) + + +WHITELIGHT = Table("whitelight.gsf") + + +class TestAllPreprocessors(WidgetTest): + def test_allpreproc_indv(self): + data = WHITELIGHT + for p in PREPROCESSORS: + with self.subTest(p.viewclass): + self.widget = self.create_widget(OWPreprocessImage) + self.send_signal("Data", data) + self.widget.add_preprocessor(p) + self.widget.commit.now() + wait_for_preview(self.widget) + self.wait_until_finished(timeout=10000) + + def test_allpreproc_indv_empty(self): + data = WHITELIGHT + for p in PREPROCESSORS: + with self.subTest(p.viewclass): + self.widget = self.create_widget(OWPreprocessImage) + self.send_signal("Data", data[:0]) + self.widget.add_preprocessor(p) + self.widget.commit.now() + wait_for_preview(self.widget) + self.wait_until_finished(timeout=10000) + # no attributes + data = data.transform( + Domain([], class_vars=data.domain.class_vars, metas=data.domain.metas) + ) + for p in PREPROCESSORS: + with self.subTest(p.viewclass, type="no attributes"): + self.widget = self.create_widget(OWPreprocessImage) + self.send_signal("Data", data) + self.widget.add_preprocessor(p) + self.widget.commit.now() + wait_for_preview(self.widget) + self.wait_until_finished(timeout=10000) + + +class TestOWPreprocess(WidgetTest): + def setUp(self): + self.widget = self.create_widget(OWPreprocessImage) + + def test_load_unload(self): + self.send_signal("Data", Table("iris.tab")) + self.send_signal("Data", None) + + def failing_saving_preprocessors(self): + settings = self.widget.settingsHandler.pack_data(self.widget) + self.assertEqual([], settings["storedsettings"]["preprocessors"]) + self.widget.add_preprocessor(self.widget.PREPROCESSORS[0]) + settings = self.widget.settingsHandler.pack_data(self.widget) + self.assertEqual( + self.widget.PREPROCESSORS[0].qualname, + settings["storedsettings"]["preprocessors"][0][0], + ) + + def test_output_preprocessor_without_data(self): + self.widget.add_preprocessor(pack_editor(CutEditor)) + self.widget.commit.now() + self.wait_until_finished() + out = self.get_output(self.widget.Outputs.preprocessor) + self.assertIsInstance(out, Preprocess) + + def test_empty_no_inputs(self): + self.widget.commit.now() + self.wait_until_finished() + p = self.get_output(self.widget.Outputs.preprocessor) + d = self.get_output(self.widget.Outputs.preprocessed_data) + self.assertEqual(None, p) + self.assertEqual(None, d) + + def test_no_preprocessors(self): + data = WHITELIGHT + self.send_signal(self.widget.Inputs.data, data) + self.widget.commit.now() + self.wait_until_finished() + d = self.get_output(self.widget.Outputs.preprocessed_data) + self.assertEqual(WHITELIGHT, d) + + def test_widget_vs_manual(self): + data = WHITELIGHT + self.send_signal(self.widget.Inputs.data, data) + self.widget.add_preprocessor(pack_editor(CutEditor)) + self.widget.add_preprocessor(pack_editor(SavitzkyGolayFilteringEditor)) + self.widget.commit.now() + self.wait_until_finished() + p = self.get_output(self.widget.Outputs.preprocessor) + d = self.get_output(self.widget.Outputs.preprocessed_data) + manual = p(data) + np.testing.assert_equal(d.X, manual.X) + + def test_invalid_preprocessors(self): + settings = {"storedsettings": {"preprocessors": [("xyz.abc.notme", {})]}} + with self.assertRaises(KeyError): + with excepthook_catch(raise_on_exit=True): + widget = self.create_widget(OWPreprocessImage, settings) + self.assertTrue(widget.Error.loading.is_shown()) + + +class TestPreprocessWarning(spectral_preprocess.TestWarning): + widget_cls = OWPreprocessImage + + def test_exception_preview_after_data(self): + self.editor.raise_exception = True + self.editor.edited.emit() + wait_for_preview(self.widget) + self.assertIsNone(self.widget.curveplot_after.data) + + self.editor.raise_exception = False + self.editor.edited.emit() + wait_for_preview(self.widget) + self.assertIsNotNone(self.widget.curveplot_after.data)