Skip to content

Commit

Permalink
Merge pull request #4174 from EdgarGF93/silx_view_imageaggregated
Browse files Browse the repository at this point in the history
Great new feature, thanks @EdgarGF93 !
  • Loading branch information
t20100 authored Nov 26, 2024
2 parents 5794746 + b4b2529 commit a9eddc5
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 100 deletions.
27 changes: 23 additions & 4 deletions src/silx/gui/data/DataViews.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
from silx.io.nxdata import get_attr_as_unicode
from silx.gui.colors import Colormap
from silx.gui.dialog.ColormapDialog import ColormapDialog
from silx.gui.plot.items.image import ImageDataAggregated
from silx.gui.plot.actions.image import AggregationModeAction

__authors__ = ["V. Valls", "P. Knobel"]
__license__ = "MIT"
Expand Down Expand Up @@ -1066,15 +1068,30 @@ def createWidget(self, parent):
widget.setDefaultColormap(self.defaultColormap())
widget.getColormapAction().setColormapDialog(self.defaultColorDialog())
widget.getIntensityHistogramAction().setVisible(True)

self.__aggregationModeAction = AggregationModeAction(parent=widget)
widget.toolBar().addAction(self.__aggregationModeAction)
self.__aggregationModeAction.sigAggregationModeChanged.connect(self._aggregationModeChanged)

self.__imageItem = ImageDataAggregated()
self.__imageItem.setAggregationMode(self.__aggregationModeAction.getAggregationMode())
self.__imageItem.setName("data")
self.__imageItem.setColormap(widget.getDefaultColormap())
widget.addItem(self.__imageItem)
widget.setActiveImage(self.__imageItem)

widget.setKeepDataAspectRatio(True)
widget.getXAxis().setLabel("X")
widget.getYAxis().setLabel("Y")
maskToolsWidget = widget.getMaskToolsDockWidget().widget()
maskToolsWidget.setItemMaskUpdated(True)
return widget

def _aggregationModeChanged(self):
self.__imageItem.setAggregationMode(self.__aggregationModeAction.getAggregationMode())

def clear(self):
self.getWidget().clear()
self.__imageItem.setData(numpy.zeros((0, 0), dtype=numpy.float32))
self.__resetZoomNextTime = True

def normalizeData(self, data):
Expand All @@ -1084,9 +1101,11 @@ def normalizeData(self, data):

def setData(self, data):
data = self.normalizeData(data)
self.getWidget().addImage(
legend="data", data=data, resetzoom=self.__resetZoomNextTime
)
plot = self.getWidget()

self.__imageItem.setData(data=data)
if self.__resetZoomNextTime:
plot.resetZoom()
self.__resetZoomNextTime = False

def setDataSelection(self, selection):
Expand Down
39 changes: 31 additions & 8 deletions src/silx/gui/data/NXdataWidgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
from silx.gui.data.NumpyAxesSelector import NumpyAxesSelector
from silx.gui.plot import Plot1D, Plot2D, StackView, ScatterView, items
from silx.gui.plot.ComplexImageView import ComplexImageView
from silx.gui.plot.items.image_aggregated import ImageDataAggregated
from silx.gui.plot.actions.image import AggregationModeAction
from silx.gui.colors import Colormap
from silx.gui.widgets.FrameBrowser import HorizontalSliderWithBrowser

Expand Down Expand Up @@ -422,7 +424,25 @@ def __init__(self, parent=None):
layout.addWidget(self._auxSigSlider)

self.setLayout(layout)

self.__aggregationModeAction = AggregationModeAction(parent=self)
self.getPlot().toolBar().addAction(self.__aggregationModeAction)
self.__aggregationModeAction.sigAggregationModeChanged.connect(self._aggregationModeChanged)

def getAggregationModeAction(self) -> AggregationModeAction:
"""Action toggling the aggregation mode action
"""
return self.__aggregationModeAction

def _aggregationModeChanged(self):
item = self.getPlot()._getItem("image")

if item is None:
return

if isinstance(item, ImageDataAggregated):
item.setAggregationMode(self.getAggregationModeAction().getAggregationMode())

def _sliderIdxChanged(self, value):
self._updateImage()

Expand Down Expand Up @@ -555,6 +575,7 @@ def _updateImage(self):
"image",
)
)

if xcalib.is_affine() and ycalib.is_affine():
# regular image
xorigin, xscale = xcalib(0), xcalib.get_slope()
Expand All @@ -564,14 +585,16 @@ def _updateImage(self):

self._plot.getXAxis().setScale("linear")
self._plot.getYAxis().setScale("linear")
self._plot.addImage(
image,
legend=legend,
origin=origin,
scale=scale,
replace=True,
resetzoom=False,
)

imageItem = ImageDataAggregated()
imageItem.setName(legend)
imageItem.setData(image)
imageItem.setOrigin(origin)
imageItem.setScale(scale)
imageItem.setColormap(self._plot.getDefaultColormap())
imageItem.setAggregationMode(self.getAggregationModeAction().getAggregationMode())
self._plot.addItem(imageItem)
self._plot.setActiveImage(imageItem)
else:
xaxisscale, yaxisscale = self._axis_scales

Expand Down
85 changes: 1 addition & 84 deletions src/silx/gui/plot/ImageView.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
from . import _utils
from .tools.profile import rois
from .actions import PlotAction
from .actions.image import AggregationModeAction
from silx._utils import NP_OPTIONAL_COPY

_logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -353,90 +354,6 @@ def _actionTriggered(self, checked=False):
self.plot.setSideHistogramDisplayed(checked)


class AggregationModeAction(qt.QWidgetAction):
"""Action providing few filters to the image"""

sigAggregationModeChanged = qt.Signal()

def __init__(self, parent):
qt.QWidgetAction.__init__(self, parent)

toolButton = qt.QToolButton(parent)

filterAction = qt.QAction(self)
filterAction.setText("No filter")
filterAction.setCheckable(True)
filterAction.setChecked(True)
filterAction.setProperty(
"aggregation", items.ImageDataAggregated.Aggregation.NONE
)
densityNoFilterAction = filterAction

filterAction = qt.QAction(self)
filterAction.setText("Max filter")
filterAction.setCheckable(True)
filterAction.setProperty(
"aggregation", items.ImageDataAggregated.Aggregation.MAX
)
densityMaxFilterAction = filterAction

filterAction = qt.QAction(self)
filterAction.setText("Mean filter")
filterAction.setCheckable(True)
filterAction.setProperty(
"aggregation", items.ImageDataAggregated.Aggregation.MEAN
)
densityMeanFilterAction = filterAction

filterAction = qt.QAction(self)
filterAction.setText("Min filter")
filterAction.setCheckable(True)
filterAction.setProperty(
"aggregation", items.ImageDataAggregated.Aggregation.MIN
)
densityMinFilterAction = filterAction

densityGroup = qt.QActionGroup(self)
densityGroup.setExclusive(True)
densityGroup.addAction(densityNoFilterAction)
densityGroup.addAction(densityMaxFilterAction)
densityGroup.addAction(densityMeanFilterAction)
densityGroup.addAction(densityMinFilterAction)
densityGroup.triggered.connect(self._aggregationModeChanged)
self.__densityGroup = densityGroup

filterMenu = qt.QMenu(toolButton)
filterMenu.addAction(densityNoFilterAction)
filterMenu.addAction(densityMaxFilterAction)
filterMenu.addAction(densityMeanFilterAction)
filterMenu.addAction(densityMinFilterAction)

toolButton.setPopupMode(qt.QToolButton.InstantPopup)
toolButton.setMenu(filterMenu)
toolButton.setText("Data filters")
toolButton.setToolTip("Enable/disable filter on the image")
icon = icons.getQIcon("aggregation-mode")
toolButton.setIcon(icon)
toolButton.setText("Pixel aggregation filter")

self.setDefaultWidget(toolButton)

def _aggregationModeChanged(self):
self.sigAggregationModeChanged.emit()

def setAggregationMode(self, mode):
"""Set an Aggregated enum from ImageDataAggregated"""
for a in self.__densityGroup.actions():
if a.property("aggregation") is mode:
a.setChecked(True)

def getAggregationMode(self):
"""Returns an Aggregated enum from ImageDataAggregated"""
densityAction = self.__densityGroup.checkedAction()
if densityAction is None:
return items.ImageDataAggregated.Aggregation.NONE
return densityAction.property("aggregation")


class ImageView(PlotWindow):
"""Display a single image with horizontal and vertical histograms.
Expand Down
14 changes: 14 additions & 0 deletions src/silx/gui/plot/StackView.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@

from silx._utils import NP_OPTIONAL_COPY
from silx.gui.plot.actions import io as silx_io
from silx.gui.plot.items.image_aggregated import ImageDataAggregated
from silx.gui.plot.actions.image import AggregationModeAction
from silx.io.nxdata import save_NXdata
from silx.utils.array_like import DatasetView, ListOfImages
from silx.math import calibration
Expand Down Expand Up @@ -289,6 +291,18 @@ def __init__(
self.__planeSelection.sigPlaneSelectionChanged.connect(
self._profileToolBar.clearProfile
)

self.__aggregationModeAction = AggregationModeAction(parent=self)
self._plot.toolBar().addAction(self.__aggregationModeAction)
self.__aggregationModeAction.sigAggregationModeChanged.connect(self._aggregationModeChanged)

def getAggregationModeAction(self) -> AggregationModeAction:
"""Action toggling the aggregation mode action
"""
return self.__aggregationModeAction

def _aggregationModeChanged(self):
self._stackItem.setAggregationMode(self.getAggregationModeAction().getAggregationMode())

def _saveImageStack(self, plot, filename, nameFilter):
"""Save all images from the stack into a volume.
Expand Down
1 change: 1 addition & 0 deletions src/silx/gui/plot/actions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@
from . import control
from . import mode
from . import io
from . import image
127 changes: 127 additions & 0 deletions src/silx/gui/plot/actions/image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# /*##########################################################################
#
# Copyright (c) 2024 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.
#
# ###########################################################################*/
"""
:mod:`silx.gui.plot.actions.image` provides a set of QAction relative to data processing
and outputs for a :class:`.PlotWidget`.
The following QAction are available:
- :class:`AggregationModeAction`
"""

__authors__ = ["T. Vincent"]
__license__ = "MIT"
__date__ = "19/09/2024"

import logging
from silx.gui import qt
from silx.gui import icons
from ..items.image_aggregated import ImageDataAggregated

_logger = logging.getLogger(__name__)

class AggregationModeAction(qt.QWidgetAction):
"""Action providing filters for an aggregated image"""

sigAggregationModeChanged = qt.Signal()
"""Signal emitted when the aggregation mode has changed"""

def __init__(self, parent):
qt.QWidgetAction.__init__(self, parent)

toolButton = qt.QToolButton(parent)

filterAction = qt.QAction(self)
filterAction.setText("No filter")
filterAction.setCheckable(True)
filterAction.setChecked(True)
filterAction.setProperty(
"aggregation", ImageDataAggregated.Aggregation.NONE
)
densityNoFilterAction = filterAction

filterAction = qt.QAction(self)
filterAction.setText("Max filter")
filterAction.setCheckable(True)
filterAction.setProperty(
"aggregation", ImageDataAggregated.Aggregation.MAX
)
densityMaxFilterAction = filterAction

filterAction = qt.QAction(self)
filterAction.setText("Mean filter")
filterAction.setCheckable(True)
filterAction.setProperty(
"aggregation", ImageDataAggregated.Aggregation.MEAN
)
densityMeanFilterAction = filterAction

filterAction = qt.QAction(self)
filterAction.setText("Min filter")
filterAction.setCheckable(True)
filterAction.setProperty(
"aggregation", ImageDataAggregated.Aggregation.MIN
)
densityMinFilterAction = filterAction

densityGroup = qt.QActionGroup(self)
densityGroup.setExclusive(True)
densityGroup.addAction(densityNoFilterAction)
densityGroup.addAction(densityMaxFilterAction)
densityGroup.addAction(densityMeanFilterAction)
densityGroup.addAction(densityMinFilterAction)
densityGroup.triggered.connect(self._aggregationModeChanged)
self.__densityGroup = densityGroup

filterMenu = qt.QMenu(toolButton)
filterMenu.addAction(densityNoFilterAction)
filterMenu.addAction(densityMaxFilterAction)
filterMenu.addAction(densityMeanFilterAction)
filterMenu.addAction(densityMinFilterAction)

toolButton.setPopupMode(qt.QToolButton.InstantPopup)
toolButton.setMenu(filterMenu)
toolButton.setText("Data filters")
toolButton.setToolTip("Enable/disable filter on the image")
icon = icons.getQIcon("aggregation-mode")
toolButton.setIcon(icon)
toolButton.setText("Pixel aggregation filter")

self.setDefaultWidget(toolButton)

def _aggregationModeChanged(self):
self.sigAggregationModeChanged.emit()

def setAggregationMode(self, mode):
"""Set an Aggregated enum from ImageDataAggregated"""
for a in self.__densityGroup.actions():
if a.property("aggregation") is mode:
a.setChecked(True)

def getAggregationMode(self):
"""Returns an Aggregated enum from ImageDataAggregated"""
densityAction = self.__densityGroup.checkedAction()
if densityAction is None:
return ImageDataAggregated.Aggregation.NONE
return densityAction.property("aggregation")
Loading

0 comments on commit a9eddc5

Please sign in to comment.