diff options
author | Alexandre Marie <alexandre.marie@synchrotron-soleil.fr> | 2020-01-30 11:47:01 +0100 |
---|---|---|
committer | Alexandre Marie <alexandre.marie@synchrotron-soleil.fr> | 2020-01-30 11:47:01 +0100 |
commit | 867a81e9d35da6d95a11deaf0deb679e58abed66 (patch) | |
tree | 5ad6620b7e0f75c12144508e4252825f0b3a7fc3 | |
parent | 4b9a78d3afad181b195ccfe5bf7f6b7c8fb6aa50 (diff) | |
parent | 33ed2a64c92b0311ae35456c016eb284e426afc2 (diff) |
Update upstream source from tag 'upstream/0.12.0+dfsg'
Update to upstream version '0.12.0+dfsg'
with Debian dir 3ea2a979401866ff2d55ac0090bf496c85038fcb
-rw-r--r-- | CHANGELOG.rst | 16 | ||||
-rw-r--r-- | PKG-INFO | 2 | ||||
-rw-r--r-- | doc/source/modules/gui/icons.rst | 18 | ||||
-rw-r--r-- | examples/dropZones.py | 4 | ||||
-rw-r--r-- | examples/fileDialog.py | 4 | ||||
-rw-r--r-- | examples/plotStats.py | 3 | ||||
-rw-r--r-- | silx.egg-info/PKG-INFO | 2 | ||||
-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 | ||||
-rw-r--r-- | version.py | 4 |
22 files changed, 192 insertions, 91 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4fe299b..0777568 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,8 +1,8 @@ Change Log ========== -0.12.0rc0: 2019/12/16 ---------------------- +0.12.0: 2020/01/09 +------------------ Python 2.7 is no longer officially supported (even if tests pass and most of the library should work). @@ -11,6 +11,7 @@ Python 2.7 is no longer officially supported (even if tests pass and most of the * Added: keep the same axes selection when changing dataset except for the stack view (PR #2701, #2780) * Added a Description column in the browsing tree to display NeXus title or name (PR #2804) * Added support of URL as filename (PR #2750) + * Behavior changed: no longer lock HDF5 files by default, can be changed with `--hdf5-file-locking` option (PR #2861) * `silx.gui`: @@ -21,8 +22,8 @@ Python 2.7 is no longer officially supported (even if tests pass and most of the * Added right axis support to `PlotWidget` marker items (PR #2744) * Added `BoundingRect` `PlotWidget` item (PR #2823) * Added more markers to `PlotWidget` items using symbols (PR #2792) - * Improved and fixed `PlotWidget` and backends rendering and picking to guarantee rendering order of items (PR #2602, #2694, #2726, #2728, #2730, #2731, #2732, #2734, #2746, #2800, #2822, #2829) - * Improved `RegionOfInterest`: Added `sigItemChanged` signal, renamed `get|setLabel` to `get|setName` (PR #2684, #2729, #2794, #2803) + * Improved and fixed `PlotWidget` and backends rendering and picking to guarantee rendering order of items (PR #2602, #2694, #2726, #2728, #2730, #2731, #2732, #2734, #2746, #2800, #2822, #2829, #2851, #2853) + * Improved `RegionOfInterest`: Added `sigItemChanged` signal, renamed `get|setLabel` to `get|setName` (PR #2684, #2729, #2794, #2803, #2860) * Improved `StackView`: Allow to save dataset to HDF5 (PR #2813) * `silx.gui.plot3d`: @@ -37,7 +38,7 @@ Python 2.7 is no longer officially supported (even if tests pass and most of the * Added `silx.gui.utils.blockSignals` context manager (PR #2697, #2702) * Added `silx.gui.utils.qtutils.getQEventName` function (PR #2725) * Added `silx.gui.colors.asQColor` function (PR #2753) - * Minor fixes (PR #2662, #2667, #2674, #2719, #2724, #2747, #2757, #2760, #2766, #2789, #2798, #2799, #2805, #2811, #2832, #2834) + * Minor fixes (PR #2662, #2667, #2674, #2719, #2724, #2747, #2757, #2760, #2766, #2789, #2798, #2799, #2805, #2811, #2832, #2834, #2839, #2849, #2852, #2857, #2864, #2867) * `silx.opencl`: @@ -54,6 +55,7 @@ Python 2.7 is no longer officially supported (even if tests pass and most of the * Added `silx.image.utils.gaussian_kernel` function (PR #2782) * Improved `silx.image.shapes.Polygon` argument check (PR #2761) * Fixed and improved `silx.math.fft` with FFTW backend (PR #2751) + * Fixed support of not finite data in fit manager (PR #2868) * `silx.io`: @@ -66,7 +68,9 @@ Python 2.7 is no longer officially supported (even if tests pass and most of the * Added `Cython` as a build dependency (PR #2795, #2807, #2808) * Added Debian 10 packaging (PR #2670, #2672, #2666, #2686, #2706) - * Improvements: documentation (PR #2673, #2680, #2679, #2772, #2759, #2779, #2801, #2802, #2833), testing tools (PR #2704, #2796, #2818), `bootstrap.py` script (PR #2727, #2733) + * Improved documentation (PR #2673, #2680, #2679, #2772, #2759, #2779, #2801, #2802, #2833, #2857, #2869) + * Improved testing tools (PR #2704, #2796, #2818) + * Improved `bootstrap.py` script (PR #2727, #2733) 0.11.0: 2019/07/03 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: silx -Version: 0.12.0b0 +Version: 0.12.0 Summary: Software library for X-ray data analysis Home-page: http://www.silx.org/ Author: data analysis unit diff --git a/doc/source/modules/gui/icons.rst b/doc/source/modules/gui/icons.rst index dbd58b4..ba7d89b 100644 --- a/doc/source/modules/gui/icons.rst +++ b/doc/source/modules/gui/icons.rst @@ -109,6 +109,18 @@ Available icons - cube-top * - |cube| - cube + * - |description-description| + - description-description + * - |description-error| + - description-error + * - |description-name| + - description-name + * - |description-program| + - description-program + * - |description-title| + - description-title + * - |description-value| + - description-value * - |document-open| - document-open * - |document-print| @@ -393,6 +405,12 @@ Available icons .. |cube-rotate| image:: ../../../../silx/resources/gui/icons/cube-rotate.png .. |cube-top| image:: ../../../../silx/resources/gui/icons/cube-top.png .. |cube| image:: ../../../../silx/resources/gui/icons/cube.png +.. |description-description| image:: ../../../../silx/resources/gui/icons/description-description.png +.. |description-error| image:: ../../../../silx/resources/gui/icons/description-error.png +.. |description-name| image:: ../../../../silx/resources/gui/icons/description-name.png +.. |description-program| image:: ../../../../silx/resources/gui/icons/description-program.png +.. |description-title| image:: ../../../../silx/resources/gui/icons/description-title.png +.. |description-value| image:: ../../../../silx/resources/gui/icons/description-value.png .. |document-open| image:: ../../../../silx/resources/gui/icons/document-open.png .. |document-print| image:: ../../../../silx/resources/gui/icons/document-print.png .. |document-save| image:: ../../../../silx/resources/gui/icons/document-save.png diff --git a/examples/dropZones.py b/examples/dropZones.py index d0d16b5..27d9df8 100644 --- a/examples/dropZones.py +++ b/examples/dropZones.py @@ -2,7 +2,7 @@ # 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 @@ -39,7 +39,7 @@ from silx.gui import qt from silx.gui.plot.PlotWidget import PlotWidget _logger = logging.getLogger(__name__) -logging.basicConfig(level=logging.DEBUG) +logging.basicConfig() class DropPlotWidget(PlotWidget): diff --git a/examples/fileDialog.py b/examples/fileDialog.py index 82e6798..40191bb 100644 --- a/examples/fileDialog.py +++ b/examples/fileDialog.py @@ -2,7 +2,7 @@ # 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 @@ -41,7 +41,7 @@ from silx.gui.dialog.DataFileDialog import DataFileDialog import silx.io -logging.basicConfig(level=logging.DEBUG) +logging.basicConfig() class Mode(enum.Enum): diff --git a/examples/plotStats.py b/examples/plotStats.py index 3929697..5f6e768 100644 --- a/examples/plotStats.py +++ b/examples/plotStats.py @@ -131,7 +131,7 @@ def main(argv): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( '--update-mode', - default='manual', + default='auto', help='update mode to display (manual or auto)') options = parser.parse_args(argv[1:]) @@ -167,4 +167,5 @@ def main(argv): if __name__ == '__main__': + import sys main(sys.argv) diff --git a/silx.egg-info/PKG-INFO b/silx.egg-info/PKG-INFO index 9c1e298..1c225b2 100644 --- a/silx.egg-info/PKG-INFO +++ b/silx.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: silx -Version: 0.12.0b0 +Version: 0.12.0 Summary: Software library for X-ray data analysis Home-page: http://www.silx.org/ Author: data analysis unit 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) @@ -2,7 +2,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2015-2019 European Synchrotron Radiation Facility +# Copyright (c) 2015-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 @@ -69,7 +69,7 @@ RELEASE_LEVEL_VALUE = {"dev": 0, MAJOR = 0 MINOR = 12 MICRO = 0 -RELEV = "rc" # <16 +RELEV = "final" # <16 SERIAL = 0 # <16 date = __date__ |