Commit 19dafdbd authored by eric pellegrini's avatar eric pellegrini
Browse files

added docstring to HDFViewer and PathSelector modules

parent 2810d9e1
import io
import os
import numpy as np
......@@ -12,14 +13,88 @@ from hdfviewer.viewers.MplDataViewer import MplDataViewer, MplDataViewerError
from hdfviewer.widgets.MplOutput import MplOutput
class HDFViewerError(Exception):
""":class:`HDFViewer` specific exception"""
pass
class HDFViewer(widgets.Accordion):
"""This class allows to inspect HDF data in the context of **Jupyter Lab**
It represents each group found in the HDF file as an accordion made of the following subitems:
- **attributes**: contains the HDF attributes of this group
- **groups**: contains the HDF subgroups of this group
- **datasets**: contains the HDF datasets of this group
If one of these subitems is empty (e.g. no attributes defined for a given group) the corresponding subitem is omitted.
When one reaches a HDF dataset, informations about the dataset are collected (dimensionality, numeric type, attributes ...) and displayed in a Jupyter output widget. In case of 1D, 2D or 3D dataset, a view of the dataset is also displayed. 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: Usage in a Jupyter Lab notebook
%matplotlib ipympl
import h5py
from hdfviewer.widgets.HDFViewer import HDFViewer
from hdfviewer.widgets.PathSelector import PathSelector
path = PathSelector(extensions=[".hdf",".h5",".nxs"])
path.widget
if path.file:
hdf5 = h5py.File(path.file,"r")
display(HDFViewer(hdf5))
:param hdf: the hdf Data to be inspected.
- 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
: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
:type startPath: str or None
:raises: :class:`HDFViewerError`: if the HDF data could not be set properly
"""
def __init__(self, hdf, startPath=None):
widgets.Accordion.__init__(self)
self._hdf = hdf
try:
if isinstance(hdf,h5py.File) and hdf.mode=="r":
self._hdf = hdf
elif isinstance(hdf,str):
self._hdf = h5py.File(hdf,"r")
elif isinstance(hdf,bytes):
self._hdf = h5py.File(io.BytesIO(hdf),"r")
else:
raise IOError("Invalid HDF stream")
except Exception as e:
raise HDFViewerError(str(e))
if startPath is None:
self._startPath = "/"
self.children = [HDFViewer(hdf,self._startPath)]
......@@ -66,6 +141,10 @@ class HDFViewer(widgets.Accordion):
self.selected_index = None
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
"""
idx = change["new"]
......
"""
Code taken and slightly modified from https://stackoverflow.com/questions/48056345/jupyter-lab-browsing-the-remote-file-system-inside-a-notebook
Adpated from `the following code base <https://stackoverflow.com/questions/48056345/jupyter-lab-browsing-the-remote-file-system-inside-a-notebook>`_
"""
import os
......@@ -9,37 +9,74 @@ import ipywidgets as widgets
from IPython.display import display
class PathSelector(object):
"""This class allows to create a file browser in the context of **Jupyter Lab**
def __init__(self,start_dir=None,select_file=True,extensions=None):
self.file = None
self.select_file = select_file
self.cwd = start_dir if start_dir else os.environ["HOME"]
self.select = widgets.SelectMultiple(options=['init'],value=(),rows=10,description='')
self.widget = widgets.Accordion(children=[self.select])
self.extensions = extensions if extensions else []
:param start_dir: the starting directory for the file browser. If not set, the current working directory will be used.
:type start_dir: str or None
self.widget.selected_index = None # Start closed (showing path only)
self.refresh(self.cwd)
self.select.observe(self.on_update,'value')
:param selectFile: if True the browser is used to select files otherwise it is used as to browse directories.
:type selectFile: bool
:param extensions: if set, only those files matching the defined extensions will be displayed otherwise all files are displayed.
:type extensions: list[int]
"""
def __init__(self,startDir=None,selectFile=True,extensions=None):
self._file = None
self._selectFile = selectFile
self._cwd = startDir if startDir else os.environ["HOME"]
self._select = widgets.SelectMultiple(options=['init'],value=(),rows=10,description='')
self._widget = widgets.Accordion(children=[self._select])
self._extensions = extensions if extensions else []
# Start closed (showing path only)
self._widget.selected_index = None
self.update(self._cwd)
self.select.observe(self._onUpdate,'value')
@property
def widget(self):
"""Return the file browser widget
:return: the file browser widget
:rtype: `ipywidgets.Accordion <https://ipywidgets.readthedocs.io/en/stable/examples/Widget%20List.html#Accordion-and-Tab-use-selected_index,-not-value>`_
"""
return self._widget
def _onUpdate(self,change):
"""A callable that is called when a new entry of the file browser is clicked
See `here <https://ipywidgets.readthedocs.io/en/stable/examples/Widget%20Events.html#Traitlet-events>`_ for more information
"""
def on_update(self,change):
if len(change['new']) > 0:
self.refresh(change['new'][0])
def refresh(self,item):
path = os.path.abspath(os.path.join(self.cwd,item))
def update(self,item):
"""Update the file browser widget with a new entry (file or directory name)
This will:
1. update the name of the current selection on top of the file browser widget
2. update the directory contents subwidget in case where the new entry is a directory
:param entry: the filename or directory name to update the file browser with
:type entry: str
"""
path = os.path.abspath(os.path.join(self._cwd,item))
if os.path.isfile(path):
if self.select_file:
self.widget.set_title(0,path)
self.file = path
self.widget.selected_index = None
if self._selectFile:
self._widget.set_title(0,path)
self._file = path
self._widget.selected_index = None
else:
self.select.value = ()
else: # os.path.isdir(path)
self.file = None
self.cwd = path
self._select.value = ()
else:
self._file = None
self._cwd = path
# Build list of files and dirs
keys = ['[..]'];
......@@ -66,7 +103,7 @@ class PathSelector(object):
vals.append(k)
# Update widget
self.widget.set_title(0,path)
self.select.options = list(zip(keys,vals))
with self.select.hold_trait_notifications():
self.select.value = ()
self._widget.set_title(0,path)
self._select.options = list(zip(keys,vals))
with self._select.hold_trait_notifications():
self._select.value = ()
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