From 0ee9e94bcdb1b81a91cc20d7a0a6ca61a4205749 Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauld.mccarthy@gmail.com> Date: Mon, 29 Jun 2015 11:19:54 +0100 Subject: [PATCH] Functional histogram list, allows add/remove histogram plots. Histograms are not recalculated if they've already been calculated. --- fsl/data/strings.py | 7 +- fsl/fslview/controls/__init__.py | 1 + fsl/fslview/controls/histogramlistpanel.py | 88 ++++++++++++++++++++++ fsl/fslview/views/histogrampanel.py | 73 ++++++++---------- 4 files changed, 123 insertions(+), 46 deletions(-) create mode 100644 fsl/fslview/controls/histogramlistpanel.py diff --git a/fsl/data/strings.py b/fsl/data/strings.py index 6d4840e5f..540eb5ff4 100644 --- a/fsl/data/strings.py +++ b/fsl/data/strings.py @@ -112,12 +112,12 @@ titles = TypeDict({ 'OrthoSettingsPanel' : 'Ortho view settings', 'LightBoxToolBar' : 'Lightbox view toolbar', 'LightBoxSettingsPanel' : 'Lightbox view settings', - 'HistogramToolBar' : 'Histogram settings', 'LookupTablePanel' : 'Lookup tables', 'LutLabelDialog' : 'New LUT label', 'NewLutDialog' : 'New LUT', 'TimeSeriesListPanel' : 'Time series list', 'TimeSeriesControlPanel' : 'Time series control', + 'HistogramListPanel' : 'Histogram list', 'LookupTablePanel.loadLut' : 'Select a lookup table file', 'LookupTablePanel.labelExists' : 'Label already exists', @@ -149,13 +149,10 @@ actions = TypeDict({ 'LightBoxPanel.toggleLightBoxToolBar' : 'View properties', - 'PlotPanel.screenshot' : 'Take screenshot', 'TimeSeriesPanel.toggleTimeSeriesList' : 'Time series list', 'TimeSeriesPanel.toggleTimeSeriesControl' : 'Time series control', - 'HistogramPanel.toggleToolbar' : 'Histogram controls', - - + 'HistogramPanel.toggleHistogramList' : 'Histogram list', 'OrthoViewProfile.centreCursor' : 'Centre cursor', 'OrthoViewProfile.resetZoom' : 'Reset zoom', diff --git a/fsl/fslview/controls/__init__.py b/fsl/fslview/controls/__init__.py index bf45f4d1a..328ec2b27 100644 --- a/fsl/fslview/controls/__init__.py +++ b/fsl/fslview/controls/__init__.py @@ -15,6 +15,7 @@ from orthosettingspanel import OrthoSettingsPanel from lookuptablepanel import LookupTablePanel from timeserieslistpanel import TimeSeriesListPanel from timeseriescontrolpanel import TimeSeriesControlPanel +from histogramlistpanel import HistogramListPanel from orthotoolbar import OrthoToolBar from orthoprofiletoolbar import OrthoProfileToolBar diff --git a/fsl/fslview/controls/histogramlistpanel.py b/fsl/fslview/controls/histogramlistpanel.py new file mode 100644 index 000000000..33c0ef887 --- /dev/null +++ b/fsl/fslview/controls/histogramlistpanel.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +# +# histogramlistpanel.py - +# +# Author: Paul McCarthy <pauldmccarthy@gmail.com> +# + + +import wx + +import pwidgets.elistbox as elistbox +import fsl.fslview.panel as fslpanel +import fsl.fslview.colourmaps as fslcm + +import timeserieslistpanel + + +class HistogramListPanel(fslpanel.FSLViewPanel): + + def __init__(self, parent, overlayList, displayCtx, histPanel): + + fslpanel.FSLViewPanel.__init__(self, parent, overlayList, displayCtx) + + self.__hsPanel = histPanel + self.__hsList = elistbox.EditableListBox( + self, style=(elistbox.ELB_NO_MOVE | + elistbox.ELB_EDITABLE)) + + self.__sizer = wx.BoxSizer(wx.VERTICAL) + self.SetSizer(self.__sizer) + + self.__sizer.Add(self.__hsList, flag=wx.EXPAND, proportion=1) + + self.__hsList.Bind(elistbox.EVT_ELB_ADD_EVENT, self.__onListAdd) + self.__hsList.Bind(elistbox.EVT_ELB_REMOVE_EVENT, self.__onListRemove) + self.__hsList.Bind(elistbox.EVT_ELB_EDIT_EVENT, self.__onListEdit) + self.__hsList.Bind(elistbox.EVT_ELB_SELECT_EVENT, self.__onListSelect) + + self.__hsPanel.addListener('dataSeries', + self._name, + self.__histSeriesChanged) + + self.__histSeriesChanged() + self.Layout() + + + def destroy(self): + fslpanel.FSLViewPanel.destroy(self) + self.__hsPanel.removeListener('dataSeries', self._name) + + + def __histSeriesChanged(self, *a): + + self.__hsList.Clear() + + for hs in self.__hsPanel.dataSeries: + widg = timeserieslistpanel.TimeSeriesWidget(self, hs) + self.__hsList.Append(hs.overlay.name, + clientData=hs, + extraWidget=widg) + + + def __onListAdd(self, ev): + + hs = self.__hsPanel.getCurrent() + + if hs is None: + return + + hs.alpha = 1 + hs.lineWidth = 2 + hs.lineStyle = '-' + hs.colour = fslcm.randomColour() + hs.label = hs.overlay.name + self.__hsPanel.dataSeries.append(hs) + + + def __onListEdit(self, ev): + ev.data.label = ev.label + + + def __onListSelect(self, ev): + overlay = ev.data.overlay + self._displayCtx.selectedOverlay = self._overlayList.index(overlay) + + + def __onListRemove(self, ev): + self.__hsPanel.dataSeries.remove(ev.data) diff --git a/fsl/fslview/views/histogrampanel.py b/fsl/fslview/views/histogrampanel.py index 46301d75d..44323757e 100644 --- a/fsl/fslview/views/histogrampanel.py +++ b/fsl/fslview/views/histogrampanel.py @@ -15,7 +15,7 @@ import props import fsl.data.image as fslimage import fsl.data.strings as strings -import fsl.fslview.colourmaps as fslcm +import fsl.fslview.controls as fslcontrols import plotpanel @@ -50,26 +50,6 @@ def autoBin(data, dataRange): return nbins -# -# Ideas: -# -# - Plot histogram for multiple images (how to select them?) -# -# - Ability to apply a label mask image, and plot separate -# histograms for each label -# -# - Ability to put an overlay on the display, showing the -# voxels that are within the histogram range -# -# - For 4D images, add an option to plot the histogram for -# the current volume only, or for all volumes -# -# - For different image types (e.g. vector), add anoption -# to plot the histogram of calculated values, e.g. -# magnitude, or separate histogram lines for xyz -# components? -# - class HistogramSeries(plotpanel.DataSeries): nbins = props.Int(minval=10, maxval=500, default=100, clamped=True) @@ -92,13 +72,13 @@ class HistogramSeries(plotpanel.DataSeries): if overlay.is4DImage(): self.setConstraint('volume', 'maxval', overlay.shape[3] - 1) + self.__calcInitDataRange() + self.histPropsChanged() + hsPanel.addListener('autoBin', self.name, self.histPropsChanged) self .addListener('nbins', self.name, self.histPropsChanged) self .addListener('dataRange', self.name, self.histPropsChanged) - self.__calcInitDataRange() - self.histPropsChanged() - def __del__(self): self.hsPanel.removeListener('autoBin', self.name) @@ -133,7 +113,13 @@ class HistogramSeries(plotpanel.DataSeries): if self.ignoreZeros: data = data[data != 0] + nvals = data.size dataRange = self.dataRange.x + + log.debug('Calculating histogram for overlay ' + '{} (number of values {})'.format( + self.overlay.name, + nvals)) if self.hsPanel.autoBin: nbins = autoBin(data, dataRange) else: nbins = self.nbins @@ -153,10 +139,18 @@ class HistogramSeries(plotpanel.DataSeries): self.xdata = np.array(histX, dtype=np.float32) self.ydata = np.array(histY, dtype=np.float32) + self.nvals = nvals def getData(self): - return self.xdata, self.ydata + + xdata = self.xdata + ydata = self.ydata + nvals = self.nvals + histType = self.hsPanel.histType + + if histType == 'count': return xdata, ydata + elif histType == 'probability': return xdata, ydata / nvals class HistogramPanel(plotpanel.PlotPanel): @@ -164,13 +158,15 @@ class HistogramPanel(plotpanel.PlotPanel): autoBin = props.Boolean(default=True) showCurrent = props.Boolean(default=True) - enableOverlay = props.Boolean(default=False) + histType = props.Choice(('probability', 'count')) def __init__(self, parent, overlayList, displayCtx): - actionz = {} + actionz = {'toggleHistogramList' : lambda *a: self.togglePanel( + fslcontrols.HistogramListPanel, False, self) + } plotpanel.PlotPanel.__init__( self, parent, overlayList, displayCtx, actionz) @@ -185,13 +181,14 @@ class HistogramPanel(plotpanel.PlotPanel): self._overlayList.addListener( 'overlays', self._name, - self.__selectedOverlayChanged) + self.__updateCurrent) self._displayCtx.addListener( 'selectedOverlay', self._name, - self.__selectedOverlayChanged) + self.__updateCurrent) + self.addGlobalListener(self._name, self.__updateCurrent) - self.__selectedOverlayChanged() + self.__updateCurrent() self.Layout() @@ -204,7 +201,8 @@ class HistogramPanel(plotpanel.PlotPanel): self._displayCtx .removeListener('selectedOverlay', self._name) - def __calcCurrent(self): + def __updateCurrent(self, *a): + self.__current = None if self._overlayList == 0: @@ -215,6 +213,9 @@ class HistogramPanel(plotpanel.PlotPanel): if not isinstance(overlay, fslimage.Image): return + if overlay in [hs.overlay for hs in self.dataSeries]: + return + hs = HistogramSeries(self, overlay) hs.colour = [0.2, 0.2, 0.2] hs.alpha = 1 @@ -228,19 +229,9 @@ class HistogramPanel(plotpanel.PlotPanel): def getCurrent(self): return self.__current - - def __selectedOverlayChanged(self, *a): - - if len(self._overlayList) == 0: - return - - - self.draw() - def draw(self, *a): - self.__calcCurrent() current = self.getCurrent() if current is not None: self.drawDataSeries([current]) -- GitLab