Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 32 additions & 3 deletions cortex/dataset/braindata.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,22 @@ def exp(self):
return self.copy(np.exp(self.data))

def uniques(self, collapse=False):
"""TODO: WHAT IS THIS
"""Yield the underlying BrainData object(s) this view is built from.

For a plain Volume/Vertex, that's just itself; composite views
(VolumeRGB, VertexRGB, Volume2D, Vertex2D) override this to yield
their individual channels instead.

Parameters
----------
collapse : bool, optional
Unused here; accepted for interface compatibility with the
composite-view overrides of this method.

Returns
-------
generator
Yields `BrainData`
"""
yield self

Expand Down Expand Up @@ -509,9 +524,23 @@ def __getitem__(self, idx):

#return VertexData(self.data[idx], self.subject, **self.attrs)
return self.copy(self.data[idx])

# TODO: simple

def to_json(self, simple: bool = False) -> dict[str, list[str]]:
"""Serialize this vertex data to a JSON-compatible dict, for the
webgl viewer / HDF5 export.

Parameters
----------
simple : bool, optional
If True, return an abbreviated summary (hemisphere split point
and frame count) rather than the full webgl payload. Default
False.

Returns
-------
dict
Serialized data.
"""
if simple:
sdict = dict(split=self.llen, frames=self.vertices.shape[0])
sdict.update(super().to_json(simple=simple))
Expand Down
131 changes: 123 additions & 8 deletions cortex/dataset/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@

class Dataset:
"""
Wrapper for multiple data objects. This often does not need to be used
explicitly--for example, if a dictionary of data objects is passed to
`cortex.webshow`, it will automatically be converted into a `Dataset`.
Wrapper for a named collection of Dataview objects (Volume, Vertex, VolumeRGB,
etc.). Provides a standard way to save/load that collection as an HDF5 file,
and lets a single Dataview be treated the same as a full Dataset elsewhere in
pycortex (e.g. `cortex.webshow`).

Each keyword argument names one view; its value can be a Dataview, a tuple
(implicitly converted to a Volume or Vertex), a filename string (loaded from
an .hdf file), a dict (converted to a nested Dataset), or another Dataset
(merged in).

# TODO: should be BrainData & Dataview, or just Dataview
All kwargs should be `BrainData` or `Dataset` objects.
"""
def __init__(self, **kwargs: Union[Dataview, dict, str, tuple, Dataset]) -> None:
self.h5: Optional[h5py.File] = None
Expand All @@ -29,8 +33,16 @@ def __init__(self, **kwargs: Union[Dataview, dict, str, tuple, Dataset]) -> None
self.append(**kwargs)

def append(self, **kwargs: Union[Dataview, dict, str, tuple, Dataset]) -> Dataset:
"""Add the `BrainData` or `Dataset` objects in `kwargs` into this
dataset.
"""Add the views in `kwargs` into this dataset. Each keyword names
one view; its value can be a Dataview, a tuple (implicitly converted
to a Volume or Vertex), a filename string (loaded from an .hdf
file), a dict (converted to a nested Dataset), or another Dataset
(merged in). See the `Dataset` class docstring for details.

Returns
-------
Dataset
This dataset, with the new views added, for chaining.
"""
for name, data in kwargs.items():
norm = normalize(data)
Expand Down Expand Up @@ -118,7 +130,19 @@ def from_file(cls, filename: str, subject: Optional[str]=None) -> Dataset:
return ds

def uniques(self, collapse: bool=False) -> set[Dataview]:
"""Return the set of unique BrainData objects contained by this dataset"""
"""Return the set of unique BrainData objects contained by this dataset

Parameters
----------
collapse : bool, optional
Passed through to each view's own `uniques()`. Default False.

Returns
-------
uniques : set
The distinct BrainData objects (e.g. Volume/Vertex channels)
referenced across all views in this dataset.
"""
uniques = set()
for name, view in self:
# .uniques() is provided by BrainData
Expand All @@ -128,6 +152,24 @@ def uniques(self, collapse: bool=False) -> set[Dataview]:
return uniques

def save(self, filename: Optional[str]=None, pack: bool=False) -> None:
"""Write this dataset's views out to an HDF5 file.

Parameters
----------
filename : str, optional
Path to write to. If omitted, writes to the file this dataset
was already opened from (raises if there is none).
pack : bool, optional
If True, also bundle each view's subject surfaces, transforms,
and masks into the file, so it can be opened without access to
the pycortex filestore/database. Default False.

Returns
-------
None
Writes `filename` (or the already-open file) as a side effect;
has no return value.
"""
if filename is not None:
self.h5 = h5py.File(filename, 'a')
elif self.h5 is None:
Expand Down Expand Up @@ -172,6 +214,32 @@ def get_surf(self, subject: str, type: str, hemi: Literal['lh', 'rh'], *, merge:
def get_surf(self, subject: str, type: str, hemi: Literal['both', 'lh', 'rh']='both', *, merge: bool=False, nudge: bool=False) -> Union[tuple[tuple[npt.NDArray[np.floating], npt.NDArray[np.integer]], tuple[npt.NDArray[np.floating], npt.NDArray[np.integer]]], tuple[npt.NDArray[np.floating], npt.NDArray[np.integer]]]: ...

def get_surf(self, subject: str, type: str, hemi: Literal['both', 'lh', 'rh']='both', *, merge: bool=False, nudge: bool=False) -> Union[tuple[tuple[npt.NDArray[np.floating], npt.NDArray[np.integer]], tuple[npt.NDArray[np.floating], npt.NDArray[np.integer]]], tuple[npt.NDArray[np.floating], npt.NDArray[np.integer]]]:
"""Retrieve a subject's surface geometry from this (packed) dataset file.

Parameters
----------
subject : str
Subject identifier, as packed into this dataset.
type : str
Surface type, e.g. 'wm', 'pia', or 'fiducial' (averaged from
'wm' and 'pia').
hemi : str, optional
'lh', 'rh', or 'both'. Default 'both'.
merge : bool, optional
If True (and `hemi='both'`), stack both hemispheres into a
single (pts, polys) pair with right-hemisphere face indices
offset. Default False.
nudge : bool, optional
If True, shift each hemisphere so they don't overlap along the
x-axis. Default False.

Returns
-------
(pts, polys) or ((lpts, lpolys), (rpts, rpolys))
Vertex coordinates and triangle faces for the requested
surface. If `hemi='both'` and `merge=False`, a pair of
(pts, polys) tuples, one per hemisphere, is returned instead.
"""
pts: npt.NDArray[np.floating]
polys: npt.NDArray[np.integer]
if hemi == 'both':
Expand Down Expand Up @@ -201,20 +269,67 @@ def get_surf(self, subject: str, type: str, hemi: Literal['both', 'lh', 'rh']='b
raise IOError('Subject not found in package')

def get_xfm(self, subject: str, xfmname: str) -> Transform:
"""Retrieve a subject's transform from this (packed) dataset file.

Parameters
----------
subject : str
Subject identifier, as packed into this dataset.
xfmname : str
Transform name, as packed into this dataset.

Returns
-------
Transform
The requested transform.
"""
try:
group: h5py.Group = self.h5['subjects'][subject]['transforms'][xfmname]
return Transform(group['xfm'][:], tuple(group['xfm'].attrs['shape']))
except (KeyError, TypeError):
raise IOError('Transform not found in package')

def get_mask(self, subject: str, xfmname: str, maskname: str):
"""Retrieve a subject's voxel mask from this (packed) dataset file.

Parameters
----------
subject : str
Subject identifier, as packed into this dataset.
xfmname : str
Transform name the mask belongs to, as packed into this dataset.
maskname : str
Name of the mask to retrieve.

Returns
-------
h5py.Dataset
The requested mask array, as stored in the HDF5 file.
"""
try:
group: h5py.Group = self.h5['subjects'][subject]['transforms'][xfmname]['masks']
return group[maskname]
except (KeyError, TypeError):
raise IOError('Mask not found in package')

def get_overlay(self, subject: str, type: str='rois', **kwargs) -> tempfile._TemporaryFileWrapper:
"""Retrieve a subject's overlay (currently only ROIs) from this
(packed) dataset file.

Parameters
----------
subject : str
Subject identifier, as packed into this dataset.
type : str, optional
Overlay type to retrieve. Only 'rois' is currently supported.
Default 'rois'.

Returns
-------
tempfile.NamedTemporaryFile
A temporary file containing the overlay's SVG data, seeked to
the start.
"""
try:
group: h5py.Group = self.h5['subjects'][subject]
if type == "rois":
Expand Down
60 changes: 58 additions & 2 deletions cortex/dataset/view2D.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@ def __init__(self, description: str="", cmap: Optional[str]=None,
self.description = description

def uniques(self, collapse=False):
"""Yield the two underlying Dataview objects (dim1, dim2) that make
up this composite 2D view.

Parameters
----------
collapse : bool, optional
Unused here; accepted for interface compatibility with
`Dataview.uniques`.

Returns
-------
generator
Yields `self.dim1`, then `self.dim2`.
"""
yield self.dim1
yield self.dim2

Expand All @@ -48,6 +62,22 @@ def _write_hdf(self, h5, name="data"):
return viewnode

def to_json(self, simple=False):
"""Serialize this 2D dataview to a JSON-compatible dict, for the
webgl viewer / HDF5 export.

Parameters
----------
simple : bool, optional
Unused here; accepted for interface compatibility with
`Dataview.to_json`.

Returns
-------
dict
Serialized view data, including both dims' names, cmap,
vmin/vmax pairs, state, attrs, description, and (if the
underlying dims are Volumes) the shared xfm.
"""
sdict = dict(data=[[self.dim1.name, self.dim2.name]],
state=self.state,
attrs=self.attrs,
Expand Down Expand Up @@ -143,7 +173,20 @@ class Volume2D(Dataview2D):
Maximum value in colormap for dim2. If not given defaults to TODO:WHAT
**kwargs
All additional arguments in kwargs are passed to the VolumeData and Dataview

state : untyped
role unclear
priority : int (default = 1)
controls the order in which datasets are viewed in webgl
stim : str
path to stimulus file
rate : numeric (default 1)
frame rate for movie/time series playback in webgl
delay : numeric (default 0)
delay for movie/time series playback in webgl
filter : str (default = "nearest")
interpolation filter for volumetric rendering in webgl (nearest/trilinear/nearlin/debug)
alpha : ndarray or Volume
overwrites the computed alpha channel from a cmap, but currently commented out in Dataview2D
"""
_cls = VolumeData
dim1: Volume
Expand Down Expand Up @@ -238,7 +281,20 @@ class Vertex2D(Dataview2D):
Maximum value in colormap for dim2. If not given defaults to TODO:WHAT
**kwargs
All additional arguments in kwargs are passed to the VolumeData and Dataview

state : untyped
role unclear
priority : int (default = 1)
controls the order in which datasets are viewed in webgl
stim : str
path to stimulus file
rate : numeric (default 1)
frame rate for movie/time series playback in webgl
delay : numeric (default 0)
delay for movie/time series playback in webgl
filter : str (default = "nearest")
interpolation filter for volumetric rendering in webgl (nearest/trilinear/nearlin/debug)
alpha : ndarray or Volume
overwrites the computed alpha channel from a cmap, but currently commented out in Dataview2D
"""
_cls = VertexData
blend_curvature = _cls.blend_curvature # hacky inheritance
Expand Down
44 changes: 44 additions & 0 deletions cortex/dataset/viewRGB.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,20 @@ def __init__(
)

def uniques(self, collapse=False):
"""Yield the underlying Dataview channels that make up this RGB view.

Parameters
----------
collapse : bool, optional
If True, yield this RGB view itself as a single unit instead of
its individual channels. Default False.

Returns
-------
generator
Yields `self` if `collapse`, otherwise `self.red`, `self.green`,
`self.blue`, and `self.alpha` (if set).
"""
if collapse:
yield self
else:
Expand Down Expand Up @@ -591,6 +605,21 @@ def alpha(self, alpha: Optional[Union[npt.NDArray, Volume]]):
self._alpha = alpha

def to_json(self, simple=False):
"""Serialize this RGB volume to a JSON-compatible dict, for the
webgl viewer / HDF5 export.

Parameters
----------
simple : bool, optional
If True, return an abbreviated summary suitable for a quick
listing rather than the full webgl payload. Default False.

Returns
-------
dict
Serialized view data, including the transform matrix (when not
`simple`) needed to place the RGB volume in the correct space.
"""
sdict = super().to_json(simple=simple)
if simple:
sdict["shape"] = self.red.shape
Expand Down Expand Up @@ -894,6 +923,21 @@ def vertices(self) -> npt.NDArray[np.uint8]:
return np.array(verts).transpose([1, 2, 0])

def to_json(self, simple=False):
"""Serialize this RGB vertex view to a JSON-compatible dict, for the
webgl viewer / HDF5 export.

Parameters
----------
simple : bool, optional
If True, return an abbreviated summary (hemisphere split point
and frame count) rather than the full webgl payload. Default
False.

Returns
-------
dict
Serialized view data.
"""
sdict = super().to_json(simple=simple)

if simple:
Expand Down
Loading