Skip to content

Commit

Permalink
Merge pull request #138 from jrussell25/xarray-slicer
Browse files Browse the repository at this point in the history
Xarray slicer
  • Loading branch information
ianhi authored Nov 20, 2020
2 parents 81f477b + 888c5e0 commit 9178a51
Show file tree
Hide file tree
Showing 7 changed files with 257 additions and 19 deletions.
Binary file added docs/_static/images/hyperslicer4.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
80 changes: 67 additions & 13 deletions examples/hyperslicer.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -36,7 +36,7 @@
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -50,17 +50,9 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(126, 512, 512)\n"
]
}
],
"outputs": [],
"source": [
"print(beads.shape) # (126, 512, 512)"
]
Expand Down Expand Up @@ -241,6 +233,68 @@
"controls8 = hyperslicer(beads4d, vmin=0, vmax=255, axes=((0, 1), \"wavenums\"))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Hyperslicer with Xarray\n",
"\n",
"[Xarray](http://xarray.pydata.org/en/stable/index.html) is a library for having named dimensions on an array and hyperslicer supports them natively. So if you're going to go to the trouble of defining the `axes` argument you might think about just using xarray and doing it once per dataset and letting xarray keep track of them. Then hyperslicer will just access the information for you.\n",
"\n",
"Xarray also integrates with dask for lazy data loading so if your data is large this is a good way to process them and now you can selectively visualize these lazy arrays with hyperslicer. Here we will just demonstrate the basics with an in memory xarray but the out of memory case is similar albeit slower to render."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import xarray as xr"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Define the coordinates for the xarray as a dict of name:array pairs\n",
"# Intensity is arbiratrily made to be 0-1\n",
"# Wns = Wns is relevant spectroscopic unit in cm^-1 as above\n",
"# X,Y = actual dimensions of the images in microns from microscope metadata\n",
"coords = {'linear':np.linspace(0,1,beads4d.shape[0]), 'wavenums':wns,\n",
" 'X':np.linspace(0, 386.44, 512), 'Y':np.linspace(0, 386.44,512)}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"x_beads4d = xr.DataArray(beads4d, dims=coords.keys(),coords=coords)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"gif": "hyperslicer4.gif"
},
"outputs": [],
"source": [
"fig9, ax9 = plt.subplots()\n",
"controls9 = hyperslicer(x_beads4d, vmin=0, vmax=255)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Hyperslicer also supports bare dask arrays with the same logic as numpy arrays."
]
},
{
"cell_type": "code",
"execution_count": null,
Expand All @@ -265,7 +319,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.8"
"version": "3.8.6"
}
},
"nbformat": 4,
Expand Down
2 changes: 1 addition & 1 deletion mpl_interactions/_version.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
version_info = (0, 11, 0)
version_info = (0, 12, 0)
__version__ = ".".join(map(str, version_info))
29 changes: 24 additions & 5 deletions mpl_interactions/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from .helpers import *
from .utils import figure, ioff, nearest_idx
from .controller import gogogo_controls
from .xarray_helpers import get_hs_axes, get_hs_extent, get_hs_fmts

# functions that are methods
__all__ = [
Expand Down Expand Up @@ -578,7 +579,14 @@ def hyperslicer(
controls
"""

arr = np.asarray(np.squeeze(arr))
arr = np.squeeze(arr)

arr_type = "numpy"
if "xarray.core.dataarray.DataArray" in str(arr.__class__):
arr_type = "xarray"
elif "dask.array.core.Array" in str(arr.__class__):
arr_type = "dask"

if arr.ndim < 3 + is_color_image:
raise ValueError(
f"arr must be at least {3+is_color_image}D but it is {arr.ndim}D. mpl_interactions.imshow for 2D images."
Expand All @@ -599,11 +607,15 @@ def hyperslicer(

names = None
axes = None
if "names" in kwargs:
names = kwargs.pop("names")
if arr_type != "xarray":
if "names" in kwargs:
names = kwargs.pop("names")

elif "axes" in kwargs:
axes = kwargs.pop("axes")

elif "axes" in kwargs:
axes = kwargs.pop("axes")
else:
axes = get_hs_axes(arr, is_color_image=is_color_image)

# Just pass in an array - no kwargs
for i in range(arr.ndim - im_dims):
Expand Down Expand Up @@ -662,6 +674,13 @@ def hyperslicer(
slider_format_strings[name] = "{:.0f}"
kwargs[name] = np.arange(arr.shape[i])

if arr_type == "xarray":
slider_format_strings = get_hs_fmts(arr, is_color_image=is_color_image)
extent = get_hs_extent(arr, is_color_image=is_color_image)
else:
if "extent" not in kwargs:
extent = None

controls, params = gogogo_controls(
kwargs, controls, display_controls, slider_format_strings, play_buttons, allow_dupes=True
)
Expand Down
29 changes: 29 additions & 0 deletions mpl_interactions/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"gogogo_display",
"create_mpl_controls_fig",
"eval_xy",
"choose_fmt_str",
]


Expand Down Expand Up @@ -627,3 +628,31 @@ def gogogo_display(ipympl, use_ipywidgets, display, controls, fig):
fig.show()
controls[0].show()
return controls


def choose_fmt_str(dtype=None):
"""
Choose the appropriate string formatting for different dtypes.
Paramters
---------
dtype : np.dtye
dtype of array containing values to be formatted.
Returns
-------
fmt : str
Format string
"""
if np.issubdtype(dtype, "float"):
fmt = r"{:0.2f}"

elif np.issubdtype(dtype, "int"):
fmt = r"{:d}"

else:
fmt = r"{:}"

return fmt
134 changes: 134 additions & 0 deletions mpl_interactions/xarray_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import numpy as np
from .helpers import choose_fmt_str


def choose_datetime_nonsense(arr, timeunit="m"):
"""
Try to do something reasonable to datetimes and timedeltas.
Parameters
----------
arr : np.array
Array with values to be formatted.
Returns
-------
out : np.array
Array modified to format decently in a slider.
"""

if np.issubdtype(arr.dtype, "datetime64"):
# print('datetime')
out = arr.astype(f"datetime64[{timeunit}]")
elif np.issubdtype(arr.dtype, "timedelta64"):
out = arr.astype(f"timedelta64[{timeunit}]").astype(int)
else:
out = arr
return out


def get_hs_axes(xarr, is_color_image=False, timeunit="m"):
"""
Read the dims and coordinates from an xarray and construct the
axes argument for hyperslicer. Called internally by hyperslicer.
Parameters
----------
xarr : xarray.DataArray
DataArray being viewed with hyperslicer
is_color_image : bool, default False
Whether the individual images of the hyperstack are color images.
timeunit : str, default "m"
Truncation level for datetime and timedelta axes.
Returns
-------
axes : list
axes kwarg for hyperslicer
"""
if not is_color_image:
dims = xarr.dims[:-2]
else:
dims = xarr.dims[:-3]
coords_list = [choose_datetime_nonsense(xarr.coords[d].values, timeunit=timeunit) for d in dims]
# print(coords_list)
axes = zip(dims, coords_list)
return list(axes)


def get_hs_extent(xarr, is_color_image=False):
"""
Read the "XY" coordinates of an xarray.DataArray to set extent of image for
imshow.
Parameters
----------
xarr : xarray.DataArray
DataArray being viewed with hyperslicer
is_color_image : bool, default False
Whether the individual images of the hyperstack are color images.
Returns
-------
extent : list
Extent argument for imshow. [d0_min, d0_max, d1_min, d1_max]
"""

if not is_color_image:
dims = xarr.dims[-2:]
else:
dims = xarr.dims[-3:-1]
extent = []
for d in dims:
vals = xarr[d].values
extent.append(vals.min())
extent.append(vals.max())
return extent


def get_hs_fmts(xarr, units=None, is_color_image=False):
"""
Get appropriate slider format strings from xarray coordinates
based the dtype of corresponding values.
Parameters
----------
xarr : xarray.DataArray
DataArray being viewed with hyperslicer
units : array-like
Units to append to end of slider value. Must have the same length
as number of non-image dimensions in xarray.
is_color_image : bool, default False
Whether the individual images of the hyperstack are color images.
Returns
-------
fmt_strings : dict
Slider format strings for hyperslicer (or other mpl-interactions?)
"""
if not is_color_image:
dims = xarr.dims[:-2]
else:
dims = xarr.dims[:-3]
fmt_strs = {}
for i, d in enumerate(dims):
fmt_strs[d] = choose_fmt_str(xarr[d].dtype)
if units is not None and units[i] is not None:
try:
fmt_strs[d] += " {}".format(units[i])
except:
continue
return fmt_strs
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"jupyter-sphinx",
"sphinx-copybutton",
"sphinx-autobuild",
"xarray",
],
"test": [
"pytest",
Expand All @@ -71,6 +72,7 @@
"pandas",
"requests",
"scipy",
"xarray",
],
"dev": [
"pre-commit",
Expand Down

0 comments on commit 9178a51

Please sign in to comment.