Commit f40460ec authored by eric pellegrini's avatar eric pellegrini
Browse files

added docstrings

parent a69ad069
"""
MatPlotLib based viewer for 1D, 2D and 3D NumPy data.
MatPlotLib viewer for NumPy 1D, 2D and 3D NumPy data.
"""
import numpy as np
......@@ -14,10 +14,55 @@ from hdfviewer.viewers.MplDataViewer3D import _MplDataViewer3D
_viewers = {1 : _MplDataViewer1D, 2 : _MplDataViewer2D, 3 : _MplDataViewer3D}
class MplDataViewerError(Exception):
""":class:`MplDataViewer` specific exception"""
pass
class MplDataViewer(object):
"""This class allows to display 1D,2D or 3D NumPy array in a :class:`matplotlib.figure.Figure`
Depending on the dimensionality of the dataset the display will consist in:
- **1D**: simple MatPlotLib 1D plot
- **2D**: matrix view of the dataset
- **3D**: matrix view of the dataset
In case of **2D** and **3D** datasets, the matrix view is made of a 2D image of the selected frame of the dataset (always `0` for 2D datasets) with a 1D column-projection view of the dataset on its top and a 1D row-projection view of the dataset on its right. The matrix view is interactive with the following interactions:
- **2D**:
- toggle between cross and integration 1D potting mode. In cross plot mode, the 1D projection views represents resp. the row and column of the matrix image point left-clicked by the user. In integration plot mode, the 1D projection views represents the sum over resp. the row and column of the image. To switch between those two modes, press the **i** key.
- **3D**:
- toggle between cross and integration 1D potting mode. See above.
- go to the last frame by pressing the **pgdn** key.
- go to the first frame by pressing the **pgup** key.
- go to the next frame by pressing the **down** or the **right** keys or wheeling **down** the mouse wheel
- go to the previous frame by pressing the **up** or the **left** keys or wheeling **up** the mouse down
- go the +n (n can be > 9) frame by pressing *n* number followed by the **down** or the **right** keys
- go the -n (n can be > 9) frame by pressing *n* number followed by the **up** or the **left** keys
.. code-block:: python
:caption: Example
import numpy as np
import matplotlib.pyplot as plt
dataset = np.random.uniform(0,1,(100,400,30))
d = MplDataViewer(dataset)
:param dataset: the NumPy array to be displayed
The dataset will be squeezed from any dimensions equal to 1
:type dataset: :class:`numpy.ndarray`
:param standAlone: if True a cursor will be displayed when hovering over the 2D view of the dataset (only for 2D or 3D datasets)
:type standAlone: bool
:raises: :class:`MplDataViewerError`: if the (squeezed) dataset has a dimension different from 1, 2 or 3
"""
def __init__(self,dataset,standAlone=True):
# Remove axis with that has dimension 1 (e.g. (20,1,30) --> (20,30))
......@@ -31,6 +76,11 @@ class MplDataViewer(object):
@property
def viewer(self):
"""Getter for the dimension specific viewer.
:return: the dimension specific viewer
:rtype: :class:`_MplDataViewer1D` or :class:`_MplDataViewer2D` or :class:`_MplDataViewer3D`
"""
return self._viewer
if __name__ == "__main__":
......@@ -38,7 +88,7 @@ if __name__ == "__main__":
import numpy as np
import matplotlib.pyplot as plt
dataset = np.random.uniform(0,1,(100,400,30))
dataset = np.random.uniform(0,1,(100,400))
d = MplDataViewer(dataset)
......
"""
MatPlotLib based viewer for 1D NumPy data.
MatPlotLib viewer for NumPy 1D NumPy data.
"""
import numpy as np
......@@ -8,6 +7,18 @@ import numpy as np
import matplotlib.pyplot as plt
class _MplDataViewer1D(object):
"""This class allows to display 1D NumPy array in a :class:`matplotlib.figure.Figure`
This will be a simple matplotlib plot.
:param dataset: the NumPy array to be displayed
The dataset will be squeezed from any dimensions equal to 1
:type dataset: :class:`numpy.ndarray`
:param `kwargs`: the keyword arguments
:type `kwargs`: dict
"""
def __init__(self,dataset,**kwargs):
......@@ -21,28 +32,41 @@ class _MplDataViewer1D(object):
@property
def figure(self):
"""Getter for the figure to be displayed.
:return: returns the figure to be displayed in the jupyter output widget
:rtype: :class:`matplotlib.figure.Figure`
"""
return self._figure
@property
def dataset(self):
"""Getter/setter for the dataset to be displayed.
:getter: returns the dataset to be displayed
:setter: sets the dataset to be displayed
:type: :class:`numpy.ndarray`
"""
return self._dataset
@dataset.setter
def dataset(self,dataset):
"""1D dataset setter
"""
self._dataset = dataset
self.update()
def _initLayout(self):
"""Initializes layout.
"""Initializes the figure layout.
"""
self._mainAxes = plt.subplot(111)
def update(self):
"""Update the figure.
"""
self._mainAxes.plot(self._dataset[:])
......
"""
MatPlotLib based viewer for 2D NumPy data.
"""MatPlotLib based viewer for 2D NumPy data.
"""
import numpy as np
......@@ -10,6 +8,26 @@ import matplotlib.pyplot as plt
import matplotlib.widgets as widgets
class _MplDataViewer2D(object):
"""This class allows to display 2D NumPy array in a :class:`matplotlib.figure.Figure`
The figure is made of an central image surrounded on top by the column-view of the dataset and on the right by a row-view of the dataset
which depending on the plotting mode corresponds to:
- slices along the row and column of a selected pixel using left-click mouse button when the cross-plot mode is set
- integrations of the 2D image along the X and Y axis when the integration mode is set
The figure is interactive with the following interactions:
- toggle between cross and integration 1D potting mode. In cross plot mode, the 1D projection views represents resp. the row and column of the matrix image point left-clicked by the user. In integration plot mode, the 1D projection views represents the sum over resp. the row and column of the image. To switch between those two modes, press the **i** key.
:param dataset: the NumPy array to be displayed
The dataset will be squeezed from any dimensions equal to 1
:type dataset: :class:`numpy.ndarray`
:param standAlone: if True a cursor will be displayed when hovering over the 2D view of the dataset
:type standAlone: bool
"""
def __init__(self,dataset,standAlone=True):
......@@ -31,16 +49,27 @@ class _MplDataViewer2D(object):
@property
def figure(self):
"""Getter for the figure to be displayed.
:return: returns the figure to be displayed in the jupyter output widget
:rtype: :class:`matplotlib.figure.Figure`
"""
return self._figure
@property
def dataset(self):
"""Getter/setter for the dataset to be displayed.
:getter: returns the dataset to be displayed
:setter: sets the dataset to be displayed
:type: :class:`numpy.ndarray`
"""
return self._dataset
@dataset.setter
def dataset(self,dataset):
"""2D dataset setter
"""
self._dataset = dataset
......@@ -53,7 +82,12 @@ class _MplDataViewer2D(object):
self._colSlice = slice(0,self._dataset.shape[1],None)
def _onChangeAxesLimits(self,event):
"""Update the cross plot according to the reduced row and/or column range
"""Callback called when the axis of the matrix view have changed.
This will update the cross plot according to the reduced row and/or column range.
:param event: the axes whose axis have been changed
:type event: :class:`matplotlib.axes.SubplotBase`
"""
self._rowSlice = slice(*sorted([int(v) for v in event.get_ylim()]))
......@@ -68,7 +102,7 @@ class _MplDataViewer2D(object):
self._updateCrossPlot()
def _initActions(self):
"""Init all the widgets actions.
"""Setup all the actions and their corresponding callbacks.
"""
self._buttonPressId = self._figure.canvas.mpl_connect('button_press_event', self._onSelectPixel)
......@@ -77,11 +111,7 @@ class _MplDataViewer2D(object):
self._mainAxes.callbacks.connect('ylim_changed', self._onChangeAxesLimits)
def _initLayout(self):
"""Initializes layout.
The figure is made of an central image surrounded on top by the column-view of the dataset and on the right by a row-view of the dataset
which depending on the plotting mode corresponds to:
- slices along the row and column of a selected pixel using left-click mouse button when the cross-plot mode is set
- integrations of the 2D image along the X and Y axis when the integration mode is set
"""Initializes the figure layout.
"""
grid = gridspec.GridSpec(3, 3, self._figure, width_ratios=[0.3, 4, 1], height_ratios=[1, 4, 0.3], wspace=0.3)
......@@ -104,7 +134,10 @@ class _MplDataViewer2D(object):
self._selectedPixel = (0,0)
def _onKeyPress(self,event):
"""Add keyboard interaction for navigating through the dataset
"""Callback called when a keyboard key is pressed.
:param event: the keyboard keypress event
:type event: :class:`matplotlib.backend_bases.KeyEvent`
"""
if event.key == "i":
......@@ -113,7 +146,10 @@ class _MplDataViewer2D(object):
self._updateCrossPlot()
def _onSelectPixel(self,event):
"""Update the cross plot according to the selected pixel of the 2D/3D image.
"""Callback called when a mouse buttton is clicked.
:param event: the mouse click event
:type event: :class:`matplotlib.backend_bases.MouseEvent`
"""
# Only left button click will produce a cross plot
......@@ -126,8 +162,7 @@ class _MplDataViewer2D(object):
self.selectPixel(int(event.ydata),int(event.xdata))
def _updateCrossPlot(self):
"""Update the cross plots for 2D/3D dataset.
Cross plots are 1D plots which correspond to the reduced view of 2D/3D datasets projected onto X and Y axis
"""Update the cross plots.
"""
if self._xyIntegration:
......@@ -155,7 +190,14 @@ class _MplDataViewer2D(object):
plt.draw()
def selectPixel(self,row,col):
"""Select a pixel on the image and update the cross plots
"""Select a pixel on the image.
This will update the croos plots.
:param row: the pixel row
:type column: int
:param row: the pixel column
:type column: int
"""
self._selectedPixel = (row,col)
......@@ -163,9 +205,10 @@ class _MplDataViewer2D(object):
self._updateCrossPlot()
def setXYIntegrationMode(self,xyIntegration):
"""Toggle the integration mode.
If True, the top and right 1D plots will be resp. the sum/integral over y and x axis
If False, the top and right 1D plots will be resp. the cross plots along corresonding to resp. y and x axis of the selected pixel
"""Switch between slice plot mode and integration mode.
:param xyIntegration: if True, the croos plots will be resp. the integral over y and x axis otherwise the cross plots will be resp. slices along the selected pixel
:type xyIntegration: bool
"""
self._xyIntegration = xyIntegration
......@@ -177,6 +220,8 @@ class _MplDataViewer2D(object):
self._updateCrossPlot()
def update(self):
"""Update the figure.
"""
# Remove the current image if any
if self._image:
......
#!/usr/bin/env python
"""
MatPlotLib based viewer for 3D NumPy data.
"""MatPlotLib based viewer for 2D NumPy data.
"""
import numpy as np
......@@ -11,6 +8,33 @@ import matplotlib.pyplot as plt
import matplotlib.widgets as widgets
class _MplDataViewer3D(object):
"""This class allows to display 3D NumPy array in a :class:`matplotlib.figure.Figure`
The figure is made of an central image surrounded on top by the column-view of the dataset and on the right by a row-view of the dataset
which depending on the plotting mode corresponds to:
- slices along the row and column of a selected pixel using left-click mouse button when the cross-plot mode is set
- integrations of the 2D image along the X and Y axis when the integration mode is set
The figure is interactive with the following interactions:
- toggle between cross and integration 1D potting mode. In cross plot mode, the 1D projection views represents resp. the row and column of the matrix image point left-clicked by the user. In integration plot mode, the 1D projection views represents the sum over resp. the row and column of the image. To switch between those two modes, press the **i** key.
- toggle between cross and integration 1D potting mode. See above.
- go to the last frame by pressing the **pgdn** key.
- go to the first frame by pressing the **pgup** key.
- go to the next frame by pressing the **down** or the **right** keys or wheeling **down** the mouse wheel
- go to the previous frame by pressing the **up** or the **left** keys or wheeling **up** the mouse down
- go the +n (n can be > 9) frame by pressing *n* number followed by the **down** or the **right** keys
- go the -n (n can be > 9) frame by pressing *n* number followed by the **up** or the **left** keys
:param dataset: the NumPy array to be displayed
The dataset will be squeezed from any dimensions equal to 1
:type dataset: :class:`numpy.ndarray`
:param standAlone: if True a cursor will be displayed when hovering over the 2D view of the dataset
:type standAlone: bool
"""
def __init__(self,dataset,standAlone=True):
......@@ -34,10 +58,23 @@ class _MplDataViewer3D(object):
@property
def figure(self):
"""Getter for the figure to be displayed.
:return: returns the figure to be displayed in the jupyter output widget
:rtype: :class:`matplotlib.figure.Figure`
"""
return self._figure
@property
def dataset(self):
"""Getter/setter for the dataset to be displayed.
:getter: returns the dataset to be displayed
:setter: sets the dataset to be displayed
:type: :class:`numpy.ndarray`
"""
return self._dataset
@dataset.setter
......@@ -57,7 +94,12 @@ class _MplDataViewer3D(object):
self._colSlice = slice(0,self._dataset.shape[1],None)
def _onChangeAxesLimits(self,event):
"""Update the cross plot according to the reduced row and/or column range
"""Callback called when the axis of the matrix view have changed.
This will update the cross plot according to the reduced row and/or column range.
:param event: the axes whose axis have been changed
:type event: :class:`matplotlib.axes.SubplotBase`
"""
self._rowSlice = slice(*sorted([int(v) for v in event.get_ylim()]))
......@@ -72,11 +114,10 @@ class _MplDataViewer3D(object):
self._updateCrossPlot()
def _initActions(self):
"""Init all the widgets actions.
"""Setup all the actions and their corresponding callbacks.
"""
# For 2D and 3D dataset clicking somewhere on the imshow plot will produce 2 plots corresponding to row and column slices
self._axesLeaveId = self._figure.canvas.mpl_connect('axes_leave_event', self._onLeaveAxes)
self._scrollId = self._figure.canvas.mpl_connect('scroll_event', self._onScrollFrame)
self._buttonPressId = self._figure.canvas.mpl_connect('button_press_event', self._onSelectPixel)
self._keyPressId = self._figure.canvas.mpl_connect('key_press_event', self._onKeyPress)
......@@ -84,11 +125,7 @@ class _MplDataViewer3D(object):
self._mainAxes.callbacks.connect('ylim_changed', self._onChangeAxesLimits)
def _initLayout(self):
"""Initializes layout.
For 1D the figure will be made of a single plot while for 2D/3D plots the figure is made of an image (top) and two 1D plots
which depending on the plotting mode corresponds to:
- slices along the row and column of a selected pixel using right-click mouse button (aka pixel mode: "c" key pressed)
- integrations of the 2D image along the X and Y axis depending on the plotting mode (aka integration mode: "i" key pressed)
"""Initializes the figure layout.
"""
grid = gridspec.GridSpec(3, 3, self._figure, width_ratios=[0.3, 4, 1], height_ratios=[1, 4, 0.3], wspace=0.3)
......@@ -115,7 +152,10 @@ class _MplDataViewer3D(object):
self._selectedPixel = (0,0)
def _onKeyPress(self,event):
"""Add keyboard interaction for navigating through the dataset
"""Callback called when a keyboard key is pressed.
:param event: the keyboard keypress event
:type event: :class:`matplotlib.backend_bases.KeyEvent`
"""
keyToSign = {"+" : 1, "right" : 1, "up" : 1, "-" : -1, "left" : -1, "down" : -1}
......@@ -134,12 +174,13 @@ class _MplDataViewer3D(object):
self._updateCrossPlot()
def _onLeaveAxes(self,event):
def _onScrollFrame(self,event):
"""Callback called when the mouse wheel is rolled.
self._figure.canvas.toolbar.set_message("selected frame: %d" % self._selectedFrame)
This will scroll to previous/next frame according to the mouse wheel direction.
def _onScrollFrame(self,event):
"""Scroll through the dataset using the mouse wheel
:param event: the mouse wheel event event
:type event: :class:`matplotlib.backend_bases.MouseEvent`
"""
incr = event.step if event.button == "up" else -event.step
......@@ -147,7 +188,10 @@ class _MplDataViewer3D(object):
self._updateCrossPlot()
def _onSelectPixel(self,event):
"""Update the cross plot according to the selected pixel of the 2D/3D image.
"""Callback called when a mouse buttton is clicked.
:param event: the mouse click event
:type event: :class:`matplotlib.backend_bases.MouseEvent`
"""
# Only left button click will produce a cross plot
......@@ -160,8 +204,7 @@ class _MplDataViewer3D(object):
self.selectPixel(int(event.ydata),int(event.xdata))
def _updateCrossPlot(self):
"""Update the cross plots for 2D/3D dataset.
Cross plots are 1D plots which correspond to the reduced view of 2D/3D datasets projected onto X and Y axis
"""Update the cross plots.
"""
if self._xyIntegration:
......@@ -189,13 +232,25 @@ class _MplDataViewer3D(object):
plt.draw()
def selectPixel(self,row,col):
"""Select a pixel on the image.
This will update the croos plots.
:param row: the pixel row
:type column: int
:param row: the pixel column
:type column: int
"""
self._selectedPixel = (row,col)
self._updateCrossPlot()
def setSelectedFrame(self,selectedFrame):
"""Change the frame in case 2D/3D data.
"""Set the frame to be displayed.
:param selectedFrame: the new frame to be displayed
:type selectedFrame: int
"""
self._selectedFrame = min(max(selectedFrame,0),self._dataset.shape[2]-1)
......@@ -205,9 +260,10 @@ class _MplDataViewer3D(object):
self.update()
def setXYIntegrationMode(self,xyIntegration):
"""Toggle the integration mode.
If True, the top and right 1D plots will be resp. the sum/integral over y and x axis
If False, the top and right 1D plots will be resp. the cross plots along corresonding to resp. y and x axis of the selected pixel
"""Switch between slice plot mode and integration mode.
:param xyIntegration: if True, the croos plots will be resp. the integral over y and x axis otherwise the cross plots will be resp. slices along the selected pixel
:type xyIntegration: bool
"""
self._xyIntegration = xyIntegration
......@@ -219,6 +275,8 @@ class _MplDataViewer3D(object):
self._updateCrossPlot()
def update(self):
"""Update the figure.
"""
# Remove the current image if any
if self._image:
......
......@@ -70,7 +70,7 @@ class HDFViewer(widgets.Accordion):
- for `str` input type, this will be the path to the HDF datafile
- for `File`, this will be the output of a prior HDF file opening in read mode
- for `bytes`, this will the byte representation of athe HDF data
:type hdf: str or bytes or h5py:File
:type hdf: str or bytes or :class:`h5py.File`
:param startPath: the hdf path from where the HDF data will be inspected.
If not set, the starting path will be the root of the HDF data
......@@ -143,7 +143,7 @@ class HDFViewer(widgets.Accordion):
def _onSelectDataset(self,change):
"""A callable that is called when a new dataset is selected
See `here <https://ipywidgets.readthedocs.io/en/stable/examples/Widget%20Events.html#Traitlet-events>`_ for more information
See `here <https://ipywidgets.readthedocs.io/en/stable/examples/Widget%20Events.html#Traitlet-events>`__ for more information
"""
idx = change["new"]
......
......@@ -3,7 +3,18 @@ import matplotlib.pyplot as plt
from ipywidgets import widgets
class MplOutput(widgets.Output):
"""This class allows to open a matplotlib figre in a **Jupyter Lab**
It derived from `ipywidgets.widgets.Output <https://ipywidgets.readthedocs.io/en/stable/examples/Widget%20List.html#Output>`_ to circumvent from the
fact that when the cell is cleared and reloaded the figure is not removed from the matplotlib side. This ends up with a neverending increasing number
of registered figures. See `here <https://github.com/matplotlib/jupyter-matplotlib/issues/4>`__ for complementary informations.
:param figure: the figure to be displayed in the output widget
:type figure: :class:`matplotlib.figure.Figure`
:param `**kwargs`: the keyword arguments to be passed to the parent class
:type `**kwargs`: dict
"""
def __init__(self,figure=None,**kwargs):
widgets.Output.__init__(self,**kwargs)
......@@ -12,6 +23,12 @@ class MplOutput(widgets.Output):
@property
def figure(self):
"""Getter/setter for the figure to be displayed in the jupyter output widget
:getter: returns the figure to be displayed in the jupyter output widget
:setter: sets the figure to be displayed in the jupyter output widget
:type: :class:`matplotlib.figure.Figure`
"""
return self._figure
......@@ -21,6 +38,11 @@ class MplOutput(widgets.Output):
self._figure = figure
def clear_output(self,**kwargs):
"""Clear the jupyter output widget
:param `**kwargs`: the keyword arguments to be forwarded to the corresponding parent class method
:type `**kwargs`: dict
"""
widgets.Output.clear_output(self,**kwargs)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment