Skip to content

Commit 79914c1

Browse files
committed
Expose naming space of accessors
1 parent ab3f4ad commit 79914c1

File tree

11 files changed

+153
-131
lines changed

11 files changed

+153
-131
lines changed

pyproject.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ requires-python = ">=3.10"
1313
dynamic = ["version", "description"]
1414

1515
dependencies = [
16-
"spatialdata>=0.2.7rc0",
17-
"opencv-python",
16+
"spatialdata>=0.3.0",
17+
"opencv-python-headless",
1818
"openslide-python",
1919
"openslide-bin",
2020
"tiffslide",

wsidata/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import wsidata.dataset as dataset
66
from ._model import WSIData, TileSpec
77
from .io import open_wsi, agg_wsi
8-
from ._accessors import (
8+
from .accessors import (
99
register_wsidata_accessor,
1010
FetchAccessor,
1111
IterAccessor,

wsidata/_model/core.py

+84-94
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from spatialdata import SpatialData
1616
from spatialdata.models import SpatialElement
1717

18-
from .._accessors import FetchAccessor, IterAccessor, DatasetAccessor
18+
from ..accessors import FetchAccessor, IterAccessor, DatasetAccessor
1919
from .._utils import find_stack_level
2020
from ..reader import ReaderBase, SlideProperties
2121

@@ -26,99 +26,80 @@ class WSIData(SpatialData):
2626
and a whole slide image reader.
2727
2828
.. note::
29-
Use the :func:`open_wsi` function to create a WSIData object.
29+
Use the :func:`open_wsi` function to create a WSIData object.
3030
3131
By default, the whole slide image is not attached to the SpatialData.
3232
A thumbnail version of the whole slide image is attached for visualization purpose.
3333
34-
The WSIData contains four main components:
35-
3634
.. list-table::
37-
:header-rows: 1
38-
39-
* -
40-
- Whole slide image
41-
- Tissue contours
42-
- Tile locations
43-
- Features
44-
45-
* - **SpatialData Slot**
46-
- :bdg-danger:`images`
47-
- :bdg-danger:`shapes`
48-
- :bdg-danger:`shapes`
49-
- :bdg-danger:`tables`
50-
51-
* - **Default Key**
52-
- :bdg-info:`wsi_thumbnail`
53-
- :bdg-info:`tissues`
54-
- :bdg-info:`tiles`
55-
- :bdg-info:`\{feature_key\}_\{tile_key\}`
56-
57-
* - | **Attributes**
58-
| **Slot**
59-
| **Key**
60-
- | :class:`SlideProperties <wsidata.reader.SlideProperties>`
61-
| :bdg-danger:`tables`
62-
| :bdg-info:`slide_properties`
63-
-
64-
- | :class:`TileSpec <wsidata.TileSpec>`
65-
| :bdg-danger:`tables`
66-
| :bdg-info:`tile_spec`
67-
-
68-
69-
* - **Content**
70-
- | :class:`DataArray <xarray.DataArray>`
71-
| (c, y, x) format.
72-
- | :class:`GeoDataFrame <geopandas.GeoDataFrame>` with columns:
73-
| :bdg-black:`tissue_id`
74-
| :bdg-black:`geometry`
75-
- | :class:`GeoDataFrame <geopandas.GeoDataFrame>` with columns:
76-
| :bdg-black:`tile_id`
77-
| :bdg-black:`x`, :bdg-black:`y`
78-
| :bdg-black:`tissue_id`
79-
| :bdg-black:`geometry`
80-
- | :class:`AnnData <anndata.AnnData>` with:
81-
| :code:`X`: The feature matrix
82-
| :code:`varm`: :bdg-black:`agg_slide`, :bdg-black:`agg_tissue`
83-
84-
85-
86-
You can interact with WSIData using the following accessors:
87-
88-
- :class:`get <wsidata.GetAccessor>`: Access data from the WSIData object.
89-
- :class:`iter <wsidata.IterAccessor>`: Iterate over data in the WSIData object.
90-
- :class:`ds <wsidata.DatasetAccessor>`: Create deep learning datasets from the WSIData object.
91-
- To implement your own accessors, use :func:`register_wsidata_accessor <wsidata.register_wsidata_accessor>`.
92-
93-
For analysis purpose, you can override two slide properties:
94-
95-
- microns per pixel (mpp): Using the :meth:`set_mpp` method.
96-
- bounds: Using the :meth:`set_bounds` method.
97-
98-
Parameters
99-
----------
100-
reader : :class:`ReaderBase <wsidata.reader.ReaderBase>`
101-
A reader object that can interface with the whole slide image file.
102-
sdata : :class:`SpatialData <spatialdata.SpatialData>`
103-
A SpatialData object for storing analysis data.
104-
backed_file : str or Path
105-
Storage location to the SpatialData object.
106-
slide_properties_source : {'slide', 'sdata'}, default: 'sdata'
107-
The source of the slide properties.
108-
109-
- "slide": load from the reader object.
110-
- "sdata": load from the SpatialData object.
111-
112-
Attributes
113-
----------
114-
properties : :class:`SlideProperties <wsidata.reader.SlideProperties>`
115-
The properties of the whole slide image.
116-
reader : :class:`ReaderBase <wsidata.reader.ReaderBase>`
117-
The reader object for interfacing with the whole slide image.
118-
sdata : :class:`SpatialData <spatialdata.SpatialData>`
119-
The SpatialData object containing the spatial data.
120-
backed_file : Path
121-
The path to the backed file.
35+
:header-rows: 1
36+
37+
* - **Content**
38+
- **Default key**
39+
- **Slot**
40+
- **Type**
41+
42+
* - Whole slide image
43+
- :bdg-info:`wsi_thumbnail`
44+
- :bdg-danger:`images`
45+
- :class:`DataArray <xarray.DataArray>` (c, y, x) format
46+
47+
* - Slide Properties
48+
- :bdg-info:`slide_properties`
49+
- :bdg-danger:`attrs`
50+
- :class:`SlideProperties <wsidata.reader.SlideProperties>`
51+
52+
* - Tissue contours
53+
- :bdg-info:`tissues`
54+
- :bdg-danger:`shapes`
55+
- :class:`GeoDataFrame <geopandas.GeoDataFrame>`
56+
57+
* - Tile locations
58+
- :bdg-info:`tiles`
59+
- :bdg-danger:`shapes`
60+
- :class:`GeoDataFrame <geopandas.GeoDataFrame>`
61+
62+
* - Tile specifications
63+
- :bdg-info:`tile_spec`
64+
- :bdg-danger:`attrs`
65+
- :class:`TileSpec <wsidata.TileSpec>`
66+
67+
* - Features
68+
- :bdg-info:`{feature_key}_{tile_key}`
69+
- :bdg-danger:`tables`
70+
- :class:`AnnData <anndata.AnnData>`
71+
72+
73+
You can interact with WSIData using the following accessors:
74+
75+
- :class:`fetch <wsidata.FetchAccessor>`: Access data from the WSIData object.
76+
- :class:`iter <wsidata.IterAccessor>`: Iterate over data in the WSIData object.
77+
- :class:`ds <wsidata.DatasetAccessor>`: Create deep learning datasets from the WSIData object.
78+
- To implement your own accessors, use :func:`register_wsidata_accessor <wsidata.register_wsidata_accessor>`.
79+
80+
For analysis purpose, you can override two slide properties:
81+
82+
- **microns per pixel (mpp)**: Using the :meth:`set_mpp` method.
83+
- **bounds**: Using the :meth:`set_bounds` method.
84+
85+
Other Parameters
86+
----------------
87+
reader : :class:`ReaderBase <wsidata.reader.ReaderBase>`
88+
A reader object that can interface with the whole slide image file.
89+
slide_properties_source : {'slide', 'sdata'}, default: 'sdata'
90+
The source of the slide properties.
91+
92+
- "slide": load from the reader object.
93+
- "sdata": load from the SpatialData object.
94+
95+
Attributes
96+
----------
97+
properties : :class:`SlideProperties <wsidata.reader.SlideProperties>`
98+
The properties of the whole slide image.
99+
reader : :class:`ReaderBase <wsidata.reader.ReaderBase>`
100+
The reader object for interfacing with the whole slide image.
101+
wsi_store :
102+
The store path for the whole slide image.
122103
123104
"""
124105

@@ -186,9 +167,11 @@ def __repr__(self):
186167
)
187168

188169
def set_exclude_elements(self, elements):
170+
"""Set the elements to be excluded from serialize to the WSIData object on disk."""
189171
self._exclude_elements.update(elements)
190172

191173
def set_wsi_store(self, store: str | Path):
174+
"""Set the on disk path for the WSIData."""
192175
self._wsi_store = Path(store)
193176

194177
def _gen_elements(
@@ -379,10 +362,17 @@ def ds(self):
379362
class TileSpec:
380363
"""Data class for storing tile specifications.
381364
382-
# There are 3 levels of tile size that we should record:
383-
# 1. The destined tile size requested by the user
384-
# 2. The tile size used by the image reader to optimize the performance
385-
# 3. The actual tile size at the level 0
365+
To enable efficient tils generation, there are 3 levels of tile size:
366+
367+
1. The destined tile size and level requested by the user
368+
2. The tile size and level used by the image reader to optimize the performance
369+
3. The actual tile size at the level 0
370+
371+
This enables user to request tile size and level that are not exist in the image pyramids.
372+
For example, if our slide is mpp=0.5 (20X) with pyramids of mpp=[0.5, 2.0, 4.0],
373+
and user request mpp=1.0 (10X) with tile size 512x512. There is no direct level to read from,
374+
we need to read from mpp=0.5 with tile size of 1024x1024 and downsample to 512x512.
375+
386376
387377
Parameters
388378
----------
@@ -404,7 +394,7 @@ class TileSpec:
404394
tissue_name : str, optional
405395
The name of the tissue.
406396
407-
Properties
397+
Attributes
408398
----------
409399
ops_{height, width} : int
410400
The height/width of the tile when retrieving images.
File renamed without changes.
File renamed without changes.
File renamed without changes.

wsidata/_accessors/iter.py wsidata/accessors/iter.py

+42-28
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,21 @@ def _normalize_polygon(polygon, bbox):
6161

6262

6363
class TissueContour:
64-
tissue_id: int
65-
shape: Polygon
66-
as_array: bool
67-
dtype: np.dtype
64+
"""The data container return by :meth:`wsidata.iter.tissue_contours <wsidata.IterAccessor.tissue_contours>`
65+
66+
Attributes
67+
----------
68+
tissue_id : int
69+
The id of tissue
70+
contour : :class:`Polygon <shapely.Polygon>` or :class:`np.ndarray <numpy.ndarray>`
71+
The contour of the tissue
72+
holes : array of :class:`Polygon <shapely.Polygon>` or :class:`np.ndarray <numpy.ndarray>`
73+
The holes of the tissue
74+
as_array : bool
75+
Whether to return the result as array or polygon, can be set to True or False.
76+
77+
78+
"""
6879

6980
def __init__(self, tissue_id, shape, as_array=False, dtype=None):
7081
self.tissue_id = tissue_id
@@ -76,18 +87,23 @@ def __init__(self, tissue_id, shape, as_array=False, dtype=None):
7687
def as_array(self):
7788
return self._as_array
7889

90+
@as_array.setter
91+
def as_array(self, v):
92+
self._as_array = v
93+
7994
@property
8095
def dtype(self):
8196
return self._dtype
8297

83-
@cached_property
98+
@property
8499
def contour(self) -> np.ndarray | Polygon:
100+
"""The contour of the tissue"""
85101
if self.as_array:
86102
return np.array(self.shape.exterior.coords, dtype=self.dtype)
87103
else:
88104
return Polygon(self.shape.exterior.coords)
89105

90-
@cached_property
106+
@property
91107
def holes(self) -> List[np.ndarray | Polygon]:
92108
if self.as_array:
93109
return [
@@ -161,10 +177,6 @@ def plot(
161177

162178

163179
class TissueImage(TissueContour):
164-
image: np.ndarray
165-
format: str
166-
mask_bg: int | None
167-
168180
def __init__(
169181
self,
170182
tissue_id,
@@ -329,7 +341,7 @@ def __repr__(self):
329341
f"TileImage(id={self.id}, x={self.x}, y={self.y}, "
330342
f"tissue_id={self.tissue_id}) {shapes_repr} "
331343
f"with attributes: \n"
332-
f"image, annot_mask, annot_shapes"
344+
f"image, has_annot, annot_mask, annot_shapes, normed_annot_shapes, annot_labels, tissue_id, x, y"
333345
)
334346

335347
def plot(
@@ -353,10 +365,11 @@ def plot(
353365
ax.imshow(self.image, **kwargs)
354366

355367
# Create palette
356-
if palette is None:
357-
palette = {k: v for k, v in zip(self.annot_labels.keys(), PALETTE)}
368+
if self.has_annot:
369+
if palette is None:
370+
palette = {k: v for k, v in zip(self.annot_labels.keys(), PALETTE)}
358371

359-
if show_annots and self.annot_shapes is not None:
372+
if show_annots and self.has_annot:
360373
for shape, name, label in self.annot_shapes:
361374
path = Path.make_compound_path(
362375
Path(np.asarray(shape.exterior.coords)[:, :2]),
@@ -379,7 +392,7 @@ def plot(
379392
alpha=alpha,
380393
)
381394
ax.add_patch(fill)
382-
if legend:
395+
if legend and self.has_annot:
383396
from legendkit import cat_legend
384397

385398
cat_legend(
@@ -395,7 +408,11 @@ def plot(
395408
class IterAccessor(object):
396409
"""An accessor to iterate over the WSI data.
397410
398-
Usage: `wsidata.iter`
411+
Usage: :code:`wsidata.iter`
412+
413+
The iter accessor will return a data container,
414+
you can access the data within container using attributes.
415+
All container has :code:`.plot()` method to visualize its content.
399416
400417
"""
401418

@@ -427,12 +444,10 @@ def tissue_contours(
427444
428445
Returns
429446
-------
430-
TissueContour
431-
A named tuple with fields:
447+
:class:`TissueContour <wsi.accessors.iter.TissueContour>`
432448
433-
- tissue_id : The tissue id.
434-
- contour : The tissue contour as a shapely geometry or an array.
435-
- holes : The holes in the tissue contour.
449+
- tissue_id : The tissue id.
450+
- shape : :class:`Polygon <shapely.Polygon>` or :class:`np.ndarray <numpy.ndarray>` The tissue shape as a shapely geometry or an array.
436451
437452
"""
438453

@@ -486,14 +501,13 @@ def tissue_images(
486501
487502
Returns
488503
-------
489-
TissueImage
490-
A named tuple with fields:
504+
:class:`TissueImage <wsi.accessors.iter.TissueImage>`
491505
492-
- tissue_id : The tissue id.
493-
- x : The x-coordinate of the image.
494-
- y : The y-coordinate of the image.
495-
- image : The tissue image.
496-
- mask : The tissue mask.
506+
- tissue_id : The tissue id.
507+
- x : The x-coordinate of the image.
508+
- y : The y-coordinate of the image.
509+
- image : The tissue image.
510+
- mask : The tissue mask.
497511
498512
"""
499513
import cv2

wsidata/_accessors/register.py wsidata/accessors/register.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def decorator(accessor):
5454
stacklevel=find_stack_level(),
5555
)
5656
setattr(cls, name, CachedAccessor(name, accessor))
57-
# cls._accessors.add(name)
57+
# cls.accessors.add(name)
5858
return accessor
5959

6060
return decorator

0 commit comments

Comments
 (0)