Commit 34a60248 authored by Eric Pellegrini's avatar Eric Pellegrini
Browse files

Added initial package contents

parent 3d9386b2
import os
from distutils.core import setup
pkginfo = {}
exec(open("src/__pkginfo__.py","r").read(),{},pkginfo)
def is_package(path):
return (os.path.isdir(path) and os.path.isfile(os.path.join(path, '__init__.py')))
def find_packages(path, base=None, exclude=None):
packages = []
for root,dirs,files in os.walk(path):
if "__init__.py" in files:
if base is not None:
root = root.replace(path,base)
package = root.replace(os.sep,".")
packages.append(package)
return packages
packages = find_packages(path="src",base="hdf5viewer")
setup(name = "hdf5viewer",
version = pkginfo["__version__"],
description = pkginfo["__description__"],
long_description = pkginfo["__long_description__"],
author = pkginfo["__author__"],
author_email = pkginfo["__author_email__"],
maintainer = pkginfo["__maintainer__"],
maintainer_email = pkginfo["__maintainer_email__"],
url = pkginfo["__url__"],
license = pkginfo["__license__"],
packages = packages,
package_dir = {"hdf5viewer" : "src"},
platforms = ['Unix','Windows'])
import collections
import os
import numpy as np
import h5py
import matplotlib.pyplot as plt
import ipywidgets as widgets
from .MplDataViewer import MplDataViewer, MplDataViewerError
from .MplOutputWidget import MplOutputWidget
class HDF5Parser(widgets.Accordion):
def __init__(self, hdf5, startPath=None):
widgets.Accordion.__init__(self)
self._hdf5 = hdf5
if startPath is None:
self._startPath = "/"
self.children = [HDF5Parser(hdf5,self._startPath)]
self.set_title(0,self._startPath)
else:
self._startPath = startPath
attributesAccordion = widgets.Accordion()
for idx,(key,value) in enumerate(self._hdf5[self._startPath].attrs.items()):
attributesAccordion.children = list(attributesAccordion.children) + [widgets.HTML(value)]
attributesAccordion.set_title(idx,key)
# Setup the groups and datasets accordion
groupsAccordion = widgets.Accordion()
datasetsAccordion = widgets.Accordion()
for value in list(self._hdf5[self._startPath].values()):
if isinstance(value,h5py.Group):
groupsAccordion.children = list(groupsAccordion.children) + [HDF5Parser(hdf5,value.name)]
groupsAccordion.set_title(len(groupsAccordion.children)-1,value.name)
elif isinstance(value,h5py.Dataset):
datasetInfo = []
shape = value.shape
# Set some informations about the current hdf value
datasetInfo.append("<i>Dimension: %s</i>" % str(shape))
datasetInfo.append("<i>Reduced dimension: %s</i>" % str(tuple([s for s in shape if s != 1])))
datasetInfo.append("<i>Type: %s</i>" % value.dtype.name)
datasetInfo = "<br>".join(datasetInfo)
vbox = widgets.VBox()
vbox.children = [widgets.HTML(datasetInfo),MplOutputWidget()]
datasetsAccordion.children = list(datasetsAccordion.children) + [vbox]
datasetsAccordion.set_title(len(datasetsAccordion.children)-1,value.name)
datasetsAccordion.observe(self._onSelectDataset,names="selected_index")
# Display only the accordions which have children
nestedAccordions = [("attributes",attributesAccordion),("groups",groupsAccordion),("datasets",datasetsAccordion)]
for title,acc in nestedAccordions:
if not acc.children:
continue
acc.selected_index = None
self.children = list(self.children) + [acc]
self.set_title(len(self.children)-1,title)
# By default, the accordion is closed at start-up
self.selected_index = None
def _onSelectDataset(self,change):
idx = change["new"]
# If the accordions is closed does nothing
if idx is None:
return
accordion = change["owner"]
path = accordion.get_title(idx)
vbox = accordion.children[idx]
output = vbox.children[1]
output.clear_output(wait=False)
with output:
try:
a = np.random.uniform(0,1,(300,400,50))
self._viewer = MplDataViewer(a,standAlone=False)
except MplDataViewerError as e:
label = widgets.Label(value=str(e))
display(label)
else:
# Bind the DataViewer figure to the MplOutput widget for allowing a "clean" output clearing (i.e. release the figure from plt)
output.figure = self._viewer.figure
hbox = widgets.HBox()
firstFrame = widgets.Button(description="first frame")
previousFrame = widgets.Button(description="previous frame")
nextFrame = widgets.Button(description="next frame")
lastFrame = widgets.Button(description="last frame")
frame = widgets.IntSlider(value=5,min=0,max=10)
plotMode = widgets.Checkbox(value=False,description="xy integration")
hbox.children = [firstFrame,previousFrame,frame,nextFrame,lastFrame,plotMode]
display(hbox)
"""
MatPlotLib based viewer for 1D, 2D and 3D NumPy data.
"""
import numpy as np
from .MplDataViewer1D import _MplDataViewer1D
from .MplDataViewer2D import _MplDataViewer2D
from .MplDataViewer3D import _MplDataViewer3D
_viewers = {1 : _MplDataViewer1D, 2 : _MplDataViewer2D, 3 : _MplDataViewer3D}
class MplDataViewerError(Exception):
pass
class MplDataViewer(object):
def __init__(self,dataset):
# Remove axis with that has dimension 1 (e.g. (20,1,30) --> (20,30))
self._dataset = np.squeeze(dataset)
ndim = self._dataset.ndim
if ndim not in _viewers:
raise MplDataViewerError("The dataset dimension ({ndim:d}) is not supported by the viewer".format(ndim=ndim))
self._viewer = _viewers[ndim](self._dataset)
@property
def viewer(self):
return self._viewer
if __name__ == "__main__":
import numpy as np
import matplotlib.pyplot as plt
dataset = np.random.uniform(0,1,(100,400,30))
d = MplDataViewer(dataset)
#!/usr/bin/env python
"""
MatPlotLib based viewer for 1D NumPy data.
"""
import numpy as np
import warnings
warnings.filterwarnings("ignore")
import matplotlib.pyplot as plt
class _MplDataViewer1D(object):
def __init__(self,dataset):
self._figure = plt.figure()
self._initLayout()
self.dataset = dataset
plt.show(self._figure)
@property
def figure(self):
return self._figure
@property
def dataset(self):
return self._dataset
@dataset.setter
def dataset(self,dataset):
"""1D dataset setter
"""
self._dataset = dataset
self.update()
def _initLayout(self):
"""Initializes layout.
"""
self._mainAxes = plt.subplot(111)
def update(self):
self._mainAxes.plot(self._dataset[:])
plt.draw()
if __name__ == "__main__":
data = np.random.uniform(0,1,(1000,))
d = DataViewer1D(data)
plt.show()
#!/usr/bin/env python
"""
MatPlotLib based viewer for 2D NumPy data.
"""
__author__ = "Eric Pellegrini"
__credits__ = []
__copyright__ = "Copyright 2019, Institut Laue Langevin"
__license__ = "MIT"
__version__ = "0.0.0"
__maintainer__ = "Eric Pellegrini"
__email__ = "pellegrini@ill.fr"
__status__ = "Prototype"
import numpy as np
import warnings
warnings.filterwarnings("ignore")
import matplotlib.gridspec as gridspec
import matplotlib.pyplot as plt
import matplotlib.widgets as widgets
class _MplDataViewer2D(object):
def __init__(self,dataset,standAlone=True):
self._standAlone = standAlone
self._figure = plt.figure()
self.dataset = dataset
self._initLayout()
self._initActions()
self.update()
self.setXYIntegrationMode(False)
plt.show(self._figure)
@property
def figure(self):
return self._figure
@property
def dataset(self):
return self._dataset
@dataset.setter
def dataset(self,dataset):
"""2D dataset setter
"""
self._dataset = dataset
self._colorbar = None
self._selectedRows = slice(0,1,None)
self._selectedCols = slice(0,1,None)
self._rowSlice = slice(0,self._dataset.shape[0],None)
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
"""
self._rowSlice = slice(*sorted([int(v) for v in event.get_ylim()]))
self._colSlice = slice(*sorted([int(v) for v in event.get_xlim()]))
data = self._dataset[self._rowSlice,self._colSlice]
if self._colorbar:
self._colorbar.set_clim(vmin=data.min(),vmax=data.max())
self._colorbar.draw_all()
self._updateCrossPlot()
def _initActions(self):
"""Init all the widgets actions.
"""
self._buttonPressId = self._figure.canvas.mpl_connect('button_press_event', self._onSelectPixel)
self._keyPressId = self._figure.canvas.mpl_connect('key_press_event', self._onKeyPress)
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
"""
grid = gridspec.GridSpec(3, 3, self._figure, width_ratios=[0.3, 4, 1], height_ratios=[1, 4, 0.3], wspace=0.3)
self._mainAxes = plt.subplot(grid[1,1])
self._mainAxes.set_xlim([0,self._dataset.shape[1]])
self._mainAxes.set_ylim([0,self._dataset.shape[0]])
if self._standAlone:
self._cursor = widgets.Cursor(self._mainAxes,useblit=True)
self._cbarAxes = plt.subplot(grid[1,0])
self._rowSliceAxes = plt.subplot(grid[0,1])
self._rowSliceAxes.xaxis.set_tick_params(bottom=False,labelbottom=False,top=True,labeltop=True)
self._colSliceAxes = plt.subplot(grid[1,2])
self._colSliceAxes.tick_params(axis="x",rotation=270)
self._colSliceAxes.yaxis.set_tick_params(bottom=False,labelbottom=False,top=True,labeltop=True)
self._image = None
self._selectedPixel = (0,0)
def _onKeyPress(self,event):
"""Add keyboard interaction for navigating through the dataset
"""
if event.key == "i":
self.setXYIntegrationMode(not self._xyIntegration)
self._updateCrossPlot()
def _onSelectPixel(self,event):
"""Update the cross plot according to the selected pixel of the 2D/3D image.
"""
# Only left button click will produce a cross plot
if event.button != 1:
return
if not event.inaxes or (event.inaxes.axes != self._mainAxes):
return
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
"""
if self._xyIntegration:
self._selectedRows = self._rowSlice
self._selectedCols = self._colSlice
else:
row,col = self._selectedPixel
self._selectedRows = slice(row,row+1,None)
self._selectedCols = slice(col,col+1,None)
self._rowSliceAxes.clear()
xValues = np.arange(*self._colSlice.indices(self._colSlice.stop))
yValues = np.sum(self._dataset[self._selectedRows,self._colSlice],axis=0)
self._rowSliceAxes.plot(xValues,yValues)
self._rowSliceAxes.set_xlim(min(xValues),max(xValues))
self._rowSliceAxes.set_ylim(min(yValues),max(yValues))
self._colSliceAxes.clear()
xValues = np.arange(*self._rowSlice.indices(self._rowSlice.stop))
yValues = np.sum(self._dataset[self._rowSlice,self._selectedCols],axis=1)
self._colSliceAxes.plot(yValues,xValues)
self._colSliceAxes.set_xlim(min(yValues),max(yValues))
self._colSliceAxes.set_ylim(min(xValues),max(xValues))
plt.draw()
def selectPixel(self,row,col):
"""Select a pixel on the image and update the cross plots
"""
self._selectedPixel = (row,col)
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
"""
self._xyIntegration = xyIntegration
self._figure.canvas.toolbar.set_message("XY integration mode activated" if self._xyIntegration else "Cross-plot mode activated")
if self._standAlone:
self._cursor.set_active(not self._xyIntegration)
self._updateCrossPlot()
def update(self):
# Remove the current image if any
if self._image:
self._image.remove()
self._image = self._mainAxes.imshow(self._dataset[:,:],aspect="auto",origin="lower")
if self._colorbar is None:
self._colorbar = self._figure.colorbar(self._image, cax=self._cbarAxes)
self._colorbar.ax.yaxis.set_ticks_position('left')
plt.draw()
if __name__ == "__main__":
data = np.random.uniform(0,1,(100,200))
d = DataViewer2D(data)
plt.show()
#!/usr/bin/env python
"""
MatPlotLib based viewer for 3D NumPy data.
"""
__author__ = "Eric Pellegrini"
__credits__ = []
__copyright__ = "Copyright 2019, Institut Laue Langevin"
__license__ = "MIT"
__version__ = "0.0.0"
__maintainer__ = "Eric Pellegrini"
__email__ = "pellegrini@ill.fr"
__status__ = "Prototype"
import numpy as np
import warnings
warnings.filterwarnings("ignore")
import matplotlib.gridspec as gridspec
import matplotlib.pyplot as plt
import matplotlib.widgets as widgets
class _MplDataViewer3D(object):
def __init__(self,dataset,standAlone=True):
self._standAlone = standAlone
self.dataset = dataset
self._figure = plt.figure()
# Setup the figure layout depending on the data dimensionality
self._initLayout()
# Setup the widget events
self._initActions()
self.setSelectedFrame(0)
self.setXYIntegrationMode(False)
plt.show(self._figure)
@property
def figure(self):
return self._figure
@property
def dataset(self):
return self._dataset
@dataset.setter
def dataset(self,dataset):
"""3D dataset setter
"""
self._dataset = dataset
self._colorbar = None
self._selectedRows = slice(0,1,None)
self._selectedCols = slice(0,1,None)
self._selectedFrame = 0
self._rowSlice = slice(0,self._dataset.shape[0],None)
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
"""
self._rowSlice = slice(*sorted([int(v) for v in event.get_ylim()]))
self._colSlice = slice(*sorted([int(v) for v in event.get_xlim()]))
data = self._dataset[self._rowSlice,self._colSlice]
if self._colorbar:
self._colorbar.set_clim(vmin=data.min(),vmax=data.max())
self._colorbar.draw_all()
self._updateCrossPlot()
def _initActions(self):
"""Init all the widgets actions.
"""
# 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)
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)
"""
grid = gridspec.GridSpec(3, 3, self._figure, width_ratios=[0.3, 4, 1], height_ratios=[1, 4, 0.3], wspace=0.3)
self._mainAxes = plt.subplot(grid[1,1])
self._mainAxes.set_xlim([0,self._dataset.shape[1]])
self._mainAxes.set_ylim([0,self._dataset.shape[0]])
if self._standAlone:
self._cursor = widgets.Cursor(self._mainAxes,useblit=True)
self._cbarAxes = plt.subplot(grid[1,0])
self._rowSliceAxes = plt.subplot(grid[0,1])
self._rowSliceAxes.xaxis.set_tick_params(bottom=False,labelbottom=False,top=True,labeltop=True)
self._colSliceAxes = plt.subplot(grid[1,2])
self._colSliceAxes.tick_params(axis="x",rotation=270)
self._colSliceAxes.yaxis.set_tick_params(bottom=False,labelbottom=False,top=True,labeltop=True)
self._image = None
self._numericKeysBuffer = ""