diff options
author | Alexandre Marie <alexandre.marie@synchrotron-soleil.fr> | 2020-01-30 11:46:45 +0100 |
---|---|---|
committer | Alexandre Marie <alexandre.marie@synchrotron-soleil.fr> | 2020-01-30 11:46:45 +0100 |
commit | 33ed2a64c92b0311ae35456c016eb284e426afc2 (patch) | |
tree | dc8276b74bebc11128d67240eb5906c9d35d8a6f /silx | |
parent | 5d647cf9a6159afd2933da594b9c79ad93d3cd9b (diff) |
New upstream version 0.12.0+dfsg
Diffstat (limited to 'silx')
-rw-r--r-- | silx/app/view/main.py | 16 | ||||
-rw-r--r-- | silx/gui/data/DataViewer.py | 2 | ||||
-rw-r--r-- | silx/gui/dialog/ColormapDialog.py | 9 | ||||
-rw-r--r-- | silx/gui/fit/FitWidget.py | 10 | ||||
-rwxr-xr-x | silx/gui/plot/PlotWidget.py | 3 | ||||
-rw-r--r-- | silx/gui/plot/ProfileMainWindow.py | 3 | ||||
-rw-r--r-- | silx/gui/plot/StatsWidget.py | 33 | ||||
-rwxr-xr-x | silx/gui/plot/backends/BackendMatplotlib.py | 23 | ||||
-rw-r--r-- | silx/gui/plot/items/shape.py | 2 | ||||
-rw-r--r-- | silx/gui/plot/tools/profile/_BaseProfileToolBar.py | 2 | ||||
-rw-r--r-- | silx/gui/plot3d/tools/PositionInfoWidget.py | 2 | ||||
-rw-r--r-- | silx/math/fit/fitmanager.py | 36 | ||||
-rw-r--r-- | silx/math/fit/test/test_fitmanager.py | 87 | ||||
-rw-r--r-- | silx/sx/_plot.py | 2 |
14 files changed, 154 insertions, 76 deletions
diff --git a/silx/app/view/main.py b/silx/app/view/main.py index 90b8b17..8139175 100644 --- a/silx/app/view/main.py +++ b/silx/app/view/main.py @@ -1,6 +1,6 @@ # coding: utf-8 # /*########################################################################## -# Copyright (C) 2016-2018 European Synchrotron Radiation Facility +# Copyright (C) 2016-2019 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 @@ -27,10 +27,11 @@ __authors__ = ["V. Valls"] __license__ = "MIT" __date__ = "17/01/2019" -import sys import argparse import logging +import os import signal +import sys _logger = logging.getLogger(__name__) @@ -61,6 +62,12 @@ def createParser(): action="store_true", default=False, help='Start the application using new fresh user preferences') + parser.add_argument( + '--hdf5-file-locking', + dest="hdf5_file_locking", + action="store_true", + default=False, + help='Start the application with HDF5 file locking enabled (it is disabled by default)') return parser @@ -73,6 +80,11 @@ def mainQt(options): # Import most of the things here to be sure to use the right logging level # + # This needs to be done prior to load HDF5 + hdf5_file_locking = 'TRUE' if options.hdf5_file_locking else 'FALSE' + _logger.info('Set HDF5_USE_FILE_LOCKING=%s', hdf5_file_locking) + os.environ['HDF5_USE_FILE_LOCKING'] = hdf5_file_locking + try: # it should be loaded before h5py import hdf5plugin # noqa diff --git a/silx/gui/data/DataViewer.py b/silx/gui/data/DataViewer.py index 67db5f9..bad4362 100644 --- a/silx/gui/data/DataViewer.py +++ b/silx/gui/data/DataViewer.py @@ -221,7 +221,7 @@ class DataViewer(qt.QFrame): self.__numpySelection.setSelection( previousSelection, previousPermutation) except ValueError as e: - _logger.error("Not restoring selection because: %s", e) + _logger.info("Not restoring selection because: %s", e) if hasattr(data, "shape"): isVisible = not (len(axisNames) == 1 and len(data.shape) == 1) diff --git a/silx/gui/dialog/ColormapDialog.py b/silx/gui/dialog/ColormapDialog.py index ed15947..dddec4c 100644 --- a/silx/gui/dialog/ColormapDialog.py +++ b/silx/gui/dialog/ColormapDialog.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2004-2018 European Synchrotron Radiation Facility +# Copyright (c) 2004-2019 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 @@ -463,6 +463,7 @@ class ColormapDialog(qt.QDialog): color='black', symbol='o', linestyle='-', + z=2, resetzoom=False) scale = self._plot.getXAxis().getScale() @@ -702,7 +703,8 @@ class ColormapDialog(qt.QDialog): legend="Histogram", color='gray', align='center', - fill=True) + fill=True, + z=1) self._updateMinMaxData() def getColormap(self): @@ -753,7 +755,8 @@ class ColormapDialog(qt.QDialog): legend="Range", color='gray', align='center', - fill=True) + fill=True, + z=1) self._dataRange = minimum, positiveMin, maximum self._updateMinMaxData() diff --git a/silx/gui/fit/FitWidget.py b/silx/gui/fit/FitWidget.py index 78230b1..c3804e1 100644 --- a/silx/gui/fit/FitWidget.py +++ b/silx/gui/fit/FitWidget.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2004-2017 European Synchrotron Radiation Facility +# Copyright (c) 2004-2020 European Synchrotron Radiation Facility # # This file is part of the PyMca X-ray Fluorescence Toolkit developed at # the ESRF by the Software group. @@ -315,8 +315,8 @@ class FitWidget(qt.QWidget): configuration.update(self.configure()) def setdata(self, x, y, sigmay=None, xmin=None, xmax=None): - warnings.warning("Method renamed to setData", - DeprecationWarning) + warnings.warn("Method renamed to setData", + DeprecationWarning) self.setData(x, y, sigmay, xmin, xmax) def setData(self, x, y, sigmay=None, xmin=None, xmax=None): @@ -525,8 +525,8 @@ class FitWidget(qt.QWidget): self._emitSignal(ddict) def startfit(self): - warnings.warning("Method renamed to startFit", - DeprecationWarning) + warnings.warn("Method renamed to startFit", + DeprecationWarning) self.startFit() def startFit(self): diff --git a/silx/gui/plot/PlotWidget.py b/silx/gui/plot/PlotWidget.py index 49e444a..e47249e 100755 --- a/silx/gui/plot/PlotWidget.py +++ b/silx/gui/plot/PlotWidget.py @@ -624,8 +624,7 @@ class PlotWidget(qt.QMainWindow): # Add item to plot self._content[key] = item item._setPlot(self) - if item.isVisible(): - self._itemRequiresUpdate(item) + self._itemRequiresUpdate(item) if isinstance(item, items.DATA_ITEMS): self._invalidateDataRange() # TODO handle this automatically diff --git a/silx/gui/plot/ProfileMainWindow.py b/silx/gui/plot/ProfileMainWindow.py index 39830d8..aaedd1c 100644 --- a/silx/gui/plot/ProfileMainWindow.py +++ b/silx/gui/plot/ProfileMainWindow.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2017-2018 European Synchrotron Radiation Facility +# Copyright (c) 2017-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 @@ -85,6 +85,7 @@ class ProfileMainWindow(qt.QMainWindow): self._plot2D.setParent(None) # necessary to avoid widget destruction if self._plot1D is None: self._plot1D = Plot1D(backend=self._backend) + self._plot1D.setDataMargins(yMinMargin=0.1, yMaxMargin=0.1) self._plot1D.setGraphYLabel('Profile') self._plot1D.setGraphXLabel('') self.setCentralWidget(self._plot1D) diff --git a/silx/gui/plot/StatsWidget.py b/silx/gui/plot/StatsWidget.py index 80bc05d..52b7e5c 100644 --- a/silx/gui/plot/StatsWidget.py +++ b/silx/gui/plot/StatsWidget.py @@ -424,7 +424,7 @@ class _StatsWidgetBase(object): if self._displayOnlyActItem: connections.append( - (self._plotWrapper.sigCurrentChanged, self._updateItemObserve)) + (self._plotWrapper.sigCurrentChanged, self._updateCurrentItem)) else: connections += [ (self._plotWrapper.sigItemAdded, self._addItem), @@ -441,6 +441,11 @@ class _StatsWidgetBase(object): """Reload table depending on mode""" raise NotImplementedError('Base class') + def _updateCurrentItem(self, *args): + """specific callback for the sigCurrentChanged and with the + _displayOnlyActItem option.""" + raise NotImplementedError('Base class') + def _updateStats(self, item): """Update displayed information for given plot item @@ -643,8 +648,6 @@ class StatsTable(_StatsWidgetBase, TableWidget): def _updateItemObserve(self, *args): """Reload table depending on mode""" - if self.getUpdateMode() is UpdateMode.MANUAL: - return self._removeAllItems() # Get selected or all items from the plot @@ -657,6 +660,27 @@ class StatsTable(_StatsWidgetBase, TableWidget): for item in items: self._addItem(item) + def _updateCurrentItem(self, *args): + """specific callback for the sigCurrentChanged and with the + _displayOnlyActItem option. + + Behavior: create the tableItems if does not exists. + If exists, update it only when we are in 'auto' mode""" + if self.getUpdateMode() is UpdateMode.MANUAL: + # when sigCurrentChanged is giving the current item + if len(args) > 0 and isinstance(args[0], (plotitems.Curve, plotitems.Histogram, plotitems.ImageData, plotitems.Scatter)): + item = args[0] + tableItems = self._itemToTableItems(item) + # if the table does not exists yet + if len(tableItems) == 0: + self._updateItemObserve() + else: + # in this case no current item + self._updateItemObserve(args) + else: + # auto mode + self._updateItemObserve(args) + def _plotCurrentChanged(self, current): """Handle change of current item and update selection in table @@ -1392,6 +1416,9 @@ class _BaseLineStatsWidget(_StatsWidgetBase, qt.QWidget): _item = items[0] if len(items) is 1 else None self._setItem(_item) + def _updateCurrentItem(self): + self._updateItemObserve() + def _createLayout(self): """create an instance of the main QLayout""" raise NotImplementedError('Base class') diff --git a/silx/gui/plot/backends/BackendMatplotlib.py b/silx/gui/plot/backends/BackendMatplotlib.py index 075f6aa..2336494 100755 --- a/silx/gui/plot/backends/BackendMatplotlib.py +++ b/silx/gui/plot/backends/BackendMatplotlib.py @@ -1094,13 +1094,16 @@ class BackendMatplotlib(BackendBase.BackendBase): # Data <-> Pixel coordinates conversion - def _mplQtYAxisCoordConversion(self, y): + def _mplQtYAxisCoordConversion(self, y, asint=True): """Qt origin (top) to/from matplotlib origin (bottom) conversion. + :param y: + :param bool asint: True to cast to int, False to keep as float + :rtype: float """ - height = self.fig.get_window_extent().height - return height - y + value = self.fig.get_window_extent().height - y + return int(value) if asint else value def dataToPixel(self, x, y, axis): ax = self.ax2 if axis == "right" else self.ax @@ -1109,7 +1112,7 @@ class BackendMatplotlib(BackendBase.BackendBase): xPixel, yPixel = pixels.T # Convert from matplotlib origin (bottom) to Qt origin (top) - yPixel = self._mplQtYAxisCoordConversion(yPixel) + yPixel = self._mplQtYAxisCoordConversion(yPixel, asint=False) return xPixel, yPixel @@ -1117,7 +1120,7 @@ class BackendMatplotlib(BackendBase.BackendBase): ax = self.ax2 if axis == "right" else self.ax # Convert from Qt origin (top) to matplotlib origin (bottom) - y = self._mplQtYAxisCoordConversion(y) + y = self._mplQtYAxisCoordConversion(y, asint=False) inv = ax.transData.inverted() x, y = inv.transform_point((x, y)) @@ -1126,10 +1129,10 @@ class BackendMatplotlib(BackendBase.BackendBase): def getPlotBoundsInPixels(self): bbox = self.ax.get_window_extent() # Warning this is not returning int... - return (bbox.xmin, - self._mplQtYAxisCoordConversion(bbox.ymax), - bbox.width, - bbox.height) + return (int(bbox.xmin), + self._mplQtYAxisCoordConversion(bbox.ymax, asint=True), + int(bbox.width), + int(bbox.height)) def setAxesDisplayed(self, displayed): """Display or not the axes. @@ -1263,7 +1266,7 @@ class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib): def _onMouseMove(self, event): if self._graphCursor: lineh, linev = self._graphCursor - if event.inaxes != self.ax and lineh.get_visible(): + if event.inaxes not in (self.ax, self.ax2) and lineh.get_visible(): lineh.set_visible(False) linev.set_visible(False) self._plot._setDirtyPlot(overlayOnly=True) diff --git a/silx/gui/plot/items/shape.py b/silx/gui/plot/items/shape.py index e6dc529..8176be1 100644 --- a/silx/gui/plot/items/shape.py +++ b/silx/gui/plot/items/shape.py @@ -197,6 +197,8 @@ class BoundingRect(Item, YAxisMixIn): self._updated(ItemChangedType.DATA) def _getBounds(self): + if self.__bounds is None: + return None plot = self.getPlot() if plot is not None: xPositive = plot.getXAxis()._isLogarithmic() diff --git a/silx/gui/plot/tools/profile/_BaseProfileToolBar.py b/silx/gui/plot/tools/profile/_BaseProfileToolBar.py index ced81da..75bb4c6 100644 --- a/silx/gui/plot/tools/profile/_BaseProfileToolBar.py +++ b/silx/gui/plot/tools/profile/_BaseProfileToolBar.py @@ -231,7 +231,7 @@ class _BaseProfileToolBar(qt.QToolBar): profilePlot.addCurve( xProfile, values, legend='Profile', color=self._color) - self._showDefaultProfileWindow() + self._showDefaultProfileWindow() def _showDefaultProfileWindow(self): """If profile window was created by this toolbar, diff --git a/silx/gui/plot3d/tools/PositionInfoWidget.py b/silx/gui/plot3d/tools/PositionInfoWidget.py index 52a6163..fc86a7f 100644 --- a/silx/gui/plot3d/tools/PositionInfoWidget.py +++ b/silx/gui/plot3d/tools/PositionInfoWidget.py @@ -189,7 +189,7 @@ class PositionInfoWidget(qt.QWidget): return # No picked item item = picking.getItem() - self._itemLabel.setText(item.getName()) + self._itemLabel.setText(item.getLabel()) positions = picking.getPositions('scene', copy=False) x, y, z = positions[0] self._xLabel.setText("%g" % x) diff --git a/silx/math/fit/fitmanager.py b/silx/math/fit/fitmanager.py index f62dedb..2dc63a1 100644 --- a/silx/math/fit/fitmanager.py +++ b/silx/math/fit/fitmanager.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*######################################################################### # -# Copyright (c) 2004-2018 European Synchrotron Radiation Facility +# Copyright (c) 2004-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 @@ -411,8 +411,9 @@ class FitManager(object): 6: 'SUM', 7: 'IGNORE'} - xwork = self.xdata - ywork = self.ydata + # Filter-out not finite data + xwork = self.xdata[self._finite_mask] + ywork = self.ydata[self._finite_mask] # estimate the background bg_params, bg_constraints = self.estimate_bkg(xwork, ywork) @@ -516,8 +517,8 @@ class FitManager(object): from a list of parameter dictionaries, if field ``code`` is not set to ``"IGNORE"``. """ - if x is None: - x = self.xdata + x = self.xdata if x is None else numpy.array(x, copy=False) + if paramlist is None: paramlist = self.fit_results active_params = [] @@ -528,8 +529,18 @@ class FitManager(object): else: active_params.append(param['estimation']) - newdata = self.fitfunction(numpy.array(x), *active_params) - return newdata + # Mask x with not finite (support nD x) + finite_mask = numpy.all(numpy.isfinite(x), axis=tuple(range(1, x.ndim))) + + if numpy.all(finite_mask): # All values are finite: fast path + return self.fitfunction(numpy.array(x, copy=True), *active_params) + + else: # Only run fitfunction on finite data and complete result with NaNs + # Create result with same number as elements as x, filling holes with NaNs + result = numpy.full((x.shape[0],), numpy.nan, dtype=numpy.float64) + result[finite_mask] = self.fitfunction( + numpy.array(x[finite_mask], copy=True), *active_params) + return result def get_estimation(self): """Return the list of fit parameter names.""" @@ -750,6 +761,10 @@ class FitManager(object): self.ydata = self.ydata[bool_array] self.sigmay = self.sigmay[bool_array] if sigmay is not None else None + self._finite_mask = numpy.logical_and( + numpy.all(numpy.isfinite(self.xdata), axis=tuple(range(1, self.xdata.ndim))), + numpy.isfinite(self.ydata)) + def enableweight(self): """This method can be called to set :attr:`sigmay`. If :attr:`sigmay0` was filled with actual uncertainties in :meth:`setdata`, use these values. @@ -822,13 +837,14 @@ class FitManager(object): param_val.append(param['estimation']) param_constraints.append([param['code'], param['cons1'], param['cons2']]) - ywork = self.ydata - + # Filter-out not finite data + ywork = self.ydata[self._finite_mask] + xwork = self.xdata[self._finite_mask] try: params, covariance_matrix, infodict = leastsq( self.fitfunction, # bg + actual model function - self.xdata, ywork, param_val, + xwork, ywork, param_val, sigma=self.sigmay, constraints=param_constraints, model_deriv=self.theories[self.selectedtheory].derivative, diff --git a/silx/math/fit/test/test_fitmanager.py b/silx/math/fit/test/test_fitmanager.py index 38c4802..7a643cb 100644 --- a/silx/math/fit/test/test_fitmanager.py +++ b/silx/math/fit/test/test_fitmanager.py @@ -1,6 +1,6 @@ # coding: utf-8 # /*########################################################################## -# Copyright (C) 2016-2017 European Synchrotron Radiation Facility +# 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 @@ -35,6 +35,7 @@ from silx.math.fit import bgtheories from silx.math.fit.fittheory import FitTheory from silx.math.fit.functions import sum_gauss, sum_stepdown, sum_stepup +from silx.utils.testutils import ParametricTestCase from silx.test.utils import temp_dir custom_function_definition = """ @@ -110,7 +111,7 @@ def _order_of_magnitude(x): return numpy.log10(x).round() -class TestFitmanager(unittest.TestCase): +class TestFitmanager(ParametricTestCase): """ Unit tests of multi-peak functions. """ @@ -132,40 +133,54 @@ class TestFitmanager(unittest.TestCase): linear_bg = 2.65 * x + 13 y = linear_bg + sum_gauss(x, *p) - # Fitting - fit = fitmanager.FitManager() - fit.setdata(x=x, y=y) - fit.loadtheories(fittheories) - # Use one of the default fit functions - fit.settheory('Gaussians') - fit.setbackground('Linear') - fit.estimate() - fit.runfit() - - # fit.fit_results[] - - # first 2 parameters are related to the linear background - self.assertEqual(fit.fit_results[0]["name"], "Constant") - self.assertAlmostEqual(fit.fit_results[0]["fitresult"], 13) - self.assertEqual(fit.fit_results[1]["name"], "Slope") - self.assertAlmostEqual(fit.fit_results[1]["fitresult"], 2.65) - - for i, param in enumerate(fit.fit_results[2:]): - param_number = i // 3 + 1 - if i % 3 == 0: - self.assertEqual(param["name"], - "Height%d" % param_number) - elif i % 3 == 1: - self.assertEqual(param["name"], - "Position%d" % param_number) - elif i % 3 == 2: - self.assertEqual(param["name"], - "FWHM%d" % param_number) - - self.assertAlmostEqual(param["fitresult"], - p[i]) - self.assertAlmostEqual(_order_of_magnitude(param["estimation"]), - _order_of_magnitude(p[i])) + y_with_nans = numpy.array(y) + y_with_nans[::10] = numpy.nan + + x_with_nans = numpy.array(x) + x_with_nans[5::15] = numpy.nan + + tests = { + 'all finite': (x, y), + 'y with NaNs': (x, y_with_nans), + 'x with NaNs': (x_with_nans, y), + } + + for name, (xdata, ydata) in tests.items(): + with self.subTest(name=name): + # Fitting + fit = fitmanager.FitManager() + fit.setdata(x=xdata, y=ydata) + fit.loadtheories(fittheories) + # Use one of the default fit functions + fit.settheory('Gaussians') + fit.setbackground('Linear') + fit.estimate() + fit.runfit() + + # fit.fit_results[] + + # first 2 parameters are related to the linear background + self.assertEqual(fit.fit_results[0]["name"], "Constant") + self.assertAlmostEqual(fit.fit_results[0]["fitresult"], 13) + self.assertEqual(fit.fit_results[1]["name"], "Slope") + self.assertAlmostEqual(fit.fit_results[1]["fitresult"], 2.65) + + for i, param in enumerate(fit.fit_results[2:]): + param_number = i // 3 + 1 + if i % 3 == 0: + self.assertEqual(param["name"], + "Height%d" % param_number) + elif i % 3 == 1: + self.assertEqual(param["name"], + "Position%d" % param_number) + elif i % 3 == 2: + self.assertEqual(param["name"], + "FWHM%d" % param_number) + + self.assertAlmostEqual(param["fitresult"], + p[i]) + self.assertAlmostEqual(_order_of_magnitude(param["estimation"]), + _order_of_magnitude(p[i])) def testLoadCustomFitFunction(self): """Test FitManager using a custom fit function defined in an external diff --git a/silx/sx/_plot.py b/silx/sx/_plot.py index 9ef52a1..74ebe84 100644 --- a/silx/sx/_plot.py +++ b/silx/sx/_plot.py @@ -548,7 +548,7 @@ class _GInputHandler(roi.InteractiveRegionOfInterestManager): """ if isinstance(roi, roi_items.PointROI): # Only handle points - roi.setLabel('%d' % len(self.__selections)) + roi.setName('%d' % len(self.__selections)) self.__updateSelection(roi) roi.sigRegionChanged.connect(self.__regionChanged) |