summaryrefslogtreecommitdiff
path: root/src/silx/app/compare
diff options
context:
space:
mode:
Diffstat (limited to 'src/silx/app/compare')
-rw-r--r--src/silx/app/compare/CompareImagesWindow.py254
-rw-r--r--src/silx/app/compare/__init__.py27
-rw-r--r--src/silx/app/compare/main.py105
-rw-r--r--src/silx/app/compare/test/__init__.py23
-rw-r--r--src/silx/app/compare/test/test_compare.py49
-rw-r--r--src/silx/app/compare/test/test_launcher.py142
6 files changed, 600 insertions, 0 deletions
diff --git a/src/silx/app/compare/CompareImagesWindow.py b/src/silx/app/compare/CompareImagesWindow.py
new file mode 100644
index 0000000..7a509ae
--- /dev/null
+++ b/src/silx/app/compare/CompareImagesWindow.py
@@ -0,0 +1,254 @@
+#!/usr/bin/env python
+# /*##########################################################################
+#
+# Copyright (c) 2016-2023 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""Main window used to compare images
+"""
+
+import logging
+import numpy
+import typing
+import os.path
+
+import silx.io
+from silx.gui import icons
+from silx.gui import qt
+from silx.gui.plot.CompareImages import CompareImages
+from silx.gui.widgets.UrlSelectionTable import UrlSelectionTable
+from ..utils import parseutils
+from silx.gui.plot.tools.profile.manager import ProfileManager
+from silx.gui.plot.tools.compare.profile import ProfileImageDirectedLineROI
+
+try:
+ import PIL
+except ImportError:
+ PIL = None
+
+
+_logger = logging.getLogger(__name__)
+
+
+def _get_image_from_file(urlPath: str) -> typing.Optional[numpy.ndarray]:
+ """Returns a dataset from an image file.
+
+ The returned layout shape is supposed to be `rows, columns, channels (rgb[a])`.
+ """
+ if PIL is None:
+ return None
+ return numpy.asarray(PIL.Image.open(urlPath))
+
+
+class CompareImagesWindow(qt.QMainWindow):
+ def __init__(self, backend=None, settings=None):
+ qt.QMainWindow.__init__(self, parent=None)
+ self.setWindowTitle("Silx compare")
+
+ silxIcon = icons.getQIcon("silx")
+ self.setWindowIcon(silxIcon)
+
+ self._plot = CompareImages(parent=self, backend=backend)
+ self._plot.setAutoResetZoom(False)
+
+ self.__manager = ProfileManager(self, self._plot.getPlot())
+ virtualItem = self._plot._getVirtualPlotItem()
+ self.__manager.setPlotItem(virtualItem)
+
+ directedLineAction = self.__manager.createProfileAction(
+ ProfileImageDirectedLineROI, self
+ )
+
+ profileToolBar = qt.QToolBar(self)
+ profileToolBar.setWindowTitle("Profile")
+ profileToolBar.addAction(directedLineAction)
+ self.__profileToolBar = profileToolBar
+ self._plot.addToolBar(profileToolBar)
+
+ self._selectionTable = UrlSelectionTable(parent=self)
+ self._selectionTable.setAcceptDrops(True)
+
+ self.__settings = settings
+ if settings:
+ self.restoreSettings(settings)
+
+ spliter = qt.QSplitter(self)
+ spliter.addWidget(self._selectionTable)
+ spliter.addWidget(self._plot)
+ spliter.setStretchFactor(1, 1)
+ spliter.setCollapsible(0, False)
+ spliter.setCollapsible(1, False)
+ self.__splitter = spliter
+
+ self.setCentralWidget(spliter)
+
+ self._selectionTable.sigImageAChanged.connect(self._updateImageA)
+ self._selectionTable.sigImageBChanged.connect(self._updateImageB)
+
+ def setUrls(self, urls):
+ self.clear()
+ for url in urls:
+ self._selectionTable.addUrl(url)
+ url1 = urls[0].path() if len(urls) >= 1 else None
+ url2 = urls[1].path() if len(urls) >= 2 else None
+ self._selectionTable.setUrlSelection(url_img_a=url1, url_img_b=url2)
+ self._plot.resetZoom()
+ self._plot.centerLines()
+
+ def clear(self):
+ self._plot.clear()
+ self._selectionTable.clear()
+
+ def _updateImageA(self, urlPath):
+ try:
+ data = self.readData(urlPath)
+ except Exception as e:
+ _logger.error("Error while loading URL %s", urlPath, exc_info=True)
+ self._selectionTable.setError(urlPath, e.args[0])
+ data = None
+ self._plot.setImage1(data)
+
+ def _updateImageB(self, urlPath):
+ try:
+ data = self.readData(urlPath)
+ except Exception as e:
+ _logger.error("Error while loading URL %s", urlPath, exc_info=True)
+ self._selectionTable.setError(urlPath, e.args[0])
+ data = None
+ self._plot.setImage2(data)
+
+ def readData(self, urlPath: str):
+ """Read an URL as an image"""
+ if urlPath in ("", None):
+ return None
+
+ data = None
+ _, ext = os.path.splitext(urlPath)
+ if ext in {".jpg", ".jpeg", ".png"}:
+ try:
+ data = _get_image_from_file(urlPath)
+ except Exception:
+ _logger.debug("Error while loading image with PIL", exc_info=True)
+
+ if data is None:
+ try:
+ data = silx.io.utils.get_data(urlPath)
+ except Exception:
+ raise ValueError(f"Data from '{urlPath}' is not readable")
+
+ if not isinstance(data, numpy.ndarray):
+ raise ValueError(f"URL '{urlPath}' does not link to a numpy array")
+ if data.dtype.kind not in set(["f", "u", "i", "b"]):
+ raise ValueError(f"URL '{urlPath}' does not link to a numeric numpy array")
+
+ if data.ndim == 2:
+ return data
+ if data.ndim == 3 and data.shape[2] in {3, 4}:
+ return data
+
+ raise ValueError(f"URL '{urlPath}' does not link to an numpy image")
+
+ def closeEvent(self, event):
+ settings = self.__settings
+ if settings:
+ self.saveSettings(self.__settings)
+
+ def saveSettings(self, settings):
+ """Save the window settings to this settings object
+
+ :param qt.QSettings settings: Initialized settings
+ """
+ isFullScreen = bool(self.windowState() & qt.Qt.WindowFullScreen)
+ if isFullScreen:
+ # show in normal to catch the normal geometry
+ self.showNormal()
+
+ settings.beginGroup("comparewindow")
+ settings.setValue("size", self.size())
+ settings.setValue("pos", self.pos())
+ settings.setValue("full-screen", isFullScreen)
+ settings.setValue("spliter", self.__splitter.sizes())
+ # NOTE: isInverted returns a numpy bool
+ settings.setValue(
+ "y-axis-inverted", bool(self._plot.getPlot().getYAxis().isInverted())
+ )
+
+ settings.setValue("visualization-mode", self._plot.getVisualizationMode().name)
+ settings.setValue("alignment-mode", self._plot.getAlignmentMode().name)
+ settings.setValue("display-keypoints", self._plot.getKeypointsVisible())
+
+ displayKeypoints = settings.value("display-keypoints", False)
+ displayKeypoints = parseutils.to_bool(displayKeypoints, False)
+
+ # self._plot.getAlignmentMode()
+ # self._plot.getVisualizationMode()
+ # self._plot.getKeypointsVisible()
+ settings.endGroup()
+
+ if isFullScreen:
+ self.showFullScreen()
+
+ def restoreSettings(self, settings):
+ """Restore the window settings using this settings object
+
+ :param qt.QSettings settings: Initialized settings
+ """
+ settings.beginGroup("comparewindow")
+ size = settings.value("size", qt.QSize(640, 480))
+ pos = settings.value("pos", qt.QPoint())
+ isFullScreen = settings.value("full-screen", False)
+ isFullScreen = parseutils.to_bool(isFullScreen, False)
+ yAxisInverted = settings.value("y-axis-inverted", False)
+ yAxisInverted = parseutils.to_bool(yAxisInverted, False)
+
+ visualizationMode = settings.value("visualization-mode", "")
+ visualizationMode = parseutils.to_enum(
+ visualizationMode,
+ CompareImages.VisualizationMode,
+ CompareImages.VisualizationMode.VERTICAL_LINE,
+ )
+ alignmentMode = settings.value("alignment-mode", "")
+ alignmentMode = parseutils.to_enum(
+ alignmentMode,
+ CompareImages.AlignmentMode,
+ CompareImages.AlignmentMode.ORIGIN,
+ )
+ displayKeypoints = settings.value("display-keypoints", False)
+ displayKeypoints = parseutils.to_bool(displayKeypoints, False)
+
+ try:
+ data = settings.value("spliter")
+ data = [int(d) for d in data]
+ self.__splitter.setSizes(data)
+ except Exception:
+ _logger.debug("Backtrace", exc_info=True)
+ settings.endGroup()
+
+ if not pos.isNull():
+ self.move(pos)
+ if not size.isNull():
+ self.resize(size)
+ if isFullScreen:
+ self.showFullScreen()
+ self._plot.setVisualizationMode(visualizationMode)
+ self._plot.setAlignmentMode(alignmentMode)
+ self._plot.setKeypointsVisible(displayKeypoints)
+ self._plot.getPlot().getYAxis().setInverted(yAxisInverted)
diff --git a/src/silx/app/compare/__init__.py b/src/silx/app/compare/__init__.py
new file mode 100644
index 0000000..e5ec4c6
--- /dev/null
+++ b/src/silx/app/compare/__init__.py
@@ -0,0 +1,27 @@
+# /*##########################################################################
+# Copyright (C) 2022-2023 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ############################################################################*/
+"""Package containing source code of the `silx compare` application"""
+
+__authors__ = ["V. Valls"]
+__license__ = "MIT"
+__date__ = "04/13/2023"
diff --git a/src/silx/app/compare/main.py b/src/silx/app/compare/main.py
new file mode 100644
index 0000000..79c33f1
--- /dev/null
+++ b/src/silx/app/compare/main.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python
+# /*##########################################################################
+#
+# Copyright (c) 2016-2021 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""GUI to compare images"""
+
+import sys
+import logging
+import argparse
+import silx
+from silx.gui import qt
+from silx.app.utils import parseutils
+from silx.app.compare.CompareImagesWindow import CompareImagesWindow
+
+_logger = logging.getLogger(__name__)
+
+
+file_description = """
+Image data to compare (HDF5 file with path, EDF files, JPEG/PNG image files).
+Data from HDF5 files can be accessed using dataset path and slicing as an URL: silx:../my_file.h5?path=/entry/data&slice=10
+EDF file frames also can can be accessed using URL: fabio:../my_file.edf?slice=10
+Using URL in command like usually have to be quoted: "URL".
+"""
+
+
+def createParser():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("files", nargs=argparse.ZERO_OR_MORE, help=file_description)
+ parser.add_argument(
+ "--debug",
+ dest="debug",
+ action="store_true",
+ default=False,
+ help="Set logging system in debug mode",
+ )
+ parser.add_argument(
+ "--use-opengl-plot",
+ dest="use_opengl_plot",
+ action="store_true",
+ default=False,
+ help="Use OpenGL for plots (instead of matplotlib)",
+ )
+ return parser
+
+
+def mainQt(options):
+ """Part of the main depending on Qt"""
+ if options.debug:
+ logging.root.setLevel(logging.DEBUG)
+
+ if options.use_opengl_plot:
+ backend = "gl"
+ else:
+ backend = "mpl"
+
+ settings = qt.QSettings(
+ qt.QSettings.IniFormat, qt.QSettings.UserScope, "silx", "silx-compare", None
+ )
+
+ urls = list(parseutils.filenames_to_dataurls(options.files))
+
+ if options.use_opengl_plot:
+ # It have to be done after the settings (after the Viewer creation)
+ silx.config.DEFAULT_PLOT_BACKEND = "opengl"
+
+ app = qt.QApplication([])
+ window = CompareImagesWindow(backend=backend, settings=settings)
+ window.setAttribute(qt.Qt.WA_DeleteOnClose, True)
+
+ # Note: Have to be before setUrls to have a proper resetZoom
+ window.setVisible(True)
+
+ window.setUrls(urls)
+
+ app.exec()
+
+
+def main(argv):
+ parser = createParser()
+ options = parser.parse_args(argv[1:])
+ mainQt(options)
+
+
+if __name__ == "__main__":
+ main(sys.argv)
diff --git a/src/silx/app/compare/test/__init__.py b/src/silx/app/compare/test/__init__.py
new file mode 100644
index 0000000..1d8207b
--- /dev/null
+++ b/src/silx/app/compare/test/__init__.py
@@ -0,0 +1,23 @@
+# /*##########################################################################
+#
+# Copyright (c) 2016-2017 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
diff --git a/src/silx/app/compare/test/test_compare.py b/src/silx/app/compare/test/test_compare.py
new file mode 100644
index 0000000..45c6838
--- /dev/null
+++ b/src/silx/app/compare/test/test_compare.py
@@ -0,0 +1,49 @@
+# /*##########################################################################
+#
+# Copyright (c) 2016-2020 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""Module testing silx.app.view"""
+
+__authors__ = ["V. Valls"]
+__license__ = "MIT"
+__date__ = "07/06/2023"
+
+
+import weakref
+import pytest
+from silx.app.compare.CompareImagesWindow import CompareImagesWindow
+from silx.gui.utils.testutils import TestCaseQt
+
+
+@pytest.mark.usefixtures("qapp")
+class TestCompare(TestCaseQt):
+ """Test for Viewer class"""
+
+ def testConstruct(self):
+ widget = CompareImagesWindow()
+ self.qWaitForWindowExposed(widget)
+
+ def testDestroy(self):
+ widget = CompareImagesWindow()
+ ref = weakref.ref(widget)
+ widget = None
+ self.qWaitForDestroy(ref)
diff --git a/src/silx/app/compare/test/test_launcher.py b/src/silx/app/compare/test/test_launcher.py
new file mode 100644
index 0000000..a42b762
--- /dev/null
+++ b/src/silx/app/compare/test/test_launcher.py
@@ -0,0 +1,142 @@
+# /*##########################################################################
+#
+# Copyright (c) 2016-2023 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""Module testing silx.app.view"""
+
+__authors__ = ["V. Valls"]
+__license__ = "MIT"
+__date__ = "07/06/2023"
+
+
+import os
+import sys
+import shutil
+import logging
+import subprocess
+import pytest
+
+from .. import main
+from silx import __main__ as silx_main
+
+_logger = logging.getLogger(__name__)
+
+
+def test_help(qapp):
+ # option -h must cause a raise SystemExit or a return 0
+ try:
+ parser = main.createParser()
+ parser.parse_args(["compare", "--help"])
+ result = 0
+ except SystemExit as e:
+ result = e.args[0]
+ assert result == 0
+
+
+def test_wrong_option(qapp):
+ try:
+ parser = main.createParser()
+ parser.parse_args(["compare", "--foo"])
+ assert False
+ except SystemExit as e:
+ result = e.args[0]
+ assert result != 0
+
+
+def test_wrong_file(qapp):
+ try:
+ parser = main.createParser()
+ result = parser.parse_args(["compare", "__file.not.found__"])
+ result = 0
+ except SystemExit as e:
+ result = e.args[0]
+ assert result == 0
+
+
+def _create_test_env():
+ """
+ Returns an associated environment with a working project.
+ """
+ env = dict((str(k), str(v)) for k, v in os.environ.items())
+ env["PYTHONPATH"] = os.pathsep.join(sys.path)
+ return env
+
+
+@pytest.fixture
+def execute_as_script(tmp_path):
+ """Execute a command line.
+
+ Log output as debug in case of bad return code.
+ """
+
+ def execute_as_script(filename, *args):
+ env = _create_test_env()
+
+ # Copy file to temporary dir to avoid import from current dir.
+ script = os.path.join(tmp_path, "launcher.py")
+ shutil.copyfile(filename, script)
+ command_line = [sys.executable, script] + list(args)
+
+ _logger.info("Execute: %s", " ".join(command_line))
+ p = subprocess.Popen(
+ command_line, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env
+ )
+ out, err = p.communicate()
+ _logger.info("Return code: %d", p.returncode)
+ try:
+ out = out.decode("utf-8")
+ except UnicodeError:
+ pass
+ try:
+ err = err.decode("utf-8")
+ except UnicodeError:
+ pass
+
+ if p.returncode != 0:
+ _logger.error("stdout:")
+ _logger.error("%s", out)
+ _logger.error("stderr:")
+ _logger.error("%s", err)
+ else:
+ _logger.debug("stdout:")
+ _logger.debug("%s", out)
+ _logger.debug("stderr:")
+ _logger.debug("%s", err)
+ assert p.returncode == 0
+
+ return execute_as_script
+
+
+def test_execute_compare_help(qapp, execute_as_script):
+ """Test if the main module is well connected.
+
+ Uses subprocess to avoid to parasite the current environment.
+ """
+ execute_as_script(main.__file__, "--help")
+
+
+def test_execute_silx_compare_help(qapp, execute_as_script):
+ """Test if the main module is well connected.
+
+ Uses subprocess to avoid to parasite the current environment.
+ """
+ execute_as_script(silx_main.__file__, "view", "--help")