diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index 7b7c1c35..c76cda95 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -52,9 +52,9 @@ jobs: python -m pip install ninja meson meson-python>=0.15.0 numpy==1.26.4 Cython>=0.29 spin pybind11 python -m pip install tifffile>=2024.7.2 pyvips>=2.2.3 tqdm>=2.66.4 pillow>=10.3.0 openslide-python>=1.3.1 python -m pip install opencv-python-headless>=4.9.0.80 shapely>=2.0.4 pybind11>=2.8.0 pydantic coverage pytest psutil darwin-py pytest-mock tox - meson setup builddir - meson compile -C builddir - meson install -C buildir + # meson setup builddir + # meson compile -C builddir + # meson install -C buildir python -m pip install . - name: Run tox run: | diff --git a/.spin/cmds.py b/.spin/cmds.py index cfac4ab7..f7f94c01 100644 --- a/.spin/cmds.py +++ b/.spin/cmds.py @@ -1,7 +1,8 @@ +import site import subprocess import webbrowser from pathlib import Path -import site + import click from spin.cmds import meson diff --git a/dlup/annotations_experimental.py b/dlup/annotations_experimental.py index ef0de39d..1682aeb0 100644 --- a/dlup/annotations_experimental.py +++ b/dlup/annotations_experimental.py @@ -249,6 +249,20 @@ def __init__( sorting: Optional[AnnotationSorting | str] = None, **kwargs: Any, ) -> None: + """ + Parameters + ---------- + layers : GeometryCollection + Geometry collection containing the polygons, boxes and points + tags: Optional[tuple[SlideTag, ...]] + A tuple of image-level tags such as staining quality + sorting: AnnotationSorting + Sorting method, see `AnnotationSorting`. This value is typically passed to the constructor + because of operations layer on (such as `__add__`). Typically the classmethod already sorts the data + **kwargs: Any + Additional keyword arguments. In this class they are used for additional metadata or offset functions. + Currently only HaloXML requires offsets. See `.from_halo_xml` for an example + """ self._layers = layers self._tags = tags self._sorting = sorting @@ -265,14 +279,19 @@ def tags(self) -> Optional[tuple[SlideTag, ...]]: @property def num_polygons(self) -> int: - return len(self._layers.polygons) + return len(self.layers.polygons) @property def num_points(self) -> int: - return len(self._layers.points) + return len(self.layers.points) + + @property + def num_boxes(self) -> int: + return len(self.layers.boxes) @property def metadata(self) -> Optional[dict[str, list[str] | str | int | float | bool]]: + """Additional metadata for the annotations""" return self._metadata @property @@ -303,7 +322,8 @@ def offset_function(self, func: Any) -> None: @property def layers(self) -> GeometryCollection: - """Get the layers of the annotations. + """ + Get the layers of the annotations. This is a GeometryCollection object which contains all the polygons and points """ return self._layers @@ -1078,6 +1098,47 @@ def read_region( scaling: float, size: tuple[GenericNumber, GenericNumber], ) -> AnnotationRegion: + """Reads the region of the annotations. Function signature is the same as `dlup.SlideImage` + so they can be used in conjunction. + + The process is as follows: + + 1. All the annotations which overlap with the requested region of interest are filtered + 2. The polygons in the GeometryContainer in `.layers` are cropped. + The boxes and points are only filtered, so it's possible the boxes have negative (x, y) values + 3. The annotation is rescaled and shifted to the origin to match the local patch coordinate system. + + The final returned data is a `dlup.geometry.AnnotationRegion`. + + Parameters + ---------- + location: tuple[GenericNumber, GenericNumber] + Top-left coordinates of the region in the requested scaling + size : tuple[GenericNumber, GenericNumber] + Output size of the region + scaling : float + Scaling to apply compared to the base level + + Returns + ------- + AnnotationRegion + + Examples + -------- + 1. To read geojson annotations and convert them into masks: + + >>> from pathlib import Path + >>> from dlup import SlideImage + >>> import numpy as np + >>> wsi = SlideImage.from_file_path(Path("path/to/image.svs")) + >>> wsi = wsi.get_scaled_view(scaling=0.5) + >>> wsi = wsi.read_region(location=(0,0), size=wsi.size) + >>> annotations = SlideAnnotations.from_geojson("path/to/geojson.json") + >>> region = annotations.read_region((0,0), 0.01, wsi.size) + >>> mask = region.to_mask() + >>> color_mask = annotations.color_lut[mask] + >>> polygons = region.polygons # This is a list of `dlup.geometry.Polygon` objects + """ region = self._layers.read_region(coordinates, scaling, size) return region diff --git a/dlup/meson.build b/dlup/meson.build index cb07faba..1aa5a2eb 100644 --- a/dlup/meson.build +++ b/dlup/meson.build @@ -9,4 +9,4 @@ background = py.extension_module('_background', subdir : 'dlup', link_args : link_args, c_args : cpp_args + ['-DNPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION'], - dependencies : [py_dep]) # Add Python dependency \ No newline at end of file + dependencies : [py_dep]) # Add Python dependency diff --git a/src/meson.build b/src/meson.build index 4c9b371a..07c9f793 100644 --- a/src/meson.build +++ b/src/meson.build @@ -24,4 +24,4 @@ geometry = py.extension_module('_geometry', include_directories : [third_party_dir, incdir_pybind11], cpp_args : cpp_args, link_args : link_args, - dependencies : base_deps + [boost_dep, opencv_dep]) \ No newline at end of file + dependencies : base_deps + [boost_dep, opencv_dep]) diff --git a/third_party/meson.build b/third_party/meson.build index 507c86bf..182000d1 100644 --- a/third_party/meson.build +++ b/third_party/meson.build @@ -61,4 +61,3 @@ incdir_pybind11 = incdir_pybind11 base_deps = base_deps boost_dep = boost_dep opencv_dep = opencv_dep - diff --git a/tox.ini b/tox.ini index 554cf1f1..64dbb484 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,10 @@ [tox] -envlist = py310, py311 +envlist = py311 isolated_build = True [testenv] +env = + GITHUB_ACTIONS=1 deps = meson meson-python>=0.15.0 @@ -11,11 +13,9 @@ deps = spin pybind11 build + pyhaloxml extras = dev,darwin commands = - sh -c 'meson setup builddir' - sh -c 'meson compile -C builddir' - sh -c 'cp builddir/*.so dlup' pytest allowlist_externals = sh