# -*- coding: utf-8 -*-
#
# Syncopy data slicing methods
#
# Builtin/3rd party package imports
import numpy as np
# Local imports
from syncopy.shared.errors import SPYInfo, SPYTypeError, SPYValueError
__all__ = ["show"]
[docs]def show(data, squeeze=True, **kwargs):
"""
Show (partial) contents of Syncopy object
**Usage Notice**
Syncopy uses HDF5 files as on-disk backing device for data storage. This
allows working with larger-than-memory data-sets by streaming only relevant
subsets of data from disk on demand without excessive RAM use. However, using
:func:`~syncopy.show` this mechanism is bypassed and the requested data subset
is loaded into memory at once. Thus, inadvertent usage of :func:`~syncopy.show`
on a large data object can lead to memory overflow or even out-of-memory errors.
**Usage Summary**
Data selectors for showing subsets of Syncopy data objects follow the syntax
of :func:`~syncopy.selectdata`. Please refer to :func:`~syncopy.selectdata`
for a list of valid data selectors for respective Syncopy data objects.
Parameters
----------
data : Syncopy data object
As for subset-selection via :func:`~syncopy.selectdata`, the type of `data`
determines which keywords can be used. Some keywords are only valid for
certain types of Syncopy objects, e.g., "freqs" is not a valid selector
for an :class:`~syncopy.AnalogData` object.
squeeze : bool
If `True` (default) any singleton dimensions are removed from the output
array, i.e., the shape of the returned array does not contain ones (e.g.,
``arr.shape = (2,)`` not ``arr.shape = (1,2,1,1)``).
**kwargs : keywords
Valid data selectors (e.g., `trials`, `channels`, `toi` etc.). Please
refer to :func:`~syncopy.selectdata` for a full list of available data
selectors.
Returns
-------
arr : NumPy nd-array
A (selection) of data retrieved from the `data` input object.
Notes
-----
This routine represents a convenience function for quickly inspecting the
contents of Syncopy objects. It is always possible to manually access an object's
numerical data by indexing the underlying HDF5 dataset: `data.data[idx]`.
The dimension labels of the dataset are encoded in `data.dimord`, e.g., if
`data` is a :class:`~syncopy.AnalogData` with `data.dimord` being `['time', 'channel']`
and `data.data.shape` is `(15000, 16)`, then `data.data[:, 3]` returns the
contents of the fourth channel across all time points.
Examples
--------
Use :func:`~syncopy.tests.misc.generate_artificial_data` to create a synthetic
:class:`syncopy.AnalogData` object.
>>> from syncopy.tests.misc import generate_artificial_data
>>> adata = generate_artificial_data(nTrials=10, nChannels=32)
Show the contents of `'channel02'` across all trials:
>>> spy.show(adata, channel='channel02')
Syncopy <show> INFO: Showing all times 10 trials
Out[2]: array([1.0871, 0.7267, 0.2816, ..., 1.0273, 0.893 , 0.7226], dtype=float32)
Note that this is equivalent to
>>> adata.show(channel='channel02')
To preserve singleton dimensions use ``squeeze=False``:
>>> adata.show(channel='channel02', squeeze=False)
Out[3]:
array([[1.0871],
[0.7267],
[0.2816],
...,
[1.0273],
[0.893 ],
[0.7226]], dtype=float32)
See also
--------
:func:`syncopy.selectdata` : Create a new Syncopy object from a selection
"""
# Account for pathological cases
if data.data is None:
SPYInfo("Empty object, nothing to show")
return
# Parse single method-specific keyword
if not isinstance(squeeze, bool):
raise SPYTypeError(squeeze, varname="squeeze", expected="True or False")
# show (hdf5 indexing that is) only supports simple, ordered indexing
# we have to painstakingly check for this
invalid = False
for sel_key in kwargs:
sel = kwargs[sel_key]
# sequence type
if np.array(sel).size != 1:
# some selections can be strings
# with no clear way of sorting ('chanY', 'chanX')
if isinstance(sel[0], str):
# temporary selection to extract numerical indices
sel_kw = {sel_key: sel}
data.selectdata(inplace=True, **sel_kw)
# extract only channel indexing (index of an index :/)
ch_idx2 = data.dimord.index(sel_key)
# this is now numeric!
ch_idx = data._preview_trial(0).idx[ch_idx2]
data.selection = None
# consecutive, ordered selections are suddenly a slice :/
# so all fine here actually
if isinstance(ch_idx, slice):
continue
if np.any(np.diff(ch_idx) < 0) or len(set(ch_idx)) != len(sel):
invalid = True
# numeric selection, e.g. [0,4,2]
else:
if np.any(np.diff(sel) < 0) or len(set(sel)) != len(sel):
invalid = True
elif isinstance(sel, slice):
if sel.start > sel.stop:
invalid = True
if invalid:
lgl = f"unique and sorted `{sel_key}` indices"
act = sel
raise SPYValueError(lgl, "show kwargs", act)
# Leverage `selectdata` to sanitize input and perform subset picking
data.selectdata(inplace=True, **kwargs)
# Truncate info message by removing any squeezed dimensions (if necessary)
msg = data.selection.__str__().partition("with")[-1]
if squeeze:
removeKeys = ["one", "1 "]
selectionTxt = np.array(msg.split(","))
txtMask = [all(qualifier not in selTxt for qualifier in removeKeys) for selTxt in selectionTxt]
msg = "".join(selectionTxt[txtMask])
transform_out = np.squeeze
else:
transform_out = lambda x: x
SPYInfo("Showing{}".format(msg))
# catch totally out of range toi selection
has_time = True if "time" in data.dimord else False
# Use an object's `_preview_trial` method fetch required indexing tuples
idxList = []
for trlno in data.selection.trial_ids:
# each dim has an entry, list only needed for mutability
idxs = list(data._preview_trial(trlno).idx)
# time/toi is a special case, all other dims get checked
# beforehand, e.g. foi, channel, ... but out of range toi's get mapped
# repeatedly to the last index, causing invalid hdf5 indexing
if has_time:
idx = idxs[data.dimord.index("time")]
if not isinstance(idx, slice) and (len(idx) != len(set(idx))):
lgl = "valid `toi` selection"
act = sel
raise SPYValueError(lgl, "show kwargs", act)
for i, prop_idx in enumerate(idxs):
if isinstance(prop_idx, list) and len(prop_idx) == 1:
idxs[i] = prop_idx[0]
idxList.append(tuple(idxs))
# Reset in-place subset selection
data.selection = None
# single trial selected
if len(idxList) == 1:
return transform_out(data.data[idxList[0]])
# return multiple trials as list
else:
return [transform_out(data.data[idx]) for idx in idxList]