From 28d7b1e0fb447dac2fe88a9f8aecd50c84c25b60 Mon Sep 17 00:00:00 2001 From: Jonas Teuwen <2347927+jonasteuwen@users.noreply.github.com> Date: Thu, 26 Sep 2024 10:49:59 +0200 Subject: [PATCH] Revert "Implement WsiAnnotations in C++ (#252)" This reverts commit a497eabe5d206fa063746c6ee236d9206e9f98b6. --- .clang-format | 3 +- .github/workflows/codecov.yml | 44 +- .github/workflows/mypy.yml | 2 +- .github/workflows/pylint.yml | 2 +- .github/workflows/tox.yml | 42 +- .gitignore | 3 +- .spin/cmds.py | 78 +- README.md | 12 - dlup/_geometry.pyi | 106 - dlup/annotations.py | 149 +- dlup/annotations_experimental.py | 1485 - dlup/data/dataset.py | 4 +- dlup/data/transforms.py | 10 +- dlup/geometry.py | 479 - dlup/meson.build | 12 - dlup/utils/annotations_utils.py | 38 +- dlup/utils/geometry_xml.py | 322 - dlup/utils/imports.py | 1 - dlup/utils/schemas/dlup_schema_v1.0.xsd | 244 - dlup/utils/schemas/generated/__init__.py | 35 - .../schemas/generated/dlup_schema_v1_0.py | 629 - examples/annotations_to_mask.py | 26 - examples/files/dlup_annotation_test.xml | 113062 --------------- meson.build | 103 +- pyproject.toml | 17 - src/geometry.cpp | 41 - src/geometry/base.h | 65 - src/geometry/box.h | 105 - src/geometry/collection.h | 524 - src/geometry/exceptions.h | 42 - src/geometry/factory.h | 69 - src/geometry/lazy_array.h | 91 - src/geometry/point.h | 74 - src/geometry/polygon.h | 216 - src/geometry/polygon_collection.h | 78 - src/geometry/region.h | 126 - src/geometry/rtree.h | 63 - src/geometry/utilities.h | 78 - src/image.h | 32 +- src/libtiff_tiff_writer.cpp | 536 +- src/meson.build | 27 - src/opencv.h | 74 - src/tiff/exceptions.h | 40 - src/tiff/writer.h | 444 - tests/files/dlup_xml_example.xml | 63 - tests/files/halo_holes.annotations | 371 - .../test_different_types_halo.annotations | 208 - tests/infer_ann.py | 3 + tests/test_annotations.py | 23 +- tests/test_background.py | 8 +- tests/test_geometry.py | 538 - tests/test_slide_annotations.py | 552 - tests/test_transforms.py | 36 +- tests/utils/test_annotation_utils.py | 48 - third_party/meson.build | 66 - tox.ini | 11 +- 56 files changed, 768 insertions(+), 120792 deletions(-) delete mode 100644 dlup/_geometry.pyi delete mode 100644 dlup/annotations_experimental.py delete mode 100644 dlup/geometry.py delete mode 100644 dlup/meson.build delete mode 100644 dlup/utils/geometry_xml.py delete mode 100644 dlup/utils/schemas/dlup_schema_v1.0.xsd delete mode 100644 dlup/utils/schemas/generated/__init__.py delete mode 100644 dlup/utils/schemas/generated/dlup_schema_v1_0.py delete mode 100644 examples/annotations_to_mask.py delete mode 100644 examples/files/dlup_annotation_test.xml delete mode 100644 src/geometry.cpp delete mode 100644 src/geometry/base.h delete mode 100644 src/geometry/box.h delete mode 100644 src/geometry/collection.h delete mode 100644 src/geometry/exceptions.h delete mode 100644 src/geometry/factory.h delete mode 100644 src/geometry/lazy_array.h delete mode 100644 src/geometry/point.h delete mode 100644 src/geometry/polygon.h delete mode 100644 src/geometry/polygon_collection.h delete mode 100644 src/geometry/region.h delete mode 100644 src/geometry/rtree.h delete mode 100644 src/geometry/utilities.h delete mode 100644 src/meson.build delete mode 100644 src/opencv.h delete mode 100644 src/tiff/exceptions.h delete mode 100644 src/tiff/writer.h delete mode 100644 tests/files/dlup_xml_example.xml delete mode 100644 tests/files/halo_holes.annotations delete mode 100644 tests/files/test_different_types_halo.annotations create mode 100644 tests/infer_ann.py delete mode 100644 tests/test_geometry.py delete mode 100644 tests/test_slide_annotations.py delete mode 100644 tests/utils/test_annotation_utils.py delete mode 100644 third_party/meson.build diff --git a/.clang-format b/.clang-format index 26a5b5c5..30b26b06 100644 --- a/.clang-format +++ b/.clang-format @@ -1,6 +1,4 @@ --- -BasedOnStyle: Google -IndentWidth: 2 Language: Cpp ColumnLimit: 120 AccessModifierOffset: -4 @@ -71,6 +69,7 @@ IncludeCategories: IncludeIsMainRegex: '(Test)?$' IndentCaseLabels: false IndentPPDirectives: None +IndentWidth: 4 IndentWrappedFunctionNames: false JavaScriptQuotes: Leave JavaScriptWrapImports: true diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 8d860cd6..01b80103 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -8,22 +8,16 @@ on: jobs: build: runs-on: ubuntu-latest + env: + CODECOV_CI: true steps: - name: Install build dependencies run: | sudo apt update sudo apt install -y meson libgl1-mesa-glx libcairo2-dev libgdk-pixbuf2.0-dev libglib2.0-dev libjpeg-dev libpng-dev libtiff5-dev libxml2-dev libopenjp2-7-dev libsqlite3-dev zlib1g-dev libzstd-dev - sudo apt install -y libfftw3-dev libexpat1-dev libgsf-1-dev liborc-0.4-dev libtiff5-dev ninja-build libboost-all-dev libopencv-dev - - name: Install Rust for pyhaloxml - run: | - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - echo "Rust and Cargo installed in:" - echo $HOME/.cargo/bin - ls $HOME/.cargo/bin - export PATH="$HOME/.cargo/bin:$PATH" + sudo apt install -y libfftw3-dev libexpat1-dev libgsf-1-dev liborc-0.4-dev libtiff5-dev ninja-build - name: Build and install OpenSlide run: | - export PATH="$HOME/.cargo/bin:$PATH" git clone https://github.com/openslide/openslide.git cd openslide meson setup builddir @@ -32,7 +26,6 @@ jobs: cd .. - name: Build and install libvips run: | - export PATH="$HOME/.cargo/bin:$PATH" git clone https://github.com/libvips/libvips.git cd libvips meson setup builddir --prefix=/usr/local @@ -41,24 +34,35 @@ jobs: sudo ldconfig cd .. - uses: actions/checkout@v4 - - name: Set up Python 3.11 + - name: Set up Python 3.10 uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.10" + - name: Clean up any existing installations + run: | + sudo rm -rf /usr/local/lib/python3.10/site-packages/dlup* + sudo rm -rf /opt/hostedtoolcache/Python/3.10.14/arm64/lib/python3.10/site-packages/dlup* + sudo rm -rf /opt/hostedtoolcache/Python/3.10.14/arm64/lib/python3.10/site-packages/_dlup_editable_loader.py + sudo rm -rf /opt/hostedtoolcache/Python/3.10.14/arm64/lib/python3.10/site-packages/easy-install.pth + sudo rm -rf dlup/build + sudo rm -rf /tmp/* - name: Install environment run: | - export PATH="$HOME/.cargo/bin:$PATH" python -m pip install --upgrade pip 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 spin - 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 - spin build - cp build/dlup/_*.so dlup/ - cp build/src/_*.so dlup/ - python -m pip install . + 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 + echo "Python executable: $(which python)" + echo "Python version: $(python --version)" + echo "Current directory: $PWD" + meson setup builddir + meson compile -C builddir + meson install -C builddir - name: Run coverage run: | - coverage run -m pytest || true + mv dlup _dlup # This is needed because otherwise it won't find the compiled libraries + export PYTHONPATH=$(python -c "import site; print(site.getsitepackages()[0])") + coverage run -m pytest - name: Upload Coverage to Codecov uses: codecov/codecov-action@v4 with: diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 718f3c4d..11d71550 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -13,7 +13,7 @@ jobs: run: | sudo apt update sudo apt install -y meson libgl1-mesa-glx libcairo2-dev libgdk-pixbuf2.0-dev libglib2.0-dev libjpeg-dev libpng-dev libtiff5-dev libxml2-dev libopenjp2-7-dev libsqlite3-dev zlib1g-dev libzstd-dev - sudo apt install -y libfftw3-dev libexpat1-dev libgsf-1-dev liborc-0.4-dev libtiff5-dev libboost-all-dev libopencv-dev + sudo apt install -y libfftw3-dev libexpat1-dev libgsf-1-dev liborc-0.4-dev libtiff5-dev - name: Build and install OpenSlide run: | git clone https://github.com/openslide/openslide.git diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 16135d05..edef6b09 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -13,7 +13,7 @@ jobs: run: | sudo apt update sudo apt install -y meson libgl1-mesa-glx libcairo2-dev libgdk-pixbuf2.0-dev libglib2.0-dev libjpeg-dev libpng-dev libtiff5-dev libxml2-dev libopenjp2-7-dev libsqlite3-dev zlib1g-dev libzstd-dev - sudo apt install -y libfftw3-dev libexpat1-dev libgsf-1-dev liborc-0.4-dev libtiff5-dev libboost-all-dev libopencv-dev + sudo apt install -y libfftw3-dev libexpat1-dev libgsf-1-dev liborc-0.4-dev libtiff5-dev - name: Build and install OpenSlide run: | git clone https://github.com/openslide/openslide.git diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index 8ef6f0bc..5f5668fd 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -8,22 +8,16 @@ on: jobs: build: runs-on: ubuntu-latest + env: + CODECOV_CI: true steps: - name: Install build dependencies run: | sudo apt update sudo apt install -y meson libgl1-mesa-glx libcairo2-dev libgdk-pixbuf2.0-dev libglib2.0-dev libjpeg-dev libpng-dev libtiff5-dev libxml2-dev libopenjp2-7-dev libsqlite3-dev zlib1g-dev libzstd-dev - sudo apt install -y libfftw3-dev libexpat1-dev libgsf-1-dev liborc-0.4-dev libtiff5-dev ninja-build libboost-all-dev libopencv-dev - - name: Install Rust for pyhaloxml - run: | - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - echo "Rust and Cargo installed in:" - echo $HOME/.cargo/bin - ls $HOME/.cargo/bin - export PATH="$HOME/.cargo/bin:$PATH" + sudo apt install -y libfftw3-dev libexpat1-dev libgsf-1-dev liborc-0.4-dev libtiff5-dev ninja-build - name: Build and install OpenSlide run: | - export PATH="$HOME/.cargo/bin:$PATH" git clone https://github.com/openslide/openslide.git cd openslide meson setup builddir @@ -32,7 +26,6 @@ jobs: cd .. - name: Build and install libvips run: | - export PATH="$HOME/.cargo/bin:$PATH" git clone https://github.com/libvips/libvips.git cd libvips meson setup builddir --prefix=/usr/local @@ -41,23 +34,28 @@ jobs: sudo ldconfig cd .. - uses: actions/checkout@v4 - - name: Set up Python 3.11 + - name: Set up Python 3.10 uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.10" + - name: Clean up any existing installations + run: | + sudo rm -rf /usr/local/lib/python3.10/site-packages/dlup* + sudo rm -rf /opt/hostedtoolcache/Python/3.10.14/arm64/lib/python3.10/site-packages/dlup* + sudo rm -rf /opt/hostedtoolcache/Python/3.10.14/arm64/lib/python3.10/site-packages/_dlup_editable_loader.py + sudo rm -rf /opt/hostedtoolcache/Python/3.10.14/arm64/lib/python3.10/site-packages/easy-install.pth + sudo rm -rf dlup/build + sudo rm -rf /tmp/* - name: Install environment run: | - export PATH="$HOME/.cargo/bin:$PATH" python -m pip install --upgrade pip 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 spin - 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 - spin build - cp build/dlup/_*.so dlup/ - cp build/src/_*.so dlup/ - python -m pip install . + 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 + echo "Python executable: $(which python)" + echo "Python version: $(python --version)" + echo "Current directory: $PWD" - name: Run tox run: | - export GITHUB_ACTIONS=1 - export PATH="$HOME/.cargo/bin:$PATH" - tox || true + python -m pip install tox + tox diff --git a/.gitignore b/.gitignore index 74039ef6..b84500c1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,8 @@ _background.c _background.cpp _background.html -_background.cpython-*.so +_background.cpython-*-darwin.so _libtiff_tiff_writer.cpython-*.so -_geometry.cython-*.so # Output files *.tif diff --git a/.spin/cmds.py b/.spin/cmds.py index f0970ec7..809da674 100644 --- a/.spin/cmds.py +++ b/.spin/cmds.py @@ -1,10 +1,8 @@ -import os import subprocess import webbrowser from pathlib import Path import click -from spin.cmds import meson @click.group() @@ -14,32 +12,11 @@ def cli(): @cli.command() -@click.option("-j", "--jobs", help="Number of parallel tasks to launch", type=int) -@click.option("--clean", is_flag=True, help="Clean build directory before build") -@click.option("-v", "--verbose", is_flag=True, help="Print all build output, even installation") -@click.argument("meson_args", nargs=-1) -@click.pass_context -def build(ctx, meson_args, jobs=None, clean=False, verbose=False, quiet=False, *args, **kwargs): +def build(): """๐Ÿ”ง Build the project""" - build_dir = Path("build") - build_dir.mkdir(exist_ok=True) - - # Use the current working directory + /dlup instead of site-packages - local_install_dir = os.path.join(os.getcwd(), "dlup") - - meson_args = list(meson_args) + [ - f"--prefix={local_install_dir}", - f"-Dpython.platlibdir={local_install_dir}", - f"-Dpython.purelibdir={local_install_dir}", - ] - - ctx.params["meson_args"] = meson_args - ctx.params["jobs"] = jobs - ctx.params["clean"] = clean - ctx.params["verbose"] = verbose - ctx.params["quiet"] = quiet - - ctx.forward(meson.build) + subprocess.run(["meson", "setup", "builddir", "--prefix", str(Path.cwd())], check=True) + subprocess.run(["meson", "compile", "-C", "builddir"], check=True) + subprocess.run(["meson", "install", "-C", "builddir"], check=True) @cli.command() @@ -48,28 +25,11 @@ def build(ctx, meson_args, jobs=None, clean=False, verbose=False, quiet=False, * def test(verbose, tests): """๐Ÿ” Run tests""" cmd = ["pytest"] - if verbose: - cmd.append("-v") - if coverage: - cmd.extend(["--cov=dlup --cov=tests --cov-report=html --cov-report=term"]) - if tests: - cmd.extend(tests) - subprocess.run(cmd, check=True) - - -@cli.command() -@click.option("-v", "--verbose", is_flag=True, help="Verbose output") -@click.argument("tests", nargs=-1) -def coverage(verbose, tests): - """๐Ÿงช Run tests and generate coverage report""" - cmd = ["pytest", "--cov=dlup", "--cov=tests", "--cov-report=html", "--cov-report=term"] if verbose: cmd.append("-v") if tests: cmd.extend(tests) subprocess.run(cmd, check=True) - coverage_path = Path.cwd() / "htmlcov" / "index.html" - webbrowser.open(f"file://{coverage_path.resolve()}") @cli.command() @@ -173,6 +133,16 @@ def clean(): path.unlink() +@cli.command() +def coverage(): + """๐Ÿงช Run tests and generate coverage report""" + subprocess.run(["coverage", "run", "--source", "dlup", "-m", "pytest"], check=True) + subprocess.run(["coverage", "report", "-m"], check=True) + subprocess.run(["coverage", "html"], check=True) + coverage_path = Path.cwd() / "htmlcov" / "index.html" + webbrowser.open(f"file://{coverage_path.resolve()}") + + @cli.command() def release(): """๐Ÿ“ฆ Package and upload a release""" @@ -193,25 +163,5 @@ def dist(): subprocess.run(["ls", "-l", "dist"], check=True) -@cli.command() -def precommit(): - """๐Ÿ› ๏ธ Run pre-commit hooks""" - subprocess.run(["pre-commit", "run", "--all-files"], check=True) - - -@cli.command() -def format(): - """๐Ÿ› ๏ธ Run clang-format and black""" - # Run clang-format - subprocess.run( - "find src -name '*.cpp' -o -name '*.h' -o -name '*.hpp' | xargs clang-format -i", - shell=True, - check=True, - ) - - # Run black - subprocess.run(["black", "."], check=True) - - if __name__ == "__main__": cli() diff --git a/README.md b/README.md index 0c2e87e0..6558c2f6 100644 --- a/README.md +++ b/README.md @@ -45,15 +45,3 @@ or the following plain bibliography: ``` Teuwen, J., Romor, L., Pai, A., Schirris, Y., Marcus E. (2024). DLUP: Deep Learning Utilities for Pathology (Version 0.7.0) [Computer software]. https://github.com/NKI-AI/dlup ``` - -## Contributors -In alphabetic order: - -## Contributors -In alphabetic order: - -## Contributors -In alphabetic order: - -| [
Ajey Pai Karkala](https://github.com/AjeyPaiK) | [
Eric Marcus](https://github.com/EricMarcus-ai) | [
Jonas Teuwen](https://github.com/jonasteuwen) | [
Leonardo Romor](https://github.com/lromor) | [
Rolf Harkes](https://github.com/rharkes) | [
Yoni Schirris](https://github.com/YoniSchirris) | -| :---: | :---: | :---: | :---: | :---: | :---: | diff --git a/dlup/_geometry.pyi b/dlup/_geometry.pyi deleted file mode 100644 index 8ec7dca3..00000000 --- a/dlup/_geometry.pyi +++ /dev/null @@ -1,106 +0,0 @@ -from typing import Callable, overload - -import numpy as np -from numpy.typing import NDArray - -from dlup._types import GenericNumber -from dlup.geometry import Box as Box_ -from dlup.geometry import Point as Point_ -from dlup.geometry import Polygon as Polygon_ - -class Polygon: - @property - def fields(self) -> list[str]: ... - def get_exterior(self) -> list[tuple[float, float]]: ... - def get_interiors(self) -> list[list[tuple[float, float]]]: ... - def contains(self, other: Polygon_) -> bool: ... - def correct_orientation(self) -> None: ... - @property - def area(self) -> float: ... - -def set_polygon_factory(factory: Callable[[Polygon_], Polygon_]) -> None: ... - -class Point: - @property - def fields(self) -> list[str]: ... - def scale(self, scaling: float) -> None: ... - def get_coordinates(self) -> tuple[float, float]: ... - @property - def x(self) -> float: ... - @property - def y(self) -> float: ... - -def set_point_factory(factory: Callable[[Point_], Point_]) -> None: ... - -class Box: - @property - def fields(self) -> list[str]: ... - def as_polygon(self) -> Polygon_: ... - @property - def area(self) -> float: ... - def scale(self, scaling: float) -> None: ... - @property - def coordinates(self) -> tuple[float, float]: ... - @property - def size(self) -> tuple[float, float]: ... - -def set_box_factory(factory: Callable[[Box_], Box_]) -> None: ... - -class LazyArray: - def __array__(self) -> NDArray[np.int_]: ... - def numpy(self) -> NDArray[np.int_]: ... - -class PolygonCollection: - def get_geometries(self) -> list[Polygon_]: ... - def to_mask(self) -> LazyArray: ... - -class AnnotationRegion: - @property - def polygons(self) -> PolygonCollection: ... - @property - def rois(self) -> PolygonCollection: ... - @property - def points(self) -> list[Point_]: ... - @property - def boxes(self) -> list[Box_]: ... - -class GeometryCollection: - def add_polygon(self, polygon: Polygon) -> None: ... - def add_roi(self, roi: Polygon) -> None: ... - def add_point(self, point: Point_) -> None: ... - def add_box(self, box: Box_) -> None: ... - @property - def polygons(self) -> list[Polygon_]: ... - @property - def rois(self) -> list[Polygon_]: ... - @property - def points(self) -> list[Point_]: ... - @property - def boxes(self) -> list[Box_]: ... - def set_offset(self, offset: tuple[float, float]) -> None: ... - def rebuild_rtree(self) -> None: ... - def reindex_polygons(self, index_map: dict[str, int]) -> None: ... - @overload - def remove_point(self, index: int) -> None: ... - @overload - def remove_point(self, point: Point_) -> None: ... - @overload - def remove_polygon(self, index: int) -> None: ... - @overload - def remove_polygon(self, polygon: Polygon_) -> None: ... - @overload - def remove_roi(self, index: int) -> None: ... - @overload - def remove_roi(self, roi: Polygon_) -> None: ... - def sort_polygons(self, key: Callable[[Polygon_], int | float | str | None], reverse: bool) -> None: ... - @property - def bounding_box(self) -> tuple[tuple[float, float], tuple[float, float]]: ... - def size(self) -> int: ... - def simplify(self, tolerance: float) -> None: ... - def scale(self, scaling: float) -> None: ... - def read_region( - self, - coordinates: tuple[GenericNumber, GenericNumber], - scaling: float, - size: tuple[GenericNumber, GenericNumber], - ) -> AnnotationRegion: ... diff --git a/dlup/annotations.py b/dlup/annotations.py index c57e7bb2..bed5c22e 100644 --- a/dlup/annotations.py +++ b/dlup/annotations.py @@ -21,7 +21,6 @@ import json import os import pathlib -import warnings import xml.etree.ElementTree as ET from dataclasses import dataclass, replace from enum import Enum @@ -43,7 +42,7 @@ from dlup._exceptions import AnnotationError from dlup._types import GenericNumber, PathLike -from dlup.utils.annotations_utils import _get_geojson_z_index, get_geojson_color, hex_to_rgb +from dlup.utils.annotations_utils import _get_geojson_color, _get_geojson_z_index, _hex_to_rgb from dlup.utils.imports import DARWIN_SDK_AVAILABLE, PYHALOXML_AVAILABLE # TODO: @@ -150,7 +149,7 @@ class DarwinV7Metadata(NamedTuple): @functools.lru_cache(maxsize=None) -def get_v7_metadata(filename: pathlib.Path) -> Optional[dict[tuple[str, str], DarwinV7Metadata]]: +def _get_v7_metadata(filename: pathlib.Path) -> Optional[dict[tuple[str, str], DarwinV7Metadata]]: if not DARWIN_SDK_AVAILABLE: raise RuntimeError("`darwin` is not available. Install using `python -m pip install darwin-py`.") import darwin.path_utils @@ -181,13 +180,13 @@ def get_v7_metadata(filename: pathlib.Path) -> Optional[dict[tuple[str, str], Da return output -def _is_rectangle(polygon: DlupShapelyPolygon | ShapelyPolygon) -> bool: +def _is_rectangle(polygon: Polygon | ShapelyPolygon) -> bool: if not polygon.is_valid or len(polygon.exterior.coords) != 5 or len(polygon.interiors) != 0: return False return bool(np.isclose(polygon.area, polygon.minimum_rotated_rectangle.area)) -def _is_alligned_rectangle(polygon: DlupShapelyPolygon | ShapelyPolygon) -> bool: +def _is_alligned_rectangle(polygon: Polygon | ShapelyPolygon) -> bool: if not _is_rectangle(polygon): return False min_rotated_rect = polygon.minimum_rotated_rectangle @@ -196,9 +195,8 @@ def _is_alligned_rectangle(polygon: DlupShapelyPolygon | ShapelyPolygon) -> bool def transform( - geometry: DlupShapelyPoint | DlupShapelyPolygon, - transformation: Callable[[npt.NDArray[np.float_]], npt.NDArray[np.float_]], -) -> DlupShapelyPoint | DlupShapelyPolygon: + geometry: Point | Polygon, transformation: Callable[[npt.NDArray[np.float_]], npt.NDArray[np.float_]] +) -> Point | Polygon: """ Transform a geometry. Function taken from Shapely 2.0.1 under the BSD 3-Clause "New" or "Revised" License. Parameters @@ -231,8 +229,8 @@ def transform( returned_geometry = geometry_arr.item() if original_class.annotation_type != "POINT": - return DlupShapelyPolygon(returned_geometry, a_cls=original_class) - return DlupShapelyPoint(returned_geometry, a_cls=original_class) + return Polygon(returned_geometry, a_cls=original_class) + return Point(returned_geometry, a_cls=original_class) class GeoJsonDict(TypedDict): @@ -305,21 +303,19 @@ def __isub__(self, other: Any) -> None: raise TypeError(f"unsupported operand type(s) for -=: {type(self)} and {type(other)}") -class DlupShapelyPoint(ShapelyPoint, AnnotatedGeometry): # type: ignore[misc] +class Point(ShapelyPoint, AnnotatedGeometry): # type: ignore[misc] __slots__ = ShapelyPoint.__slots__ - def __new__( - cls, coord: ShapelyPoint | tuple[float, float], a_cls: Optional[AnnotationClass] = None - ) -> "DlupShapelyPoint": + def __new__(cls, coord: ShapelyPoint | tuple[float, float], a_cls: Optional[AnnotationClass] = None) -> "Point": point = super().__new__(cls, coord) point.__class__ = cls - return cast("DlupShapelyPoint", point) + return cast("Point", point) def __reduce__(self) -> tuple[type, tuple[tuple[float, float], Optional[AnnotationClass]]]: return (self.__class__, ((self.x, self.y), self.annotation_class)) -class DlupShapelyPolygon(ShapelyPolygon, AnnotatedGeometry): # type: ignore[misc] +class Polygon(ShapelyPolygon, AnnotatedGeometry): # type: ignore[misc] __slots__ = ShapelyPolygon.__slots__ def __new__( @@ -327,15 +323,15 @@ def __new__( shell: Union[tuple[float, float], ShapelyPolygon], holes: Optional[list[list[list[float]]] | list[npt.NDArray[np.float_]]] = None, a_cls: Optional[AnnotationClass] = None, - ) -> "DlupShapelyPolygon": + ) -> "Polygon": instance = super().__new__(cls, shell, holes) instance.__class__ = cls - return cast("DlupShapelyPolygon", instance) + return cast("Polygon", instance) def intersect_with_box( self, other: ShapelyPolygon, - ) -> Optional[list["DlupShapelyPolygon"]]: + ) -> Optional[list["Polygon"]]: result = make_valid(self).intersection(other) if self.area > 0 and result.area == 0: return None @@ -347,9 +343,9 @@ def intersect_with_box( annotation_class = self.annotation_class if isinstance(result, ShapelyPolygon): - return [DlupShapelyPolygon(result, a_cls=annotation_class)] + return [Polygon(result, a_cls=annotation_class)] elif isinstance(result, (ShapelyMultiPolygon, shapely.geometry.collection.GeometryCollection)): - return [DlupShapelyPolygon(geom, a_cls=annotation_class) for geom in result.geoms if geom.area > 0] + return [Polygon(geom, a_cls=annotation_class) for geom in result.geoms if geom.area > 0] else: raise NotImplementedError(f"{type(result)}") @@ -372,7 +368,7 @@ def shape( label: str, color: Optional[tuple[int, int, int]] = None, z_index: Optional[int] = None, -) -> list[DlupShapelyPolygon | DlupShapelyPoint]: +) -> list[Polygon | Point]: geom_type = coordinates.get("type", "not_found").lower() if geom_type == "not_found": raise ValueError("No type found in coordinates.") @@ -383,7 +379,7 @@ def shape( annotation_class = AnnotationClass(label=label, annotation_type=AnnotationType.POINT, color=color, z_index=None) _coordinates = coordinates["coordinates"] return [ - DlupShapelyPoint(np.asarray(c), a_cls=annotation_class) + Point(np.asarray(c), a_cls=annotation_class) for c in (_coordinates if geom_type == "multipoint" else [_coordinates]) ] elif geom_type in ["polygon", "multipolygon"]: @@ -391,14 +387,12 @@ def shape( # TODO: Give every polygon in multipolygon their own annotation_class / annotation_type annotation_type = ( AnnotationType.BOX - if geom_type == "polygon" and _is_rectangle(DlupShapelyPolygon(_coordinates[0])) + if geom_type == "polygon" and _is_rectangle(Polygon(_coordinates[0])) else AnnotationType.POLYGON ) annotation_class = AnnotationClass(label=label, annotation_type=annotation_type, color=color, z_index=z_index) return [ - DlupShapelyPolygon( - shell=np.asarray(c[0]), holes=[np.asarray(hole) for hole in c[1:]], a_cls=annotation_class - ) + Polygon(shell=np.asarray(c[0]), holes=[np.asarray(hole) for hole in c[1:]], a_cls=annotation_class) for c in (_coordinates if geom_type == "multipolygon" else [_coordinates]) ] @@ -406,7 +400,7 @@ def shape( def _geometry_to_geojson( - geometry: DlupShapelyPolygon | DlupShapelyPoint, label: str, color: tuple[int, int, int] | None, z_index: int | None + geometry: Polygon | Point, label: str, color: tuple[int, int, int] | None, z_index: int | None ) -> dict[str, Any]: """Function to convert a geometry to a GeoJSON object. @@ -450,7 +444,7 @@ class WsiAnnotations: def __init__( self, - layers: list[DlupShapelyPoint | DlupShapelyPolygon], + layers: list[Point | Polygon], tags: Optional[list[AnnotationClass]] = None, offset_to_slide_bounds: bool = False, sorting: AnnotationSorting | str = AnnotationSorting.NONE, @@ -478,9 +472,6 @@ def __init__( self._sort_layers_in_place() self._available_classes: set[AnnotationClass] = {layer.annotation_class for layer in self._layers} self._str_tree = STRtree(self._layers) - warnings.warn( - "WsiAnnotations will be deprecated in the next release. Use SlideAnnotations instead.", DeprecationWarning - ) @property def available_classes(self) -> set[AnnotationClass]: @@ -543,7 +534,7 @@ def bounding_box(self) -> tuple[tuple[float, float], tuple[float, float]]: [ ( annotation.bounds - if isinstance(annotation, DlupShapelyPolygon) + if isinstance(annotation, Polygon) else (annotation.x, annotation.y, annotation.x, annotation.y) ) for annotation in self._layers @@ -590,7 +581,7 @@ def from_geojson( _geojsons: Iterable[Any] = [pathlib.Path(geojsons)] _geojsons = [geojsons] if not isinstance(geojsons, (tuple, list)) else geojsons - layers: list[DlupShapelyPolygon | DlupShapelyPoint] = [] + layers: list[Polygon | Point] = [] tags = None for path in _geojsons: path = pathlib.Path(path) @@ -611,11 +602,11 @@ def from_geojson( properties = x["properties"] if "classification" in properties: _label = properties["classification"]["name"] - _color = get_geojson_color(properties["classification"]) + _color = _get_geojson_color(properties["classification"]) _z_index = _get_geojson_z_index(properties["classification"]) elif properties.get("objectType", None) == "annotation": _label = properties["name"] - _color = get_geojson_color(properties) + _color = _get_geojson_color(properties) _z_index = _get_geojson_z_index(properties) else: raise ValueError("Could not find label in the GeoJSON properties.") @@ -656,14 +647,14 @@ def from_asap_xml( """ tree = ET.parse(asap_xml) opened_annotation = tree.getroot() - layers: list[DlupShapelyPolygon | DlupShapelyPoint] = [] + layers: list[Polygon | Point] = [] opened_annotations = 0 for parent in opened_annotation: for child in parent: if child.tag != "Annotation": continue label = child.attrib.get("PartOfGroup").strip() # type: ignore - color = hex_to_rgb(child.attrib.get("Color").strip()) # type: ignore + color = _hex_to_rgb(child.attrib.get("Color").strip()) # type: ignore _type = child.attrib.get("Type").lower() # type: ignore annotation_type = AnnotationTypeToDLUPAnnotationType.from_string(_type) @@ -692,9 +683,9 @@ def from_asap_xml( for coordinates in coordinates_list: _cls = AnnotationClass(label=label, annotation_type=annotation_type, color=color) if isinstance(coordinates, ShapelyPoint): - layers.append(DlupShapelyPoint(coordinates, a_cls=_cls)) + layers.append(Point(coordinates, a_cls=_cls)) elif isinstance(coordinates, ShapelyPolygon): - layers.append(DlupShapelyPolygon(coordinates, a_cls=_cls)) + layers.append(Polygon(coordinates, a_cls=_cls)) else: raise NotImplementedError @@ -744,13 +735,13 @@ def from_halo_xml( curr_geometry = pyhaloxml.shapely.region_to_shapely(region) if region.type == pyhaloxml.RegionType.Rectangle: _cls = AnnotationClass(label=layer.name, annotation_type=AnnotationType.BOX) - output_layers.append(DlupShapelyPolygon(curr_geometry, a_cls=_cls)) + output_layers.append(Polygon(curr_geometry, a_cls=_cls)) if region.type in [pyhaloxml.RegionType.Ellipse, pyhaloxml.RegionType.Polygon]: _cls = AnnotationClass(label=layer.name, annotation_type=AnnotationType.POLYGON) - output_layers.append(DlupShapelyPolygon(curr_geometry, a_cls=_cls)) + output_layers.append(Polygon(curr_geometry, a_cls=_cls)) if region.type == pyhaloxml.RegionType.Pin: _cls = AnnotationClass(label=layer.name, annotation_type=AnnotationType.POINT) - output_layers.append(DlupShapelyPoint(curr_geometry, a_cls=_cls)) + output_layers.append(Point(curr_geometry, a_cls=_cls)) else: raise NotImplementedError(f"Regiontype {region.type} is not implemented in DLUP") @@ -793,7 +784,7 @@ def from_darwin_json( darwin_json_fn = pathlib.Path(darwin_json) darwin_an = darwin.utils.parse_darwin_json(darwin_json_fn, None) - v7_metadata = get_v7_metadata(darwin_json_fn.parent) + v7_metadata = _get_v7_metadata(darwin_json_fn.parent) tags = [] layers = [] @@ -819,26 +810,26 @@ def from_darwin_json( _cls = AnnotationClass(label=name, annotation_type=annotation_type, color=annotation_color, z_index=z_index) if annotation_type == AnnotationType.POINT: - curr_point = DlupShapelyPoint((curr_data["x"], curr_data["y"]), a_cls=_cls) + curr_point = Point((curr_data["x"], curr_data["y"]), a_cls=_cls) layers.append(curr_point) continue elif annotation_type == AnnotationType.POLYGON: if "path" in curr_data: # This is a regular polygon - curr_polygon = DlupShapelyPolygon([(_["x"], _["y"]) for _ in curr_data["path"]]) - layers.append(DlupShapelyPolygon(curr_polygon, a_cls=_cls)) + curr_polygon = Polygon([(_["x"], _["y"]) for _ in curr_data["path"]]) + layers.append(Polygon(curr_polygon, a_cls=_cls)) elif "paths" in curr_data: # This is a complex polygon which needs to be parsed with the even-odd rule curr_complex_polygon = _parse_darwin_complex_polygon(curr_data) for polygon in curr_complex_polygon.geoms: - layers.append(DlupShapelyPolygon(polygon, a_cls=_cls)) + layers.append(Polygon(polygon, a_cls=_cls)) else: raise ValueError(f"Got unexpected data keys: {curr_data.keys()}") elif annotation_type == AnnotationType.BOX: x, y, w, h = list(map(curr_data.get, ["x", "y", "w", "h"])) curr_polygon = shapely.geometry.box(x, y, x + w, y + h) - layers.append(DlupShapelyPolygon(curr_polygon, a_cls=_cls)) + layers.append(Polygon(curr_polygon, a_cls=_cls)) else: ValueError(f"Annotation type {annotation_type} is not supported.") @@ -885,7 +876,7 @@ def as_geojson(self) -> GeoJsonDict: curr_annotation, label=curr_annotation.label, color=curr_annotation.color, - z_index=curr_annotation.z_index if isinstance(curr_annotation, DlupShapelyPolygon) else None, + z_index=curr_annotation.z_index if isinstance(curr_annotation, Polygon) else None, ) json_dict["id"] = str(idx) data["features"].append(json_dict) @@ -914,14 +905,14 @@ def simplify(self, tolerance: float, *, preserve_topology: bool = True) -> None: if a_cls.annotation_type == AnnotationType.POINT: continue layer.simplify(tolerance, preserve_topology=preserve_topology) - self._layers[idx] = DlupShapelyPolygon(self._layers[idx], a_cls=a_cls) + self._layers[idx] = Polygon(self._layers[idx], a_cls=a_cls) def read_region( self, location: npt.NDArray[np.int_ | np.float_] | tuple[GenericNumber, GenericNumber], scaling: float, size: npt.NDArray[np.int_ | np.float_] | tuple[GenericNumber, GenericNumber], - ) -> list[DlupShapelyPolygon | DlupShapelyPoint]: + ) -> list[Polygon | Point]: """Reads the region of the annotations. API is the same as `dlup.SlideImage` so they can be used in conjunction. The process is as follows: @@ -965,13 +956,6 @@ def read_region( The polygons can be converted to masks using `dlup.data.transforms.convert_annotations` or `dlup.data.transforms.ConvertAnnotationsToMask`. """ - - warnings.warn( - "WsiAnnotations.read_region() will be deprecated in the next release. " - "Use SlideAnnotations.read_region() instead, which has a different return type", - DeprecationWarning, - ) - box = list(location) + list(np.asarray(location) + np.asarray(size)) box = (np.asarray(box) / scaling).tolist() query_box = geometry.box(*box) @@ -979,11 +963,9 @@ def read_region( curr_indices = self._str_tree.query(query_box) # This is needed because the STRTree returns (seemingly) arbitrary order, and this would destroy the order curr_indices.sort() - filtered_annotations: list[DlupShapelyPoint | DlupShapelyPolygon] = self._str_tree.geometries.take( - curr_indices - ).tolist() + filtered_annotations: list[Point | Polygon] = self._str_tree.geometries.take(curr_indices).tolist() - cropped_annotations: list[DlupShapelyPoint | DlupShapelyPolygon] = [] + cropped_annotations: list[Point | Polygon] = [] for annotation in filtered_annotations: if annotation.annotation_type in (AnnotationType.BOX, AnnotationType.POLYGON): _annotations = annotation.intersect_with_box(query_box) @@ -995,7 +977,7 @@ def read_region( def _affine_coords(coords: npt.NDArray[np.float_]) -> npt.NDArray[np.float_]: return coords * scaling - np.asarray(location, dtype=np.float_) - output: list[DlupShapelyPolygon | DlupShapelyPoint] = [] + output: list[Polygon | Point] = [] for annotation in cropped_annotations: annotation = transform(annotation, _affine_coords) output.append(annotation) @@ -1007,32 +989,29 @@ def __str__(self) -> str: f"tags={[tag.label for tag in self.tags] if self.tags else None})" ) - def __contains__(self, item: Union[str, AnnotationClass, DlupShapelyPoint, DlupShapelyPolygon]) -> bool: + def __contains__(self, item: Union[str, AnnotationClass, Point, Polygon]) -> bool: if isinstance(item, str): return item in [_.label for _ in self.available_classes] - elif isinstance(item, (DlupShapelyPoint, DlupShapelyPolygon)): + elif isinstance(item, (Point, Polygon)): return item in self._layers return item in self.available_classes - def __getitem__(self, idx: int) -> DlupShapelyPoint | DlupShapelyPolygon: + def __getitem__(self, idx: int) -> Point | Polygon: return self._layers[idx] - def __iter__(self) -> Iterable[DlupShapelyPoint | DlupShapelyPolygon]: + def __iter__(self) -> Iterable[Point | Polygon]: for layer in self._layers: yield layer def __len__(self) -> int: return len(self._layers) - def __add__( - self, - other: WsiAnnotations | DlupShapelyPoint | DlupShapelyPolygon | list[DlupShapelyPoint | DlupShapelyPolygon], - ) -> WsiAnnotations: - if isinstance(other, (DlupShapelyPoint, DlupShapelyPolygon)): + def __add__(self, other: WsiAnnotations | Point | Polygon | list[Point | Polygon]) -> WsiAnnotations: + if isinstance(other, (Point, Polygon)): other = [other] if isinstance(other, list): - if not all(isinstance(item, (DlupShapelyPoint, DlupShapelyPolygon)) for item in other): + if not all(isinstance(item, (Point, Polygon)) for item in other): raise TypeError("can only add list purely containing Point and Polygon objects to WsiAnnotations") new_layers = self._layers + other new_tags = self.tags @@ -1049,15 +1028,12 @@ def __add__( layers=new_layers, tags=new_tags, offset_to_slide_bounds=self.offset_to_slide_bounds, sorting=self.sorting ) - def __iadd__( - self, - other: WsiAnnotations | DlupShapelyPoint | DlupShapelyPolygon | list[DlupShapelyPoint | DlupShapelyPolygon], - ) -> WsiAnnotations: - if isinstance(other, (DlupShapelyPoint, DlupShapelyPolygon)): + def __iadd__(self, other: WsiAnnotations | Point | Polygon | list[Point | Polygon]) -> WsiAnnotations: + if isinstance(other, (Point, Polygon)): other = [other] if isinstance(other, list): - if not all(isinstance(item, (DlupShapelyPoint, DlupShapelyPolygon)) for item in other): + if not all(isinstance(item, (Point, Polygon)) for item in other): raise TypeError("can only add list purely containing Point and Polygon objects to WsiAnnotations") self._layers += other @@ -1083,15 +1059,12 @@ def __iadd__( self._str_tree = STRtree(self._layers) return self - def __radd__( - self, - other: WsiAnnotations | DlupShapelyPoint | DlupShapelyPolygon | list[DlupShapelyPoint | DlupShapelyPolygon], - ) -> WsiAnnotations: + def __radd__(self, other: WsiAnnotations | Point | Polygon | list[Point | Polygon]) -> WsiAnnotations: # in-place addition (+=) of Point and Polygon will raise a TypeError - if not isinstance(other, (WsiAnnotations, DlupShapelyPoint, DlupShapelyPolygon, list)): + if not isinstance(other, (WsiAnnotations, Point, Polygon, list)): return NotImplemented if isinstance(other, list): - if not all(isinstance(item, (DlupShapelyPoint, DlupShapelyPolygon)) for item in other): + if not all(isinstance(item, (Point, Polygon)) for item in other): raise TypeError("can only add list purely containing Point and Polygon objects to WsiAnnotations") raise TypeError( "use the __add__ or __iadd__ operator instead of __radd__ when working with lists to avoid \ @@ -1099,10 +1072,10 @@ def __radd__( ) return self + other - def __sub__(self, other: WsiAnnotations | DlupShapelyPoint | DlupShapelyPolygon) -> WsiAnnotations: + def __sub__(self, other: WsiAnnotations | Point | Polygon) -> WsiAnnotations: return NotImplemented - def __isub__(self, other: WsiAnnotations | DlupShapelyPoint | DlupShapelyPolygon) -> WsiAnnotations: + def __isub__(self, other: WsiAnnotations | Point | Polygon) -> WsiAnnotations: return NotImplemented def __rsub__(self, other: WsiAnnotations) -> WsiAnnotations: diff --git a/dlup/annotations_experimental.py b/dlup/annotations_experimental.py deleted file mode 100644 index 43651a74..00000000 --- a/dlup/annotations_experimental.py +++ /dev/null @@ -1,1485 +0,0 @@ -# Copyright (c) dlup contributors -""" -Experimental annotations module for dlup. - -""" -from __future__ import annotations - -import copy -import errno -import functools -import json -import os -import pathlib -import warnings -import xml.etree.ElementTree as ET -from dataclasses import asdict -from datetime import datetime -from enum import Enum -from typing import Any, Callable, Iterable, NamedTuple, Optional, Type, TypedDict, TypeVar - -import numpy as np -import numpy.typing as npt -from xsdata.formats.dataclass.parsers import XmlParser -from xsdata.formats.dataclass.serializers import XmlSerializer -from xsdata.formats.dataclass.serializers.config import SerializerConfig -from xsdata.models.datatype import XmlDate - -from dlup import SlideImage, __version__ -from dlup._exceptions import AnnotationError -from dlup._geometry import AnnotationRegion # pylint: disable=no-name-in-module -from dlup._types import GenericNumber, PathLike -from dlup.geometry import Box, GeometryCollection, Point, Polygon -from dlup.utils.annotations_utils import get_geojson_color, hex_to_rgb, rgb_to_hex -from dlup.utils.geometry_xml import ( - create_xml_geometries, - create_xml_rois, - parse_dlup_xml_polygon, - parse_dlup_xml_roi_box, -) -from dlup.utils.imports import DARWIN_SDK_AVAILABLE, PYHALOXML_AVAILABLE -from dlup.utils.schemas.generated import DlupAnnotations as XMLDlupAnnotations -from dlup.utils.schemas.generated import Metadata as XMLMetadata -from dlup.utils.schemas.generated import Tag as XMLTag -from dlup.utils.schemas.generated import Tags as XMLTags - -_TSlideAnnotations = TypeVar("_TSlideAnnotations", bound="SlideAnnotations") - - -class AnnotationType(str, Enum): - POINT = "POINT" - BOX = "BOX" - POLYGON = "POLYGON" - TAG = "TAG" - RASTER = "RASTER" - - -class GeoJsonDict(TypedDict): - """ - TypedDict for standard GeoJSON output - """ - - id: str | None - type: str - features: list[dict[str, str | dict[str, str]]] - metadata: Optional[dict[str, str | list[str]]] - - -class CoordinatesDict(TypedDict): - type: str - coordinates: list[list[list[float]]] - - -class DarwinV7Metadata(NamedTuple): - label: str - color: Optional[tuple[int, int, int]] - annotation_type: AnnotationType - - -@functools.lru_cache(maxsize=None) -def get_v7_metadata(filename: pathlib.Path) -> Optional[dict[tuple[str, str], DarwinV7Metadata]]: - if not DARWIN_SDK_AVAILABLE: - raise ImportError("`darwin` is not available. Install using `python -m pip install darwin-py`.") - import darwin.path_utils - - if not filename.is_dir(): - raise ValueError("Provide the path to the root folder of the Darwin V7 annotations") - - v7_metadata_fn = filename / ".v7" / "metadata.json" - if not v7_metadata_fn.exists(): - return None - v7_metadata = darwin.path_utils.parse_metadata(v7_metadata_fn) - output = {} - for sample in v7_metadata["classes"]: - annotation_type = sample["type"] - # This is not implemented and can be skipped. The main function will raise a NonImplementedError - if annotation_type == "raster_layer": - continue - - label = sample["name"] - color = sample["color"][5:-1].split(",") - if color[-1] != "1.0": - raise RuntimeError("Expected A-channel of color to be 1.0") - rgb_colors = (int(color[0]), int(color[1]), int(color[2])) - - output[(label, annotation_type)] = DarwinV7Metadata( - label=label, color=rgb_colors, annotation_type=annotation_type - ) - return output - - -class AnnotationSorting(str, Enum): - """The ways to sort the annotations. This is used in the constructors of the `SlideAnnotations` class, and applied - to the output of `SlideAnnotations.read_region()`. - - - REVERSE: Sort the output in reverse order. - - AREA: Often when the annotation tools do not properly support hierarchical order, one would annotate in a way - that the smaller objects are on top of the larger objects. This option sorts the output by area, so that the - larger objects appear first in the output and then the smaller objects. - - Z_INDEX: Sort the output by the z-index of the annotations. This is useful when the annotations have a z-index - - NONE: Do not apply any sorting and output as is presented in the input file. - """ - - REVERSE = "REVERSE" - AREA = "AREA" - Z_INDEX = "Z_INDEX" - NONE = "NONE" - - def to_sorting_params(self) -> Any: - """Get the sorting parameters for the annotation sorting.""" - if self == AnnotationSorting.REVERSE: - return lambda x: None, True - - if self == AnnotationSorting.AREA: - return lambda x: x.area, False - - if self == AnnotationSorting.Z_INDEX: - return lambda x: x.get_field("z_index"), False - - -def _geometry_to_geojson( - geometry: Polygon | Point, label: str | None, color: tuple[int, int, int] | None -) -> dict[str, Any]: - """Function to convert a geometry to a GeoJSON object. - - Parameters - ---------- - geometry : DlupPolygon | DlupPoint - A polygon or point object - label : str - The label name - color : tuple[int, int, int] - The color of the object in RGB values - - Returns - ------- - dict[str, Any] - Output dictionary representing the data in GeoJSON - - """ - geojson: dict[str, Any] = { - "type": "Feature", - "properties": { - "classification": { - "name": label, - }, - }, - "geometry": {}, - } - - if isinstance(geometry, Polygon): - # Construct the coordinates for the polygon - exterior = geometry.get_exterior() # Get exterior coordinates - interiors = geometry.get_interiors() # Get interior coordinates (holes) - - # GeoJSON requires [ [x1, y1], [x2, y2], ... ] format - geojson["geometry"] = { - "type": "Polygon", - "coordinates": [[list(coord) for coord in exterior]] # Exterior ring - + [[list(coord) for coord in interior] for interior in interiors], # Interior rings (holes) - } - - elif isinstance(geometry, Point): - # Construct the coordinates for the point - geojson["geometry"] = { - "type": "Point", - "coordinates": [geometry.x, geometry.y], - } - else: - raise ValueError(f"Unsupported geometry type {type(geometry)}") - - if color is not None: - classification: dict[str, Any] = geojson["properties"]["classification"] - classification["color"] = color - - return geojson - - -def geojson_to_dlup( - coordinates: CoordinatesDict, - label: str, - color: Optional[tuple[int, int, int]] = None, - z_index: Optional[int] = None, -) -> list[Polygon | Point]: - geom_type = coordinates.get("type", None) - if geom_type is None: - raise ValueError("No type found in coordinates.") - geom_type = geom_type.lower() - - if geom_type in ["point", "multipoint"] and z_index is not None: - raise AnnotationError("z_index is not supported for point annotations.") - - if geom_type == "point": - x, y = np.asarray(coordinates["coordinates"]) - return [Point(*(x, y), label=label, color=color)] - - if geom_type == "multipoint": - return [Point(*np.asarray(c).tolist(), label=label, color=color) for c in coordinates["coordinates"]] - - if geom_type == "polygon": - _coordinates = coordinates["coordinates"] - polygon = Polygon( - np.asarray(_coordinates[0]), [np.asarray(hole) for hole in _coordinates[1:]], label=label, color=color - ) - return [polygon] - if geom_type == "multipolygon": - output: list[Polygon | Point] = [] - for polygon_coords in coordinates["coordinates"]: - exterior = np.asarray(polygon_coords[0]) - interiors = [np.asarray(hole) for hole in polygon_coords[1:]] - output.append(Polygon(exterior, interiors, label=label, color=color)) - return output - - raise AnnotationError(f"Unsupported geom_type {geom_type}") - - -class TagAttribute(NamedTuple): - label: str - color: Optional[tuple[int, int, int]] - - -class SlideTag(NamedTuple): - attributes: Optional[list[TagAttribute]] - label: str - color: Optional[tuple[int, int, int]] - - -class SlideAnnotations: - """Class that holds all annotations for a specific image""" - - def __init__( - self, - layers: GeometryCollection, - tags: Optional[tuple[SlideTag, ...]] = None, - 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 - self._offset_function: bool = bool(kwargs.get("offset_function", False)) - self._metadata: Optional[dict[str, list[str] | str | int | float | bool]] = kwargs.get("metadata", None) - - @property - def sorting(self) -> Optional[AnnotationSorting | str]: - return self._sorting - - @property - def tags(self) -> Optional[tuple[SlideTag, ...]]: - return self._tags - - @property - def num_polygons(self) -> int: - return len(self.layers.polygons) - - @property - def num_points(self) -> int: - 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 - def offset_function(self) -> Any: - """ - In some cases a function needs to be applied to the coordinates which cannot be handled in this class as - it might require additional information. This function will be applied to the coordinates of all annotations. - This is useful from a file format which requires this, for instance HaloXML. - - Example - ------- - For HaloXML this is `offset = slide.slide_bounds[0] - slide.slide_bounds[0] % 256`. - >>> slide = Slide.from_file_path("image.svs") - >>> ann = SlideAnnotations.from_halo_xml("annotations.xml") - >>> assert ann.offset_function == lambda slide: slide.slide_bounds[0] - slide.slide_bounds[0] % 256 - >>> ann.set_offset(annotation.offset_function(slide)) - - Returns - ------- - Callable - - """ - return self._offset_function - - @offset_function.setter - def offset_function(self, func: Any) -> None: - self._offset_function = func - - @property - def layers(self) -> GeometryCollection: - """ - Get the layers of the annotations. - This is a GeometryCollection object which contains all the polygons and points - """ - return self._layers - - @classmethod - def from_geojson( - cls: Type[_TSlideAnnotations], - geojsons: PathLike, - scaling: float | None = None, - sorting: AnnotationSorting | str = AnnotationSorting.NONE, - roi_names: Optional[list[str]] = None, - ) -> _TSlideAnnotations: - """ - Read annotations from a GeoJSON file. - - Parameters - ---------- - geojsons : PathLike - GeoJSON file. In the properties key there must be a name which is the label of this - object. - scaling : float, optional - Scaling factor. Sometimes required when GeoJSON annotations are stored in a different resolution than the - original image. - sorting : AnnotationSorting - The sorting to apply to the annotations. Check the `AnnotationSorting` enum for more information. - By default, the annotations are sorted by area. - roi_names : list[str], optional - List of names that should be considered as regions of interest. If set, these will be added as ROIs rather - than polygons. - - Returns - ------- - SlideAnnotations - """ - roi_names = [] if roi_names is None else roi_names - collection = GeometryCollection() - path = pathlib.Path(geojsons) - if not path.exists(): - raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), str(path)) - - with open(path, "r", encoding="utf-8") as annotation_file: - geojson_dict = json.load(annotation_file) - features = geojson_dict["features"] - for x in features: - properties = x["properties"] - if "classification" in properties: - _label = properties["classification"]["name"] - _color = get_geojson_color(properties["classification"]) - elif properties.get("objectType", None) == "annotation": - _label = properties["name"] - _color = get_geojson_color(properties) - else: - raise ValueError("Could not find label in the GeoJSON properties.") - - _geometries = geojson_to_dlup(x["geometry"], label=_label, color=_color) - for geometry in _geometries: - if isinstance(geometry, Polygon): - if geometry.label in roi_names: - collection.add_roi(geometry) - else: - collection.add_polygon(geometry) - elif isinstance(geometry, Point): - collection.add_point(geometry) - else: - raise ValueError(f"Unsupported geometry type {geometry}") - - SlideAnnotations._in_place_sort_and_scale(collection, scaling, sorting) - return cls(layers=collection) - - @classmethod - def from_asap_xml( - cls: Type[_TSlideAnnotations], - asap_xml: PathLike, - scaling: float | None = None, - sorting: AnnotationSorting | str = AnnotationSorting.AREA, - roi_names: Optional[list[str]] = None, - ) -> _TSlideAnnotations: - """ - Read annotations as an ASAP [1] XML file. ASAP is a tool for viewing and annotating whole slide images. - - Parameters - ---------- - asap_xml : PathLike - Path to ASAP XML annotation file. - scaling : float, optional - Scaling factor. Sometimes required when ASAP annotations are stored in a different resolution than the - original image. - sorting: AnnotationSorting - The sorting to apply to the annotations. Check the `AnnotationSorting` enum for more information. - By default, the annotations are sorted by area. - roi_names : list[str], optional - List of names that should be considered as regions of interest. If set, these will be added as ROIs rather - than polygons. - - References - ---------- - .. [1] https://github.com/computationalpathologygroup/ASAP - - Returns - ------- - SlideAnnotations - """ - path = pathlib.Path(asap_xml) - roi_names = [] if roi_names is None else roi_names - if not path.exists(): - raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), str(path)) - - tree = ET.parse(asap_xml) - opened_annotation = tree.getroot() - collection: GeometryCollection = GeometryCollection() - for parent in opened_annotation: - for child in parent: - if child.tag != "Annotation": - continue - label = child.attrib.get("PartOfGroup").strip() # type: ignore - color = hex_to_rgb(child.attrib.get("Color").strip()) # type: ignore - - annotation_type = child.attrib.get("Type").lower() # type: ignore - coordinates = _parse_asap_coordinates(child) - - if annotation_type == "pointset": - for point in coordinates: - collection.add_point(Point(point, label=label, color=color)) - - elif annotation_type == "polygon": - if label in roi_names: - collection.add_roi(Polygon(coordinates, [], label=label, color=color)) - else: - collection.add_polygon(Polygon(coordinates, [], label=label, color=color)) - - SlideAnnotations._in_place_sort_and_scale(collection, scaling, sorting) - return cls(layers=collection) - - @classmethod - def from_darwin_json( - cls: Type[_TSlideAnnotations], - darwin_json: PathLike, - scaling: float | None = None, - sorting: AnnotationSorting | str = AnnotationSorting.NONE, - z_indices: Optional[dict[str, int]] = None, - roi_names: Optional[list[str]] = None, - ) -> _TSlideAnnotations: - """ - Read annotations as a V7 Darwin [1] JSON file. If available will read the `.v7/metadata.json` file to extract - colors from the annotations. - - Parameters - ---------- - darwin_json : PathLike - Path to the Darwin JSON file. - sorting: AnnotationSorting - The sorting to apply to the annotations. Check the `AnnotationSorting` enum for more information. - By default, the annotations are sorted by the z-index which is generated by the order of the saved - annotations. - scaling : float, optional - Scaling factor. Sometimes required when Darwin annotations are stored in a different resolution - than the original image. - z_indices: dict[str, int], optional - If set, these z_indices will be used rather than the default order. - roi_names : list[str], optional - List of names that should be considered as regions of interest. If set, these will be added as ROIs rather - than polygons. - - References - ---------- - .. [1] https://darwin.v7labs.com/ - - Returns - ------- - SlideAnnotations - - """ - if not DARWIN_SDK_AVAILABLE: - raise RuntimeError("`darwin` is not available. Install using `python -m pip install darwin-py`.") - import darwin - - roi_names = [] if roi_names is None else roi_names - - darwin_json_fn = pathlib.Path(darwin_json) - if not darwin_json_fn.exists(): - raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), str(darwin_json_fn)) - - darwin_an = darwin.utils.parse_darwin_json(darwin_json_fn, None) - v7_metadata = get_v7_metadata(darwin_json_fn.parent) - - tags = [] - polygons: list[tuple[Polygon, int]] = [] - - collection = GeometryCollection() - for curr_annotation in darwin_an.annotations: - name = curr_annotation.annotation_class.name - annotation_type = curr_annotation.annotation_class.annotation_type - if annotation_type == "raster_layer": - raise NotImplementedError("Raster annotations are not supported.") - - annotation_color = v7_metadata[(name, annotation_type)].color if v7_metadata else None - - if annotation_type == "tag": - attributes = [] - if curr_annotation.subs: - for subannotation in curr_annotation.subs: - if subannotation.annotation_type == "attributes": - attributes.append(TagAttribute(label=subannotation.data, color=None)) - - tags.append( - SlideTag( - attributes=attributes if attributes != [] else None, - label=name, - color=annotation_color if annotation_color else None, - ) - ) - continue - - z_index = 0 if annotation_type == "keypoint" or z_indices is None else z_indices[name] - curr_data = curr_annotation.data - - if annotation_type == "keypoint": - x, y = curr_data["x"], curr_data["y"] - curr_point = Point(curr_data["x"], curr_data["y"]) - curr_point.label = name - curr_point.color = annotation_color - collection.add_point(curr_point) - - elif annotation_type in ("polygon", "complex_polygon"): - if "path" in curr_data: # This is a regular polygon - curr_polygon = Polygon( - [(_["x"], _["y"]) for _ in curr_data["path"]], [], label=name, color=annotation_color - ) - polygons.append((curr_polygon, z_index)) - - elif "paths" in curr_data: # This is a complex polygon which needs to be parsed with the even-odd rule - for curr_polygon in _parse_darwin_complex_polygon(curr_data, label=name, color=annotation_color): - polygons.append((curr_polygon, z_index)) - else: - raise ValueError(f"Got unexpected data keys: {curr_data.keys()}") - elif annotation_type == "bounding_box": - warnings.warn( - "Bounding box annotations are not fully supported and will be converted to Polygons.", UserWarning - ) - x, y, w, h = curr_data["x"], curr_data["y"], curr_data["w"], curr_data["h"] - curr_polygon = Polygon( - [(x, y), (x + w, y), (x + w, y + h), (x, y + h)], [], label=name, color=annotation_color - ) - polygons.append((curr_polygon, z_index)) - - else: - raise ValueError(f"Annotation type {annotation_type} is not supported.") - - if sorting == "Z_INDEX": - for polygon, _ in sorted(polygons, key=lambda x: x[1]): - if polygon.label in roi_names: - collection.add_roi(polygon) - else: - collection.add_polygon(polygon) - else: - for polygon, _ in polygons: - if polygon.label in roi_names: - collection.add_roi(polygon) - else: - collection.add_polygon(polygon) - - SlideAnnotations._in_place_sort_and_scale( - collection, scaling, sorting="NONE" if sorting == "Z_INDEX" else sorting - ) - return cls(layers=collection, tags=tuple(tags), sorting=sorting) - - @classmethod - def from_dlup_xml(cls: Type[_TSlideAnnotations], dlup_xml: PathLike) -> _TSlideAnnotations: - """ - Read annotations as a DLUP XML file. - - Parameters - ---------- - dlup_xml : PathLike - Path to the DLUP XML file. - - Returns - ------- - SlideAnnotations - """ - path = pathlib.Path(dlup_xml) - if not path.exists(): - raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), str(path)) - - parser = XmlParser() - with open(dlup_xml, "rb") as f: - dlup_annotations = parser.from_bytes(f.read(), XMLDlupAnnotations) - - metadata = None if not dlup_annotations.metadata else asdict(dlup_annotations.metadata) - tags: list[SlideTag] = [] - if dlup_annotations.tags: - for tag in dlup_annotations.tags.tag: - if not tag.label: - raise ValueError("Tag does not have a label.") - curr_tag = SlideTag(attributes=[], label=tag.label, color=hex_to_rgb(tag.color) if tag.color else None) - tags.append(curr_tag) - - collection = GeometryCollection() - polygons: list[tuple[Polygon, int]] = [] - if not dlup_annotations.geometries: - return cls(layers=collection, tags=tuple(tags)) - - if dlup_annotations.geometries.polygon: - polygons += parse_dlup_xml_polygon(dlup_annotations.geometries.polygon) - - # Complain if there are multipolygons - if dlup_annotations.geometries.multi_polygon: - for curr_polygons in dlup_annotations.geometries.multi_polygon: - polygons += parse_dlup_xml_polygon( - curr_polygons.polygon, - order=curr_polygons.order, - label=curr_polygons.label, - index=curr_polygons.index, - ) - - # Now we sort the polygons on order - for polygon, _ in sorted(polygons, key=lambda x: x[1]): - collection.add_polygon(polygon) - - for curr_point in dlup_annotations.geometries.point: - point = Point( - curr_point.x, - curr_point.y, - label=curr_point.label, - color=hex_to_rgb(curr_point.color) if curr_point.color else None, - ) - collection.add_point(point) - - # Complain if there are multipoints - if dlup_annotations.geometries.multi_point: - raise NotImplementedError("Multipoints are not supported.") - - for curr_box in dlup_annotations.geometries.box: - # mypy struggles - assert isinstance(curr_box.x_min, float) - assert isinstance(curr_box.y_min, float) - assert isinstance(curr_box.x_max, float) - assert isinstance(curr_box.y_max, float) - box = Box( - (curr_box.x_min, curr_box.y_min), - (curr_box.x_max - curr_box.x_min, curr_box.y_max - curr_box.y_min), - label=curr_box.label, - color=hex_to_rgb(curr_box.color) if curr_box.color else None, - ) - collection.add_box(box) - - rois: list[tuple[Polygon, int]] = [] - if dlup_annotations.regions_of_interest: - for region_of_interest in dlup_annotations.regions_of_interest.multi_polygon: - raise NotImplementedError( - "MultiPolygon regions of interest are not yet supported. " - "If you have a use case for this, " - "please open an issue at https://github.com/NKI-AI/dlup/issues." - ) - - if dlup_annotations.regions_of_interest.polygon: - rois += parse_dlup_xml_polygon(dlup_annotations.regions_of_interest.polygon) - - if dlup_annotations.regions_of_interest.box: - for _curr_box in dlup_annotations.regions_of_interest.box: - box, curr_order = parse_dlup_xml_roi_box(_curr_box) - rois.append((box.as_polygon(), curr_order)) - for roi, _ in sorted(rois, key=lambda x: x[1]): - collection.add_roi(roi) - - return cls(layers=collection, tags=tuple(tags), metadata=metadata) - - @classmethod - def from_halo_xml( - cls: Type[_TSlideAnnotations], - halo_xml: PathLike, - scaling: float | None = None, - sorting: AnnotationSorting | str = AnnotationSorting.NONE, - box_as_polygon: bool = False, - roi_names: Optional[list[str]] = None, - ) -> _TSlideAnnotations: - """ - Read annotations as a Halo [1] XML file. - This function requires `pyhaloxml` [2] to be installed. - - Parameters - ---------- - halo_xml : PathLike - Path to the Halo XML file. - scaling : float, optional - The scaling to apply to the annotations. - sorting: AnnotationSorting - The sorting to apply to the annotations. Check the `AnnotationSorting` enum for more information. By default - the annotations are not sorted as HALO supports hierarchical annotations. - box_as_polygon : bool - If True, rectangles are converted to polygons, and added as such. - This is useful when the rectangles are actually implicitly bounding boxes. - roi_names : list[str], optional - List of names that should be considered as regions of interest. If set, these will be added as ROIs rather - than polygons. - - References - ---------- - .. [1] https://indicalab.com/halo/ - .. [2] https://github.com/rharkes/pyhaloxml - - Returns - ------- - SlideAnnotations - """ - path = pathlib.Path(halo_xml) - roi_names = [] if roi_names is None else roi_names - - if not path.exists(): - raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), str(path)) - - if not PYHALOXML_AVAILABLE: - raise RuntimeError("`pyhaloxml` is not available. Install using `python -m pip install pyhaloxml`.") - import pyhaloxml.shapely - - collection = GeometryCollection() - with pyhaloxml.HaloXMLFile(halo_xml) as hx: - hx.matchnegative() - for layer in hx.layers: - _color = layer.linecolor.rgb - color = (_color[0], _color[1], _color[2]) - for region in layer.regions: - if region.type == pyhaloxml.RegionType.Rectangle: - # The data is a CCW polygon, so the first and one to last coordinates are the coordinates - vertices = region.getvertices() - min_x = min(v[0] for v in vertices) - max_x = max(v[0] for v in vertices) - min_y = min(v[1] for v in vertices) - max_y = max(v[1] for v in vertices) - curr_box = Box((min_x, min_y), (max_x - min_x, max_y - min_y)) - - if box_as_polygon: - polygon = curr_box.as_polygon() - if polygon.label in roi_names: - collection.add_roi(polygon) - else: - collection.add_polygon(polygon) - else: - collection.add_box(curr_box) - continue - - elif region.type in [pyhaloxml.RegionType.Ellipse, pyhaloxml.RegionType.Polygon]: - polygon = Polygon( - region.getvertices(), [x.getvertices() for x in region.holes], label=layer.name, color=color - ) - if polygon.label in roi_names: - collection.add_roi(polygon) - else: - collection.add_polygon(polygon) - elif region.type == pyhaloxml.RegionType.Pin: - point = Point(*(region.getvertices()[0]), label=layer.name, color=color) - collection.add_point(point) - elif region.type == pyhaloxml.RegionType.Ruler: - warnings.warn( - f"Ruler annotations are not supported. Annotation {layer.name} will be skipped", - UserWarning, - ) - continue - else: - raise NotImplementedError(f"Regiontype {region.type} is not implemented in dlup") - - SlideAnnotations._in_place_sort_and_scale(collection, scaling, sorting) - - def offset_function(slide: "SlideImage") -> tuple[int, int]: - return ( - slide.slide_bounds[0][0] - slide.slide_bounds[0][0] % 256, - slide.slide_bounds[0][1] - slide.slide_bounds[0][1] % 256, - ) - - return cls(collection, tags=None, sorting=sorting, offset_function=offset_function) - - @staticmethod - def _in_place_sort_and_scale( - collection: GeometryCollection, scaling: Optional[float], sorting: Optional[AnnotationSorting | str] - ) -> None: - if sorting == "REVERSE": - raise NotImplementedError("This doesn't work for now.") - - if scaling != 1.0 and scaling is not None: - collection.scale(scaling) - if sorting == AnnotationSorting.NONE or sorting is None: - return - if isinstance(sorting, str): - key, reverse = AnnotationSorting[sorting].to_sorting_params() - else: - key, reverse = sorting.to_sorting_params() - collection.sort_polygons(key, reverse) - - def as_geojson(self) -> GeoJsonDict: - """ - Output the annotations as proper geojson. These outputs are sorted according to the `AnnotationSorting` selected - for the annotations. This ensures the annotations are correctly sorted in the output. - - The output is not completely GeoJSON compliant as some parts such as the metadata and properties are not part - of the standard. However, these are implemented to ensure the output is compatible with QuPath. - - Returns - ------- - GeoJsonDict - The output as a GeoJSON dictionary. - """ - data: GeoJsonDict = {"type": "FeatureCollection", "metadata": None, "features": [], "id": None} - if self.tags: - data["metadata"] = {"tags": [_.label for _ in self.tags]} - - if self._layers.boxes: - warnings.warn("Bounding boxes are not supported in GeoJSON and will be skipped.", UserWarning) - - all_layers = self.layers.polygons + self.layers.points - for idx, curr_annotation in enumerate(all_layers): - json_dict = _geometry_to_geojson(curr_annotation, label=curr_annotation.label, color=curr_annotation.color) - json_dict["id"] = str(idx) - data["features"].append(json_dict) - - return data - - def as_dlup_xml( - self, - image_id: Optional[str] = None, - description: Optional[str] = None, - version: Optional[str] = None, - authors: Optional[list[str]] = None, - indent: Optional[int] = 2, - ) -> str: - """ - Output the annotations as DLUP XML. - This format supports the complete serialization of a SlideAnnotations object. - - Parameters - ---------- - image_id : str, optional - The image ID corresponding to this annotation. - description : str, optional - Description of the annotations. - version : str, optional - Version of the annotations. - authors : list[str], optional - Authors of the annotations. - indent : int, optional - Indent for pretty printing. - - Returns - ------- - str - The output as a DLUP XML string. - """ - - metadata = XMLMetadata( - image_id=image_id if image_id is not None else "", - description=description if description is not None else "", - version=version if version is not None else "", - authors=XMLMetadata.Authors(authors) if authors is not None else None, - date_created=XmlDate.from_string(datetime.now().strftime("%Y-%m-%d")), - software=f"dlup {__version__}", - ) - xml_tags: list[XMLTag] = [] - if self.tags: - for tag in self.tags: - if tag.attributes: - attrs = [ - XMLTag.Attribute(value=_.label, color=rgb_to_hex(*_.color) if _.color else None) - for _ in tag.attributes - ] - xml_tag = XMLTag( - attribute=attrs if tag.attributes else [], - label=tag.label, - color=rgb_to_hex(*tag.color) if tag.color else None, - ) - xml_tags.append(xml_tag) - - tags = XMLTags(tag=xml_tags) if xml_tags else None - - geometries = create_xml_geometries(self._layers) - rois = create_xml_rois(self._layers) - - extra_annotation_params: dict[str, XMLTags] = {} - if tags: - extra_annotation_params["tags"] = tags - - dlup_annotations = XMLDlupAnnotations( - metadata=metadata, geometries=geometries, regions_of_interest=rois, **extra_annotation_params - ) - config = SerializerConfig(pretty_print=True) - serializer = XmlSerializer(config=config) - return serializer.render(dlup_annotations) - - @property - def bounding_box(self) -> tuple[tuple[float, float], tuple[float, float]]: - """Get the bounding box of the annotations combining points and polygons. - - Returns - ------- - tuple[tuple[float, float], tuple[float, float]] - The bounding box of the annotations. - - """ - return self._layers.bounding_box - - def bounding_box_at_scaling(self, scaling: float) -> tuple[tuple[float, float], tuple[float, float]]: - """Get the bounding box of the annotations at a specific scaling factor. - - Parameters - ---------- - scaling : float - The scaling factor to apply to the annotations. - - Returns - ------- - tuple[tuple[float, float], tuple[float, float]] - The bounding box of the annotations at the specific scaling factor. - - """ - bbox = self.bounding_box - return ((bbox[0][0] * scaling, bbox[0][1] * scaling), (bbox[1][0] * scaling, bbox[1][1] * scaling)) - - def simplify(self, tolerance: float) -> None: - """Simplify the polygons in the annotation (i.e. reduce points). Other annotations will remain unchanged. - All points in the resulting polygons object will be in the tolerance distance of the original polygon. - - Parameters - ---------- - tolerance : float - The tolerance to simplify the polygons with. - Returns - ------- - None - """ - self._layers.simplify(tolerance) - - def __contains__(self, item: str | Point | Polygon) -> bool: - if isinstance(item, str): - return item in self.available_classes - if isinstance(item, Point): - return item in self.layers.points - if isinstance(item, Polygon): - return item in self.layers.polygons - - return False - - def __len__(self) -> int: - return self._layers.size() - - @property - def available_classes(self) -> set[str]: - """Get the available classes in the annotations. - - Returns - ------- - set[str] - The available classes in the annotations. - - """ - available_classes = set() - for polygon in self.layers.polygons: - if polygon.label is not None: - available_classes.add(polygon.label) - for point in self.layers.points: - if point.label is not None: - available_classes.add(point.label) - - return available_classes - - def __iter__(self) -> Iterable[Polygon | Point]: - # First returns all the polygons then all points - for polygon in self.layers.polygons: - yield polygon - - for point in self.layers.points: - yield point - - def __add__(self, other: Any) -> "SlideAnnotations": - """ - Add two annotations together. This will return a new `SlideAnnotations` object with the annotations combined. - The polygons will be added from left to right followed the points from left to right. - - Notes - ----- - - The polygons and points are shared between the objects. This means that if you modify the polygons or points - in the new object, the original objects will also be modified. If you wish to avoid this, you must add two - copies together. - - Note that the sorting is not applied to this object. You can apply this by calling `sort_polygons()` on - the resulting object. - - Parameters - ---------- - other : SlideAnnotations - The other annotations to add. - - """ - if not isinstance(other, (SlideAnnotations, Point, Polygon, list)): - raise TypeError(f"Unsupported type {type(other)}") - - if isinstance(other, SlideAnnotations): - if not self.sorting == other.sorting: - raise TypeError("Cannot add annotations with different sorting.") - if self.offset_function != other.offset_function: - raise TypeError( - "Cannot add annotations with different requirements for offsetting to slide bounds " - "(`offset_function`)." - ) - - tags: tuple[SlideTag, ...] = () - if self.tags is None and other.tags is not None: - tags = other.tags - - if other.tags is None and self.tags is not None: - tags = self.tags - - if self.tags is not None and other.tags is not None: - tags = tuple(set(self.tags + other.tags)) - - # Let's add the annotations - collection = GeometryCollection() - for polygon in self.layers.polygons: - collection.add_polygon(copy.deepcopy(polygon)) - for point in self.layers.points: - collection.add_point(copy.deepcopy(point)) - - for polygon in other.layers.polygons: - collection.add_polygon(copy.deepcopy(polygon)) - for point in other.layers.points: - collection.add_point(copy.deepcopy(point)) - - SlideAnnotations._in_place_sort_and_scale(collection, None, self.sorting) - return self.__class__(layers=collection, tags=tuple(tags) if tags else None, sorting=self.sorting) - - if isinstance(other, (Point, Polygon)): - other = [other] - - if isinstance(other, list): - if not all(isinstance(item, (Point, Polygon)) for item in other): - raise TypeError( - f"can only add list purely containing Point and Polygon objects to {self.__class__.__name__}" - ) - - collection = copy.copy(self._layers) - for item in other: - if isinstance(item, Polygon): - collection.add_polygon(item) - elif isinstance(item, Point): - collection.add_point(item) - SlideAnnotations._in_place_sort_and_scale(collection, None, self.sorting) - return self.__class__(layers=collection, tags=copy.copy(self._tags), sorting=self.sorting) - - raise ValueError(f"Unsupported type {type(other)}") - - def __iadd__(self, other: Any) -> "SlideAnnotations": - if isinstance(other, (Point, Polygon)): - other = [other] - - if isinstance(other, list): - if not all(isinstance(item, (Point, Polygon)) for item in other): - raise TypeError( - f"can only add list purely containing Point and Polygon objects {self.__class__.__name__}" - ) - - for item in other: - if isinstance(item, Polygon): - self._layers.add_polygon(copy.deepcopy(item)) - elif isinstance(item, Point): - self._layers.add_point(copy.deepcopy(item)) - - elif isinstance(other, SlideAnnotations): - if self.sorting != other.sorting or self.offset_function != other.offset_function: - raise ValueError( - f"Both sorting and offset_function must be the same to " f"add {self.__class__.__name__}s together." - ) - - if self._tags is None: - self._tags = other._tags - elif other._tags is not None: - assert self - self._tags += other._tags - - for polygon in other.layers.polygons: - self._layers.add_polygon(copy.deepcopy(polygon)) - for point in other.layers.points: - self._layers.add_point(copy.deepcopy(point)) - else: - return NotImplemented - SlideAnnotations._in_place_sort_and_scale(self._layers, None, self.sorting) - - return self - - def __radd__(self, other: Any) -> "SlideAnnotations": - # in-place addition (+=) of Point and Polygon will raise a TypeError - if not isinstance(other, (SlideAnnotations, Point, Polygon, list)): - raise TypeError(f"Unsupported type {type(other)}") - if isinstance(other, list): - if not all(isinstance(item, (Polygon, Point)) for item in other): - raise TypeError( - f"can only add list purely containing Point and Polygon objects to {self.__class__.__name__}" - ) - raise TypeError( - "use the __add__ or __iadd__ operator instead of __radd__ when working with lists to avoid \ - unexpected behavior." - ) - return self + other - - def __sub__(self, other: Any) -> "SlideAnnotations": - return NotImplemented - - def __isub__(self, other: Any) -> "SlideAnnotations": - return NotImplemented - - def __rsub__(self, other: Any) -> "SlideAnnotations": - return NotImplemented - - def __eq__(self, other: Any) -> bool: - if not isinstance(other, SlideAnnotations): - return False - - our_sorting = self._sorting if self._sorting != AnnotationSorting.NONE else None - other_sorting = other._sorting if other._sorting != AnnotationSorting.NONE else None - - if our_sorting != other_sorting: - return False - - if self._tags != other._tags: - return False - - if self._layers != other._layers: - return False - - if self._offset_function != other._offset_function: - return False - - return True - - def read_region( - self, - coordinates: tuple[GenericNumber, GenericNumber], - 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.get_geometries() # This is a list of `dlup.geometry.Polygon` objects - """ - region = self._layers.read_region(coordinates, scaling, size) - return region - - def scale(self, scaling: float) -> None: - """ - Scale the annotations by a multiplication factor. - This operation will be performed in-place. - - Parameters - ---------- - scaling : float - The scaling factor to apply to the annotations. - - Notes - ----- - This invalidates the R-tree. You could rebuild this manually using `.rebuild_rtree()`, or have the function - `read_region()` do it for you on-demand. - - Returns - ------- - None - """ - self._layers.scale(scaling) - - def set_offset(self, offset: tuple[float, float]) -> None: - """Set the offset for the annotations. This operation will be performed in-place. - - For example, if the offset is 1, 1, the annotations will be moved by 1 unit in the x and y direction. - - Parameters - ---------- - offset : tuple[float, float] - The offset to apply to the annotations. - - Notes - ----- - This invalidates the R-tree. You could rebuild this manually using `.rebuild_rtree()`, or have the function - `read_region()` do it for you on-demand. - - Returns - ------- - None - """ - self._layers.set_offset(offset) - - def rebuild_rtree(self) -> None: - """ - Rebuild the R-tree for the annotations. This operation will be performed in-place. - The R-tree is used for fast spatial queries on the annotations and is invalidated when the annotations are - modified. This function will rebuild the R-tree. Strictly speaking, this is not required as the R-tree will be - rebuilt on-demand when you invoke a `read_region()`. You could however do this if you want to avoid - the `read_region()` to do it for you the first time it runs. - """ - - self._layers.rebuild_rtree() - - def reindex_polygons(self, index_map: dict[str, int]) -> None: - """ - Reindex the polygons in the annotations. This operation will be performed in-place. - This is useful if you want to change the index of the polygons in the annotations. - - This requires that the `.label` property on the polygons is set. - - Parameters - ---------- - index_map : dict[str, int] - A dictionary that maps the label to the new index. - - Returns - ------- - None - """ - self._layers.reindex_polygons(index_map) - - def relabel_polygons(self, relabel_map: dict[str, str]) -> None: - """ - Relabel the polygons in the annotations. This operation will be performed in-place. - - Parameters - ---------- - relabel_map : dict[str, str] - A dictionary that maps the label to the new label. - - Returns - ------- - None - """ - # TODO: Implement in C++ - for polygon in self._layers.polygons: - if not polygon.label: - continue - if polygon.label in relabel_map: - polygon.label = relabel_map[polygon.label] - - def filter_polygons(self, label: str) -> None: - """Filter polygons in-place. - - Note - ---- - This will internally invalidate the R-tree. You could rebuild this manually using `.rebuild_rtree()`, or - have the function itself do this on-demand (typically when you invoke a `.read_region()`) - - Parameters - ---------- - label : str - The label to filter. - - """ - for polygon in self._layers.polygons: - if polygon.label == label: - self._layers.remove_polygon(polygon) - - def filter_points(self, label: str) -> None: - """Filter points in-place. - - Note - ---- - This will internally invalidate the R-tree. You could rebuild this manually using `.rebuild_rtree()`, or - have the function itself do this on-demand (typically when you invoke a `.read_region()`) - - Parameters - ---------- - label : str - The label to filter. - - """ - for point in self._layers.points: - if point.label == label: - self._layers.remove_point(point) - - def filter(self, label: str) -> None: - """Filter annotations in-place. - - Note - ---- - This will internally invalidate the R-tree. You could rebuild this manually using `.rebuild_rtree()`, or - have the function itself do this on-demand (typically when you invoke a `.read_region()`) - - Parameters - ---------- - label : str - The label to filter. - - """ - self.filter_polygons(label) - self.filter_points(label) - - def sort_polygons(self, key: Callable[[Polygon], int | float | str], reverse: bool = False) -> None: - """Sort the polygons in-place. - - Parameters - ---------- - key : callable - The key to sort the polygons on, this has to be a lambda function or similar. - For instance `lambda polygon: polygon.area` will sort the polygons on the area, or - `lambda polygon: polygon.get_field(field_name)` will sort the polygons on that field. - reverse : bool - Whether to sort in reverse order. - - Note - ---- - This will internally invalidate the R-tree. You could rebuild this manually using `.rebuild_rtree()`, or - have the function itself do this on-demand (typically when you invoke a `.read_region()`) - - Returns - ------- - None - - """ - self._layers.sort_polygons(key, reverse) - - @property - def color_lut(self) -> npt.NDArray[np.uint8]: - """Get the color lookup table for the annotations. - - Requires that the polygons have an index and color set. Be aware that for the background always - the value 0 is assumed. - So if you are using the `to_mask(default_value=0)` with a default value other than 0, - the LUT will still have this as index 0. - - Example - ------- - >>> color_lut = annotations.color_lut - >>> region = annotations.read_region(region_start, 0.02, region_size).to_mask() - >>> colored_mask = PIL.Image.fromarray(color_lut[mask]) - - Returns - ------- - np.ndarray - The color lookup table. - - """ - return self._layers.color_lut - - def __copy__(self) -> "SlideAnnotations": - return self.__class__(layers=copy.copy(self._layers), tags=copy.copy(self._tags)) - - def __deepcopy__(self, memo: dict[int, Any]) -> "SlideAnnotations": - return self.__class__(layers=copy.deepcopy(self._layers, memo), tags=copy.deepcopy(self._tags, memo)) - - def copy(self) -> "SlideAnnotations": - return self.__copy__() - - -def _parse_asap_coordinates( - annotation_structure: ET.Element, -) -> list[tuple[float, float]]: - """ - Parse ASAP XML coordinates into list. - - Parameters - ---------- - annotation_structure : list of strings - - Returns - ------- - list[tuple[float, float]] - - """ - coordinates = [] - coordinate_structure = annotation_structure[0] - - for coordinate in coordinate_structure: - coordinates.append( - ( - float(coordinate.get("X").replace(",", ".")), # type: ignore - float(coordinate.get("Y").replace(",", ".")), # type: ignore - ) - ) - - return coordinates - - -def _parse_darwin_complex_polygon( - annotation: dict[str, Any], label: str, color: Optional[tuple[int, int, int]] -) -> Iterable[Polygon]: - """ - Parse a complex polygon (i.e. polygon with holes) from a Darwin annotation. - - Parameters - ---------- - annotation : dict - The annotation dictionary - label : str - The label of the annotation - color : tuple[int, int, int] - The color of the annotation - - Returns - ------- - Iterable[DlupPolygon] - """ - # Create Polygons and sort by area in descending order - polygons = [Polygon([(p["x"], p["y"]) for p in path], []) for path in annotation["paths"]] - polygons.sort(key=lambda x: x.area, reverse=True) - - outer_polygons: list[tuple[Polygon, list[Any], bool]] = [] - for polygon in polygons: - is_hole = False - # Check if the polygon can be a hole in any of the previously processed polygons - for outer_poly, holes, outer_poly_is_hole in reversed(outer_polygons): - contains = outer_poly.contains(polygon) - # If polygon is contained by a hole, it should be added as new polygon - if contains and outer_poly_is_hole: - break - # Polygon is added as hole if outer polygon is not a hole - elif contains: - holes.append(polygon.get_exterior()) - is_hole = True - break - outer_polygons.append((polygon, [], is_hole)) - - for outer_poly, holes, _is_hole in outer_polygons: - if not _is_hole: - polygon = Polygon(outer_poly.get_exterior(), holes) - polygon.label = label - polygon.color = color - yield polygon diff --git a/dlup/data/dataset.py b/dlup/data/dataset.py index 80cae0f4..a602905e 100644 --- a/dlup/data/dataset.py +++ b/dlup/data/dataset.py @@ -33,7 +33,7 @@ from dlup import BoundaryMode, SlideImage from dlup._types import PathLike, ROIType -from dlup.annotations import DlupShapelyPoint, DlupShapelyPolygon, WsiAnnotations +from dlup.annotations import Point, Polygon, WsiAnnotations from dlup.backends.common import AbstractSlideBackend from dlup.background import compute_masked_indices from dlup.tiling import Grid, GridOrder, TilingMode @@ -42,7 +42,7 @@ MaskTypes = Union["SlideImage", npt.NDArray[np.int_], "WsiAnnotations"] -_AnnotationsTypes = DlupShapelyPoint | DlupShapelyPolygon +_AnnotationsTypes = Point | Polygon T_co = TypeVar("T_co", covariant=True) T = TypeVar("T") diff --git a/dlup/data/transforms.py b/dlup/data/transforms.py index ca969acb..2240690e 100644 --- a/dlup/data/transforms.py +++ b/dlup/data/transforms.py @@ -14,7 +14,7 @@ from dlup.annotations import AnnotationClass, AnnotationType from dlup.data.dataset import PointType, TileSample, TileSampleWithAnnotationData -_AnnotationsTypes = dlup.annotations.DlupShapelyPoint | dlup.annotations.DlupShapelyPolygon +_AnnotationsTypes = dlup.annotations.Point | dlup.annotations.Polygon def convert_annotations( @@ -83,7 +83,7 @@ def convert_annotations( has_roi = False for curr_annotation in annotations: holes_mask = None - if isinstance(curr_annotation, dlup.annotations.DlupShapelyPoint): + if isinstance(curr_annotation, dlup.annotations.Point): coords = tuple(curr_annotation.coords) points[curr_annotation.label] += tuple(coords) continue @@ -234,13 +234,13 @@ def rename_labels(annotations: Iterable[_AnnotationsTypes], remap_labels: dict[s if annotation.annotation_class.annotation_type == AnnotationType.BOX: a_cls = AnnotationClass(label=remap_labels[label], annotation_type=AnnotationType.BOX) - output_annotations.append(dlup.annotations.DlupShapelyPolygon(annotation, a_cls=a_cls)) + output_annotations.append(dlup.annotations.Polygon(annotation, a_cls=a_cls)) elif annotation.annotation_class.annotation_type == AnnotationType.POLYGON: a_cls = AnnotationClass(label=remap_labels[label], annotation_type=AnnotationType.POLYGON) - output_annotations.append(dlup.annotations.DlupShapelyPolygon(annotation, a_cls=a_cls)) + output_annotations.append(dlup.annotations.Polygon(annotation, a_cls=a_cls)) elif annotation.annotation_class.annotation_type == AnnotationType.POINT: a_cls = AnnotationClass(label=remap_labels[label], annotation_type=AnnotationType.POINT) - output_annotations.append(dlup.annotations.DlupShapelyPoint(annotation, a_cls=a_cls)) + output_annotations.append(dlup.annotations.Point(annotation, a_cls=a_cls)) else: raise AnnotationError(f"Unsupported annotation type {annotation.annotation_class.annotation_type}") diff --git a/dlup/geometry.py b/dlup/geometry.py deleted file mode 100644 index 82891402..00000000 --- a/dlup/geometry.py +++ /dev/null @@ -1,479 +0,0 @@ -# Copyright (c) dlup contributors -"""Module for geometric objects""" -import copy -import warnings -from typing import Any, Optional - -import numpy as np -import numpy.typing as npt - -import dlup._geometry as _dg # pylint: disable=no-name-in-module -from dlup.utils.imports import SHAPELY_AVAILABLE - -if SHAPELY_AVAILABLE: - from shapely.geometry import Point as ShapelyPoint - from shapely.geometry import Polygon as ShapelyPolygon - - -class _BaseGeometry: - def __init__(self, *args: Any, **kwargs: Any): - pass - - @classmethod - def from_shapely(cls, shapely_geometry: ShapelyPoint | ShapelyPolygon) -> "_BaseGeometry": - """Create a new instance of the geometry from a Shapely geometry - - Parameters - ---------- - shapely_geometry : ShapelyPoint | ShapelyPolygon - The Shapely geometry to convert - - Returns - ------- - _BaseGeometry - The new instance of the geometry - """ - raise NotImplementedError - - def set_field(self, name: str, value: Any) -> None: - """Set a field on the geometry. This can be in arbitrary python object. - - Parameters - ---------- - name : str - The name of the field to set - value : Any - The value of the field - - Returns - ------- - None - """ - raise NotImplementedError - - def get_field(self, name: str) -> Any: - """Get a field from the geometry. This can be in arbitrary python object. - - Parameters - ---------- - name : str - The name of the field to get - - Returns - ------- - Any - The value of the field - """ - raise NotImplementedError - - @property - def fields(self) -> list[str]: - - raise NotImplementedError - - @property - def wkt(self) -> str: - raise NotImplementedError - - @property - def label(self) -> Optional[str]: - field = self.get_field("label") - if field is None: - return None - # if field is not isinstance(field, str): - # raise ValueError(f"Label must be a string, got {type(field)}") - assert isinstance(field, str) - return field - - @label.setter - def label(self, value: str) -> None: - if not isinstance(value, str): - raise ValueError(f"Label must be a string, got {type(value)}") - self.set_field("label", value) - - @property - def index(self) -> Optional[int]: - field = self.get_field("index") - if field is None: - return None - if not isinstance(field, int): - raise ValueError(f"Index must be an integer, got {type(field)}") - assert isinstance(field, int) - return field - - @index.setter - def index(self, value: int) -> None: - if not isinstance(value, int): - raise ValueError(f"Index must be an integer, got {type(value)}") - self.set_field("index", value) - - @property - def color(self) -> Optional[tuple[int, int, int]]: - field = self.get_field("color") - if field is None: - return None - if not isinstance(field, tuple) or len(field) != 3: - raise ValueError(f"Color must be an RGB tuple, got {type(field)}") - assert isinstance(field, tuple) - return field - - @color.setter - def color(self, value: tuple[int, int, int]) -> None: - if not isinstance(value, tuple) or len(value) != 3: - raise ValueError(f"Color must be an RGB tuple, got {type(value)}") - self.set_field("color", value) - - def __eq__(self, other: Any) -> bool: - if not isinstance(other, type(self)): - return False - - fields = self.fields - other_fields = other.fields - - if sorted(fields) != sorted(other_fields): - return False - - for field in fields: - if self.get_field(field) != other.get_field(field): - return False - if self.wkt != other.wkt: - return False - - return True - - def __iadd__(self, other: Any) -> None: - # TODO: We can support MultiPoint and MultiPolygon - raise TypeError(f"Unsupported operand type(s) for +=: {type(self)} and {type(other)}") - - def __isub__(self, other: Any) -> None: - # TODO: We can support MultiPoint and MultiPolygon - raise TypeError(f"Unsupported operand type(s) for -=: {type(self)} and {type(other)}") - - def __repr__(self) -> str: - repr_string = f"<{self.__class__.__name__}(" - parts = [] - for field in sorted(self.fields): - value = self.get_field(field) - parts.append(f"{field}={value}") - - repr_string += ", ".join(parts) - - if len(self.wkt) > 30: - repr_string += f") WKT='{self.wkt[:30]}...'>" - else: - repr_string += f") WKT='{self.wkt}'>" - return repr_string - - -class Polygon(_dg.Polygon, _BaseGeometry): - def __init__(self, *args: Any, **kwargs: Any): - _BaseGeometry.__init__(self) - if SHAPELY_AVAILABLE: - if len(args) == 1 and len(kwargs) == 0 and isinstance(args[0], ShapelyPolygon): - warnings.warn( - "Creating a Polygon from a Shapely Polygon is deprecated and will be removed dlup v1.0.0. " - "Please use the `from_shapely` method instead.", - UserWarning, - ) - shapely_polygon = args[0] - exterior = list(shapely_polygon.exterior.coords) - interiors = [list(interior.coords) for interior in shapely_polygon.interiors] - args = (exterior, interiors) - - # Ensure no new Polygon is created; just wrap the existing one - if len(args) == 1 and len(kwargs) == 0 and isinstance(args[0], _dg.Polygon): - super().__init__(args[0]) # This should keep the original parameters intact - else: # This needs to be way more elaborate - fields = {} - if "label" in kwargs: - fields["label"] = kwargs.pop("label") - if "index" in kwargs: - fields["index"] = kwargs.pop("index") - if "color" in kwargs: - fields["color"] = kwargs.pop("color") - - if len(args) == 1: - # No interior - args = (args[0], []) - - super().__init__(*args, **kwargs) - for key, value in fields.items(): - self.set_field(key, value) - - @classmethod - def from_shapely(cls, shapely_polygon: "ShapelyPolygon") -> "Polygon": - if not SHAPELY_AVAILABLE: - raise ImportError( - "Shapely is not available, and this functionality requires it. " - "Install it using `pip install shapely`, " - "or consult the documentation https://shapely.readthedocs.io/en/stable/installation.html " - "for more information." - ) - - if not isinstance(shapely_polygon, ShapelyPolygon): - raise ValueError(f"Expected a shapely.geometry.Polygon, but got {type(shapely_polygon)}") - - exterior = list(shapely_polygon.exterior.coords) - interiors = [list(interior.coords) for interior in shapely_polygon.interiors] - return cls(exterior, interiors) - - def __getstate__(self) -> dict[str, dict[str, Any]]: - state = { - "_fields": {field: self.get_field(field) for field in self.fields}, - "_object": {"interiors": self.get_interiors(), "exterior": self.get_exterior()}, - } - return state - - def __setstate__(self, state: dict[str, dict[str, Any]]) -> None: - exterior = state["_object"]["exterior"] - interiors = state["_object"]["interiors"] - - Polygon.__init__(self, exterior, interiors) - - for key, value in state["_fields"].items(): - self.set_field(key, value) - - def __copy__(self) -> "Polygon": - warnings.warn( - "Copying a Polygon currently creates a complete new object, without reference to the previous one, " - "and is essentially the same as a deepcopy." - ) - new_copy = Polygon(self) - return new_copy - - def __deepcopy__(self, memo: Any) -> "Polygon": - # Create a deepcopy of the geometry - new_copy = Polygon(copy.deepcopy(self.get_exterior(), memo), copy.deepcopy(self.get_interiors(), memo)) - - # Deepcopy the fields - for field in self.fields: - new_copy.set_field(field, copy.deepcopy(self.get_field(field), memo)) - - return new_copy - - def to_shapely(self) -> "ShapelyPolygon": - if not SHAPELY_AVAILABLE: - raise ImportError( - "Shapely is not available, and this functionality requires it. " - "Install it using `pip install shapely`, " - "or consult the documentation https://shapely.readthedocs.io/en/stable/installation.html " - "for more information." - ) - - import shapely.geometry - - exterior = self.get_exterior() - interiors = self.get_interiors() - return shapely.geometry.Polygon(exterior, interiors) - - -def _polygon_factory(polygon: _dg.Polygon) -> "Polygon": - return Polygon(polygon) - - -# This is required to ensure that the polygons created in the C++ code are converted to the correct Python class -_dg.set_polygon_factory(_polygon_factory) - - -class Point(_dg.Point, _BaseGeometry): - def __init__(self, *args: Any, **kwargs: Any) -> None: - _BaseGeometry.__init__(self) - if SHAPELY_AVAILABLE: - if len(args) == 1 and len(kwargs) == 0 and isinstance(args[0], ShapelyPoint): - warnings.warn( - "Creating a Polygon from a Shapely Point is deprecated and will be removed dlup v1.0.0. " - "Please use the `from_shapely` method instead.", - UserWarning, - ) - shapely_point = args[0] - args = (shapely_point.x, shapely_point.y) - - # Ensure no new Point is created; just wrap the existing one - if len(args) == 1 and len(kwargs) == 0 and isinstance(args[0], _dg.Point): - super().__init__(args[0]) # This should keep the original parameters intact - else: # This needs to be way more elaborate - fields = {} - if "label" in kwargs: - fields["label"] = kwargs.pop("label") - if "index" in kwargs: - fields["index"] = kwargs.pop("index") - if "color" in kwargs: - fields["color"] = kwargs.pop("color") - - super().__init__(*args, **kwargs) - for key, value in fields.items(): - self.set_field(key, value) - - @classmethod - def from_shapely(cls, shapely_point: "ShapelyPoint") -> "Point": - if not SHAPELY_AVAILABLE: - raise ImportError( - "Shapely is not available, and this functionality requires it. " - "Install it using `pip install shapely`, " - "or consult the documentation https://shapely.readthedocs.io/en/stable/installation.html " - "for more information." - ) - - if not isinstance(shapely_point, ShapelyPoint): - raise ValueError(f"Expected a shapely.geometry.Point, but got {type(shapely_point)}") - - return cls(shapely_point.x, shapely_point.y) - - def to_shapely(self) -> "ShapelyPoint": - if not SHAPELY_AVAILABLE: - raise ImportError( - "Shapely is not available, and this functionality requires it. " - "Install it using `pip install shapely`, " - "or consult the documentation https://shapely.readthedocs.io/en/stable/installation.html " - "for more information." - ) - - return ShapelyPoint(self.x, self.y) - - def __copy__(self) -> "Point": - # Create a new instance of DlupPolygon with the same geometry - new_copy = Point(self.x, self.y) - - for field in self.fields: - new_copy.set_field(field, self.get_field(field)) - - return new_copy - - def __deepcopy__(self, memo: Any) -> "Point": - # Create a deepcopy of the geometry - new_copy = Point(copy.deepcopy(self.x), copy.deepcopy(self.y)) - - # Deepcopy the fields - for field in self.fields: - new_copy.set_field(field, copy.deepcopy(self.get_field(field), memo)) - - return new_copy - - def __getstate__(self) -> dict[str, dict[str, Any]]: - state = { - "_fields": {field: self.get_field(field) for field in self.fields}, - "_object": {"coordinates": (self.x, self.y)}, - } - return state - - def __setstate__(self, state: dict[str, dict[str, Any]]) -> None: - coordinates = state["_object"]["coordinates"] - Point.__init__(self, coordinates[0], coordinates[1]) - for key, value in state["_fields"].items(): - self.set_field(key, value) - - -def _point_factory(point: _dg.Point) -> Point: - return Point(point) - - -# Register the point factory -_dg.set_point_factory(_point_factory) - - -class Box(_dg.Box, _BaseGeometry): - def __init__(self, *args: Any, **kwargs: Any) -> None: - _BaseGeometry.__init__(self) - # Ensure no new Point is created; just wrap the existing one - if len(args) == 1 and len(kwargs) == 0 and isinstance(args[0], _dg.Box): - super().__init__(args[0]) # This should keep the original parameters intact - else: # This needs to be way more elaborate - fields = {} - if "label" in kwargs: - fields["label"] = kwargs.pop("label") - if "index" in kwargs: - fields["index"] = kwargs.pop("index") - if "color" in kwargs: - fields["color"] = kwargs.pop("color") - - super().__init__(*args, **kwargs) - for key, value in fields.items(): - self.set_field(key, value) - - -def _box_factory(box: _dg.Box) -> Box: - return Box(box) - - -# Register the box factory -_dg.set_box_factory(_box_factory) - - -# TODO: Allow to construct geometry collection from a list of polygons, bypassing the python loop -class GeometryCollection(_dg.GeometryCollection): - def __init__(self) -> None: - super().__init__() - - @property - def color_lut(self) -> npt.NDArray[np.uint8]: - color_map = {} - for r in self.polygons: - color = r.color - index = r.index - if not index: - raise ValueError("Index needs to be set on Polygon to create a color lookup table") - if not color: - raise ValueError("Color needs to be set on Polygon to create a color lookup table") - - color_map[index] = color - - max_index = max(color_map.keys()) - LUT = np.zeros((max_index + 1, 3), dtype=np.uint8) - for key, color in color_map.items(): - LUT[key] = color - - return LUT - - def __getstate__(self) -> dict[str, Any]: - state = { - "_polygons": [polygon.__getstate__() for polygon in self.polygons], - "_points": [point.__getstate__() for point in self.points], - } - return state - - def __setstate__(self, state: dict[str, list[dict[str, Any]]]) -> None: - polygons = [Polygon.__new__(Polygon) for _ in state["_polygons"]] - for polygon, polygon_state in zip(polygons, state["_polygons"]): - polygon.__setstate__(polygon_state) - - points = [Point.__new__(Point) for _ in state["_points"]] - for point, point_state in zip(points, state["_points"]): - point.__setstate__(point_state) - - GeometryCollection.__init__(self) - for polygon in polygons: - self.add_polygon(polygon) - - for point in points: - self.add_point(point) - - def __copy__(self) -> "GeometryCollection": - collection = GeometryCollection() - for polygon in self.polygons: - collection.add_polygon(polygon.__copy__()) - for point in self.points: - collection.add_point(point.__copy__()) - collection.rebuild_rtree() - return collection - - def __eq__(self, other: Any) -> bool: - if not isinstance(other, type(self)): - return False - - if len(self) != len(other): - return False - - if self.boxes != other.boxes: - return False - - if self.polygons != other.polygons: - return False - - if self.points != other.points: - return False - - return True - - def __len__(self) -> int: - # Also self.size() - return len(self.polygons) + len(self.points) + len(self.boxes) diff --git a/dlup/meson.build b/dlup/meson.build deleted file mode 100644 index 1aa5a2eb..00000000 --- a/dlup/meson.build +++ /dev/null @@ -1,12 +0,0 @@ -# Include the NumPy headers -incdir_numpy = get_variable('incdir_numpy', []) - -# Cython extension module -background = py.extension_module('_background', - '_background.pyx', - include_directories : [incdir_numpy], - install : true, - 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 diff --git a/dlup/utils/annotations_utils.py b/dlup/utils/annotations_utils.py index c460b07d..18f380bb 100644 --- a/dlup/utils/annotations_utils.py +++ b/dlup/utils/annotations_utils.py @@ -1,16 +1,10 @@ from typing import Optional, cast -def hex_to_rgb(hex_color: str) -> tuple[int, int, int]: - if not hex_color.startswith("#"): +def _hex_to_rgb(hex_color: str) -> tuple[int, int, int]: + if "#" not in hex_color: if hex_color == "black": return 0, 0, 0 - else: - raise ValueError(f"Invalid HEX color code {hex_color}") - - if len(hex_color) not in [7, 4]: - raise ValueError(f"Invalid HEX color code {hex_color}") - hex_color = hex_color.lstrip("#") # Convert the string from hex to an integer and extract each color component @@ -20,33 +14,7 @@ def hex_to_rgb(hex_color: str) -> tuple[int, int, int]: return r, g, b -def rgb_to_hex(r: int, g: int, b: int) -> str: - """ - Convert RGB color to HEX. - - Parameters - ---------- - r : int - Red value (0-255) - g : int - Green value (0-255) - b : int - Blue value (0-255) - - Returns - ------- - str - HEX color code - """ - # Ensure the RGB values are within the correct range - if not (0 <= r <= 255 and 0 <= g <= 255 and 0 <= b <= 255): - raise ValueError("RGB values must be in the range 0-255.") - - # Convert RGB to HEX - return "#{:02X}{:02X}{:02X}".format(r, g, b) - - -def get_geojson_color(properties: dict[str, str | list[int]]) -> Optional[tuple[int, int, int]]: +def _get_geojson_color(properties: dict[str, str | list[int]]) -> Optional[tuple[int, int, int]]: """Parse the properties dictionary of a GeoJSON object to get the color. Arguments diff --git a/dlup/utils/geometry_xml.py b/dlup/utils/geometry_xml.py deleted file mode 100644 index 6dce3a9f..00000000 --- a/dlup/utils/geometry_xml.py +++ /dev/null @@ -1,322 +0,0 @@ -# Copyright (c) dlup contributors -"""Utilities to convert GeometryCollection objects into XML-like objects""" - -from typing import Any, Optional - -from dlup.geometry import Box, GeometryCollection, Point, Polygon -from dlup.utils.annotations_utils import hex_to_rgb, rgb_to_hex -from dlup.utils.schemas.generated import ( - BasePolygonType, - BoxType, - Geometries, - RegionBoxType, - RegionPolygonType, - RegionsOfInterest, - StandalonePolygonType, -) - - -def _create_base_polygon_type(polygon: Polygon) -> tuple[BasePolygonType.Exterior, BasePolygonType.Interiors]: - """ - Helper function to create the exterior and interiors of a polygon. - - Parameters - ---------- - polygon : Polygon - The Polygon object to convert. - - Returns - ------- - tuple - A tuple containing exterior and interiors. - """ - exterior_coords = [BasePolygonType.Exterior.Point(x=coord[0], y=coord[1]) for coord in polygon.get_exterior()] - exterior = BasePolygonType.Exterior(point=exterior_coords) - - interiors_list = [] - for interior in polygon.get_interiors(): - interior_coords = [BasePolygonType.Interiors.Interior.Point(x=coord[0], y=coord[1]) for coord in interior] - interiors_list.append(BasePolygonType.Interiors.Interior(point=interior_coords)) - interiors = BasePolygonType.Interiors(interior=interiors_list) if interiors_list else BasePolygonType.Interiors() - - return exterior, interiors - - -def is_box(polygon: Polygon) -> bool: - """ - Determines whether a polygon is an axis-aligned box. - - Parameters - ---------- - polygon : Polygon - The Polygon object to check. - - Returns - ------- - bool - True if the polygon is an axis-aligned box, False otherwise. - """ - # Check for no interiors - if polygon.get_interiors(): - return False - - exterior = polygon.get_exterior() - - # A box should have exactly 5 coordinates (first and last are the same) - if len(exterior) != 5: - return False - - # Check that all edges are axis-aligned - for i in range(4): - x1, y1 = exterior[i] - x2, y2 = exterior[i + 1] - if not (x1 == x2 or y1 == y2): - return False - - return True - - -def create_xml_box_from_polygon(polygon: Polygon) -> RegionBoxType: - """ - Convert a Polygon object that is a box into a BoxType XML object. - - Parameters - ---------- - polygon : Polygon - The Polygon object to convert. - - Returns - ------- - BoxType - The converted Box XML object. - """ - exterior = polygon.get_exterior() - x_coords = [coord[0] for coord in exterior[:-1]] # Exclude the closing coordinate - y_coords = [coord[1] for coord in exterior[:-1]] - - x_min = min(x_coords) - y_min = min(y_coords) - x_max = max(x_coords) - y_max = max(y_coords) - - return RegionBoxType( - x_min=x_min, - y_min=y_min, - x_max=x_max, - y_max=y_max, - label=polygon.label, - ) - - -def create_xml_polygon(polygon: Polygon, order: int) -> StandalonePolygonType: - """ - Convert a Polygon object to a StandalonePolygonType XML object. - - Parameters - ---------- - polygon : Polygon - The Polygon object to convert. - order : int - The order of the polygon. - - Returns - ------- - StandalonePolygonType - The converted Polygon XML object. - """ - exterior, interiors = _create_base_polygon_type(polygon) - return StandalonePolygonType( - exterior=exterior, - interiors=interiors, - label=polygon.label, - color=rgb_to_hex(*polygon.color) if polygon.color else None, - index=polygon.index, - order=order, - ) - - -def create_xml_roi_polygon(polygon: Polygon, order: int) -> RegionPolygonType: - """ - Convert a Polygon object to a RegionPolygonType XML object. - - Parameters - ---------- - polygon : Polygon - The Polygon object to convert. - order : int - The order of the polygon. - - Returns - ------- - RegionPolygonType - The converted Polygon XML object. - """ - exterior, interiors = _create_base_polygon_type(polygon) - return RegionPolygonType( - exterior=exterior, - interiors=interiors, - label=polygon.label, - index=polygon.index, - order=order, - ) - - -def create_xml_point(point: Point) -> Geometries.Point: - """ - Convert a Point object to a Point XML object. - - Parameters - ---------- - point : Point - The Point object to convert. - - Returns - ------- - Geometries.Point - The converted Point XML object. - """ - return Geometries.Point( - x=point.x, y=point.y, label=point.label, color=rgb_to_hex(*point.color) if point.color else None - ) - - -def create_xml_box(box: Box) -> BoxType: - """ - Convert a Box object to a BoxType XML object. - - Parameters - ---------- - box : Box - The Box object to convert. - - Returns - ------- - BoxType - The converted Box XML object. - """ - x_min, y_min = box.coordinates - width, height = box.size - x_max = x_min + width - y_max = y_min + height - return BoxType( - x_min=x_min, - y_min=y_min, - x_max=x_max, - y_max=y_max, - label=box.label, - color=rgb_to_hex(*box.color) if box.color else None, - ) - - -def create_xml_geometries(collection: GeometryCollection) -> Geometries: - """ - Convert a GeometryCollection to Geometries XML object. - - Parameters - ---------- - collection : GeometryCollection - The GeometryCollection to convert. - - Returns - ------- - Geometries - The converted Geometries XML object. - """ - polygons = [create_xml_polygon(polygon, order=idx) for idx, polygon in enumerate(collection.polygons)] - points = [create_xml_point(point) for point in collection.points] - boxes = [create_xml_box(box) for box in collection.boxes] - - return Geometries(polygon=polygons, multi_polygon=[], box=boxes, point=points) - - -def create_xml_rois(collection: GeometryCollection) -> RegionsOfInterest: - """ - Convert a GeometryCollection to RegionsOfInterest XML object, distinguishing between polygons and boxes. - - Parameters - ---------- - collection : GeometryCollection - The GeometryCollection to convert. - - Returns - ------- - RegionsOfInterest - The converted RegionsOfInterest XML object, including both polygons and boxes. - """ - polygons = [] - boxes = [] - - for idx, polygon in enumerate(collection.rois): - if is_box(polygon): - boxes.append(create_xml_box_from_polygon(polygon)) - else: - polygons.append(create_xml_roi_polygon(polygon, order=idx)) - - return RegionsOfInterest( - polygon=polygons, box=boxes, multi_polygon=[] # Ensure that RegionsOfInterest schema includes 'box' - ) - - -def parse_dlup_xml_polygon( - polygons: list[Any], order: Optional[int] = None, label: Optional[str] = None, index: Optional[int] = None -) -> list[tuple[Polygon, int]]: - output: list[tuple[Polygon, int]] = [] - for curr_polygon in polygons: - if not curr_polygon.exterior: - raise ValueError("Polygon does not have an exterior.") - exterior = [(point.x, point.y) for point in curr_polygon.exterior.point] - if curr_polygon.interiors: - interiors = [ - [(point.x, point.y) for point in interior.point] for interior in curr_polygon.interiors.interior - ] - else: - interiors = [] - - label = label if label else curr_polygon.label - - polygon = Polygon( - exterior, - interiors, - ) - - order = 0 - if hasattr(curr_polygon, "order"): - if curr_polygon.order is not None: - order = curr_polygon.order - assert isinstance(order, int) - - # We use the given values if they are set, otherwise we take them from the properties - if index is not None: - polygon.index = index - elif hasattr(curr_polygon, "index") and curr_polygon.index is not None: - polygon.index = curr_polygon.index - - if label is not None: - polygon.label = label - elif hasattr(curr_polygon, "label"): - polygon.label = curr_polygon.label - - if hasattr(curr_polygon, "color"): - polygon.color = hex_to_rgb(curr_polygon.color) - - output.append((polygon, order)) - return output - - -def parse_dlup_xml_roi_box(box: RegionBoxType) -> tuple[Box, int]: - # mypy struggles here - assert isinstance(box.x_min, float) - assert isinstance(box.y_min, float) - assert isinstance(box.x_max, float) - assert isinstance(box.y_max, float) - - output_box = Box((box.x_min, box.y_min), (box.x_max - box.x_min, box.y_max - box.y_min)) - - if box.label: - output_box.label = box.label - if box.order: - order = box.order - else: - order = 0 - - return (output_box, order) diff --git a/dlup/utils/imports.py b/dlup/utils/imports.py index 3eb24e4d..151a6e66 100644 --- a/dlup/utils/imports.py +++ b/dlup/utils/imports.py @@ -23,4 +23,3 @@ def _module_available(module_path: str) -> bool: PYTORCH_AVAILABLE = _module_available("pytorch") PYHALOXML_AVAILABLE = _module_available("pyhaloxml") DARWIN_SDK_AVAILABLE = _module_available("darwin") -SHAPELY_AVAILABLE = _module_available("shapely") diff --git a/dlup/utils/schemas/dlup_schema_v1.0.xsd b/dlup/utils/schemas/dlup_schema_v1.0.xsd deleted file mode 100644 index df3f2163..00000000 --- a/dlup/utils/schemas/dlup_schema_v1.0.xsd +++ /dev/null @@ -1,244 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/dlup/utils/schemas/generated/__init__.py b/dlup/utils/schemas/generated/__init__.py deleted file mode 100644 index 7616cffa..00000000 --- a/dlup/utils/schemas/generated/__init__.py +++ /dev/null @@ -1,35 +0,0 @@ -from dlup.utils.schemas.generated.dlup_schema_v1_0 import ( - BasePolygonType, - BoundingBoxType, - BoxType, - DlupAnnotations, - Geometries, - Metadata, - MultiPolygonType, - RectangleType, - RegionBoxType, - RegionMultiPolygonType, - RegionPolygonType, - RegionsOfInterest, - StandalonePolygonType, - Tag, - Tags, -) - -__all__ = [ - "BasePolygonType", - "BoundingBoxType", - "BoxType", - "DlupAnnotations", - "Geometries", - "Metadata", - "MultiPolygonType", - "RectangleType", - "RegionBoxType", - "RegionMultiPolygonType", - "RegionPolygonType", - "RegionsOfInterest", - "StandalonePolygonType", - "Tag", - "Tags", -] diff --git a/dlup/utils/schemas/generated/dlup_schema_v1_0.py b/dlup/utils/schemas/generated/dlup_schema_v1_0.py deleted file mode 100644 index 8b1bd018..00000000 --- a/dlup/utils/schemas/generated/dlup_schema_v1_0.py +++ /dev/null @@ -1,629 +0,0 @@ -from dataclasses import dataclass, field -from typing import List, Optional - -from xsdata.models.datatype import XmlDate - - -@dataclass -class BasePolygonType: - exterior: Optional["BasePolygonType.Exterior"] = field( - default=None, - metadata={ - "name": "Exterior", - "type": "Element", - "required": True, - }, - ) - interiors: Optional["BasePolygonType.Interiors"] = field( - default=None, - metadata={ - "name": "Interiors", - "type": "Element", - }, - ) - - @dataclass - class Exterior: - point: List["BasePolygonType.Exterior.Point"] = field( - default_factory=list, - metadata={ - "name": "Point", - "type": "Element", - "min_occurs": 1, - }, - ) - - @dataclass - class Point: - x: Optional[float] = field( - default=None, - metadata={ - "type": "Attribute", - "required": True, - }, - ) - y: Optional[float] = field( - default=None, - metadata={ - "type": "Attribute", - "required": True, - }, - ) - - @dataclass - class Interiors: - interior: List["BasePolygonType.Interiors.Interior"] = field( - default_factory=list, - metadata={ - "name": "Interior", - "type": "Element", - "min_occurs": 1, - }, - ) - - @dataclass - class Interior: - point: List["BasePolygonType.Interiors.Interior.Point"] = field( - default_factory=list, - metadata={ - "name": "Point", - "type": "Element", - "min_occurs": 1, - }, - ) - - @dataclass - class Point: - x: Optional[float] = field( - default=None, - metadata={ - "type": "Attribute", - "required": True, - }, - ) - y: Optional[float] = field( - default=None, - metadata={ - "type": "Attribute", - "required": True, - }, - ) - - -@dataclass -class Metadata: - image_id: Optional[str] = field( - default=None, - metadata={ - "name": "ImageID", - "type": "Element", - "required": True, - }, - ) - description: Optional[str] = field( - default=None, - metadata={ - "name": "Description", - "type": "Element", - }, - ) - version: Optional[str] = field( - default=None, - metadata={ - "name": "Version", - "type": "Element", - "required": True, - }, - ) - authors: Optional["Metadata.Authors"] = field( - default=None, - metadata={ - "name": "Authors", - "type": "Element", - }, - ) - date_created: Optional[XmlDate] = field( - default=None, - metadata={ - "name": "DateCreated", - "type": "Element", - "required": True, - }, - ) - software: Optional[str] = field( - default=None, - metadata={ - "name": "Software", - "type": "Element", - "required": True, - }, - ) - - @dataclass - class Authors: - author: List[str] = field( - default_factory=list, - metadata={ - "name": "Author", - "type": "Element", - "min_occurs": 1, - }, - ) - - -@dataclass -class RectangleType: - x_min: Optional[float] = field( - default=None, - metadata={ - "name": "xMin", - "type": "Attribute", - "required": True, - }, - ) - y_min: Optional[float] = field( - default=None, - metadata={ - "name": "yMin", - "type": "Attribute", - "required": True, - }, - ) - x_max: Optional[float] = field( - default=None, - metadata={ - "name": "xMax", - "type": "Attribute", - "required": True, - }, - ) - y_max: Optional[float] = field( - default=None, - metadata={ - "name": "yMax", - "type": "Attribute", - "required": True, - }, - ) - - -@dataclass -class Tag: - attribute: List["Tag.Attribute"] = field( - default_factory=list, - metadata={ - "name": "Attribute", - "type": "Element", - }, - ) - text: Optional[str] = field( - default=None, - metadata={ - "name": "Text", - "type": "Element", - }, - ) - label: Optional[str] = field( - default=None, - metadata={ - "type": "Attribute", - "required": True, - }, - ) - color: Optional[str] = field( - default=None, - metadata={ - "type": "Attribute", - "pattern": r"#[0-9a-fA-F]{6}", - }, - ) - - @dataclass - class Attribute: - value: str = field( - default="", - metadata={ - "required": True, - }, - ) - color: Optional[str] = field( - default=None, - metadata={ - "type": "Attribute", - "pattern": r"#[0-9a-fA-F]{6}", - }, - ) - - -@dataclass -class BoundingBoxType(RectangleType): - label: Optional[str] = field( - default=None, - metadata={ - "type": "Attribute", - }, - ) - color: Optional[str] = field( - default=None, - metadata={ - "type": "Attribute", - "pattern": r"#[0-9a-fA-F]{6}", - }, - ) - index: Optional[int] = field( - default=None, - metadata={ - "type": "Attribute", - }, - ) - - -@dataclass -class BoxType(RectangleType): - label: Optional[str] = field( - default=None, - metadata={ - "type": "Attribute", - "required": True, - }, - ) - color: Optional[str] = field( - default=None, - metadata={ - "type": "Attribute", - "pattern": r"#[0-9a-fA-F]{6}", - }, - ) - - -@dataclass -class MultiPolygonType: - polygon: List[BasePolygonType] = field( - default_factory=list, - metadata={ - "name": "Polygon", - "type": "Element", - "min_occurs": 1, - }, - ) - label: Optional[str] = field( - default=None, - metadata={ - "type": "Attribute", - "required": True, - }, - ) - color: Optional[str] = field( - default=None, - metadata={ - "type": "Attribute", - "pattern": r"#[0-9a-fA-F]{6}", - }, - ) - index: Optional[int] = field( - default=None, - metadata={ - "type": "Attribute", - }, - ) - order: Optional[int] = field( - default=None, - metadata={ - "type": "Attribute", - "required": True, - }, - ) - - -@dataclass -class RegionBoxType(RectangleType): - label: Optional[str] = field( - default=None, - metadata={ - "type": "Attribute", - "required": True, - }, - ) - index: Optional[int] = field( - default=None, - metadata={ - "type": "Attribute", - }, - ) - order: Optional[int] = field( - default=None, - metadata={ - "type": "Attribute", - "required": True, - }, - ) - - -@dataclass -class RegionMultiPolygonType: - polygon: List[BasePolygonType] = field( - default_factory=list, - metadata={ - "name": "Polygon", - "type": "Element", - "min_occurs": 1, - }, - ) - label: Optional[str] = field( - default=None, - metadata={ - "type": "Attribute", - "required": True, - }, - ) - index: Optional[int] = field( - default=None, - metadata={ - "type": "Attribute", - }, - ) - order: Optional[int] = field( - default=None, - metadata={ - "type": "Attribute", - "required": True, - }, - ) - - -@dataclass -class RegionPolygonType(BasePolygonType): - label: Optional[str] = field( - default=None, - metadata={ - "type": "Attribute", - "required": True, - }, - ) - index: Optional[int] = field( - default=None, - metadata={ - "type": "Attribute", - }, - ) - order: Optional[int] = field( - default=None, - metadata={ - "type": "Attribute", - "required": True, - }, - ) - - -@dataclass -class StandalonePolygonType(BasePolygonType): - label: Optional[str] = field( - default=None, - metadata={ - "type": "Attribute", - "required": True, - }, - ) - color: Optional[str] = field( - default=None, - metadata={ - "type": "Attribute", - "pattern": r"#[0-9a-fA-F]{6}", - }, - ) - index: Optional[int] = field( - default=None, - metadata={ - "type": "Attribute", - }, - ) - order: Optional[int] = field( - default=None, - metadata={ - "type": "Attribute", - "required": True, - }, - ) - - -@dataclass -class Tags: - tag: List[Tag] = field( - default_factory=list, - metadata={ - "name": "Tag", - "type": "Element", - "min_occurs": 1, - }, - ) - - -@dataclass -class Geometries: - polygon: List[StandalonePolygonType] = field( - default_factory=list, - metadata={ - "name": "Polygon", - "type": "Element", - }, - ) - multi_polygon: List[MultiPolygonType] = field( - default_factory=list, - metadata={ - "name": "MultiPolygon", - "type": "Element", - }, - ) - box: List[BoxType] = field( - default_factory=list, - metadata={ - "name": "Box", - "type": "Element", - }, - ) - bounding_box: Optional[BoundingBoxType] = field( - default=None, - metadata={ - "name": "BoundingBox", - "type": "Element", - }, - ) - point: List["Geometries.Point"] = field( - default_factory=list, - metadata={ - "name": "Point", - "type": "Element", - }, - ) - multi_point: List["Geometries.MultiPoint"] = field( - default_factory=list, - metadata={ - "name": "MultiPoint", - "type": "Element", - }, - ) - - @dataclass - class Point: - x: Optional[float] = field( - default=None, - metadata={ - "type": "Attribute", - "required": True, - }, - ) - y: Optional[float] = field( - default=None, - metadata={ - "type": "Attribute", - "required": True, - }, - ) - label: Optional[str] = field( - default=None, - metadata={ - "type": "Attribute", - "required": True, - }, - ) - color: Optional[str] = field( - default=None, - metadata={ - "type": "Attribute", - "pattern": r"#[0-9a-fA-F]{6}", - }, - ) - - @dataclass - class MultiPoint: - point: List["Geometries.MultiPoint.Point"] = field( - default_factory=list, - metadata={ - "name": "Point", - "type": "Element", - "min_occurs": 1, - }, - ) - label: Optional[str] = field( - default=None, - metadata={ - "type": "Attribute", - "required": True, - }, - ) - color: Optional[str] = field( - default=None, - metadata={ - "type": "Attribute", - "pattern": r"#[0-9a-fA-F]{6}", - }, - ) - index: Optional[int] = field( - default=None, - metadata={ - "type": "Attribute", - }, - ) - - @dataclass - class Point: - x: Optional[float] = field( - default=None, - metadata={ - "type": "Attribute", - "required": True, - }, - ) - y: Optional[float] = field( - default=None, - metadata={ - "type": "Attribute", - "required": True, - }, - ) - - -@dataclass -class RegionsOfInterest: - polygon: List[RegionPolygonType] = field( - default_factory=list, - metadata={ - "name": "Polygon", - "type": "Element", - }, - ) - multi_polygon: List[RegionMultiPolygonType] = field( - default_factory=list, - metadata={ - "name": "MultiPolygon", - "type": "Element", - }, - ) - box: List[RegionBoxType] = field( - default_factory=list, - metadata={ - "name": "Box", - "type": "Element", - }, - ) - - -@dataclass -class DlupAnnotations: - metadata: Optional[Metadata] = field( - default=None, - metadata={ - "name": "Metadata", - "type": "Element", - "required": True, - }, - ) - tags: Optional[Tags] = field( - default=None, - metadata={ - "name": "Tags", - "type": "Element", - }, - ) - geometries: Optional[Geometries] = field( - default=None, - metadata={ - "name": "Geometries", - "type": "Element", - "required": True, - }, - ) - regions_of_interest: Optional[RegionsOfInterest] = field( - default=None, - metadata={ - "name": "RegionsOfInterest", - "type": "Element", - }, - ) - version: str = field( - init=False, - default="1.0", - metadata={ - "type": "Attribute", - }, - ) diff --git a/examples/annotations_to_mask.py b/examples/annotations_to_mask.py deleted file mode 100644 index c8c8f35d..00000000 --- a/examples/annotations_to_mask.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) dlup contributors -"""This code provides an example of how to convert annotations to a mask.""" -from pathlib import Path - -import PIL.Image - -from dlup.annotations_experimental import SlideAnnotations - - -def convert_annotations_to_mask() -> None: - scaling = 0.02 - annotations = SlideAnnotations.from_dlup_xml(Path(__file__).parent / "files" / "dlup_annotation_test.xml") - - bbox = annotations.bounding_box_at_scaling(scaling) - - region = annotations.read_region((0, 0), scaling, bbox[1]) - LUT = annotations.color_lut - - bbox = annotations.bounding_box_at_scaling(scaling) - - curr_mask = region.polygons.to_mask().numpy() - print(curr_mask.shape) - PIL.Image.fromarray(LUT[curr_mask]).save("output.png") - - -convert_annotations_to_mask() diff --git a/examples/files/dlup_annotation_test.xml b/examples/files/dlup_annotation_test.xml deleted file mode 100644 index 00972a0a..00000000 --- a/examples/files/dlup_annotation_test.xml +++ /dev/null @@ -1,113062 +0,0 @@ - - - - - - - 2024-09-02 - dlup 0.7.0 - - - - - Optimal staining - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/meson.build b/meson.build index da01a7cc..da3db1b8 100644 --- a/meson.build +++ b/meson.build @@ -1,24 +1,101 @@ project('dlup', 'cpp', 'cython', - version : '0.7.0', - default_options : ['buildtype=release', 'warning_level=3', 'cpp_std=c++20']) + version : '0.7.0', + default_options : ['warning_level=3', 'cpp_std=c++17']) -ninja = find_program('ninja', required : true) py_mod = import('python') py = py_mod.find_installation(pure: false) py_dep = py.dependency() -# Base compiler and linker arguments -cpp_args = ['-O3', '-march=native', '-ffast-math', '-funroll-loops', '-flto', '-pipe', '-fomit-frame-pointer'] -link_args = ['-flto'] +libtiff_dep = dependency('libtiff-4', required : false) +if not libtiff_dep.found() + libtiff_dep = dependency('tiff', required : false) +endif +if not libtiff_dep.found() + libtiff_dep = cc.find_library('tiff', required : false) +endif +if not libtiff_dep.found() + error('libtiff not found. Please install libtiff development files.') +endif -# Unity build option -unity_build = true +# Check for ZSTD library +zstd_dep = dependency('libzstd', required : false) +have_zstd = zstd_dep.found() +if have_zstd + message('ZSTD support enabled') +else + message('ZSTD support disabled') +endif +# Capture the output of the run_command and convert to relative paths +numpy_include = run_command(py, ['-c', ''' +import os +import numpy +print(os.path.relpath(numpy.get_include(), os.getcwd())) +'''], check: true).stdout().strip() -subdir('third_party') -subdir('src') -subdir('dlup') +pybind11_include = run_command(py, ['-c', ''' +import os +import pybind11 +print(os.path.relpath(pybind11.get_include(), os.getcwd())) +'''], check: true).stdout().strip() -# Include subdirectories for building -install_subdir('dlup', install_dir : py.get_install_dir()) +# Use the relative paths +incdir_numpy = include_directories(numpy_include) +incdir_pybind11 = include_directories(pybind11_include) + +# Check if the CODECOV_CI environment variable is set to 'true' +codecov_ci = run_command('sh', ['-c', ''' +if [ "$CODECOV_CI" = "true" ]; then + echo "true" +else + echo "false" +fi +'''], check: true).stdout().strip() + +# Conditionally set install_dir based on the CODECOV_CI variable +if codecov_ci == 'true' + install_dir = run_command('sh', ['-c', ''' + if [ -z "$PYTHONPATH" ]; then + python -c "import sysconfig; print(sysconfig.get_path('purelib'))" + else + echo $PYTHONPATH + fi + '''], check: true).stdout().strip() + + # Ensure the install_dir is not empty + if install_dir == '' + error('Could not determine install_dir from PYTHONPATH or sysconfig.get_path().') + endif +else + install_dir = py.get_install_dir(pure: false) +endif + +message('Installing to: ' + install_dir) +install_subdir('dlup', install_dir : install_dir) + +_background = py.extension_module('_background', + 'dlup/_background.pyx', + include_directories : [incdir_numpy], + install : true, + install_dir : install_dir / 'dlup', + cpp_args : ['-O3', '-march=native', '-ffast-math']) + +# Define the base dependencies and compiler arguments +base_deps = [libtiff_dep] +base_cpp_args = ['-std=c++17', '-O3', '-march=native', '-ffast-math'] + +# Add ZSTD support if available +if have_zstd + base_deps += [zstd_dep] + base_cpp_args += ['-DHAVE_ZSTD'] +endif + +# pybind11 extension +_libtiff_tiff_writer = py.extension_module('_libtiff_tiff_writer', + 'src/libtiff_tiff_writer.cpp', + include_directories : [incdir_pybind11], + install : true, + install_dir : install_dir / 'dlup', + cpp_args : base_cpp_args, + dependencies : base_deps) diff --git a/pyproject.toml b/pyproject.toml index b20e483e..e48d8941 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,6 @@ dependencies = [ "shapely>=2.0.4", "packaging>=24.0", "pybind11>=2.8.0", - "xsdata>=24.7", ] [project.optional-dependencies] @@ -76,11 +75,8 @@ package = 'dlup' "Build" = [ ".spin/cmds.py:build", ".spin/cmds.py:test", - ".spin/cmds.py:coverage", ".spin/cmds.py:mypy", ".spin/cmds.py:lint", - ".spin/cmds.py:format", - ".spin/cmds.py:precommit", ] "Environments" = [ "spin.cmds.meson.run", @@ -116,12 +112,6 @@ exclude = ''' profile = "black" line_length = 120 -[tool.pylint] -disable = [ - "possibly-used-before-assignment", - "import-error", -] - [tool.pylint.format] max-line-length = "120" @@ -137,10 +127,3 @@ max-line-length = 120 [tool.pytest.ini_options] addopts = "--ignore=libvips" - -[tool.coverage.run] -branch = true -parallel = false # To see if there are threading issues - -[tool.coverage.html] -directory = "htmlcov" diff --git a/src/geometry.cpp b/src/geometry.cpp deleted file mode 100644 index 9749847b..00000000 --- a/src/geometry.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include -#include -#include - -#include "geometry/base.h" -#include "geometry/box.h" -#include "geometry/collection.h" -#include "geometry/exceptions.h" -#include "geometry/factory.h" -#include "geometry/lazy_array.h" -#include "geometry/point.h" -#include "geometry/polygon.h" -#include "geometry/region.h" -namespace py = pybind11; - -template class FactoryManager; -template class FactoryManager; -template class FactoryManager; - -PYBIND11_MODULE(_geometry, m) { - declare_base_geometry(m); - declare_polygon(m); - declare_box(m); - declare_point(m); - - m.def("set_polygon_factory", &FactoryManager::setFactory, "Set the factory function for Polygons"); - m.def("set_box_factory", &FactoryManager::setFactory, "Set the factory function for Boxes"); - m.def("set_point_factory", &FactoryManager::setFactory, "Set the factory function for Points"); - - declare_pybind_collection(m); - declare_lazy_array(m, "LazyArrayInt"); - declare_pybind_polygon_collection(m); - declare_pybind_region(m); - - py::register_exception(m, "GeometryError"); - py::register_exception(m, "GeometryIntersectionError"); - py::register_exception(m, "GeometryTransformationError"); - py::register_exception(m, "GeometryFactoryFunctionError"); - py::register_exception(m, "GeometryNotFoundError"); - py::register_exception(m, "GeometryCoordinatesError"); -} diff --git a/src/geometry/base.h b/src/geometry/base.h deleted file mode 100644 index 79b85e47..00000000 --- a/src/geometry/base.h +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef DLUP_GEOMETRY_BASE_H -#define DLUP_GEOMETRY_BASE_H -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace bg = boost::geometry; -namespace py = pybind11; - -using BoostPoint = bg::model::d2::point_xy; -using BoostPolygon = bg::model::polygon; -using BoostRing = bg::model::ring; - -class BaseGeometry { - public: - virtual ~BaseGeometry() = default; - std::unordered_map parameters_; - - virtual void setField(const std::string &name, py::object value) { parameters_[name] = value; } - - std::optional getField(const std::string &name) const { - if (auto it = parameters_.find(name); it != parameters_.end()) { - return it->second; - } - return std::nullopt; - } - - auto getFields() const { - std::vector field_names_; - field_names_.reserve(parameters_.size()); - std::transform(parameters_.begin(), parameters_.end(), std::back_inserter(field_names_), - [](const auto ¶m) { return param.first; }); - return field_names_; - } - - std::uintptr_t getPointerId() const { return reinterpret_cast(this); } - virtual std::string toWkt() const = 0; // Force derived classes to provide the WKT - - template - std::string convertToWkt(const GeometryType &geometry) const { - std::stringstream ss; - ss << boost::geometry::wkt(geometry); - return ss.str(); - } - - protected: -}; - -inline void declare_base_geometry(py::module &m) { - py::class_>(m, "BaseGeometry") - .def("set_field", &BaseGeometry::setField) - .def("get_field", &BaseGeometry::getField) - .def_property_readonly("fields", &BaseGeometry::getFields) - .def_property_readonly("pointer_id", &BaseGeometry::getPointerId); -} - -#endif // DLUP_GEOMETRY_BASE_H diff --git a/src/geometry/box.h b/src/geometry/box.h deleted file mode 100644 index 889afa9d..00000000 --- a/src/geometry/box.h +++ /dev/null @@ -1,105 +0,0 @@ -#ifndef DLUP_GEOMETRY_BOX_H -#define DLUP_GEOMETRY_BOX_H -#pragma once - -#include "exceptions.h" -#include "factory.h" -#include "polygon.h" -#include "utilities.h" -#include -#include - -namespace bg = boost::geometry; - -using BoostPoint = bg::model::d2::point_xy; -using BoostBox = bg::model::box; - -class Box : public BaseGeometry { - public: - ~Box() override = default; - std::shared_ptr box_; - - Box() : box_(std::make_shared()) {} - Box(const BoostBox &p) : box_(std::make_shared(p)) {} - Box(std::shared_ptr p) : box_(p) {} - - // TODO: This create a list, not a tuple. - Box(const std::array &coordinates, const std::array &size) - : box_(std::make_shared()) { - setBoxParameters(std::move(coordinates), std::move(size)); - } - - void setBoxParameters(const std::array &coordinates, const std::array &size) { - bg::set(*box_, coordinates[0]); - bg::set(*box_, coordinates[1]); - bg::set(*box_, coordinates[0] + size[0]); - bg::set(*box_, coordinates[1] + size[1]); - } - - inline std::array getCoordinates() const { - return {bg::get(*box_), bg::get(*box_)}; - } - - inline std::array getSize() const { - auto x1 = bg::get(*box_); - auto y1 = bg::get(*box_); - auto x2 = bg::get(*box_); - auto y2 = bg::get(*box_); - - return {x2 - x1, y2 - y1}; - } - - inline double getArea() const { - std::array size = getSize(); - return size[0] * size[1]; - } - std::shared_ptr asPolygon() const { - BoostPolygon poly; - bg::convert(*box_, poly); - std::shared_ptr polygon = std::make_shared(poly); - for (const auto ¶m : parameters_) { - polygon->setField(param.first, param.second); - } - - return polygon; - } - - std::vector> getExterior() const { return asPolygon()->getExterior(); } - - inline py::object asPolygonPyObject() const { return FactoryManager::callFactoryFunction(asPolygon()); } - - void scale(double scaling) { utilities::AffineTransform(*box_, {0.0, 0.0}, scaling); } - - // Factory function for creating boxes from Python - static std::shared_ptr create(std::array coordinates, std::array size) { - return std::make_shared(coordinates, size); - } - std::string toWkt() const override { return convertToWkt(*box_); } -}; - -inline void declare_box(py::module &m) { - py::class_>(m, "Box") - .def(py::init<>()) - .def(py::init()) - .def(py::init &, const std::array &>()) - .def(py::init([](const std::shared_ptr &p) { - // Share the same C++ object, not creating a new one - return p; - })) - .def(py::init([](const Box &other) { - // Explicitly copy parameters when copying the Box - auto newBox = std::make_shared(*other.box_); - newBox->parameters_ = other.parameters_; // Copy the parameters - return newBox; - })) - .def("as_polygon", &Box::asPolygonPyObject, "Convert the box to a polygon") - .def("scale", &Box::scale, py::arg("scaling"), "Scale the box in-place by a factor") - - .def_property_readonly("coordinates", &Box::getCoordinates, - "Get the top-left coordinates of the box as an (x, y) tuple") - .def_property_readonly("size", &Box::getSize, "Get the size of the box as an (h, w) tuple") - .def_property_readonly("area", &Box::getArea) - .def_property_readonly("wkt", &Box::toWkt, "Get the WKT representation of the box"); -} - -#endif // DLUP_GEOMETRY_BOX_H \ No newline at end of file diff --git a/src/geometry/collection.h b/src/geometry/collection.h deleted file mode 100644 index 8870c14d..00000000 --- a/src/geometry/collection.h +++ /dev/null @@ -1,524 +0,0 @@ -#ifndef DLUP_GEOMETRY_COLLECTION_H -#define DLUP_GEOMETRY_COLLECTION_H -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "../opencv.h" -#include "base.h" -#include "box.h" -#include "exceptions.h" -#include "factory.h" -#include "point.h" -#include "polygon.h" -#include "region.h" -#include "rtree.h" -#include "utilities.h" -#include -#include -#include -#include -#include -#include -#include -#include - -namespace bg = boost::geometry; -namespace bgi = boost::geometry::index; -namespace py = pybind11; - -using BoostPoint = bg::model::d2::point_xy; -using BoostPolygon = bg::model::polygon; -using BoostBox = bg::model::box; -using BoostRing = bg::model::ring; -using BoostLineString = bg::model::linestring; -using BoostMultiPolygon = bg::model::multi_polygon; - -namespace py = pybind11; - -class GeometryCollection; // Forward declaration of GeometryCollection - -class RTreeWrapper : public RTreeBase { - public: - explicit RTreeWrapper(GeometryCollection *geometryCollection) : geometryCollection(geometryCollection) {} - - void rebuild() override; - - private: - GeometryCollection *geometryCollection; // Pointer to GeometryCollection -}; - -class GeometryCollection { - public: - GeometryCollection(); - // Whatever any LLM says this has to be a shared pointer as we share it with the python interpreter - using PolygonPtr = std::shared_ptr; - using PointPtr = std::shared_ptr; - using BoxPtr = std::shared_ptr; - - void addPolygon(const PolygonPtr &p); - void addRoi(const PolygonPtr &p); - void addPoint(const PointPtr &p); - void addBox(const BoxPtr &p); - - py::list getPolygons(); - py::list getRois(); - py::list getPoints(); - py::list getBoxes(); - std::pair, std::pair> computeBoundingBox() const; - void sortPolygons(const py::function &keyFunc, bool reverse); - - void removePolygon(const PolygonPtr &p); - void removePolygon(size_t index); - void removeRoi(const PolygonPtr &p); - void removeRoi(size_t index); - void removePoint(const PointPtr &p); - void removePoint(size_t index); - void removeBox(const BoxPtr &p); - void removeBox(size_t index); - - void scale(double scaling); - void setOffset(std::pair offset); - void rebuildRTree() { rtree_wrapper_.rebuild(); } - void simplifyPolygons(double tolerance) { - std::lock_guard lock(collection_mutex_); - for (auto &polygon : polygons_) { - polygon->simplifyPolygon(tolerance); - } - } - - int size() const { return polygons_.size() + rois_.size() + points_.size() + boxes_.size(); } - - std::uintptr_t getPointerId() const { return reinterpret_cast(this); } - - bool isRTreeInvalidated() const { return rtree_wrapper_.isInvalidated(); } - - AnnotationRegion readRegion(const std::pair &coordinates, double scaling, - const std::pair &size); - - // TODO: Rethink the need for this function. - void reindexPolygons(const std::map &indexMap); - - private: - friend class RTreeWrapper; - std::vector polygons_; - std::vector rois_; - std::vector points_; - std::vector boxes_; - RTreeWrapper rtree_wrapper_; - mutable std::mutex collection_mutex_; -}; - -GeometryCollection::GeometryCollection() : rtree_wrapper_(this) {} - -std::pair, std::pair> GeometryCollection::computeBoundingBox() const { - std::lock_guard lock(collection_mutex_); - BoostBox overall_bounding_box_; - bool is_first_ = true; - - // Iterate over all polygons and compute their bounding boxes - for (const auto &polygon : polygons_) { - BoostBox polygon_box; - bg::envelope(*(polygon->polygon_), polygon_box); - - if (is_first_) { - overall_bounding_box_ = polygon_box; - is_first_ = false; - } else { - bg::expand(overall_bounding_box_, polygon_box); - } - } - - // Iterate over the ROIs and compute their bounding boxes - for (const auto &roi : rois_) { - BoostBox roi_box; - bg::envelope(*(roi->polygon_), roi_box); - - if (is_first_) { - overall_bounding_box_ = roi_box; - is_first_ = false; - } else { - bg::expand(overall_bounding_box_, roi_box); - } - } - - // Iterate over all boxes and compute their bounding boxes - for (const auto &box : boxes_) { - if (is_first_) { - overall_bounding_box_ = *(box->box_); - is_first_ = false; - } else { - bg::expand(overall_bounding_box_, *(box->box_)); - } - } - - // Iterate over all points and compute their bounding boxes - for (const auto &point : points_) { - BoostBox point_box(*(point->point_), *(point->point_)); - - if (is_first_) { - overall_bounding_box_ = point_box; - is_first_ = false; - } else { - bg::expand(overall_bounding_box_, point_box); - } - } - - // Extract min and max points - const auto &min_corner = overall_bounding_box_.min_corner(); - const auto &max_corner = overall_bounding_box_.max_corner(); - - double min_x = bg::get<0>(min_corner); - double min_y = bg::get<1>(min_corner); - double max_x = bg::get<0>(max_corner); - double max_y = bg::get<1>(max_corner); - - double width = max_x - min_x; - double height = max_y - min_y; - - return std::make_pair(std::make_pair(min_x, min_y), std::make_pair(width, height)); -} - -void GeometryCollection::reindexPolygons(const std::map &indexMap) { - std::lock_guard lock(collection_mutex_); - for (auto &polygon : polygons_) { - std::optional label_opt = polygon->getField("label"); - - if (label_opt.has_value()) { - std::string label = label_opt->cast(); - auto it = indexMap.find(label); - if (it != indexMap.end()) { - polygon->setField("index", py::int_(it->second)); - } else { - throw std::invalid_argument("Label '" + label + "' not found in indexMap"); - } - } else { - throw std::invalid_argument("Polygon does not have a value for the 'label' field"); - } - } -} - -void RTreeWrapper::rebuild() { - // Mutex is handled in the wrapper - clear(); // Clear the existing R-tree - - // Rebuild the tree using polygons, boxes, and points from GeometryCollection - - // First insert polygons - const auto &polygons = geometryCollection->polygons_; - for (size_t i = 0; i < polygons.size(); ++i) { - BoostBox box; - bg::envelope(*(polygons[i]->polygon_), box); - insert(box, i); - } - - // Next insert ROIs - const auto &rois = geometryCollection->rois_; - for (size_t i = 0; i < rois.size(); ++i) { - BoostBox box; - bg::envelope(*(rois[i]->polygon_), box); - insert(box, polygons.size() + i); - } - - // Next insert boxes - const auto &boxes = geometryCollection->boxes_; - for (size_t i = 0; i < boxes.size(); ++i) { - insert(*(boxes[i]->box_), polygons.size() + rois.size() + i); - } - - // Finally, insert points - const auto &points = geometryCollection->points_; - for (size_t i = 0; i < points.size(); ++i) { - BoostBox box(*(points[i]->point_), *(points[i]->point_)); - insert(box, polygons.size() + rois.size() + boxes.size() + i); - } - - rtree_invalidated_ = false; -} - -void GeometryCollection::addPolygon(const PolygonPtr &p) { - std::lock_guard lock(collection_mutex_); - polygons_.emplace_back(p); - rtree_wrapper_.invalidate(); -} - -void GeometryCollection::addRoi(const PolygonPtr &p) { - std::lock_guard lock(collection_mutex_); - rois_.emplace_back(p); - rtree_wrapper_.invalidate(); -} - -void GeometryCollection::addPoint(const PointPtr &p) { - std::lock_guard lock(collection_mutex_); - points_.emplace_back(p); - rtree_wrapper_.invalidate(); -} - -void GeometryCollection::addBox(const BoxPtr &p) { - std::lock_guard lock(collection_mutex_); - boxes_.emplace_back(p); - rtree_wrapper_.invalidate(); -} - -py::list GeometryCollection::getPolygons() { - std::lock_guard lock(collection_mutex_); - py::list py_polygons; - for (const auto &polygon : polygons_) { - py::object processed_polygon = FactoryManager::callFactoryFunction(polygon); - py_polygons.append(processed_polygon); - } - return py_polygons; -} - -py::list GeometryCollection::getRois() { - std::lock_guard lock(collection_mutex_); - py::list py_rois; - for (const auto &roi : rois_) { - py::object processed_roi = FactoryManager::callFactoryFunction(roi); - py_rois.append(processed_roi); - } - return py_rois; -} - -py::list GeometryCollection::getPoints() { - std::lock_guard lock(collection_mutex_); - py::list py_points; - for (const auto &point : points_) { - py_points.append(FactoryManager::callFactoryFunction(point)); - } - return py_points; -} - -py::list GeometryCollection::getBoxes() { - std::lock_guard lock(collection_mutex_); - py::list py_boxes; - for (const auto &box : boxes_) { - py_boxes.append(FactoryManager::callFactoryFunction(box)); - } - return py_boxes; -} - -void GeometryCollection::sortPolygons(const py::function &key_func, bool reverse) { - std::lock_guard lock(collection_mutex_); - std::sort(polygons_.begin(), polygons_.end(), [&key_func, reverse](const PolygonPtr &a, const PolygonPtr &b) { - py::object key_a = key_func(a); - py::object key_b = key_func(b); - - if (py::isinstance(key_a) && py::isinstance(key_b)) { - return reverse ? (key_a.cast() > key_b.cast()) - : (key_a.cast() < key_b.cast()); - } else if (py::isinstance(key_a) && py::isinstance(key_b)) { - return reverse ? (key_a.cast() > key_b.cast()) : (key_a.cast() < key_b.cast()); - } else if (py::isinstance(key_a) && py::isinstance(key_b)) { - return reverse ? (key_a.cast() > key_b.cast()) : (key_a.cast() < key_b.cast()); - } else if (py::isinstance(key_a) && py::isinstance(key_b)) { - return false; - } else { - throw std::invalid_argument("Unsupported key type for sorting."); - } - }); - rtree_wrapper_.invalidate(); -} - -void GeometryCollection::scale(double scaling) { - std::lock_guard lock(collection_mutex_); - for (auto &point : points_) { - point->scale(scaling); - } - for (auto &polygon : polygons_) { - polygon->scale(scaling); - } - - for (auto &roi : rois_) { - roi->scale(scaling); - } - - for (auto &box : boxes_) { - box->scale(scaling); - } - rtree_wrapper_.invalidate(); -} - -void GeometryCollection::setOffset(std::pair offset) { - std::lock_guard lock(collection_mutex_); - for (auto &point : points_) { - utilities::AffineTransform(*point->point_, {-offset.first, -offset.second}, 1.0); - } - for (auto &polygon : polygons_) { - utilities::AffineTransform(*polygon->polygon_, {-offset.first, -offset.second}, 1.0); - } - for (auto &roi : rois_) { - utilities::AffineTransform(*roi->polygon_, {-offset.first, -offset.second}, 1.0); - } - for (auto &box : boxes_) { - utilities::AffineTransform(*box->box_, {-offset.first, -offset.second}, 1.0); - } - - rtree_wrapper_.invalidate(); -} - -void GeometryCollection::removePolygon(const PolygonPtr &p) { - std::lock_guard lock(collection_mutex_); - auto it = std::find(polygons_.begin(), polygons_.end(), p); - if (it != polygons_.end()) { - polygons_.erase(it); - rtree_wrapper_.invalidate(); - } else { - throw GeometryNotFoundError("Polygon not found"); - } -} - -void GeometryCollection::removePolygon(size_t index) { - std::lock_guard lock(collection_mutex_); - if (index >= polygons_.size()) { - throw std::out_of_range("Polygon index out of range"); - } - - polygons_.erase(polygons_.begin() + index); - rtree_wrapper_.invalidate(); -} - -void GeometryCollection::removeRoi(const PolygonPtr &p) { - std::lock_guard lock(collection_mutex_); - auto it = std::find(rois_.begin(), rois_.end(), p); - if (it != rois_.end()) { - rois_.erase(it); - rtree_wrapper_.invalidate(); - } else { - throw GeometryNotFoundError("ROI not found"); - } -} - -void GeometryCollection::removeRoi(size_t index) { - std::lock_guard lock(collection_mutex_); - if (index >= rois_.size()) { - throw std::out_of_range("ROI index out of range"); - } - - rois_.erase(rois_.begin() + index); - rtree_wrapper_.invalidate(); -} - -void GeometryCollection::removePoint(const PointPtr &p) { - std::lock_guard lock(collection_mutex_); - auto it = std::find(points_.begin(), points_.end(), p); - if (it != points_.end()) { - points_.erase(it); - rtree_wrapper_.invalidate(); - } else { - throw GeometryNotFoundError("Point not found"); - } -} - -void GeometryCollection::removePoint(size_t index) { - std::lock_guard lock(collection_mutex_); - if (index >= points_.size()) { - throw std::out_of_range("Point index out of range"); - } - - points_.erase(points_.begin() + index); - rtree_wrapper_.invalidate(); -} - -AnnotationRegion GeometryCollection::readRegion(const std::pair &coordinates, double scaling, - const std::pair &size) { - return AnnotationRegion([=, this]() { - std::lock_guard lock(collection_mutex_); - if (rtree_wrapper_.isInvalidated()) { - rtree_wrapper_.rebuild(); - } - - BoostPoint top_left(coordinates.first / scaling, coordinates.second / scaling); - BoostPoint bottom_right((coordinates.first + size.first) / scaling, (coordinates.second + size.second) / scaling); - BoostBox query_box(top_left, bottom_right); - - BoostPolygon intersection_polygon; - bg::convert(query_box, intersection_polygon); - std::vector> results; - rtree_wrapper_.query(bgi::intersects(query_box), std::back_inserter(results)); - - std::sort(results.begin(), results.end(), [](const auto &a, const auto &b) { return a.second < b.second; }); - - std::vector> intersected_polygons; - std::vector> intersected_rois; - std::vector> current_points; - std::vector> current_boxes; - - for (const auto &result : results) { - size_t index = result.second; - if (index < polygons_.size()) { - auto &polygon = polygons_[index]; - auto intersections = polygon->intersection(intersection_polygon); - for (const auto &intersected_polygon : intersections) { - utilities::AffineTransform(*intersected_polygon->polygon_, coordinates, scaling); - intersected_polygons.push_back(intersected_polygon); - } - } else if (index < polygons_.size() + boxes_.size()) { - auto &roi = rois_[index - polygons_.size()]; - auto intersection = roi->intersection(intersection_polygon); - for (const auto &intersected_roi : intersection) { - utilities::AffineTransform(*intersected_roi->polygon_, coordinates, scaling); - intersected_rois.push_back(intersected_roi); - } - } else if (index < polygons_.size() + rois_.size() + boxes_.size()) { - auto &box = boxes_[index - polygons_.size() - rois_.size()]; - auto transformed_box = std::make_shared(*box); - utilities::AffineTransform(*transformed_box->box_, coordinates, scaling); - current_boxes.push_back(transformed_box); - } else { - auto &point = points_[index - polygons_.size() - rois_.size() - boxes_.size()]; - auto transformed_point = std::make_shared(*point); - utilities::AffineTransform(*transformed_point->point_, coordinates, scaling); - current_points.push_back(transformed_point); - } - } - - return AnnotationRegion(std::move(intersected_polygons), std::move(intersected_rois), std::move(current_boxes), - std::move(current_points), std::make_tuple(size.first, size.second)); - }); -} - -void declare_pybind_collection(py::module &m) { - py::class_>(m, "GeometryCollection") - .def(py::init<>()) - .def("add_polygon", &GeometryCollection::addPolygon) - .def("add_roi", &GeometryCollection::addRoi) - .def("add_point", &GeometryCollection::addPoint) - .def("add_box", &GeometryCollection::addBox) - - // Overload remove_polygon to handle both object and index - .def("remove_polygon", py::overload_cast &>(&GeometryCollection::removePolygon), - "Remove a polygon by passing the Polygon object") - .def("remove_polygon", py::overload_cast(&GeometryCollection::removePolygon), - "Remove a polygon by its index") - .def("remove_roi", py::overload_cast &>(&GeometryCollection::removeRoi), - "Remove an ROI by passing the ROI object") - .def("remove_roi", py::overload_cast(&GeometryCollection::removeRoi), "Remove an ROI by its index") - .def("reindex_polygons", &GeometryCollection::reindexPolygons) - .def("sort_polygons", &GeometryCollection::sortPolygons, "Sort polygons by a custom key function") - .def("simplify_polygons", &GeometryCollection::simplifyPolygons) - .def("size", &GeometryCollection::size) - - // Overload remove_point to handle both object and index - .def("remove_point", py::overload_cast &>(&GeometryCollection::removePoint), - "Remove a point by passing the Point object") - .def("remove_point", py::overload_cast(&GeometryCollection::removePoint), "Remove a point by its index") - .def("read_region", &GeometryCollection::readRegion) - .def("rebuild_rtree", &GeometryCollection::rebuildRTree, "Rebuild the R-tree index manually") - .def("scale", &GeometryCollection::scale, "Scale all geometries by a factor") - .def("set_offset", &GeometryCollection::setOffset, "Set an offset for all geometries") - .def_property_readonly("rtree_invalidated", &GeometryCollection::isRTreeInvalidated) - .def_property_readonly("pointer_id", &GeometryCollection::getPointerId) - .def_property_readonly("bounding_box", &GeometryCollection::computeBoundingBox) - .def_property_readonly("polygons", &GeometryCollection::getPolygons) - .def_property_readonly("rois", &GeometryCollection::getRois) - .def_property_readonly("boxes", &GeometryCollection::getBoxes) - .def_property_readonly("points", &GeometryCollection::getPoints); -} - -#endif // DLUP_GEOMETRY_COLLECTION_H diff --git a/src/geometry/exceptions.h b/src/geometry/exceptions.h deleted file mode 100644 index 41b2e814..00000000 --- a/src/geometry/exceptions.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef DLUP_GEOMETRY_EXCEPTIONS_H -#define DLUP_GEOMETRY_EXCEPTIONS_H - -#include -#include - -class GeometryError : public std::runtime_error { - public: - explicit GeometryError(const std::string &message) : std::runtime_error(message) {} -}; - -class GeometryNotFoundError : public GeometryError { - public: - explicit GeometryNotFoundError(const std::string &message) : GeometryError(message) {} -}; - -class GeometryCoordinatesError : public GeometryError { - public: - explicit GeometryCoordinatesError(const std::string &message) : GeometryError(message) {} -}; - -class GeometryIntersectionError : public GeometryError { - public: - explicit GeometryIntersectionError(const std::string &message) : GeometryError(message) {} -}; - -class GeometryTransformationError : public GeometryError { - public: - explicit GeometryTransformationError(const std::string &message) : GeometryError(message) {} -}; - -class GeometryFactoryFunctionError : public GeometryError { - public: - explicit GeometryFactoryFunctionError(const std::string &message) : GeometryError(message) {} -}; - -class GeometryInvalidPolygonError : public GeometryError { - public: - explicit GeometryInvalidPolygonError(const std::string &message) : GeometryError(message) {} -}; - -#endif // DLUP_GEOMETRY_EXCEPTIONS_H diff --git a/src/geometry/factory.h b/src/geometry/factory.h deleted file mode 100644 index 50ec4c0d..00000000 --- a/src/geometry/factory.h +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef DLUP_GEOMETRY_FACTORY_H -#define DLUP_GEOMETRY_FACTORY_H -#pragma once - -#include -#include -#include - -// FactoryGuard class definition -class FactoryGuard { - public: - FactoryGuard(py::function &factory_ref, py::function new_factory) - : factory_ref_(factory_ref), original_factory_(factory_ref) { - factory_ref_ = new_factory; - } - - ~FactoryGuard() { factory_ref_ = original_factory_; } - - private: - py::function &factory_ref_; - py::function original_factory_; -}; - -// Template class to manage factory functions -template -class FactoryManager { - public: - static void setFactory(py::function factory) { factoryFunction() = std::move(factory); } - - static py::object callFactoryFunction(const std::shared_ptr &object) { - return invokeFactoryFunction(factoryFunction(), object); - } - - static FactoryGuard createFactoryGuard(py::function factory) { return FactoryGuard(factoryFunction(), factory); } - - // New method to streamline setting factories and creating guards - template - static void setAndCreateFactoryGuard(py::function factory) { - setFactory(factory); - createFactoryGuard(factory); - } - - private: - static py::function &factoryFunction() { - static py::function instance; - return instance; - } - - static py::object invokeFactoryFunction(py::function factoryFunction, const std::shared_ptr &object) { - if (!factoryFunction || !PyCallable_Check(factoryFunction.ptr())) { - return py::cast(object); - } - - try { - py::object result = factoryFunction(object); - if (!result.is_none()) { - return result; - } else { - throw std::runtime_error("Factory function returned null object"); - } - } catch (const std::exception &e) { - throw std::runtime_error(std::string("Exception in factory function: ") + e.what()); - } catch (...) { - throw std::runtime_error("Unknown exception in factory function"); - } - } -}; - -#endif // DLUP_GEOMETRY_FACTORY_H diff --git a/src/geometry/lazy_array.h b/src/geometry/lazy_array.h deleted file mode 100644 index cb30d78c..00000000 --- a/src/geometry/lazy_array.h +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef DLUP_GEOMETRY_LAZY_ARRAY_H -#define DLUP_GEOMETRY_LAZY_ARRAY_H - -#include -#include -#include -#include -#include - -namespace py = pybind11; - -template -class LazyArray { - public: - using ComputeFunction = std::function()>; - - LazyArray(ComputeFunction compute_func, std::vector shape) - : compute_func_(std::move(compute_func)), computed_(false), shape_(std::move(shape)) {} - - py::array_t numpy() const { - if (!computed_) { - data_ = compute_func_(); - computed_ = true; - } - return data_; - } - - py::array_t operator*() const { return numpy(); } - - py::array_t py_numpy() const { return numpy(); } - - std::vector shape() const { return shape_; } - - LazyArray transpose(const std::vector &axes = {}) const { - std::vector new_shape(shape_); - if (axes.empty()) { - std::reverse(new_shape.begin(), new_shape.end()); - } else { - for (size_t i = 0; i < axes.size(); ++i) { - new_shape[i] = shape_[axes[i]]; - } - } - - return LazyArray( - [this, axes]() { return this->numpy().attr("transpose")(axes).template cast>(); }, new_shape); - } - - LazyArray reshape(const std::vector &new_shape) const { - return LazyArray([this, new_shape]() { return this->numpy().reshape(new_shape); }, new_shape); - } - - LazyArray operator+(const LazyArray &other) const { - return LazyArray([this, &other]() { return this->numpy() + other.numpy(); }, shape_); - } - - LazyArray operator-(const LazyArray &other) const { - return LazyArray([this, &other]() { return this->numpy() - other.numpy(); }, shape_); - } - - LazyArray multiply(const LazyArray &other) const { - return LazyArray([this, &other]() { return this->numpy() * other.numpy(); }, shape_); - } - - LazyArray operator/(const LazyArray &other) const { - return LazyArray([this, &other]() { return this->numpy() / other.numpy(); }, shape_); - } - - private: - ComputeFunction compute_func_; - mutable py::array_t data_; - mutable bool computed_; - std::vector shape_; -}; - -template -void declare_lazy_array(py::module &m, const std::string &type_name) { - py::class_>(m, type_name.c_str()) - .def(py::init::ComputeFunction, std::vector>()) - .def("numpy", &LazyArray::py_numpy) - .def("shape", &LazyArray::shape) - .def("transpose", &LazyArray::transpose, py::arg("axes") = std::vector()) - .def("reshape", &LazyArray::reshape) - .def("__array__", &LazyArray::py_numpy) - .def("__repr__", [](const LazyArray &) { return ""; }) - .def("__add__", &LazyArray::operator+) - .def("__sub__", &LazyArray::operator-) - .def("__mul__", &LazyArray::multiply) - .def("__truediv__", &LazyArray::operator/); -} - -#endif // DLUP_GEOMETRY_LAZY_ARRAY_H diff --git a/src/geometry/point.h b/src/geometry/point.h deleted file mode 100644 index f1f32475..00000000 --- a/src/geometry/point.h +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef DLUP_GEOMETRY_POINT_H -#define DLUP_GEOMETRY_POINT_H -#pragma once - -#include - -namespace bg = boost::geometry; - -using BoostPoint = bg::model::d2::point_xy; - -class Point : public BaseGeometry { - public: - ~Point() override = default; - std::shared_ptr point_; - - Point() : point_(std::make_shared()) {} - Point(const BoostPoint &p) : point_(std::make_shared(p)) {} - Point(std::shared_ptr p) : point_(p) {} - Point(double x, double y) : point_(std::make_shared(x, y)) {} - - Point(const Point &other) : BaseGeometry(other), point_(std::make_shared(*other.point_)) { - parameters_ = other.parameters_; // Copy parameters - } - - // Factory function for creating points from Python - static std::shared_ptr create(double x, double y) { return std::make_shared(x, y); } - std::pair getCoordinates() const { return std::make_pair(bg::get<0>(*point_), bg::get<1>(*point_)); } - std::string toWkt() const override { return convertToWkt(*point_); } - - inline double getX() const { return bg::get<0>(*point_); } - inline double getY() const { return bg::get<1>(*point_); } - double distanceTo(const Point &other) const { return bg::distance(*point_, *(other.point_)); } - bool equals(const Point &other) const { - bool pointEqual = bg::equals(*point_, *(other.point_)); - return parameters_ == other.parameters_ && pointEqual; - } - bool within(const Polygon &polygon) const { return bg::within(*point_, *(polygon.polygon_)); } - - void scale(double scaling) { setCoordinates(getX() * scaling, getY() * scaling); } - - private: - void setCoordinates(double x, double y) { - bg::set<0>(*point_, x); - bg::set<1>(*point_, y); - } -}; - -inline void declare_point(py::module &m) { - py::class_>(m, "Point") - .def(py::init<>()) - .def(py::init()) - .def(py::init()) - .def(py::init([](const std::shared_ptr &p) { - // Share the same C++ object, not creating a new one - return p; - })) - .def(py::init([](const Point &other) { - // Explicitly copy parameters when copying the polygon - auto newPoint = std::make_shared(*other.point_); - newPoint->parameters_ = other.parameters_; // Copy the parameters - return newPoint; - })) - .def_property_readonly("coordinates", &Point::getCoordinates, - "Get the coordinates of the point as an (x, y) tuple") - .def_property_readonly("x", &Point::getX, "Get the X coordinate") - .def_property_readonly("y", &Point::getY, "Get the Y coordinate") - .def("distance_to", &Point::distanceTo, py::arg("other"), "Calculate the distance to another point") - .def("equals", &Point::equals, py::arg("other"), "Check if the point is equal to another point") - .def("within", &Point::within, py::arg("polygon"), "Check if the point is within a polygon") - .def("scale", &Point::scale, py::arg("scaling"), "Scale the point in-place point by a factor") - .def_property_readonly("wkt", &Point::toWkt, "Get the WKT representation of the point"); -} - -#endif // DLUP_GEOMETRY_POINT_H \ No newline at end of file diff --git a/src/geometry/polygon.h b/src/geometry/polygon.h deleted file mode 100644 index ffd43e91..00000000 --- a/src/geometry/polygon.h +++ /dev/null @@ -1,216 +0,0 @@ - -#ifndef DLUP_GEOMETRY_POLYGON_H -#define DLUP_GEOMETRY_POLYGON_H -#pragma once - -#include "utilities.h" -#include -#include -#include - -namespace bg = boost::geometry; - -using BoostPoint = bg::model::d2::point_xy; -using BoostPolygon = bg::model::polygon; -using BoostRing = bg::model::ring; - -class Polygon : public BaseGeometry { - public: - using ExteriorRing = std::vector &; - using InteriorRings = std::vector &; - - ~Polygon() override = default; - std::shared_ptr polygon_; - - Polygon() : polygon_(std::make_shared()) {} - Polygon(const BoostPolygon &p) : polygon_(std::make_shared(p)) {} - // This doesn't work, but is probably - // Polygon(BoostPolygon &&p) : polygon(std::make_shared(std::move(p))) {} - Polygon(std::shared_ptr p) : polygon_(p) {} - - Polygon(const std::vector> &exterior, - const std::vector>> &interiors = {}) - : polygon_(std::make_shared()) { - setExterior(std::move(exterior)); - setInteriors(std::move(interiors)); - } - - bool equals(const Polygon &other) const { - bool polygon_is_equal = bg::equals(*polygon_, *(other.polygon_)); - return parameters_ == other.parameters_ && polygon_is_equal; - } - - // TODO: Box is probably sufficient. - std::vector> intersection(const BoostPolygon &otherPolygon) const; - - std::string toWkt() const override { return convertToWkt(*polygon_); } - - std::vector> getExterior() const; - std::vector>> getInteriors() const; - - bool contains(const Polygon &other) const { return bg::within(*(other.polygon_), *polygon_); } - bool isValid() const { return bg::is_valid(*polygon_); } - - void makeValid() { *polygon_ = utilities::MakeValid(*polygon_); } - - ExteriorRing getExteriorAsIterator() { return bg::exterior_ring(*polygon_); } - InteriorRings getInteriorAsIterator() { return polygon_->inners(); } - - double getArea() const { - // Shapely reorients the polygon in memory if it is not oriented correctly, but keeps the coordinates - // So we need to make a copy here to avoid modifying the original polygon - if (!is_corrected_) { - // Make a copy of the current polygon - BoostPolygon new_polygon = *polygon_; - bg::correct(new_polygon); // Correct the copied polygon - return bg::area(new_polygon); - } - - return bg::area(*polygon_); - } - - void setExterior(const std::vector> &coordinates); - void setInteriors(const std::vector>> &interiors); - void correctIfNeeded() const; - void scale(double scaling); - void simplifyPolygon(double tolerance); - - private: - mutable bool is_corrected_ = false; // mutable allows modification in const methods -}; - -void Polygon::scale(double scaling) { utilities::AffineTransform(*polygon_, {0.0, 0.0}, scaling); } -void Polygon::setInteriors(const std::vector>> &interiors) { - bg::interior_rings(*polygon_).clear(); - polygon_->inners().resize(interiors.size()); - - for (size_t i = 0; i < interiors.size(); ++i) { - const auto &interior_coords = interiors[i]; - auto &inner = polygon_->inners()[i]; - inner.clear(); - - for (const auto &coord : interior_coords) { - bg::append(inner, BoostPoint(coord.first, coord.second)); - } - - // Close the ring if it's not already closed - if (interior_coords.front() != interior_coords.back()) { - bg::append(inner, BoostPoint(interior_coords.front().first, interior_coords.front().second)); - } - } - - is_corrected_ = false; // Mark as not corrected. Correction reorients and closes -} -std::vector> Polygon::intersection(const BoostPolygon &otherPolygon) const { - // correctIfNeeded(); - // Make the polygon valid if needed before performing the intersection - // TODO: This simplifies the polygon!! - BoostPolygon validPolygon = utilities::MakeValid(*polygon_); - - std::vector intersectionResult; - bg::intersection(validPolygon, otherPolygon, intersectionResult); - - std::vector> result; - for (const auto &intersectedBoostPolygon : intersectionResult) { - auto intersectedPolygon = std::make_shared(intersectedBoostPolygon); - // Copy the parameters from this polygon to the new one - - for (const auto ¶m : parameters_) { - intersectedPolygon->setField(param.first, param.second); - } - - result.emplace_back(intersectedPolygon); - } - - return result; -} -void Polygon::simplifyPolygon(double tolerance) { bg::simplify(*polygon_, *polygon_, tolerance); } -void Polygon::correctIfNeeded() const { - if (!is_corrected_) { - bg::correct(*polygon_); // Dereference the shared pointer to apply the correction - is_corrected_ = true; - } -} - -std::vector> Polygon::getExterior() const { - std::vector> result; - result.reserve(bg::exterior_ring(*polygon_).size()); - for (const auto &point : bg::exterior_ring(*polygon_)) { - result.emplace_back(bg::get<0>(point), bg::get<1>(point)); - } - return result; -} - -std::vector>> Polygon::getInteriors() const { - // correctIfNeeded(); - std::vector>> result; - result.reserve(polygon_->inners().size()); - for (const auto &inner : polygon_->inners()) { - std::vector> inner_result; - for (const auto &point : inner) { - inner_result.emplace_back(bg::get<0>(point), bg::get<1>(point)); - } - result.emplace_back(inner_result); - } - return result; -} - -void Polygon::setExterior(const std::vector> &coordinates) { - bg::exterior_ring(*polygon_).clear(); - bg::exterior_ring(*polygon_).reserve(coordinates.size()); - for (const auto &coord : coordinates) { - bg::append(*polygon_, BoostPoint(coord.first, coord.second)); - } - - // Close the ring if it's not already closed - // Shapely does this, so we want to keep compatibility. - if (coordinates.front() != coordinates.back()) { - bg::append(*polygon_, BoostPoint(coordinates.front().first, coordinates.front().second)); - } - - is_corrected_ = false; // Mark as not corrected. Correction reorients and closes -} - -inline void declare_polygon(py::module &m) { - py::class_>(m, "Polygon") - .def(py::init<>()) - .def(py::init()) - .def(py::init> &, - const std::vector>> &>()) - .def(py::init([](const std::shared_ptr &p) { - // Share the same C++ object, not creating a new one - return p; - })) - .def(py::init([](const Polygon &other) { - // Explicitly copy parameters when copying the polygon - auto newPolygon = std::make_shared(*other.polygon_); - newPolygon->parameters_ = other.parameters_; // Copy the parameters - return newPolygon; - })) - .def("set_exterior", &Polygon::setExterior) - .def("set_interiors", &Polygon::setInteriors) - .def("get_exterior", &Polygon::getExterior) - .def("get_exterior_iterator", - [](Polygon &self) { - return py::make_iterator(self.getExteriorAsIterator().begin(), self.getExteriorAsIterator().end()); - }) - .def("get_interiors_iterator", - [](Polygon &self) { - return py::make_iterator(self.getInteriorAsIterator().begin(), self.getInteriorAsIterator().end()); - }) - .def("scale", &Polygon::scale, py::arg("scaling")) - .def("get_interiors", &Polygon::getInteriors) - .def("correct_orientation", &Polygon::correctIfNeeded) - .def("simplify", &Polygon::simplifyPolygon) - .def("contains", &Polygon::contains, py::arg("other"), - "Check if the polygon fully contains another polygon. Does not check if the fields are equal") - .def("make_valid", &Polygon::makeValid, - "Make the polygon valid by removing self-intersections and duplicate points") - .def("equals", &Polygon::equals, py::arg("other"), - "Check if the polygon is equal to another polygon. Checks if the fields are equal.") - .def_property_readonly("wkt", &Polygon::toWkt) - .def_property_readonly("is_valid", &Polygon::isValid) - .def_property_readonly("area", &Polygon::getArea); -} - -#endif // DLUP_GEOMETRY_POLYGON_H diff --git a/src/geometry/polygon_collection.h b/src/geometry/polygon_collection.h deleted file mode 100644 index ae8ad5f8..00000000 --- a/src/geometry/polygon_collection.h +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef DLUP_POLYGON_COLLECTION_H -#define DLUP_POLYGON_COLLECTION_H -#pragma once - -#include "factory.h" -#include "lazy_array.h" -#include -#include -#include -#include -#include - -class Polygon; - -class PolygonCollection { - public: - PolygonCollection(std::vector> polygons, std::tuple mask_size) - : polygons_(std::move(polygons)), mask_size_(std::move(mask_size)), initialized_(true) {} - - // Lazy initialization constructor - PolygonCollection(std::function>()> initializer, std::tuple mask_size) - : initialized_(false), polygons_(), mask_size_(std::move(mask_size)), initializer_(std::move(initializer)) {} - - std::vector getGeometries() const { - ensureInitialized(); - std::vector py_objects; - py_objects.reserve(polygons_.size()); - for (const auto &polygon : polygons_) { - py_objects.push_back(FactoryManager::callFactoryFunction(polygon)); - } - return py_objects; - } - - auto getMaskSize() const { return mask_size_; } - - LazyArray toMask(int default_value = 0) const { - ensureInitialized(); - // Capture polygons_ and mask_size_ by value - auto polygons_copy = polygons_; - auto mask_size_copy = mask_size_; - return LazyArray( - [polygons_copy, mask_size_copy, default_value]() { - auto mask = generateMaskFromAnnotations(polygons_copy, mask_size_copy, default_value); - int width = std::get<0>(mask_size_copy); - int height = std::get<1>(mask_size_copy); - return py::array_t({height, width}, mask->data()); - }, - {std::get<1>(mask_size_copy), std::get<0>(mask_size_copy)}); - } - - const std::vector> &getPolygonsVector() const { - ensureInitialized(); - return polygons_; - } - - private: - void ensureInitialized() const { - if (!initialized_ && initializer_) { - polygons_ = initializer_(); // Execute the lambda to initialize the polygons - initialized_ = true; - } else { - // Do nothing if already initialized or no initializer provided - } - } - - mutable bool initialized_; - mutable std::vector> polygons_; - std::tuple mask_size_; - std::function>()> initializer_; -}; - -void declare_pybind_polygon_collection(py::module &m) { - py::class_>(m, "PolygonCollection") - .def("get_geometries", &PolygonCollection::getGeometries) - .def("to_mask", &PolygonCollection::toMask, py::arg("default_value") = 0); -}; - -#endif // DLUP_POLYGON_COLLECTION_H diff --git a/src/geometry/region.h b/src/geometry/region.h deleted file mode 100644 index d8d26d1a..00000000 --- a/src/geometry/region.h +++ /dev/null @@ -1,126 +0,0 @@ -#ifndef DLUP_GEOMETRY_REGION_H -#define DLUP_GEOMETRY_REGION_H -#pragma once - -#include "factory.h" -#include "polygon_collection.h" -#include -#include -#include -#include -#include - -class Polygon; -class Box; -class Point; - -template -class AnnotationRegionBase { - public: - AnnotationRegionBase(std::vector> objects) : objects_(std::move(objects)) {} - - std::vector> getObjectVector() const { return objects_; } - - std::vector getObjects() const { - std::vector py_objects; - py_objects.reserve(objects_.size()); - for (const auto &object : objects_) { - py_objects.push_back(FactoryManager::callFactoryFunction(object)); - } - return py_objects; - } - - private: - std::vector> objects_; -}; - -class AnnotationRegion { - public: - AnnotationRegion(std::function region_generator) - : region_generator_(region_generator), initialized_(false), - polygon_collection_( - std::make_shared(std::vector>(), std::tuple{0, 0})), - roi_collection_( - std::make_shared(std::vector>(), std::tuple{0, 0})), - point_region_({}), box_region_({}) {} - - AnnotationRegion(std::vector> polygons, std::vector> rois, - std::vector> boxes, std::vector> points, - std::tuple mask_size) - : polygon_collection_(std::make_shared(std::move(polygons), std::move(mask_size))), - roi_collection_(std::make_shared(std::move(rois), std::move(mask_size))), - point_region_(std::move(points)), box_region_(std::move(boxes)), initialized_(true) {} - - std::shared_ptr getPolygonsEager() { - ensureInitialized(); - return polygon_collection_; - } - - std::shared_ptr getPolygons() { - ensureInitialized(); - if (!lazy_polygon_collection_) { - lazy_polygon_collection_ = std::make_shared( - [this]() -> std::vector> { - this->ensureInitialized(); - return polygon_collection_->getPolygonsVector(); // Ensure polygons are initialized - }, - polygon_collection_->getMaskSize()); - } - return lazy_polygon_collection_; - } - - std::shared_ptr getRois() { - ensureInitialized(); - if (!lazy_roi_collection_) { - lazy_roi_collection_ = std::make_shared( - [this]() -> std::vector> { - this->ensureInitialized(); - return roi_collection_->getPolygonsVector(); // Ensure rois are initialized - }, - roi_collection_->getMaskSize()); - } - return lazy_roi_collection_; - } - - std::vector getPoints() { - ensureInitialized(); - return point_region_.getObjects(); - } - - std::vector getBoxes() { - ensureInitialized(); - return box_region_.getObjects(); - } - - private: - void ensureInitialized() { - if (!initialized_) { - AnnotationRegion generated_region = region_generator_(); - polygon_collection_ = std::move(generated_region.polygon_collection_); - roi_collection_ = std::move(generated_region.roi_collection_); - point_region_ = std::move(generated_region.point_region_); - box_region_ = std::move(generated_region.box_region_); - initialized_ = true; - } - } - - std::function region_generator_; - bool initialized_; - std::shared_ptr polygon_collection_; - std::shared_ptr roi_collection_; - AnnotationRegionBase point_region_; - AnnotationRegionBase box_region_; - mutable std::shared_ptr lazy_polygon_collection_; - mutable std::shared_ptr lazy_roi_collection_; -}; - -void declare_pybind_region(py::module &m) { - py::class_>(m, "AnnotationRegion") - .def_property_readonly("polygons", &AnnotationRegion::getPolygons) - .def_property_readonly("rois", &AnnotationRegion::getRois) - .def_property_readonly("polygons_eager", &AnnotationRegion::getPolygonsEager) - .def_property_readonly("boxes", &AnnotationRegion::getBoxes) - .def_property_readonly("points", &AnnotationRegion::getPoints); -}; - -#endif // DLUP_GEOMETRY_REGION_H diff --git a/src/geometry/rtree.h b/src/geometry/rtree.h deleted file mode 100644 index 68b12696..00000000 --- a/src/geometry/rtree.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef DLUP_GEOMETRY_RTREE_H -#define DLUP_GEOMETRY_RTREE_H -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace bg = boost::geometry; -namespace bgi = boost::geometry::index; - -using BoostPoint = bg::model::d2::point_xy; -using BoostBox = bg::model::box; - -class RTreeBase { - public: - using RTreeType = bgi::rtree, bgi::quadratic<16>>; - - virtual ~RTreeBase() = default; - - virtual void rebuild() = 0; // Pure virtual function for rebuilding the R-tree - - void insert(const BoostBox &box, size_t index) { - std::lock_guard lock(rtree_mutex_); // Lock the mutex for thread safety - rtree_.insert(std::make_pair(box, index)); - rtree_invalidated_ = false; - } - - template - void query(const QueryType &query, OutputIterator out) { - std::lock_guard lock(rtree_mutex_); // Lock the mutex for thread safety - if (rtree_invalidated_) { - rebuild(); - } - rtree_.query(query, out); - } - - void invalidate() { - std::lock_guard lock(rtree_mutex_); // Lock the mutex for thread safety - rtree_invalidated_ = true; - } - - void clear() { - std::lock_guard lock(rtree_mutex_); // Lock the mutex for thread safety - rtree_.clear(); - rtree_invalidated_ = true; - } - - bool isInvalidated() const { - std::lock_guard lock(rtree_mutex_); // Lock the mutex for thread safety - return rtree_invalidated_; - } - - protected: - RTreeType rtree_; - bool rtree_invalidated_ = true; - mutable std::mutex rtree_mutex_; // Mutex to protect R-tree operations -}; - -#endif // DLUP_GEOMETRY_RTREE_H diff --git a/src/geometry/utilities.h b/src/geometry/utilities.h deleted file mode 100644 index bfde36b5..00000000 --- a/src/geometry/utilities.h +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef DLUP_GEOMETRY_UTILITIES_H -#define DLUP_GEOMETRY_UTILITIES_H -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace utilities { - -namespace bg = boost::geometry; - -// Aliases for common types -using BoostPoint = bg::model::d2::point_xy; -using BoostPolygon = bg::model::polygon; -using BoostBox = bg::model::box; - -// Function to make a polygon valid -BoostPolygon MakeValid(const BoostPolygon &polygon) { - BoostPolygon valid_polygon = polygon; - - // Check if the polygon is valid - if (!bg::is_valid(valid_polygon)) { - // Correct the polygon (removing self-intersections and duplicate points) - bg::correct(valid_polygon); - - // If still not valid, simplify it - if (!bg::is_valid(valid_polygon)) { - BoostPolygon simplifiedPolygon; - // TODO: emit a warning - bg::simplify(valid_polygon, simplifiedPolygon, 0.01); // TODO: Adjust tolerance - valid_polygon = simplifiedPolygon; - } - } - - return valid_polygon; -} - -void AffineTransform(BoostPolygon &polygon, const std::pair &origin, double scaling) { - bg::strategy::transform::matrix_transformer transform(scaling, 0, -origin.first, 0, scaling, - -origin.second, 0, 0, 1); - - // TODO: This is a bit weird that we can't just immediately apply this to the polygon - // Apply the transformation to each point of the exterior ring - for (auto &point : bg::exterior_ring(polygon)) { - bg::transform(point, point, transform); - } - - // Apply the transformation to each point of each interior ring - for (auto &ring : bg::interior_rings(polygon)) { - for (auto &point : ring) { - bg::transform(point, point, transform); - } - } -} - -// Function to apply an affine transformation to a point -void AffineTransform(BoostPoint &point, const std::pair &origin, double scaling) { - double x = (bg::get<0>(point) - origin.first) * scaling; - double y = (bg::get<1>(point) - origin.second) * scaling; - bg::set<0>(point, x); - bg::set<1>(point, y); -} - -void AffineTransform(BoostBox &box, const std::pair &origin, double scaling) { - bg::strategy::transform::matrix_transformer transform(scaling, 0, -origin.first, 0, scaling, - -origin.second, 0, 0, 1); - - // Apply the transformation to the min corner - bg::transform(bg::return_envelope(box), box, transform); -} -} // namespace utilities -// namespace geometry_utils - -#endif // DLUP_GEOMETRY_UTILITIES_H diff --git a/src/image.h b/src/image.h index c17763f0..01541a16 100644 --- a/src/image.h +++ b/src/image.h @@ -11,25 +11,25 @@ namespace image_utils { void downsample2x2(const std::vector &input, uint32_t inputWidth, uint32_t inputHeight, std::vector &output, uint32_t outputWidth, uint32_t outputHeight, int channels) { - for (uint32_t y = 0; y < outputHeight; ++y) { - for (uint32_t x = 0; x < outputWidth; ++x) { - for (int c = 0; c < channels; ++c) { - uint32_t sum = 0; - uint32_t count = 0; - for (uint32_t dy = 0; dy < 2; ++dy) { - for (uint32_t dx = 0; dx < 2; ++dx) { - uint32_t sx = 2 * x + dx; - uint32_t sy = 2 * y + dy; - if (sx < inputWidth && sy < inputHeight) { - sum += std::to_integer(input[(sy * inputWidth + sx) * channels + c]); - ++count; + for (uint32_t y = 0; y < outputHeight; ++y) { + for (uint32_t x = 0; x < outputWidth; ++x) { + for (int c = 0; c < channels; ++c) { + uint32_t sum = 0; + uint32_t count = 0; + for (uint32_t dy = 0; dy < 2; ++dy) { + for (uint32_t dx = 0; dx < 2; ++dx) { + uint32_t sx = 2 * x + dx; + uint32_t sy = 2 * y + dy; + if (sx < inputWidth && sy < inputHeight) { + sum += std::to_integer(input[(sy * inputWidth + sx) * channels + c]); + ++count; + } + } + } + output[(y * outputWidth + x) * channels + c] = static_cast(sum / count); } - } } - output[(y * outputWidth + x) * channels + c] = static_cast(sum / count); - } } - } } } // namespace image_utils diff --git a/src/libtiff_tiff_writer.cpp b/src/libtiff_tiff_writer.cpp index 73a80edb..7ec3bf2b 100644 --- a/src/libtiff_tiff_writer.cpp +++ b/src/libtiff_tiff_writer.cpp @@ -1,7 +1,5 @@ #include "constants.h" #include "image.h" -#include "tiff/exceptions.h" -#include "tiff/writer.h" #include #include #include @@ -17,44 +15,500 @@ #include #include +#ifdef HAVE_ZSTD +#include +#endif + +namespace fs = std::filesystem; +namespace py = pybind11; + +class TiffException : public std::runtime_error { +public: + explicit TiffException(const std::string &message) : std::runtime_error(message) {} +}; + +class TiffCompressionNotSupportedError : public TiffException { +public: + explicit TiffCompressionNotSupportedError(const std::string &message) + : TiffException("Compression not supported: " + message) {} +}; + +class TiffOpenException : public TiffException { +public: + explicit TiffOpenException(const std::string &message) : TiffException("Failed to open TIFF file: " + message) {} +}; + +class TiffWriteException : public TiffException { +public: + explicit TiffWriteException(const std::string &message) : TiffException("Failed to write TIFF data: " + message) {} +}; + +class TiffSetupException : public TiffException { +public: + explicit TiffSetupException(const std::string &message) : TiffException("Failed to setup TIFF: " + message) {} +}; + +class TiffReadException : public TiffException { +public: + explicit TiffReadException(const std::string &message) : TiffException("Failed to read TIFF data: " + message) {} +}; + +enum class CompressionType { NONE, JPEG, LZW, DEFLATE, ZSTD }; + +CompressionType string_to_compression_type(const std::string &compression) { + if (compression == "NONE") + return CompressionType::NONE; + if (compression == "JPEG") + return CompressionType::JPEG; + if (compression == "LZW") + return CompressionType::LZW; + if (compression == "DEFLATE") + return CompressionType::DEFLATE; + if (compression == "ZSTD") + return CompressionType::ZSTD; + throw std::invalid_argument("Invalid compression type: " + compression); +} + +struct TIFFDeleter { + void operator()(TIFF *tif) const noexcept { + if (tif) { + // Disable error reporting temporarily + TIFFErrorHandler oldHandler = TIFFSetErrorHandler(nullptr); + + // Attempt to flush any pending writes + if (TIFFFlush(tif) == 0) { + TIFFError("TIFFDeleter", "Failed to flush TIFF data"); + } + + TIFFClose(tif); + TIFFSetErrorHandler(oldHandler); + } + } +}; + +using TIFFPtr = std::unique_ptr; + +class LibtiffTiffWriter { +public: + LibtiffTiffWriter(fs::path filename, std::array imageSize, std::array mpp, + std::array tileSize, CompressionType compression = CompressionType::JPEG, + int quality = 100) + : filename(std::move(filename)), imageSize(imageSize), mpp(mpp), tileSize(tileSize), compression(compression), + quality(quality), tif(nullptr) { + + validateInputs(); + + TIFF *tiff_ptr = TIFFOpen(this->filename.c_str(), "w"); + if (!tiff_ptr) { + throw TiffOpenException("Unable to create TIFF file"); + } + tif.reset(tiff_ptr); + + setupTIFFDirectory(0); + } + + ~LibtiffTiffWriter(); + void writeTile(py::array_t tile, int row, int col); + void flush(); + void finalize(); + void writePyramid(); + +private: + std::string filename; + std::array imageSize; + std::array mpp; + std::array tileSize; + CompressionType compression; + int quality; + uint32_t tileCounter; + int numLevels = calculateLevels(); + TIFFPtr tif; + + void validateInputs() const; + int calculateLevels(); + std::pair calculateTiles(int level); + uint32_t calculateNumTiles(int level); + void setupTIFFDirectory(int level); + void writeTIFFDirectory(); + void writeDownsampledResolutionPage(int level); + + std::pair getLevelDimensions(int level); + std::vector read2x2TileGroup(TIFF *readTif, uint32_t row, uint32_t col, uint32_t prevWidth, + uint32_t prevHeight); + void setupReadTIFF(TIFF *readTif); +}; + +LibtiffTiffWriter::~LibtiffTiffWriter() { finalize(); } + +void LibtiffTiffWriter::writeTile(py::array_t tile, int row, + int col) { + auto numTiles = calculateNumTiles(0); + if (tileCounter >= numTiles) { + throw TiffWriteException("all tiles have already been written"); + } + auto buf = tile.request(); + if (buf.ndim < 2 || buf.ndim > 3) { + throw TiffWriteException("invalid number of dimensions in tile data. Expected 2 or 3, got " + + std::to_string(buf.ndim)); + } + auto [height, width, channels] = std::tuple{buf.shape[0], buf.shape[1], buf.ndim > 2 ? buf.shape[2] : 1}; + + // Verify dimensions and buffer size + size_t expected_size = static_cast(width) * height * channels; + if (static_cast(buf.size) != expected_size) { + throw TiffWriteException("buffer size does not match expected size. Expected " + std::to_string(expected_size) + + ", got " + std::to_string(buf.size)); + } + + // Check if tile coordinates are within bounds + if (row < 0 || row >= imageSize[0] || col < 0 || col >= imageSize[1]) { + auto [imageWidth, imageHeight] = getLevelDimensions(0); + throw TiffWriteException("tile coordinates out of bounds for row " + std::to_string(row) + ", col " + + std::to_string(col) + ". Image size is " + std::to_string(imageWidth) + "x" + + std::to_string(imageHeight)); + } + + // Write the tile + if (TIFFWriteTile(tif.get(), buf.ptr, col, row, 0, 0) < 0) { + throw TiffWriteException("TIFFWriteTile failed for row " + std::to_string(row) + ", col " + + std::to_string(col)); + } + tileCounter++; + if (tileCounter == numTiles) { + flush(); + } +} + +void LibtiffTiffWriter::validateInputs() const { + // check positivity of image size + if (imageSize[0] <= 0 || imageSize[1] <= 0 || imageSize[2] <= 0) { + throw std::invalid_argument("Invalid size parameters"); + } + + // check positivity of mpp + if (mpp[0] <= 0 || mpp[1] <= 0) { + throw std::invalid_argument("Invalid mpp value"); + } + + // check positivity of tile size + if (tileSize[0] <= 0 || tileSize[1] <= 0) { + throw std::invalid_argument("Invalid tile size"); + } + + // check quality parameter + if (quality < 0 || quality > 100) { + throw std::invalid_argument("Invalid quality value"); + } + + // check if tile size is power of two + if ((tileSize[0] & (tileSize[0] - 1)) != 0 || (tileSize[1] & (tileSize[1] - 1)) != 0) { + throw std::invalid_argument("Tile size must be a power of two"); + } +} + +int LibtiffTiffWriter::calculateLevels() { + int maxDim = std::max(imageSize[0], imageSize[1]); + int minTileDim = std::min(tileSize[0], tileSize[1]); + int numLevels = 1; + while (maxDim > minTileDim * 2) { + maxDim /= 2; + numLevels++; + } + return numLevels; +} + +std::pair LibtiffTiffWriter::calculateTiles(int level) { + auto [currentWidth, currentHeight] = getLevelDimensions(level); + auto [tileWidth, tileHeight] = tileSize; + + uint32_t numTilesX = (currentWidth + tileWidth - 1) / tileWidth; + uint32_t numTilesY = (currentHeight + tileHeight - 1) / tileHeight; + return {numTilesX, numTilesY}; +} + +uint32_t LibtiffTiffWriter::calculateNumTiles(int level) { + auto [numTilesX, numTilesY] = calculateTiles(level); + return numTilesX * numTilesY; +} + +std::pair LibtiffTiffWriter::getLevelDimensions(int level) { + uint32_t levelWidth = std::max(1, imageSize[1] >> level); + uint32_t levelHeight = std::max(1, imageSize[0] >> level); + return {levelWidth, levelHeight}; +} + +void LibtiffTiffWriter::flush() { + if (tif) { + if (TIFFFlush(tif.get()) != 1) { + throw TiffWriteException("failed to flush TIFF file"); + } + } +} + +void LibtiffTiffWriter::finalize() { + if (tif) { + // Only write directory if we haven't written all directories yet + if (TIFFCurrentDirectory(tif.get()) < TIFFNumberOfDirectories(tif.get()) - 1) { + TIFFWriteDirectory(tif.get()); + } + TIFFClose(tif.get()); + tif.release(); + } +} + +void LibtiffTiffWriter::setupReadTIFF(TIFF *readTif) { + auto set_field = [readTif](uint32_t tag, auto... value) { + if (TIFFSetField(readTif, tag, value...) != 1) { + throw TiffSetupException("failed to set TIFF field for reading: " + std::to_string(tag)); + } + }; + + uint16_t compression; + if (TIFFGetField(readTif, TIFFTAG_COMPRESSION, &compression) == 1) { + if (compression == COMPRESSION_JPEG) { + set_field(TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB); + } + } +} + +void LibtiffTiffWriter::setupTIFFDirectory(int level) { + auto set_field = [this](uint32_t tag, auto... value) { + if (TIFFSetField(tif.get(), tag, value...) != 1) { + throw TiffSetupException("failed to set TIFF field: " + std::to_string(tag)); + } + }; + + auto [width, height] = getLevelDimensions(level); + int channels = imageSize[2]; + + set_field(TIFFTAG_IMAGEWIDTH, width); + set_field(TIFFTAG_IMAGELENGTH, height); + set_field(TIFFTAG_SAMPLESPERPIXEL, channels); + set_field(TIFFTAG_BITSPERSAMPLE, 8); + set_field(TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + set_field(TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + set_field(TIFFTAG_TILEWIDTH, tileSize[1]); + set_field(TIFFTAG_TILELENGTH, tileSize[0]); + + if (channels == 3 || channels == 4) { + if (compression != CompressionType::JPEG) { + set_field(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + } + } else { + set_field(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK); + } + + if (channels == 4) { + uint16_t extra_samples = EXTRASAMPLE_ASSOCALPHA; + set_field(TIFFTAG_EXTRASAMPLES, 1, &extra_samples); + } else if (channels > 4) { + std::vector extra_samples(channels - 3, EXTRASAMPLE_UNSPECIFIED); + set_field(TIFFTAG_EXTRASAMPLES, channels - 3, extra_samples.data()); + } + + switch (compression) { + case CompressionType::NONE: + set_field(TIFFTAG_COMPRESSION, COMPRESSION_NONE); + break; + case CompressionType::JPEG: + set_field(TIFFTAG_COMPRESSION, COMPRESSION_JPEG); + set_field(TIFFTAG_JPEGQUALITY, quality); + set_field(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_YCBCR); + set_field(TIFFTAG_YCBCRSUBSAMPLING, 2, 2); + set_field(TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB); + break; + case CompressionType::LZW: + set_field(TIFFTAG_COMPRESSION, COMPRESSION_LZW); + break; + case CompressionType::DEFLATE: + set_field(TIFFTAG_COMPRESSION, COMPRESSION_ADOBE_DEFLATE); + break; + case CompressionType::ZSTD: +#ifdef HAVE_ZSTD + set_field(TIFFTAG_COMPRESSION, COMPRESSION_ZSTD); + set_field(TIFFTAG_ZSTD_LEVEL, 3); // 3 is the default + break; +#else + throw TiffCompressionNotSupportedError("ZSTD"); +#endif + default: + throw TiffSetupException("Unknown compression type"); + } + + // Convert mpp (micrometers per pixel) to pixels per centimeter + double pixels_per_cm_x = 10000.0 / mpp[0]; + double pixels_per_cm_y = 10000.0 / mpp[1]; + + set_field(TIFFTAG_RESOLUTIONUNIT, RESUNIT_CENTIMETER); + set_field(TIFFTAG_XRESOLUTION, pixels_per_cm_x); + set_field(TIFFTAG_YRESOLUTION, pixels_per_cm_y); + + // Set the image description + // TODO: This needs to be configurable + std::string description = "TODO"; + // set_field(TIFFTAG_IMAGEDESCRIPTION, description.c_str()); + + // Set the software tag with version from dlup + std::string software_tag = + "dlup " + std::string(DLUP_VERSION) + " (libtiff " + std::to_string(TIFFLIB_VERSION) + ")"; + set_field(TIFFTAG_SOFTWARE, software_tag.c_str()); + + // Set SubFileType for pyramid levels + if (level == 0) { + set_field(TIFFTAG_SUBFILETYPE, 0); + } else { + set_field(TIFFTAG_SUBFILETYPE, FILETYPE_REDUCEDIMAGE); + } +} + +std::vector LibtiffTiffWriter::read2x2TileGroup(TIFF *readTif, uint32_t row, uint32_t col, + uint32_t prevWidth, uint32_t prevHeight) { + auto [tileWidth, tileHeight] = tileSize; + int channels = imageSize[2]; + uint32_t fullGroupWidth = 2 * tileWidth; + uint32_t fullGroupHeight = 2 * tileHeight; + + // Initialize a zero buffer for the 2x2 group + std::vector groupBuffer(fullGroupWidth * fullGroupHeight * channels, std::byte(0)); + + for (int i = 0; i < 2; ++i) { + for (int j = 0; j < 2; ++j) { + uint32_t tileRow = row + i * tileHeight; + uint32_t tileCol = col + j * tileWidth; + + // Skip if this tile is out of bounds, this can happen when the image dimensions are smaller than 2x2 in + // tileSize + if (tileRow >= prevHeight || tileCol >= prevWidth) { + continue; + } + + std::vector tileBuf(TIFFTileSize(readTif)); + + if (TIFFReadTile(readTif, tileBuf.data(), tileCol, tileRow, 0, 0) < 0) { + throw TiffReadException("failed to read tile at row " + std::to_string(tileRow) + ", col " + + std::to_string(tileCol)); + } + + // Copy tile data to groupBuffer + uint32_t copyWidth = std::min(tileWidth, prevWidth - tileCol); + uint32_t copyHeight = std::min(tileHeight, prevHeight - tileRow); + for (uint32_t y = 0; y < copyHeight; ++y) { + for (uint32_t x = 0; x < copyWidth; ++x) { + for (int c = 0; c < channels; ++c) { + size_t groupIndex = + ((i * tileHeight + y) * fullGroupWidth + (j * tileWidth + x)) * channels + c; + size_t tileIndex = (y * tileWidth + x) * channels + c; + groupBuffer[groupIndex] = static_cast(tileBuf[tileIndex]); + } + } + } + } + } + + return groupBuffer; +} +void LibtiffTiffWriter::writeDownsampledResolutionPage(int level) { + if (level <= 0 || level >= numLevels) { + throw std::invalid_argument("Invalid level for downsampled resolution page"); + } + + auto [prevWidth, prevHeight] = getLevelDimensions(level - 1); + int channels = imageSize[2]; + auto [tileWidth, tileHeight] = tileSize; + + TIFFPtr readTif(TIFFOpen(filename.c_str(), "r")); + if (!readTif) { + throw TiffOpenException("failed to open TIFF file for reading"); + } + + if (!TIFFSetDirectory(readTif.get(), level - 1)) { + throw TiffReadException("failed to set directory to level " + std::to_string(level - 1)); + } + setupReadTIFF(readTif.get()); + + if (!TIFFSetDirectory(tif.get(), level - 1)) { + throw TiffReadException("failed to set directory to level " + std::to_string(level - 1)); + } + + if (!TIFFWriteDirectory(tif.get())) { + throw TiffWriteException("failed to create new directory for downsampled image"); + } + + setupTIFFDirectory(level); + + auto [numTilesX, numTilesY] = calculateTiles(level); + + for (uint32_t tileY = 0; tileY < numTilesY; ++tileY) { + for (uint32_t tileX = 0; tileX < numTilesX; ++tileX) { + uint32_t row = tileY * tileHeight * 2; + uint32_t col = tileX * tileWidth * 2; + + std::vector groupBuffer = read2x2TileGroup(readTif.get(), row, col, prevWidth, prevHeight); + std::vector downsampledBuffer(tileHeight * tileWidth * channels); + + image_utils::downsample2x2(groupBuffer, 2 * tileWidth, 2 * tileHeight, downsampledBuffer, tileWidth, + tileHeight, channels); + + if (TIFFWriteTile(tif.get(), reinterpret_cast(downsampledBuffer.data()), tileX * tileWidth, + tileY * tileHeight, 0, 0) < 0) { + throw TiffWriteException("failed to write downsampled tile at level " + std::to_string(level) + + ", row " + std::to_string(tileY) + ", col " + std::to_string(tileX)); + } + } + } + + readTif.reset(); + flush(); +} + +void LibtiffTiffWriter::writePyramid() { + numLevels = calculateLevels(); + + // The base level (level 0) is already written, so we start from level 1 + for (int level = 1; level < numLevels; ++level) { + writeDownsampledResolutionPage(level); + flush(); + } +} + PYBIND11_MODULE(_libtiff_tiff_writer, m) { - py::class_(m, "LibtiffTiffWriter") - .def(py::init([](py::object path, std::array Size, std::array mpp, std::array tileSize, - py::object compression, int quality) { - fs::path cpp_path; - if (py::isinstance(path)) { - cpp_path = fs::path(path.cast()); - } else if (py::hasattr(path, "__fspath__")) { - cpp_path = fs::path(path.attr("__fspath__")().cast()); - } else { - throw py::type_error("Expected str or os.PathLike object"); - } - - CompressionType comp_type; - if (py::isinstance(compression)) { - comp_type = string_to_compression_type(compression.cast()); - } else if (py::isinstance(compression)) { - comp_type = compression.cast(); - } else { - throw py::type_error("Expected str or CompressionType for compression"); - } - - return new LibtiffTiffWriter(std::move(cpp_path), Size, mpp, tileSize, comp_type, quality); - })) - .def("write_tile", &LibtiffTiffWriter::writeTile) - .def("write_pyramid", &LibtiffTiffWriter::writePyramid) - .def("finalize", &LibtiffTiffWriter::finalize); - - py::enum_(m, "CompressionType") - .value("NONE", CompressionType::NONE) - .value("JPEG", CompressionType::JPEG) - .value("LZW", CompressionType::LZW) - .value("DEFLATE", CompressionType::DEFLATE); - - py::register_exception(m, "TiffException"); - py::register_exception(m, "TiffOpenException"); - py::register_exception(m, "TiffReadException"); - py::register_exception(m, "TiffWriteException"); - py::register_exception(m, "TiffSetupException"); - py::register_exception(m, "TiffCompressionNotSupportedError"); + py::class_(m, "LibtiffTiffWriter") + .def(py::init([](py::object path, std::array size, std::array mpp, + std::array tileSize, py::object compression, int quality) { + fs::path cpp_path; + if (py::isinstance(path)) { + cpp_path = fs::path(path.cast()); + } else if (py::hasattr(path, "__fspath__")) { + cpp_path = fs::path(path.attr("__fspath__")().cast()); + } else { + throw py::type_error("Expected str or os.PathLike object"); + } + + CompressionType comp_type; + if (py::isinstance(compression)) { + comp_type = string_to_compression_type(compression.cast()); + } else if (py::isinstance(compression)) { + comp_type = compression.cast(); + } else { + throw py::type_error("Expected str or CompressionType for compression"); + } + + return new LibtiffTiffWriter(std::move(cpp_path), size, mpp, tileSize, comp_type, quality); + })) + .def("write_tile", &LibtiffTiffWriter::writeTile) + .def("write_pyramid", &LibtiffTiffWriter::writePyramid) + .def("finalize", &LibtiffTiffWriter::finalize); + + py::enum_(m, "CompressionType") + .value("NONE", CompressionType::NONE) + .value("JPEG", CompressionType::JPEG) + .value("LZW", CompressionType::LZW) + .value("DEFLATE", CompressionType::DEFLATE); + + py::register_exception(m, "TiffException"); + py::register_exception(m, "TiffOpenException"); + py::register_exception(m, "TiffReadException"); + py::register_exception(m, "TiffWriteException"); + py::register_exception(m, "TiffSetupException"); + py::register_exception(m, "TiffCompressionNotSupportedError"); } diff --git a/src/meson.build b/src/meson.build deleted file mode 100644 index 07c9f793..00000000 --- a/src/meson.build +++ /dev/null @@ -1,27 +0,0 @@ -# Include the dependencies module -third_party_dir = include_directories('..') - -# Importing necessary variables from the dependencies build file -incdir_pybind11 = get_variable('incdir_pybind11', []) -base_deps = get_variable('base_deps', []) -boost_dep = dependency('boost', modules : ['system', 'serialization']) -opencv_dep = dependency('opencv4') - -# Pybind11 modules -libtiff_tiff_writer = py.extension_module('_libtiff_tiff_writer', - 'libtiff_tiff_writer.cpp', - install : true, - subdir : 'dlup', # This is crucial - include_directories : [third_party_dir, incdir_pybind11], - cpp_args : cpp_args, - link_args : link_args, - dependencies : base_deps) - -geometry = py.extension_module('_geometry', - 'geometry.cpp', - install : true, - subdir : 'dlup', # This is crucial - include_directories : [third_party_dir, incdir_pybind11], - cpp_args : cpp_args, - link_args : link_args, - dependencies : base_deps + [boost_dep, opencv_dep]) diff --git a/src/opencv.h b/src/opencv.h deleted file mode 100644 index ae624351..00000000 --- a/src/opencv.h +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef DLUP_OPENCV_H -#define DLUP_OPENCV_H - -#include "geometry/polygon.h" -#include -#include -#include -#include -#include -#include -#include - -std::shared_ptr> generateMaskFromAnnotations(const std::vector> &annotations, - const std::tuple &mask_size, - int default_value) { - - int width = std::get<0>(mask_size); - int height = std::get<1>(mask_size); - - // Use a shared_ptr to manage the mask's lifetime - auto mask = std::make_shared>(width * height, default_value); - - cv::Mat mask_view(height, width, CV_32S, mask->data()); - - std::vector exterior_cv_points; - std::vector> interiors_cv_points; - - for (const auto &annotation : annotations) { - auto index_value_field = annotation->getField("index"); - if (!index_value_field) { - auto label = annotation->getField("label"); - throw std::runtime_error("Annotation with label '" + label->cast() + "' does not have an index."); - } - int index_value = index_value_field->cast(); - - // Convert exterior points - exterior_cv_points.clear(); - const auto &exterior = annotation->getExterior(); - exterior_cv_points.reserve(exterior.size()); - for (const auto &[x, y] : exterior) { - exterior_cv_points.emplace_back(static_cast(std::round(x)), static_cast(std::round(y))); - } - - // Convert interior points - interiors_cv_points.clear(); - const auto &interiors = annotation->getInteriors(); - interiors_cv_points.reserve(interiors.size()); - for (const auto &interior : interiors) { - interiors_cv_points.emplace_back(); // Create a new vector in place - interiors_cv_points.back().reserve(interior.size()); - for (const auto &[x, y] : interior) { - interiors_cv_points.back().emplace_back(static_cast(std::round(x)), static_cast(std::round(y))); - } - } - - if (!interiors_cv_points.empty()) { - // Create a mask for holes - cv::Mat holes_mask = cv::Mat::zeros(height, width, CV_8U); - cv::fillPoly(holes_mask, interiors_cv_points, cv::Scalar(1)); - - // Apply exterior mask first, then restore original values in holes - cv::Mat original_values = mask_view.clone(); - cv::fillPoly(mask_view, std::vector>{exterior_cv_points}, cv::Scalar(index_value)); - original_values.copyTo(mask_view, holes_mask); - } else { - // Directly fill the exterior mask if no interiors exist - cv::fillPoly(mask_view, std::vector>{exterior_cv_points}, cv::Scalar(index_value)); - } - } - - return mask; -} - -#endif // DLUP_OPENCV_H diff --git a/src/tiff/exceptions.h b/src/tiff/exceptions.h deleted file mode 100644 index 863cffff..00000000 --- a/src/tiff/exceptions.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef DLUP_TIFF_EXCEPTIONS_H -#define DLUP_TIFF_EXCEPTIONS_H -#pragma once - -#include -#include -#include - -class TiffException : public std::runtime_error { - public: - explicit TiffException(const std::string &message) : std::runtime_error(message) {} -}; - -class TiffCompressionNotSupportedError : public TiffException { - public: - explicit TiffCompressionNotSupportedError(const std::string &message) - : TiffException("Compression not supported: " + message) {} -}; - -class TiffOpenException : public TiffException { - public: - explicit TiffOpenException(const std::string &message) : TiffException("Failed to open TIFF file: " + message) {} -}; - -class TiffWriteException : public TiffException { - public: - explicit TiffWriteException(const std::string &message) : TiffException("Failed to write TIFF data: " + message) {} -}; - -class TiffSetupException : public TiffException { - public: - explicit TiffSetupException(const std::string &message) : TiffException("Failed to setup TIFF: " + message) {} -}; - -class TiffReadException : public TiffException { - public: - explicit TiffReadException(const std::string &message) : TiffException("Failed to read TIFF data: " + message) {} -}; - -#endif // DLUP_TIFF_EXCEPTIONS_H diff --git a/src/tiff/writer.h b/src/tiff/writer.h deleted file mode 100644 index f53e038c..00000000 --- a/src/tiff/writer.h +++ /dev/null @@ -1,444 +0,0 @@ -#ifndef DLUP_TIFF_WRITER_H -#define DLUP_TIFF_WRITER_H -#pragma once - -#include "../constants.h" -#include "../image.h" -#include "exceptions.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_ZSTD -#include -#endif - -namespace fs = std::filesystem; -namespace py = pybind11; - -enum class CompressionType { NONE, JPEG, LZW, DEFLATE, ZSTD }; - -CompressionType string_to_compression_type(const std::string &compression) { - if (compression == "NONE") - return CompressionType::NONE; - if (compression == "JPEG") - return CompressionType::JPEG; - if (compression == "LZW") - return CompressionType::LZW; - if (compression == "DEFLATE") - return CompressionType::DEFLATE; - if (compression == "ZSTD") - return CompressionType::ZSTD; - throw std::invalid_argument("Invalid compression type: " + compression); -} - -struct TIFFDeleter { - void operator()(TIFF *tif) const noexcept { - if (tif) { - // Disable error reporting temporarily - TIFFErrorHandler oldHandler = TIFFSetErrorHandler(nullptr); - - // Attempt to flush any pending writes - if (TIFFFlush(tif) == 0) { - TIFFError("TIFFDeleter", "Failed to flush TIFF data"); - } - - TIFFClose(tif); - TIFFSetErrorHandler(oldHandler); - } - } -}; - -using TIFFPtr = std::unique_ptr; - -class LibtiffTiffWriter { - public: - LibtiffTiffWriter(fs::path filename, std::array imageSize, std::array mpp, - std::array tileSize, CompressionType compression = CompressionType::JPEG, int quality = 100) - : filename(std::move(filename)), imageSize(imageSize), mpp(mpp), tileSize(tileSize), compression(compression), - quality(quality), tif(nullptr) { - - validateInputs(); - - TIFF *tiff_ptr = TIFFOpen(this->filename.c_str(), "w"); - if (!tiff_ptr) { - throw TiffOpenException("Unable to create TIFF file"); - } - tif.reset(tiff_ptr); - - setupTIFFDirectory(0); - } - - ~LibtiffTiffWriter(); - void writeTile(py::array_t tile, int row, int col); - void flush(); - void finalize(); - void writePyramid(); - - private: - std::string filename; - std::array imageSize; - std::array mpp; - std::array tileSize; - CompressionType compression; - int quality; - uint32_t tileCounter; - int numLevels = calculateLevels(); - TIFFPtr tif; - - void validateInputs() const; - int calculateLevels(); - std::pair calculateTiles(int level); - uint32_t CalculateNumTiles(int level); - void setupTIFFDirectory(int level); - void writeTIFFDirectory(); - void writeDownsampledResolutionPage(int level); - - std::pair getLevelDimensions(int level); - std::vector read2x2TileGroup(TIFF *readTif, uint32_t row, uint32_t col, uint32_t prevWidth, - uint32_t prevHeight); - void setupReadTIFF(TIFF *readTif); -}; - -LibtiffTiffWriter::~LibtiffTiffWriter() { finalize(); } - -void LibtiffTiffWriter::writeTile(py::array_t tile, int row, - int col) { - auto num_tiles = CalculateNumTiles(0); - if (tileCounter >= num_tiles) { - throw TiffWriteException("all tiles have already been written"); - } - auto buf = tile.request(); - if (buf.ndim < 2 || buf.ndim > 3) { - throw TiffWriteException("invalid number of dimensions in tile data. Expected 2 or 3, got " + - std::to_string(buf.ndim)); - } - auto [height, width, channels] = std::tuple{buf.shape[0], buf.shape[1], buf.ndim > 2 ? buf.shape[2] : 1}; - - // Verify dimensions and buffer Size - size_t expected_size = static_cast(width) * height * channels; - if (static_cast(buf.size) != expected_size) { - throw TiffWriteException("buffer Size does not match expected Size. Expected " + std::to_string(expected_size) + - ", got " + std::to_string(buf.size)); - } - - // Check if tile coordinates are within bounds - if (row < 0 || row >= imageSize[0] || col < 0 || col >= imageSize[1]) { - auto [imageWidth, imageHeight] = getLevelDimensions(0); - throw TiffWriteException("tile coordinates out of bounds for row " + std::to_string(row) + ", col " + - std::to_string(col) + ". Image Size is " + std::to_string(imageWidth) + "x" + - std::to_string(imageHeight)); - } - - // Write the tile - if (TIFFWriteTile(tif.get(), buf.ptr, col, row, 0, 0) < 0) { - throw TiffWriteException("TIFFWriteTile failed for row " + std::to_string(row) + ", col " + std::to_string(col)); - } - tileCounter++; - if (tileCounter == num_tiles) { - flush(); - } -} - -void LibtiffTiffWriter::validateInputs() const { - // check positivity of image Size - if (imageSize[0] <= 0 || imageSize[1] <= 0 || imageSize[2] <= 0) { - throw std::invalid_argument("Invalid Size parameters"); - } - - // check positivity of mpp - if (mpp[0] <= 0 || mpp[1] <= 0) { - throw std::invalid_argument("Invalid mpp value"); - } - - // check positivity of tile Size - if (tileSize[0] <= 0 || tileSize[1] <= 0) { - throw std::invalid_argument("Invalid tile Size"); - } - - // check quality parameter - if (quality < 0 || quality > 100) { - throw std::invalid_argument("Invalid quality value"); - } - - // check if tile Size is power of two - if ((tileSize[0] & (tileSize[0] - 1)) != 0 || (tileSize[1] & (tileSize[1] - 1)) != 0) { - throw std::invalid_argument("Tile Size must be a power of two"); - } -} - -int LibtiffTiffWriter::calculateLevels() { - int maxDim = std::max(imageSize[0], imageSize[1]); - int minTileDim = std::min(tileSize[0], tileSize[1]); - int numLevels = 1; - while (maxDim > minTileDim * 2) { - maxDim /= 2; - numLevels++; - } - return numLevels; -} - -std::pair LibtiffTiffWriter::calculateTiles(int level) { - auto [currentWidth, currentHeight] = getLevelDimensions(level); - auto [tileWidth, tileHeight] = tileSize; - - uint32_t numTilesX = (currentWidth + tileWidth - 1) / tileWidth; - uint32_t numTilesY = (currentHeight + tileHeight - 1) / tileHeight; - return {numTilesX, numTilesY}; -} - -uint32_t LibtiffTiffWriter::CalculateNumTiles(int level) { - auto [numTilesX, numTilesY] = calculateTiles(level); - return numTilesX * numTilesY; -} - -std::pair LibtiffTiffWriter::getLevelDimensions(int level) { - uint32_t levelWidth = std::max(1, imageSize[1] >> level); - uint32_t levelHeight = std::max(1, imageSize[0] >> level); - return {levelWidth, levelHeight}; -} - -void LibtiffTiffWriter::flush() { - if (tif) { - if (TIFFFlush(tif.get()) != 1) { - throw TiffWriteException("failed to flush TIFF file"); - } - } -} - -void LibtiffTiffWriter::finalize() { - if (tif) { - // Only write directory if we haven't written all directories yet - if (TIFFCurrentDirectory(tif.get()) < TIFFNumberOfDirectories(tif.get()) - 1) { - TIFFWriteDirectory(tif.get()); - } - TIFFClose(tif.get()); - tif.release(); - } -} - -void LibtiffTiffWriter::setupReadTIFF(TIFF *readTif) { - auto set_field = [readTif](uint32_t tag, auto... value) { - if (TIFFSetField(readTif, tag, value...) != 1) { - throw TiffSetupException("failed to set TIFF field for reading: " + std::to_string(tag)); - } - }; - - uint16_t compression; - if (TIFFGetField(readTif, TIFFTAG_COMPRESSION, &compression) == 1) { - if (compression == COMPRESSION_JPEG) { - set_field(TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB); - } - } -} - -void LibtiffTiffWriter::setupTIFFDirectory(int level) { - auto set_field = [this](uint32_t tag, auto... value) { - if (TIFFSetField(tif.get(), tag, value...) != 1) { - throw TiffSetupException("failed to set TIFF field: " + std::to_string(tag)); - } - }; - - auto [width, height] = getLevelDimensions(level); - int channels = imageSize[2]; - - set_field(TIFFTAG_IMAGEWIDTH, width); - set_field(TIFFTAG_IMAGELENGTH, height); - set_field(TIFFTAG_SAMPLESPERPIXEL, channels); - set_field(TIFFTAG_BITSPERSAMPLE, 8); - set_field(TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); - set_field(TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); - set_field(TIFFTAG_TILEWIDTH, tileSize[1]); - set_field(TIFFTAG_TILELENGTH, tileSize[0]); - - if (channels == 3 || channels == 4) { - if (compression != CompressionType::JPEG) { - set_field(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); - } - } else { - set_field(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK); - } - - if (channels == 4) { - uint16_t extra_samples = EXTRASAMPLE_ASSOCALPHA; - set_field(TIFFTAG_EXTRASAMPLES, 1, &extra_samples); - } else if (channels > 4) { - std::vector extra_samples(channels - 3, EXTRASAMPLE_UNSPECIFIED); - set_field(TIFFTAG_EXTRASAMPLES, channels - 3, extra_samples.data()); - } - - switch (compression) { - case CompressionType::NONE: - set_field(TIFFTAG_COMPRESSION, COMPRESSION_NONE); - break; - case CompressionType::JPEG: - set_field(TIFFTAG_COMPRESSION, COMPRESSION_JPEG); - set_field(TIFFTAG_JPEGQUALITY, quality); - set_field(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_YCBCR); - set_field(TIFFTAG_YCBCRSUBSAMPLING, 2, 2); - set_field(TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB); - break; - case CompressionType::LZW: - set_field(TIFFTAG_COMPRESSION, COMPRESSION_LZW); - break; - case CompressionType::DEFLATE: - set_field(TIFFTAG_COMPRESSION, COMPRESSION_ADOBE_DEFLATE); - break; - case CompressionType::ZSTD: -#ifdef HAVE_ZSTD - set_field(TIFFTAG_COMPRESSION, COMPRESSION_ZSTD); - set_field(TIFFTAG_ZSTD_LEVEL, 3); // 3 is the default - break; -#else - throw TiffCompressionNotSupportedError("ZSTD"); -#endif - default: - throw TiffSetupException("Unknown compression type"); - } - - // Convert mpp (micrometers per pixel) to pixels per centimeter - double pixels_per_cm_x = 10000.0 / mpp[0]; - double pixels_per_cm_y = 10000.0 / mpp[1]; - - set_field(TIFFTAG_RESOLUTIONUNIT, RESUNIT_CENTIMETER); - set_field(TIFFTAG_XRESOLUTION, pixels_per_cm_x); - set_field(TIFFTAG_YRESOLUTION, pixels_per_cm_y); - - // Set the image description - // TODO: This needs to be configurable - std::string description = "TODO"; - // set_field(TIFFTAG_IMAGEDESCRIPTION, description.c_str()); - - // Set the software tag with version from dlup - std::string software_tag = "dlup " + std::string(DLUP_VERSION) + " (libtiff " + std::to_string(TIFFLIB_VERSION) + ")"; - set_field(TIFFTAG_SOFTWARE, software_tag.c_str()); - - // Set SubFileType for pyramid levels - if (level == 0) { - set_field(TIFFTAG_SUBFILETYPE, 0); - } else { - set_field(TIFFTAG_SUBFILETYPE, FILETYPE_REDUCEDIMAGE); - } -} - -std::vector LibtiffTiffWriter::read2x2TileGroup(TIFF *readTif, uint32_t row, uint32_t col, - uint32_t prevWidth, uint32_t prevHeight) { - auto [tileWidth, tileHeight] = tileSize; - int channels = imageSize[2]; - uint32_t fullGroupWidth = 2 * tileWidth; - uint32_t fullGroupHeight = 2 * tileHeight; - - // Initialize a zero buffer for the 2x2 group - std::vector groupBuffer(fullGroupWidth * fullGroupHeight * channels, std::byte(0)); - - for (int i = 0; i < 2; ++i) { - for (int j = 0; j < 2; ++j) { - uint32_t tileRow = row + i * tileHeight; - uint32_t tileCol = col + j * tileWidth; - - // Skip if this tile is out of bounds, this can happen when the image dimensions are smaller than 2x2 in - // tileSize - if (tileRow >= prevHeight || tileCol >= prevWidth) { - continue; - } - - std::vector tileBuf(TIFFTileSize(readTif)); - - if (TIFFReadTile(readTif, tileBuf.data(), tileCol, tileRow, 0, 0) < 0) { - throw TiffReadException("failed to read tile at row " + std::to_string(tileRow) + ", col " + - std::to_string(tileCol)); - } - - // Copy tile data to groupBuffer - uint32_t copyWidth = std::min(tileWidth, prevWidth - tileCol); - uint32_t copyHeight = std::min(tileHeight, prevHeight - tileRow); - for (uint32_t y = 0; y < copyHeight; ++y) { - for (uint32_t x = 0; x < copyWidth; ++x) { - for (int c = 0; c < channels; ++c) { - size_t groupIndex = ((i * tileHeight + y) * fullGroupWidth + (j * tileWidth + x)) * channels + c; - size_t tileIndex = (y * tileWidth + x) * channels + c; - groupBuffer[groupIndex] = static_cast(tileBuf[tileIndex]); - } - } - } - } - } - - return groupBuffer; -} -void LibtiffTiffWriter::writeDownsampledResolutionPage(int level) { - if (level <= 0 || level >= numLevels) { - throw std::invalid_argument("Invalid level for downsampled resolution page"); - } - - auto [prevWidth, prevHeight] = getLevelDimensions(level - 1); - int channels = imageSize[2]; - auto [tileWidth, tileHeight] = tileSize; - - TIFFPtr readTif(TIFFOpen(filename.c_str(), "r")); - if (!readTif) { - throw TiffOpenException("failed to open TIFF file for reading"); - } - - if (!TIFFSetDirectory(readTif.get(), level - 1)) { - throw TiffReadException("failed to set directory to level " + std::to_string(level - 1)); - } - setupReadTIFF(readTif.get()); - - if (!TIFFSetDirectory(tif.get(), level - 1)) { - throw TiffReadException("failed to set directory to level " + std::to_string(level - 1)); - } - - if (!TIFFWriteDirectory(tif.get())) { - throw TiffWriteException("failed to create new directory for downsampled image"); - } - - setupTIFFDirectory(level); - - auto [numTilesX, numTilesY] = calculateTiles(level); - - for (uint32_t tileY = 0; tileY < numTilesY; ++tileY) { - for (uint32_t tileX = 0; tileX < numTilesX; ++tileX) { - uint32_t row = tileY * tileHeight * 2; - uint32_t col = tileX * tileWidth * 2; - - std::vector groupBuffer = read2x2TileGroup(readTif.get(), row, col, prevWidth, prevHeight); - std::vector downsampledBuffer(tileHeight * tileWidth * channels); - - image_utils::downsample2x2(groupBuffer, 2 * tileWidth, 2 * tileHeight, downsampledBuffer, tileWidth, tileHeight, - channels); - - if (TIFFWriteTile(tif.get(), reinterpret_cast(downsampledBuffer.data()), tileX * tileWidth, - tileY * tileHeight, 0, 0) < 0) { - throw TiffWriteException("failed to write downsampled tile at level " + std::to_string(level) + ", row " + - std::to_string(tileY) + ", col " + std::to_string(tileX)); - } - } - } - - readTif.reset(); - flush(); -} - -void LibtiffTiffWriter::writePyramid() { - numLevels = calculateLevels(); - - // The base level (level 0) is already written, so we start from level 1 - for (int level = 1; level < numLevels; ++level) { - writeDownsampledResolutionPage(level); - flush(); - } -} - -#endif diff --git a/tests/files/dlup_xml_example.xml b/tests/files/dlup_xml_example.xml deleted file mode 100644 index 7658e63d..00000000 --- a/tests/files/dlup_xml_example.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - example_image_01 - 1.0 - 2024-09-03 - ExampleSoftware - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/files/halo_holes.annotations b/tests/files/halo_holes.annotations deleted file mode 100644 index 86029e44..00000000 --- a/tests/files/halo_holes.annotations +++ /dev/null @@ -1,371 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/files/test_different_types_halo.annotations b/tests/files/test_different_types_halo.annotations deleted file mode 100644 index 3b2c924a..00000000 --- a/tests/files/test_different_types_halo.annotations +++ /dev/null @@ -1,208 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/infer_ann.py b/tests/infer_ann.py new file mode 100644 index 00000000..e7be73c0 --- /dev/null +++ b/tests/infer_ann.py @@ -0,0 +1,3 @@ +from dlup.annotations import WsiAnnotations + +ann = WsiAnnotations.from_geojson("/Users/jteuwen/annotations.json") diff --git a/tests/test_annotations.py b/tests/test_annotations.py index 118c906c..4e055afe 100644 --- a/tests/test_annotations.py +++ b/tests/test_annotations.py @@ -9,14 +9,7 @@ import pytest import shapely.geometry -from dlup.annotations import ( - AnnotationClass, - AnnotationType, - DlupShapelyPoint, - DlupShapelyPolygon, - WsiAnnotations, - shape, -) +from dlup.annotations import AnnotationClass, AnnotationType, Point, Polygon, WsiAnnotations, shape from dlup.utils.imports import DARWIN_SDK_AVAILABLE ASAP_XML_EXAMPLE = b""" @@ -61,12 +54,12 @@ class TestAnnotations: _v7_raster_annotations = None additinoaL_point_a_cls = AnnotationClass(label="example", annotation_type=AnnotationType.POINT, color=(255, 0, 0)) - additional_point = DlupShapelyPoint((1, 2), a_cls=additinoaL_point_a_cls) + additional_point = Point((1, 2), a_cls=additinoaL_point_a_cls) additional_polygon_a_cls = AnnotationClass( label="example", annotation_type=AnnotationType.POLYGON, color=(255, 0, 0), z_index=1 ) - additional_polygon = DlupShapelyPolygon([(0, 0), (4, 0), (4, 4), (0, 4)], a_cls=additional_polygon_a_cls) + additional_polygon = Polygon([(0, 0), (4, 0), (4, 4), (0, 4)], a_cls=additional_polygon_a_cls) @property def v7_annotations(self): @@ -116,7 +109,7 @@ def test_asap_to_geojson(self): shape1 = shape(elem1["geometry"], label="") assert len(set([_.label for _ in shape0])) == 1 assert len(set([_.label for _ in shape1])) == 1 - if isinstance(shape0[0], DlupShapelyPolygon): + if isinstance(shape0[0], Polygon): complete_shape0 = shapely.geometry.MultiPolygon(shape0) complete_shape1 = shapely.geometry.MultiPolygon(shape1) else: @@ -133,7 +126,7 @@ def test_read_region(self, region): assert len(region) == 1 assert region[0].area == area assert region[0].label == "healthy glands" - assert isinstance(region[0], DlupShapelyPolygon) + assert isinstance(region[0], Polygon) if not area: assert region == [] @@ -208,8 +201,8 @@ def test_polygon_pickling(self): exterior = [(0, 0), (4, 0), (4, 4), (0, 4)] hole1 = [(1, 1), (2, 1), (2, 2), (1, 2)] hole2 = [(3, 3), (3, 3.5), (3.5, 3.5), (3.5, 3)] - dlup_polygon_with_holes = DlupShapelyPolygon(exterior, [hole1, hole2], a_cls=annotation_class) - dlup_solid_polygon = DlupShapelyPolygon([(0, 0), (1, 0), (1, 1), (0, 1)], a_cls=annotation_class) + dlup_polygon_with_holes = Polygon(exterior, [hole1, hole2], a_cls=annotation_class) + dlup_solid_polygon = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)], a_cls=annotation_class) with tempfile.NamedTemporaryFile(suffix=".pkl", mode="w+b") as pickled_polygon_file: pickle.dump(dlup_solid_polygon, pickled_polygon_file) pickled_polygon_file.flush() @@ -228,7 +221,7 @@ def test_point_pickling(self): annotation_class = AnnotationClass( label="example", annotation_type=AnnotationType.POINT, color=(255, 0, 0), z_index=None ) - dlup_point = DlupShapelyPoint([(1, 2)], a_cls=annotation_class) + dlup_point = Point([(1, 2)], a_cls=annotation_class) with tempfile.NamedTemporaryFile(suffix=".pkl", mode="w+b") as pickled_point_file: pickle.dump(dlup_point, pickled_point_file) pickled_point_file.flush() diff --git a/tests/test_background.py b/tests/test_background.py index 34b34598..2da62f88 100644 --- a/tests/test_background.py +++ b/tests/test_background.py @@ -6,7 +6,7 @@ from dlup import AnnotationType, SlideImage, WsiAnnotations from dlup._exceptions import DlupError -from dlup.annotations import AnnotationClass, DlupShapelyPolygon +from dlup.annotations import AnnotationClass, Polygon from dlup.background import compute_masked_indices from dlup.data.dataset import _coords_to_region from dlup.tiling import Grid @@ -59,10 +59,8 @@ def test_wsiannotations(self, dlup_wsi, threshold): # Let's make a shapely polygon thats equal to # background_mask[14:20, 10:20] = True # background_mask[85:100, 50:80] = True - polygon0 = DlupShapelyPolygon( - box(100, 140, 200, 200), AnnotationClass(annotation_type=AnnotationType.POLYGON, label="bg") - ) - polygon1 = DlupShapelyPolygon( + polygon0 = Polygon(box(100, 140, 200, 200), AnnotationClass(annotation_type=AnnotationType.POLYGON, label="bg")) + polygon1 = Polygon( box(500, 850, 800, 1000), AnnotationClass(annotation_type=AnnotationType.POLYGON, label="bg") ) diff --git a/tests/test_geometry.py b/tests/test_geometry.py deleted file mode 100644 index 3c30753f..00000000 --- a/tests/test_geometry.py +++ /dev/null @@ -1,538 +0,0 @@ -# Copyright (c) dlup contributors - -"""Test the geometry classes.""" - -import copy -import pickle -import tempfile - -import pytest -import shapely.geometry -from shapely.geometry import Point as ShapelyPoint -from shapely.geometry import Polygon as ShapelyPolygon - -import dlup._geometry as dg -from dlup.geometry import ( - Box, - GeometryCollection, - Point, - Polygon, - _BaseGeometry, - _box_factory, - _point_factory, - _polygon_factory, -) - -polygons = [ - Polygon(dg.Polygon([(0, 0), (0, 3), (3, 3), (3, 0)], [])), - Polygon(dg.Polygon([(2, 2), (2, 5), (5, 5), (5, 2)], [])), - Polygon(dg.Polygon([(4, 2), (4, 7), (7, 7), (7, 4)], [])), - Polygon(dg.Polygon([(6, 6), (6, 9), (9, 9), (9, 6)], [])), -] - -points = [Point(1, 1, label="label0"), Point(4, 4, index=1), Point(6, 6), Point(8, 8)] - - -class TestGeometry: - def test_base_geometry(self): - _BaseGeometry() - with pytest.raises(NotImplementedError): - _BaseGeometry().from_shapely(None) - - with pytest.raises(NotImplementedError): - _BaseGeometry().set_field("name", "test") - - with pytest.raises(NotImplementedError): - _BaseGeometry().get_field("name") - - def test_try_to_set_incorrect_field_type(self): - base = Polygon() - with pytest.raises(ValueError): - base.label = True - with pytest.raises(ValueError): - base.color = "red" - with pytest.raises(ValueError): - base.index = "1" - - def test_point_factory(self): - c_point = dg.Point(1, 1) - point = _point_factory(c_point) - assert point == Point(1, 1) - - def test_polygon_factory(self): - c_polygon = dg.Polygon([(0, 0), (0, 3), (3, 3), (3, 0)], []) - polygon = _polygon_factory(c_polygon) - assert polygon == Polygon([(0, 0), (0, 3), (3, 3), (3, 0)]) - - def test_box_factory(self): - c_box = dg.Box((1, 1), (2, 2)) - box = _box_factory(c_box) - assert box == Box((1, 1), (2, 2)) - - def test_box_area(self): - box = Box((1, 1), (2, 2)) - box.area == 4 - box.as_polygon().area == box.area - - def box_to_polygon(self): - box = Box((1, 1), (2, 2)) - polygon = box.as_polygon() - assert isinstance(box, Box) - assert isinstance(polygon, Polygon) - assert polygon == Polygon([(1, 1), (1, 2), (2, 2), (2, 1)]) - - @pytest.mark.parametrize( - "exterior,interiors,expected_area", - [ - ( - [(0, 0), (0, 3), (3, 3), (3, 0)], - [[(1, 1), (1, 2), (2, 2), (2, 1)], [(1.5, 1.5), (1.5, 2.5), (2.5, 2.5), (2.5, 1.5)]], - 7.0, - ) - ], - ) - def test_if_area_is_correct(self, exterior, interiors, expected_area): - shapely_polygon = shapely.geometry.Polygon(exterior, interiors) - dlup_polygon = Polygon(exterior, interiors) - assert dlup_polygon.area == dlup_polygon.to_shapely().area == shapely_polygon.area == expected_area - - @pytest.mark.parametrize( - "field_name,field_value", - [ - ("arbitrary field", [1, 2, 3, 4]), - ("random", None), - ], - ) - def test_set_arbitrary_field(self, field_name, field_value): - polygon = Polygon([(0, 0), (0, 3), (3, 3), (3, 0)]) - polygon.set_field(field_name, field_value) - assert polygon.get_field(field_name) == field_value - - @pytest.mark.parametrize("object_to_pickle", [polygons[0], points[0]]) - def test_pickle_objects(self, object_to_pickle): - object_to_pickle = copy.deepcopy(object_to_pickle) - object_to_pickle.set_field("random", True) - with tempfile.NamedTemporaryFile() as f: - pickle.dump(object_to_pickle, f) - f.seek(0) - new_object = pickle.load(f) - assert new_object == object_to_pickle - - def test_repr(self): - polygon = Polygon([(1, 1), (2, 3), (3, 4), (0, 0)], label="label", index=1, color=(1, 1, 1)) - polygon.set_field("random", True) - assert ( - repr(polygon) - == "" - ) - - point = Point(1, 1, label="label", index=1, color=(1, 1, 1)) - assert repr(point) == "" - - polygon = Polygon([(1, 1) for _ in range(100)]) - assert repr(polygon) == "" - - @pytest.mark.parametrize("original_object", polygons + points) - def test_deep_copy(self, original_object): - copied_object = copy.deepcopy(original_object) - assert original_object is not copied_object - assert original_object == copied_object - - def test_copy_polygon(self): - polygon = polygons[0] - polygon_copy = copy.copy(polygon) - - # TODO: Figure out way to not make a copy. Creating an InteriorRing object might be an option. - assert polygon.get_interiors() == polygon_copy.get_interiors() - assert polygon.get_exterior() == polygon_copy.get_exterior() - - def test_copy_point(self): - point = points[0] - point_copy = copy.copy(point) - - assert point == point_copy - - def test_collection_add_object(self): - collection = GeometryCollection() - collection.add_polygon(polygons[0]) - collection.add_polygon(polygons[1]) - assert collection.polygons == polygons[:2] - - collection.add_point(points[0]) - collection.add_point(points[1]) - assert collection.points == points[:2] - - def test_if_keeps_reference(self): - collection = GeometryCollection() - for polygon in polygons: - collection.add_polygon(polygon) - - for point in points: - collection.add_point(point) - - for idx, polygon in enumerate(collection.polygons): - assert polygon == polygons[idx] - assert polygon.pointer_id == polygons[idx].pointer_id - - for idx, point in enumerate(collection.points): - assert point == points[idx] - assert point.pointer_id == points[idx].pointer_id - - def test_pointers(self): - pointers = [poly.pointer_id for poly in polygons] - point_pointers = [point.pointer_id for point in points] - - collection = GeometryCollection() - for poly in polygons: - collection.add_polygon(poly) - - for point in points: - collection.add_point(point) - - for idx, poly in enumerate(collection.polygons): - assert poly.pointer_id == pointers[idx] - - for idx, point in enumerate(collection.points): - assert point.pointer_id == point_pointers[idx] - - def test_remove_geometry_from_collection(self): - collection = GeometryCollection() - for poly in polygons: - collection.add_polygon(poly) - - for point in points: - collection.add_point(point) - - assert collection.rtree_invalidated - - assert len(collection.polygons) == 4 - assert len(collection.points) == 4 - - collection.remove_polygon(polygons[0]) - assert collection.polygons == polygons[1:] - assert len(collection.polygons) == 3 - - collection.remove_point(points[0]) - assert len(collection.points) == 3 - - collection.remove_polygon(0) - assert len(collection.polygons) == 2 - - collection.remove_point(0) - assert len(collection.points) == 2 - - assert collection.rtree_invalidated - collection.rebuild_rtree() - assert not collection.rtree_invalidated - - def test_wkt(self): - polygon = Polygon([(0, 0), (0, 3), (3, 3), (3, 0)], [[(1, 1), (1, 2), (2, 2), (2, 1)]]) - assert polygon.wkt == "POLYGON((0 0,0 3,3 3,3 0,0 0),(1 1,1 2,2 2,2 1,1 1))" - - @pytest.mark.parametrize("object_type", [Polygon, Point]) - def test_setting_properties(self, object_type): - obj = object_type() - obj.label = "test" - obj.color = (1, 1, 1) - - if isinstance(obj, Polygon): - obj.index = 1 - assert obj.index == 1 - - assert obj.label == "test" - assert obj.color == (1, 1, 1) - - def test_color_lut(self): - collection = GeometryCollection() - for idx, polygon in enumerate(polygons): - collection.add_polygon(polygon) - polygon.set_field("label", f"label {idx}") - polygon.set_field("color", (idx + 1, idx + 1, idx + 1)) - polygon.set_field("index", idx + 1) - - # Add expected color LUT test here - - def test_close_loop(self): - polygon = Polygon([(0, 0), (0, 3), (3, 3), (3, 0)], [[(1, 1), (1, 2), (2, 2), (2, 1)]]) - - assert polygon.get_exterior() == [(0, 0), (0, 3), (3, 3), (3, 0), (0, 0)] - assert polygon.get_interiors() == [[(1, 1), (1, 2), (2, 2), (2, 1), (1, 1)]] - - def test_from_shapely_polygon(self): - exterior = [(0, 0), (0, 3), (3, 3), (3, 0)] - interiors = [[(1, 1), (1, 2), (2, 2), (2, 1)]] - - shapely_polygon = ShapelyPolygon(exterior, interiors) - polygon_converted = Polygon.from_shapely(shapely_polygon) - polygon_direct = Polygon(exterior, interiors) - - polygon_shapely_2 = Polygon(shapely_polygon) - assert polygon_shapely_2 == polygon_converted - - assert ( - polygon_converted.get_exterior() == list(shapely_polygon.exterior.coords) == polygon_direct.get_exterior() - ) - assert ( - polygon_converted.get_interiors() - == [list(_.coords) for _ in shapely_polygon.interiors] - == polygon_direct.get_interiors() - ) - assert shapely_polygon == polygon_direct.to_shapely() - - def test_from_shapely_point(self): - dlup_point = Point(1, 1) - shapely_point = ShapelyPoint(1, 1) - dlup_point2 = Point(shapely_point) - - assert dlup_point2 == dlup_point - - assert dlup_point == Point.from_shapely(shapely_point) - assert dlup_point.to_shapely() == shapely_point - - @pytest.mark.parametrize("object_type", [Point, Polygon]) - def test_shapely_wrong_type(self, object_type): - with pytest.raises(ValueError): - object_type.from_shapely([]) - - def test_sort_polygon(self): - collection = GeometryCollection() - for poly in polygons: - collection.add_polygon(poly) - - assert [_.area for _ in collection.polygons] == [9.0, 9.0, 12.0, 9.0] - - collection.sort_polygons(lambda x: x.area, True) - - assert [_.area for _ in collection.polygons] == [12.0, 9.0, 9.0, 9.0] - assert collection.polygons[0] == polygons[2] - - collection.sort_polygons(lambda x: x.area, False) - assert [_.area for _ in collection.polygons] == [9.0, 9.0, 9.0, 12.0] - assert collection.polygons[0] == polygons[0] - - @pytest.mark.parametrize("object_type", [Point, Polygon]) - def test_to_shapely_missing(self, object_type, monkeypatch): - monkeypatch.setattr("dlup.geometry.SHAPELY_AVAILABLE", False) - with pytest.raises(ImportError): - object_type().to_shapely() - - @pytest.mark.parametrize("object_type", [ShapelyPoint, ShapelyPolygon]) - def test_from_shapely_missing(self, object_type, monkeypatch): - monkeypatch.setattr("dlup.geometry.SHAPELY_AVAILABLE", False) - with pytest.raises(ImportError): - if object_type == ShapelyPoint: - Point.from_shapely(object_type()) - else: - Polygon.from_shapely(object_type()) - - def test_point_scaling(self): - point = Point(1, 1) - pointer_id = point.pointer_id - point.scale(2) - - assert point == Point(2, 2) - assert point.pointer_id == pointer_id - - def test_polygon_scaling(self): - polygon = Polygon([(0, 0), (0, 3), (3, 3), (3, 0)], [[(1, 1), (1, 2), (2, 2), (2, 1)]]) - pointer_id = polygon.pointer_id - polygon.scale(2) - - assert polygon == Polygon([(0, 0), (0, 6), (6, 6), (6, 0)], [[(2, 2), (2, 4), (4, 4), (4, 2)]]) - assert polygon.pointer_id == pointer_id - - @pytest.mark.parametrize("scaling", [1.0, 2.0]) - def test_read_region(self, scaling): - collection = GeometryCollection() - for poly in polygons: - collection.add_polygon(poly) - - for idx, poly in enumerate(polygons): - poly.set_field("label", f"label {idx}") - - assert collection.rtree_invalidated - region = collection.read_region((2, 2), scaling, (10, 10)) - # It's still invalid because of the lazy evaluation! - assert collection.rtree_invalidated - region.polygons # Call to ensure that the polygons are obtained - assert not collection.rtree_invalidated - - # TODO: Add more elaborate tests for regions - - @pytest.mark.parametrize("shapely_available", [True, False]) - def test_importerror_for_from_shapely(self, shapely_available, monkeypatch): - def mock_shapely_available(): - return shapely_available - - monkeypatch.setattr("dlup.geometry.SHAPELY_AVAILABLE", shapely_available) - - if shapely_available: - shapely_polygon = ShapelyPolygon([(0, 0), (0, 3), (3, 3), (3, 0)]) - Polygon.from_shapely(shapely_polygon) - Polygon(shapely_polygon) - else: - with pytest.raises(ImportError): - Polygon.from_shapely(None) - - def test_compare_mismatch(self): - point = Point(1, 1) - polygon = Polygon([(0, 0), (0, 3), (3, 3), (3, 0)], [[(1, 1), (1, 2), (2, 2), (2, 1)]]) - - assert point != polygon - - def test_compare_incorrect_fields(self): - point0 = Point(1, 1, label="label0") - point1 = Point(1, 1, label="label1") - - assert point0 != point1 - - polygon0 = copy.deepcopy(polygons[0]) - polygon1 = copy.deepcopy(polygons[1]) - polygon1.set_field("random", False) - - assert polygon0 != polygon1 - - def test_cannot_add_geometries(self): - with pytest.raises(TypeError): - polygons[0] += points[0] - - with pytest.raises(TypeError): - polygons[0] += polygons[0] - - def test_cannot_subtract_geometries(self): - with pytest.raises(TypeError): - polygons[0] -= points[0] - - with pytest.raises(TypeError): - polygons[0] -= polygons[0] - - def test_inequality(self): - polygon0 = Polygon([(0, 0), (0, 3), (3, 3), (3, 0)], []) - polygon1 = Polygon([(0, 0), (1, 3), (3, 3), (3, 0)], []) - - polygon0.label = "test" - polygon1.label = "test" - - assert polygon0 != polygon1 - - point0 = Point(0, 1) - point1 = Point(1, 1) - - point0.color = (1, 2, 3) - point1.color = (1, 2, 3) - - assert polygon0 != point1 - assert point0 != point1 - - def test_geometry_collection_lut(self): - collection = GeometryCollection() - for idx, polygon in enumerate(polygons[:3]): - collection.add_polygon(polygon) - polygon.set_field("label", f"label {idx}") - polygon.set_field("color", (idx + 1, idx + 1, idx + 1)) - polygon.set_field("index", idx + 1) - - assert collection.color_lut.tolist() == [[0, 0, 0], [1, 1, 1], [2, 2, 2], [3, 3, 3]] - - def test_geometry_collection_lut_exceptions(self): - collection = GeometryCollection() - polygon = Polygon([(0, 0), (0, 3), (3, 3), (3, 0)], []) - collection.add_polygon(polygon) - with pytest.raises(ValueError): - collection.color_lut - - polygon.index = 1 - with pytest.raises(ValueError): - collection.color_lut - - def test_geometry_collection_pickle(self): - collection = GeometryCollection() - for polygon in polygons: - collection.add_polygon(polygon) - - for point in points: - collection.add_point(point) - - with tempfile.NamedTemporaryFile() as f: - pickle.dump(collection, f) - f.seek(0) - new_collection = pickle.load(f) - assert new_collection == collection - - def test_geometry_collection_length(self): - collection = GeometryCollection() - for polygon in polygons: - collection.add_polygon(polygon) - - assert len(collection) == 4 - - for point in points: - collection.add_point(point) - - assert len(collection) == 8 - - def test_geometry_equality_different_type_and_length(self): - collection0 = GeometryCollection() - assert collection0 is not None - collection1 = GeometryCollection() - - assert collection0 == collection1 - collection0.add_polygon(polygons[0]) - collection1.add_polygon(polygons[1]) - assert collection0 != collection1 - - collection0.remove_polygon(polygons[0]) - collection1.remove_polygon(polygons[1]) - - collection0.add_point(points[0]) - collection1.add_point(points[1]) - assert collection0 != collection1 - - def test_geometry_read_region(self): - collection = GeometryCollection() - - # Let's make a nice polygon that's a square - polygon = Polygon([(0, 0), (0, 8), (8, 8), (8, 0)], []) - - collection.add_polygon(polygon) - - for point in points: - collection.add_point(point) - - regions = collection.read_region((2, 2), 1.0, (5, 5)) - - assert len(regions.points) == 2 - assert len(regions.polygons.get_geometries()) == 1 - assert regions.points == [Point(2, 2, index=1), Point(4, 4)] - assert regions.polygons.get_geometries() == [Polygon([(0, 0), (0, 5), (5, 5), (5, 0)], [])] - - def test_geometry_scaling(self): - collection = GeometryCollection() - for polygon in polygons: - collection.add_polygon(polygon) - - for point in points: - collection.add_point(point) - - collection.scale(2) - - polygon0 = collection.polygons[0] - points0 = collection.points[0] - - assert points0 == Point(2, 2, label="label0") - assert polygon0.get_exterior() == [(0, 0), (0, 6), (6, 6), (6, 0), (0, 0)] - assert polygon0.get_interiors() == [] - - assert polygon0 == Polygon( - [(0, 0), (0, 6), (6, 6), (6, 0), (0, 0)], [], color=(1, 1, 1), index=1, label="label 0" - ) - - def test_mask(self): - collection = GeometryCollection() - polygon = Box((1, 1), (4, 4)).as_polygon() - polygon.index = 2 - collection.add_polygon(polygon) - - region = collection.read_region((0, 0), 1.0, (5, 5)) - mask = region.polygons.to_mask().numpy() - assert mask.sum() == 16 * 2 diff --git a/tests/test_slide_annotations.py b/tests/test_slide_annotations.py deleted file mode 100644 index 0f9922dc..00000000 --- a/tests/test_slide_annotations.py +++ /dev/null @@ -1,552 +0,0 @@ -# Copyright (c) dlup contributors - -"""Test the annotation facilities.""" -import copy -import json -import os -import pathlib -import pickle -import tempfile - -import numpy as np -import pytest - -from dlup.annotations_experimental import GeometryCollection, SlideAnnotations, geojson_to_dlup, get_v7_metadata -from dlup.geometry import Point as Point -from dlup.geometry import Polygon as Polygon -from dlup.utils.imports import DARWIN_SDK_AVAILABLE - -ASAP_XML_EXAMPLE = b""" - - - - - - - - - - - - - - - - - - - - - -""" - -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# - - -DLUP_XML_EXAMPLE = b""" - - IMG_12345 - Sample annotations with polygons, multipolygons, points, and boxes. - 1.0 - - Jane Doe - John Smith - - 2024-08-19 - dlup v0.8.0 - - - - - Attribute 1 - Attribute 2 - This is the single text field for this tag. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -""" - - -polygons = [ - Polygon([(0, 0), (0, 3), (3, 3), (3, 0)], []), - Polygon([(2, 2), (2, 5), (5, 5), (5, 2)], []), - Polygon([(4, 2), (4, 7), (7, 7), (7, 4)], []), - Polygon([(6, 6), (6, 9), (9, 9), (9, 6)], []), -] - - -class TestAnnotations: - with tempfile.NamedTemporaryFile(suffix=".xml") as asap_file: - asap_file.write(ASAP_XML_EXAMPLE) - asap_file.flush() - asap_annotations = SlideAnnotations.from_asap_xml(pathlib.Path(asap_file.name)) - asap_annotations.rebuild_rtree() - - with tempfile.NamedTemporaryFile(suffix=".xml") as dlup_file: - dlup_file.write(DLUP_XML_EXAMPLE) - dlup_file.flush() - dlup_annotations = SlideAnnotations.from_dlup_xml(pathlib.Path(dlup_file.name)) - - with tempfile.NamedTemporaryFile(suffix=".json") as geojson_out: - asap_geojson = asap_annotations.as_geojson() - geojson_out.write(json.dumps(asap_geojson).encode("utf-8")) - geojson_out.flush() - - geojson_annotations = SlideAnnotations.from_geojson(pathlib.Path(geojson_out.name)) - - _v7_annotations = None - _v7_raster_annotations = None - - _halo_annotations = None - - additional_point = Point(*(1, 2), label="example", color=(255, 0, 0)) - additional_polygon = Polygon([(0, 0), (4, 0), (4, 4), (0, 4)], label="example", color=(255, 0, 0)) - additional_polygon.set_field("z_index", 1) - - @property - def v7_annotations(self): - if self._v7_annotations is None: - assert pathlib.Path(pathlib.Path(__file__).parent / "files/103S.json").exists() - self._v7_annotations = SlideAnnotations.from_darwin_json(pathlib.Path(__file__).parent / "files/103S.json") - return self._v7_annotations - - @property - def halo_annotations(self): - if self._halo_annotations is None: - assert pathlib.Path(pathlib.Path(__file__).parent / "files/halo_holes.annotations").exists() - self._halo_annotations = SlideAnnotations.from_halo_xml( - pathlib.Path(__file__).parent / "files/halo_holes.annotations", - box_as_polygon=False, - ) - return self._halo_annotations - - def test_raster_annotations(self): - if self._v7_raster_annotations is None: - assert pathlib.Path(pathlib.Path(__file__).parent / "files/raster.json").exists() - with pytest.raises(NotImplementedError): - SlideAnnotations.from_darwin_json(pathlib.Path(__file__).parent / "files/raster.json") - - def test_conversion_geojson_v7(self): - # We need to read the asap annotations and compare them to the geojson annotations - with tempfile.NamedTemporaryFile(suffix=".json") as geojson_out: - geojson_out.write(json.dumps(self.v7_annotations.as_geojson()).encode("utf-8")) - geojson_out.flush() - annotations = SlideAnnotations.from_geojson(pathlib.Path(geojson_out.name), sorting="NONE") - - assert self.v7_annotations.num_points == annotations.num_points - assert self.v7_annotations.num_polygons == annotations.num_polygons - - assert self.v7_annotations.layers.polygons == annotations.layers.polygons - assert self.v7_annotations.layers.points == annotations.layers.points - - self.v7_annotations.rebuild_rtree() - annotations.rebuild_rtree() - - v7_region = self.v7_annotations.read_region((15300, 19000), 1.0, (2500.0, 2500.0)) - geojson_region = annotations.read_region((15300, 19000), 1.0, (2500.0, 2500.0)) - - assert len(v7_region.polygons.get_geometries()) == len(geojson_region.polygons.get_geometries()) - - for elem0, elem1 in zip(v7_region.polygons.get_geometries(), geojson_region.polygons.get_geometries()): - assert elem0.wkt == elem1.wkt - assert elem0.label == elem1.label - elem0.index = 1 - elem1.index = 1 - - assert np.allclose(v7_region.polygons.to_mask(), geojson_region.polygons.to_mask()) - - for elem0, elem1 in zip(v7_region.points, geojson_region.points): - assert elem0.wkt == elem1.wkt - assert elem0.label == elem1.label - - def test_conversion_halo_geojson(self): - # We read the halo annotations and compare them to the geojson annotations - with tempfile.NamedTemporaryFile(suffix=".json") as geojson_out: - geojson_out.write(json.dumps(self.halo_annotations.as_geojson()).encode("utf-8")) - geojson_out.flush() - geojson_annotations = SlideAnnotations.from_geojson(pathlib.Path(geojson_out.name), sorting="NONE") - geojson_annotations.offset_function = self.halo_annotations.offset_function - - assert self.halo_annotations.num_points == geojson_annotations.num_points - assert self.halo_annotations.num_polygons == geojson_annotations.num_polygons - assert self.halo_annotations.layers.polygons == geojson_annotations.layers.polygons - assert self.halo_annotations.layers.points == geojson_annotations.layers.points - # This won't work because we have no boxes in GeoJSON - assert self.halo_annotations.layers.boxes != geojson_annotations.layers.boxes - # assert self.halo_annotations.__eq__(geojson_annotations) - - def test_halo_annotations(self): - halo_annotations = self.halo_annotations.copy() - bounding_box = halo_annotations.bounding_box - assert bounding_box[0] == (-29349.0, 50000.55808864343) - halo_annotations.set_offset((29349.0, -50000.55808864343)) - assert halo_annotations.bounding_box[0] == (0, 0) - - for polygon in halo_annotations.layers.polygons: - polygon.index = 1 - - new_bbox = halo_annotations.bounding_box_at_scaling(0.01) - region = halo_annotations.read_region(new_bbox[0], 0.01, new_bbox[1]) - output_color_mask = halo_annotations.color_lut[region.polygons.to_mask().numpy()] - assert output_color_mask.sum() == 51485183 - - def test_reexpert_dlup_xml(self): - with tempfile.NamedTemporaryFile(suffix=".xml") as dlup_file: - with open(dlup_file.name, "w") as f: - f.write(self.dlup_annotations.as_dlup_xml()) - - annotations = SlideAnnotations.from_dlup_xml(dlup_file.name) - assert self.dlup_annotations._layers == annotations._layers - assert self.dlup_annotations.tags == annotations.tags - assert self.dlup_annotations.sorting == annotations.sorting - assert self.dlup_annotations.offset_function == annotations.offset_function - assert self.dlup_annotations == annotations - - def test_reading_qupath05_geojson_export(self): - annotations = SlideAnnotations.from_geojson(pathlib.Path("tests/files/qupath05.geojson")) - assert len(annotations.available_classes) == 2 - - @pytest.mark.parametrize("class_method", ["from_geojson", "from_halo_xml", "from_dlup_xml", "from_asap_xml"]) - def test_missing_file_constructor(self, class_method): - constructor = getattr(SlideAnnotations, class_method) - with pytest.raises(FileNotFoundError): - constructor("doesnotexist.xml.json") - - def test_asap_to_geojson(self): - # TODO: Make sure that the annotations hit the border of the region. - asap_geojson = self.asap_annotations.as_geojson() - geojson_geojson = self.geojson_annotations.as_geojson() - assert len(asap_geojson) == len(geojson_geojson) - - # TODO: Collect the geometries together per name and compare - for elem0, elem1 in zip(asap_geojson["features"], geojson_geojson["features"]): - assert elem0["type"] == elem1["type"] - assert elem0["properties"] == elem1["properties"] - assert elem0["id"] == elem1["id"] - - # Now we need to compare the geometries, given the sorting they could become different - shape0 = geojson_to_dlup(elem0["geometry"], label="") - shape1 = geojson_to_dlup(elem1["geometry"], label="") - assert len(set([_.label for _ in shape0])) == 1 - assert len(set([_.label for _ in shape1])) == 1 - if isinstance(shape0[0], Polygon): - pass - else: - raise NotImplementedError("Different shape types not implemented yet.") - - for p0, p1 in zip(shape0, shape1): - # The shapes should be equal - assert p0 == p1 - - @pytest.mark.parametrize("region", [((10000, 10000), (5000, 5000), 3756.0), ((0, 0), (5000, 5000), None)]) - def test_read_region(self, region): - coordinates, size, area = region - region = self.asap_annotations.read_region(coordinates, 1.0, size) - - polygons = region.polygons.get_geometries() - - if area and area > 0: - assert len(polygons) == 1 - assert polygons[0].area == area - assert polygons[0].label == "healthy glands" - assert isinstance(polygons[0], Polygon) - - if not area: - assert region.polygons.get_geometries() == [] - assert region.points == [] - - def test_copy(self): - copied_annotations = copy.copy(self.asap_annotations) - - copied_annotations.tags == self.asap_annotations.tags - copied_annotations._layers = self.asap_annotations._layers - - def test_pickle(self): - with tempfile.NamedTemporaryFile(suffix=".pkl") as pkl_file: - pickle.dump(self.asap_annotations, pkl_file) - pkl_file.flush() - - with open(pkl_file.name, "rb") as pkl_file: - annotations = pickle.load(pkl_file) - - assert annotations.tags == self.asap_annotations.tags - assert annotations._layers == self.asap_annotations._layers - - def test_reindex_polygons(self): - ann = self.dlup_annotations.copy() - ann.reindex_polygons({"Polygon1": 7}) - for polygon in ann._layers.polygons: - assert polygon.index == 7 - - def test_relabel_polygons(self): - ann = self.dlup_annotations.copy() - ann.relabel_polygons({"Polygon1": "Polygon2"}) - for polygon in ann._layers.polygons: - assert polygon.label == "Polygon2" - - @pytest.mark.parametrize("scaling", [0.5, 0.3, 1.0]) - def test_bounding_box(self, scaling): - assert self.v7_annotations.bounding_box == ( - (15291.49, 18094.48), - (5122.9400000000005, 4597.509999999998), - ) - - assert self.v7_annotations.bounding_box_at_scaling(scaling) == ( - (15291.49 * scaling, 18094.48 * scaling), - (5122.9400000000005 * scaling, 4597.509999999998 * scaling), - ) - - def test_read_darwin_v7(self): - if not DARWIN_SDK_AVAILABLE: - return None - - assert len(self.v7_annotations.available_classes) == 5 - - assert "lymphocyte (cell)" in self.v7_annotations - assert "ROI (segmentation)" in self.v7_annotations - assert "stroma (area)" in self.v7_annotations - assert "tumor (cell)" in self.v7_annotations - assert "tumor (area)" in self.v7_annotations - - assert self.v7_annotations.bounding_box == ( - (15291.49, 18094.48), - (5122.9400000000005, 4597.509999999998), - ) - - region = self.v7_annotations.read_region((15300, 19000), 1.0, (2500.0, 2500.0)) - - expected_output_polygon = [ - (6250000.0, "ROI (segmentation)"), - (1616768.0657540853, "stroma (area)"), - (398284.54274999996, "stroma (area)"), - (5124.669949999994, "stroma (area)"), - (103262.97951705182, "stroma (area)"), - (141.48809999997553, "tumor (cell)"), - (171.60999999998563, "tumor (cell)"), - (181.86480000002044, "tumor (cell)"), - (100.99830000001506, "tumor (cell)"), - (132.57199999999582, "tumor (cell)"), - (0.5479999999621504, "tumor (cell)"), - (7705.718799999958, "tumor (area)"), - (10985.104649999948, "tumor (area)"), - (585.8433000000018, "tumor (cell)"), - ] - for x, y in zip(region.polygons.get_geometries(), expected_output_polygon): - if os.environ.get("GITHUB_ACTIONS", False): - if x.area <= 1: - assert np.allclose(x.area, y[0], atol=1e-3) - else: - assert np.allclose(x.area, y[0]) - else: - assert [(_.area, _.label) for _ in region.polygons.get_geometries()] == expected_output_polygon - assert x.label == y[1] - assert len(region.points) == 3 - - def test_annotation_filter(self): - annotations = self.asap_annotations.copy() - annotations.filter(["healthy glands"]) - assert "healthy glands" in annotations - - annotations.filter_polygons(["non-existing"]) - assert len(annotations._layers.polygons) == 1 - - def test_length(self): - annotations = self.geojson_annotations - assert len(annotations._layers) == len(annotations) == 1 - - def test_dunder_add_methods_with_point(self): - annotations = self.geojson_annotations.copy() - initial_annotations_id = id(annotations) - initial_length = len(annotations) - - # __add__ - new_annotations = annotations + self.additional_point - assert initial_annotations_id != id(new_annotations) - assert initial_length + 1 == len(new_annotations) - assert self.additional_point in new_annotations - - # __radd__ - new_annotations = self.additional_point + annotations - assert initial_annotations_id != id(new_annotations) - assert initial_length + 1 == len(new_annotations) - assert self.additional_point in new_annotations - with pytest.raises(TypeError): - self.additional_point += annotations - - # __iadd__ - annotations += self.additional_point - assert initial_annotations_id == id(annotations) - assert initial_length + 1 == len(annotations) - assert self.additional_point in annotations - - def test_add_with_polygon(self): - annotations = self.geojson_annotations.copy() - initial_annotations_id = id(annotations) - initial_length = len(annotations) - - # __add__ - new_annotations = annotations + self.additional_polygon - assert initial_annotations_id != id(new_annotations) - assert initial_length + 1 == len(new_annotations) - assert self.additional_polygon in new_annotations - - # __radd__ - new_annotations = self.additional_polygon + annotations - assert initial_annotations_id != id(new_annotations) - assert initial_length + 1 == len(new_annotations) - assert self.additional_polygon in new_annotations - with pytest.raises(TypeError): - self.additional_polygon += annotations - - # __iadd__ - annotations += self.additional_polygon - assert initial_annotations_id == id(annotations) - assert initial_length + 1 == len(annotations) - assert self.additional_polygon in annotations - - def test_add_with_list(self): - annotations = self.geojson_annotations.copy() - initial_annotations_id = id(annotations) - initial_length = len(annotations) - - # __add__ - new_annotations = annotations + [self.additional_point, self.additional_polygon] - assert initial_annotations_id != id(new_annotations) - assert initial_length + 2 == len(new_annotations) - assert self.additional_polygon in new_annotations - assert self.additional_point in new_annotations - - # __radd__ - _annotations_list = [self.additional_point, self.additional_polygon] - with pytest.raises(TypeError): - new_annotations = _annotations_list + annotations - - _annotations_list = [self.additional_point, self.additional_polygon] - with pytest.raises(TypeError): - _annotations_list += annotations - - # __iadd__ - annotations += [self.additional_point, self.additional_polygon] - assert initial_annotations_id == id(annotations) - assert initial_length + 2 == len(annotations) - assert all(ann in new_annotations for ann in annotations) - - def test_add_with_wsi_annotations(self): - annotations = self.geojson_annotations.copy() - other_annotations = self.geojson_annotations.copy() - initial_annotations_id = id(annotations) - initial_length = len(annotations) - - # __add__ - new_annotations = annotations + other_annotations - assert initial_annotations_id != id(new_annotations) - assert len(annotations) + len(other_annotations) == len(new_annotations) - assert all(ann in new_annotations for ann in annotations) - - # __iadd__ - annotations += other_annotations - assert initial_annotations_id == id(annotations) - assert initial_length + len(other_annotations) == len(annotations) - assert all(ann in annotations for ann in other_annotations) - - def test_add_with_invalid_type(self): - annotations = self.geojson_annotations.copy() - with pytest.raises(TypeError): - _ = annotations + "invalid type" - with pytest.raises(TypeError): - annotations += "invalid type" - with pytest.raises(TypeError): - _ = "invalid type" + annotations - - def test_v7_metadata(self, monkeypatch): - with pytest.raises(ValueError): - get_v7_metadata(pathlib.Path("../tests")) - - monkeypatch.setattr("dlup.annotations_experimental.DARWIN_SDK_AVAILABLE", False) - with pytest.raises(ImportError): - get_v7_metadata(pathlib.Path(".")) - - @pytest.mark.parametrize("sorting_type", ["NONE", "REVERSE", "AREA", "Z_INDEX", "NON_EXISTENT"]) - def test_sorting(self, sorting_type): - collection = GeometryCollection() - for polygon in polygons: - collection.add_polygon(polygon) - - if sorting_type == "NONE": - curr_collection = collection.__copy__() - SlideAnnotations._in_place_sort_and_scale(curr_collection, scaling=1.0, sorting=sorting_type) - assert curr_collection == collection - - if sorting_type == "REVERSE": - with pytest.raises(NotImplementedError): - curr_collection = collection.__copy__() - SlideAnnotations._in_place_sort_and_scale(curr_collection, scaling=1.0, sorting=sorting_type) - # Needs fixing - # assert curr_collection.polygons == collection.polygons[::-1] - - if sorting_type == "Z_INDEX": - curr_collection = collection.__copy__() - for idx, polygon in enumerate(curr_collection.polygons): - polygon.set_field("z_index", len(curr_collection.polygons) - idx) - SlideAnnotations._in_place_sort_and_scale(curr_collection, scaling=1.0, sorting=sorting_type) - assert curr_collection.polygons == collection.polygons[::-1] - - if sorting_type == "NON_EXISTENT": - with pytest.raises(KeyError): - SlideAnnotations._in_place_sort_and_scale(collection, scaling=1.0, sorting=sorting_type) - - def test_halo_annotations_with_pins(self): - assert pathlib.Path(pathlib.Path(__file__).parent / "files/test_different_types_halo.annotations").exists() - annotations = SlideAnnotations.from_halo_xml( - pathlib.Path(__file__).parent / "files/test_different_types_halo.annotations" - ) - assert len(annotations.layers.polygons) == 5 # 2 ellipses, 3 polygons - assert len(annotations.layers.points) == 1 diff --git a/tests/test_transforms.py b/tests/test_transforms.py index b81b6af8..9f382a64 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -5,7 +5,7 @@ from shapely.geometry import Polygon as ShapelyPolygon # TODO: Our Polygon should support holes as well from dlup._exceptions import AnnotationError -from dlup.annotations import DlupShapelyPoint, DlupShapelyPolygon +from dlup.annotations import Point, Polygon from dlup.data.transforms import ( AnnotationClass, AnnotationType, @@ -16,7 +16,7 @@ def test_convert_annotations_points_only(): - point = DlupShapelyPoint((5, 5), a_cls=AnnotationClass(label="point1", annotation_type=AnnotationType.POINT)) + point = Point((5, 5), a_cls=AnnotationClass(label="point1", annotation_type=AnnotationType.POINT)) points, mask, roi_mask = convert_annotations([point], (10, 10), {"point1": 1}) assert mask.sum() == 0 @@ -34,7 +34,7 @@ def test_convert_annotations_default_value(): def test_convert_annotations_polygons_only(): - polygon = DlupShapelyPolygon( + polygon = Polygon( [(2, 2), (2, 8), (8, 8), (8, 2)], a_cls=AnnotationClass(label="polygon1", annotation_type=AnnotationType.POLYGON), ) @@ -49,7 +49,7 @@ def test_convert_annotations_polygons_only(): @pytest.mark.parametrize("top_add", [0.0, 0.1, 0.49, 0.51, 0.9]) @pytest.mark.parametrize("bottom_add", [0.0, 0.1, 0.49, 0.51, 0.9]) def test_convert_annotations_polygons_with_floats(top_add, bottom_add): - polygon = DlupShapelyPolygon( + polygon = Polygon( [ (2 + top_add, 2 + top_add), (2 + top_add, 8 + bottom_add), @@ -77,7 +77,7 @@ def test_convert_annotations_polygons_with_floats(top_add, bottom_add): def test_convert_annotations_label_not_present(): - polygon = DlupShapelyPolygon( + polygon = Polygon( [(1, 1), (1, 7), (7, 7), (7, 1)], a_cls=AnnotationClass(label="polygon", annotation_type=AnnotationType.POLYGON), ) @@ -86,7 +86,7 @@ def test_convert_annotations_label_not_present(): def test_roi_exception(): - box = DlupShapelyPolygon( + box = Polygon( [(1, 1), (1, 7), (7, 7), (7, 1)], a_cls=AnnotationClass(label="polygon", annotation_type=AnnotationType.BOX) ) @@ -98,19 +98,15 @@ def _create_complex_polygons(): shell0 = [(1, 1), (1, 7), (7, 7), (7, 1)] holes0 = [[(2, 2), (2, 4), (4, 4), (4, 1)]] spolygon = ShapelyPolygon(shell0, holes=holes0) - polygon0 = DlupShapelyPolygon( - spolygon, a_cls=AnnotationClass(label="polygon1", annotation_type=AnnotationType.POLYGON) - ) + polygon0 = Polygon(spolygon, a_cls=AnnotationClass(label="polygon1", annotation_type=AnnotationType.POLYGON)) shell1 = [(4, 4), (4, 9), (9, 9), (9, 4)] holes1 = [[(5, 5), (5, 7), (7, 7), (7, 5)], [(7, 7), (5, 9), (5, 9), (9, 9)]] spolygon = ShapelyPolygon(shell1, holes=holes1) - polygon1 = DlupShapelyPolygon( - spolygon, a_cls=AnnotationClass(label="polygon2", annotation_type=AnnotationType.POLYGON) - ) + polygon1 = Polygon(spolygon, a_cls=AnnotationClass(label="polygon2", annotation_type=AnnotationType.POLYGON)) shell_roi = [(3, 3), (3, 6), (6, 6), (6, 3)] - roi = DlupShapelyPolygon(shell_roi, a_cls=AnnotationClass(label="roi", annotation_type=AnnotationType.POLYGON)) + roi = Polygon(shell_roi, a_cls=AnnotationClass(label="roi", annotation_type=AnnotationType.POLYGON)) target = np.asarray( [ @@ -144,7 +140,7 @@ def test_convert_annotations_multiple_polygons_and_holes(): def test_convert_annotations_out_of_bounds(): - polygon = DlupShapelyPolygon( + polygon = Polygon( [(2, 2), (2, 11), (11, 11), (11, 2)], a_cls=AnnotationClass(label="polygon1", annotation_type=AnnotationType.POLYGON), ) @@ -196,7 +192,7 @@ def transformer1(self): return RenameLabels(remap_labels={"old_name": "new_name", "some_point": "some_point2", "some_box": "some_box"}) def test_no_remap(self, transformer0): - old_annotation = DlupShapelyPolygon( + old_annotation = Polygon( [(2, 2), (2, 8), (8, 8), (8, 2)], a_cls=AnnotationClass(label="unchanged_name", annotation_type=AnnotationType.POLYGON), ) @@ -205,26 +201,24 @@ def test_no_remap(self, transformer0): assert transformed_sample["annotations"][0].label == "unchanged_name" def test_remap_polygon(self, transformer1): - old_annotation = DlupShapelyPolygon( + old_annotation = Polygon( [(2, 2), (2, 8), (8, 8), (8, 2)], a_cls=AnnotationClass(label="old_name", annotation_type=AnnotationType.POLYGON), ) - random_box = DlupShapelyPolygon( + random_box = Polygon( [(2, 2), (2, 8), (8, 8), (8, 2)], a_cls=AnnotationClass(label="some_box", annotation_type=AnnotationType.BOX), ) - random_point = DlupShapelyPoint( - (1, 1), a_cls=AnnotationClass(label="some_point", annotation_type=AnnotationType.POINT) - ) + random_point = Point((1, 1), a_cls=AnnotationClass(label="some_point", annotation_type=AnnotationType.POINT)) sample = {"annotations": [old_annotation, random_box, random_point]} transformed_sample = transformer1(sample) assert transformed_sample["annotations"][0].label == "new_name" assert transformed_sample["annotations"][1].label == "some_box" assert transformed_sample["annotations"][2].label == "some_point2" - assert isinstance(transformed_sample["annotations"][0], DlupShapelyPolygon) + assert isinstance(transformed_sample["annotations"][0], Polygon) assert transformed_sample["annotations"][0].annotation_type == AnnotationType.POLYGON assert transformed_sample["annotations"][1].annotation_type == AnnotationType.BOX assert transformed_sample["annotations"][2].annotation_type == AnnotationType.POINT diff --git a/tests/utils/test_annotation_utils.py b/tests/utils/test_annotation_utils.py deleted file mode 100644 index b2d4966d..00000000 --- a/tests/utils/test_annotation_utils.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright (c) dlup contributors -import pytest - -from dlup.utils.annotations_utils import hex_to_rgb, rgb_to_hex - - -@pytest.mark.parametrize("rgb", [(0, 0, 0), (255, 10, 255), (255, 127, 0), (0, 28, 0), (0, 0, 255)]) -def test_rgb_to_hex_to_rgb(rgb): - hex_repr = rgb_to_hex(*rgb) - rgb2 = hex_to_rgb(hex_repr) - assert rgb == rgb2 - - -def test_fixed_colors(): - assert hex_to_rgb("black") == (0, 0, 0) - - -def test_exceptions(): - with pytest.raises(ValueError): - rgb_to_hex(256, 0, 0) - with pytest.raises(ValueError): - rgb_to_hex(0, 256, 0) - with pytest.raises(ValueError): - rgb_to_hex(0, 0, 256) - with pytest.raises(ValueError): - rgb_to_hex(-1, 0, 0) - with pytest.raises(ValueError): - rgb_to_hex(0, -1, 0) - with pytest.raises(ValueError): - rgb_to_hex(0, 0, -1) - with pytest.raises(ValueError): - hex_to_rgb("1234567") - with pytest.raises(ValueError): - hex_to_rgb("#1234567") - with pytest.raises(ValueError): - hex_to_rgb("#12345") - with pytest.raises(ValueError): - hex_to_rgb("#1234") - with pytest.raises(ValueError): - hex_to_rgb("#123") - with pytest.raises(ValueError): - hex_to_rgb("#12") - with pytest.raises(ValueError): - hex_to_rgb("#1") - with pytest.raises(ValueError): - hex_to_rgb("#") - with pytest.raises(ValueError): - hex_to_rgb("") diff --git a/third_party/meson.build b/third_party/meson.build deleted file mode 100644 index 293b5ab6..00000000 --- a/third_party/meson.build +++ /dev/null @@ -1,66 +0,0 @@ -### third_party/meson.build ### - -py_mod = import('python') -py = py_mod.find_installation(pure: false) -py_dep = py.dependency() - -python_path = run_command(py, ['-c', 'import sys; print(sys.executable)'], check: true).stdout().strip() -message('Using Python: ' + python_path) - -# LibTIFF -libtiff_dep = dependency('libtiff-4', required : false) -if not libtiff_dep.found() - libtiff_dep = dependency('tiff', required : false) -endif -if not libtiff_dep.found() - libtiff_dep = cc.find_library('tiff', required : false) -endif -if not libtiff_dep.found() - error('libtiff not found. Please install libtiff development files.') -endif - -# ZSTD -zstd_dep = dependency('libzstd', required : false) -have_zstd = zstd_dep.found() -if have_zstd - message('ZSTD support enabled') -else - message('ZSTD support disabled') -endif - -# Numpy and PYBIND11 -numpy_include = run_command(py, ['-c', ''' -import os -import numpy -print(os.path.relpath(numpy.get_include(), os.getcwd())) -'''], check: true).stdout().strip() - -pybind11_include = run_command(py, ['-c', ''' -import os -import pybind11 -print(os.path.relpath(pybind11.get_include(), os.getcwd())) -'''], check: true).stdout().strip() - -incdir_numpy = include_directories(numpy_include) -incdir_pybind11 = include_directories(pybind11_include) - -# Find Boost with necessary components -boost_modules = ['system', 'serialization'] -boost_dep = dependency('boost', modules : boost_modules, required : true) - -# OpenCV -opencv_dep = dependency('opencv4', required : true) - -# Shared dependencies -base_deps = [libtiff_dep] -if have_zstd - base_deps += [zstd_dep] - cpp_args += ['-DHAVE_ZSTD'] -endif - -# Export dependencies and includes -incdir_numpy = incdir_numpy -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 ba9f2262..554cf1f1 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,8 @@ [tox] -envlist = py311 +envlist = py310, py311 isolated_build = True [testenv] -envdir = {toxworkdir}/env -setenv = - GITHUB_ACTIONS = 1 deps = meson meson-python>=0.15.0 @@ -14,10 +11,12 @@ deps = spin pybind11 build - pyhaloxml extras = dev,darwin commands = - pytest --maxfail=1 --disable-warnings + sh -c 'meson setup builddir' + sh -c 'meson compile -C builddir' + sh -c 'cp builddir/*.so dlup' + pytest allowlist_externals = sh pytest