From 9551b578355843203a9e696d11f4a3539d77347f Mon Sep 17 00:00:00 2001 From: Jiaqi-Lv <60471431+Jiaqi-Lv@users.noreply.github.com> Date: Mon, 12 Feb 2024 10:18:35 +0000 Subject: [PATCH 1/6] :hammer: `mypy` type check `tools/` (#782) Add `mpypy` checks to: - `tiatoolbox/tools/__init__.py` - `tiatoolbox/tools/stainextract.py` - `tiatoolbox/tools/pyramid.py` - `tiatoolbox/tools/tissuemask.py` - `tiatoolbox/tools/graph.py` --- .github/workflows/mypy-type-check.yml | 7 +++- tiatoolbox/tools/graph.py | 25 +++++++------ tiatoolbox/tools/pyramid.py | 41 ++++++++++++--------- tiatoolbox/tools/tissuemask.py | 53 +++++++++++++-------------- tiatoolbox/utils/visualization.py | 1 + 5 files changed, 69 insertions(+), 58 deletions(-) diff --git a/.github/workflows/mypy-type-check.yml b/.github/workflows/mypy-type-check.yml index e87cf1c28..a22f339c5 100644 --- a/.github/workflows/mypy-type-check.yml +++ b/.github/workflows/mypy-type-check.yml @@ -39,4 +39,9 @@ jobs: tiatoolbox/__main__.py \ tiatoolbox/typing.py \ tiatoolbox/tiatoolbox.py \ - tiatoolbox/utils/*.py + tiatoolbox/utils/*.py \ + tiatoolbox/tools/__init__.py \ + tiatoolbox/tools/stainextract.py \ + tiatoolbox/tools/pyramid.py \ + tiatoolbox/tools/tissuemask.py \ + tiatoolbox/tools/graph.py diff --git a/tiatoolbox/tools/graph.py b/tiatoolbox/tools/graph.py index 6114b9b48..c3b138ddd 100644 --- a/tiatoolbox/tools/graph.py +++ b/tiatoolbox/tools/graph.py @@ -18,7 +18,7 @@ from numpy.typing import ArrayLike -def delaunay_adjacency(points: ArrayLike, dthresh: Number) -> list: +def delaunay_adjacency(points: ArrayLike, dthresh: float) -> list: """Create an adjacency matrix via Delaunay triangulation from a list of coordinates. Points which are further apart than dthresh will not be connected. @@ -28,7 +28,7 @@ def delaunay_adjacency(points: ArrayLike, dthresh: Number) -> list: Args: points (ArrayLike): An nxm list of coordinates. - dthresh (int): + dthresh (float): Distance threshold for triangulation. Returns: @@ -57,6 +57,7 @@ def delaunay_adjacency(points: ArrayLike, dthresh: Number) -> list: tessellation = Delaunay(points) # Find all connected neighbours for each point in the set of # triangles. Starting with an empty dictionary. + triangle_neighbours: defaultdict triangle_neighbours = defaultdict(set) # Iterate over each triplet of point indexes which denotes a # triangle within the tessellation. @@ -157,7 +158,7 @@ def edge_index_to_triangles(edge_index: ArrayLike) -> ArrayLike: def affinity_to_edge_index( affinity_matrix: torch.Tensor | ArrayLike, - threshold: Number = 0.5, + threshold: float = 0.5, ) -> torch.tensor | ArrayLike: """Convert an affinity matrix (similarity matrix) to an edge index. @@ -233,12 +234,12 @@ def _umap_reducer(graph: dict[str, ArrayLike]) -> ArrayLike: def build( points: ArrayLike, features: ArrayLike, - lambda_d: Number = 3.0e-3, - lambda_f: Number = 1.0e-3, - lambda_h: Number = 0.8, - connectivity_distance: Number = 4000, - neighbour_search_radius: Number = 2000, - feature_range_thresh: Number | None = 1e-4, + lambda_d: float = 3.0e-3, + lambda_f: float = 1.0e-3, + lambda_h: float = 0.8, + connectivity_distance: int = 4000, + neighbour_search_radius: int = 2000, + feature_range_thresh: float | None = 1e-4, ) -> dict[str, ArrayLike]: """Build a graph via hybrid clustering in spatial and feature space. @@ -416,7 +417,7 @@ def build( @classmethod def visualise( - cls: SlideGraphConstructor, + cls: type[SlideGraphConstructor], graph: dict[str, ArrayLike], color: ArrayLike | str | Callable | None = None, node_size: Number | ArrayLike | Callable = 25, @@ -510,8 +511,8 @@ def visualise( # Plot the nodes plt.scatter( *nodes.T, - c=color(graph) if isinstance(color, Callable) else color, - s=node_size(graph) if isinstance(node_size, Callable) else node_size, + c=color(graph) if callable(color) else color, + s=node_size(graph) if callable(node_size) else node_size, zorder=2, ) diff --git a/tiatoolbox/tools/pyramid.py b/tiatoolbox/tools/pyramid.py index 1a797ebc3..a6506fb46 100644 --- a/tiatoolbox/tools/pyramid.py +++ b/tiatoolbox/tools/pyramid.py @@ -129,7 +129,7 @@ def level_count(self: TilePyramidGenerator) -> int: total_level_count = super_level_count + 1 + self.sub_tile_level_count return int(total_level_count) - def get_thumb_tile(self: TilePyramidGenerator) -> Image: + def get_thumb_tile(self: TilePyramidGenerator) -> Image.Image: """Return a thumbnail which fits the whole slide in one tile. The thumbnail output size has the longest edge equal to the tile @@ -157,7 +157,7 @@ def get_tile( pad_mode: str = "constant", interpolation: str = "optimise", transparent_value: int | None = None, - ) -> Image: + ) -> Image.Image: """Get a tile at a given level and coordinate. Note that levels are in the reverse order of those in WSIReader. @@ -223,7 +223,7 @@ def get_tile( ) output_size = np.repeat(output_size, 2).astype(int) thumb = self.get_thumb_tile() - thumb.thumbnail(output_size) + thumb.thumbnail((output_size[0], output_size[1])) return thumb slide_dimensions = np.array(self.wsi.info.slide_dimensions) if all(slide_dimensions < [baseline_x, baseline_y]): @@ -331,7 +331,7 @@ def save_tile(tile_path: Path, tile: Image.Image) -> None: msg = "Unsupported compression for zip." raise ValueError(msg) - archive = zipfile.ZipFile( + zip_archive = zipfile.ZipFile( path, mode="w", compression=compression2enum[compression], @@ -343,7 +343,7 @@ def save_tile(tile_path: Path, tile: Image.Image) -> None: tile.save(bio, format="jpeg") bio.seek(0) data = bio.read() - archive.writestr( + zip_archive.writestr( str(tile_path), data, compress_type=compression2enum[compression], @@ -360,7 +360,7 @@ def save_tile(tile_path: Path, tile: Image.Image) -> None: msg = "Unsupported compression for tar." raise ValueError(msg) - archive = tarfile.TarFile.open(path, mode=compression2mode[compression]) + tar_archive = tarfile.TarFile.open(path, mode=compression2mode[compression]) def save_tile(tile_path: Path, tile: Image.Image) -> None: """Write the tile to the output zip.""" @@ -368,9 +368,9 @@ def save_tile(tile_path: Path, tile: Image.Image) -> None: tile.save(bio, format="jpeg") bio.seek(0) tar_info = tarfile.TarInfo(name=str(tile_path)) - tar_info.mtime = time.time() + tar_info.mtime = int(time.time()) tar_info.size = bio.tell() - archive.addfile(tarinfo=tar_info, fileobj=bio) + tar_archive.addfile(tarinfo=tar_info, fileobj=bio) for level in range(self.level_count): for x, y in np.ndindex(self.tile_grid_size(level)): @@ -378,13 +378,17 @@ def save_tile(tile_path: Path, tile: Image.Image) -> None: tile_path = self.tile_path(level, x, y) save_tile(tile_path, tile) - if container is not None: - archive.close() + if container == "zip": + zip_archive.close() + if container == "tar": + tar_archive.close() def __len__(self: TilePyramidGenerator) -> int: """Return length of instance attributes.""" - return sum( - np.prod(self.tile_grid_size(level)) for level in range(self.level_count) + return int( + sum( + np.prod(self.tile_grid_size(level)) for level in range(self.level_count) + ), ) def __iter__(self: TilePyramidGenerator) -> Iterator: @@ -452,7 +456,7 @@ def tile_group(self: ZoomifyGenerator, level: int, x: int, y: int) -> int: cumulative_sum = sum(np.prod(self.tile_grid_size(n)) for n in range(level)) index_in_level = np.ravel_multi_index((y, x), self.tile_grid_size(level)[::-1]) tile_index = cumulative_sum + index_in_level - return tile_index // 256 # the tile group + return int(tile_index // 256) # the tile group def tile_path(self: ZoomifyGenerator, level: int, x: int, y: int) -> Path: """Generate the Zoomify path for a specified tile. @@ -537,7 +541,7 @@ def __init__( mapper = {key: (*color, 1) for key, color in zip(types, colors)} self.renderer.mapper = lambda x: mapper[x] - def get_thumb_tile(self: AnnotationTileGenerator) -> Image: + def get_thumb_tile(self: AnnotationTileGenerator) -> Image.Image: """Return a thumbnail which fits the whole slide in one tile. The thumbnail output size has the longest edge equal to the tile @@ -587,7 +591,7 @@ def get_tile( pad_mode: str | None = None, interpolation: str | None = None, transparent_value: int | None = None, # noqa: ARG002 - ) -> Image: + ) -> Image.Image: """Render a tile at a given level and coordinate. Note that levels are in the reverse order of those in WSIReader. @@ -646,20 +650,21 @@ def get_tile( scale = self.level_downsample(level) baseline_x = (x * self.tile_size * scale) - (self.overlap * scale) baseline_y = (y * self.tile_size * scale) - (self.overlap * scale) - coord = [baseline_x, baseline_y] + coord = (int(baseline_x), int(baseline_y)) if level < self.sub_tile_level_count: output_size = self.output_tile_size // 2 ** ( self.sub_tile_level_count - level ) output_size = np.repeat(output_size, 2).astype(int) thumb = self.get_thumb_tile() - thumb.thumbnail(output_size) + thumb.thumbnail((output_size[0], output_size[1])) return thumb slide_dimensions = np.array(self.info.slide_dimensions) if all(slide_dimensions < [baseline_x, baseline_y]): raise IndexError - bounds = locsize2bounds(coord, [self.output_tile_size * scale] * 2) + size = [self.output_tile_size * scale] * 2 + bounds = locsize2bounds(coord, (int(size[0]), int(size[1]))) tile = self.renderer.render_annotations( self.store, bounds, diff --git a/tiatoolbox/tools/tissuemask.py b/tiatoolbox/tools/tissuemask.py index ac99490d8..c2ea74d80 100644 --- a/tiatoolbox/tools/tissuemask.py +++ b/tiatoolbox/tools/tissuemask.py @@ -18,11 +18,6 @@ class TissueMasker(ABC): """ - def __init__(self: TissueMasker) -> None: - """Initialize :class:`TissueMasker`.""" - super().__init__() - self.fitted = False - @abstractmethod def fit( self: TissueMasker, @@ -55,9 +50,6 @@ def transform(self: TissueMasker, images: np.ndarray) -> np.ndarray: e.g. regions of tissue vs background. """ - if not self.fitted: - msg = "Fit must be called before transform." - raise SyntaxError(msg) def fit_transform( self: TissueMasker, @@ -76,7 +68,7 @@ def fit_transform( **kwargs (dict): Other key word arguments passed to fit. """ - self.fit(images, **kwargs) + self.fit(images, masks=None, **kwargs) return self.transform(images) @@ -97,13 +89,15 @@ class OtsuTissueMasker(TissueMasker): """ - def __init__(self: TissueMasker) -> None: + def __init__(self: OtsuTissueMasker) -> None: """Initialize :class:`OtsuTissueMasker`.""" - super().__init__() + self.threshold: float | None + self.fitted: bool self.threshold = None + self.fitted = False def fit( - self: TissueMasker, + self: OtsuTissueMasker, images: np.ndarray, masks: np.ndarray | None = None, # noqa: ARG002 ) -> None: @@ -141,7 +135,7 @@ def fit( self.fitted = True - def transform(self: TissueMasker, images: np.ndarray) -> np.ndarray: + def transform(self: OtsuTissueMasker, images: np.ndarray) -> np.ndarray: """Create masks using the threshold found during :func:`fit`. Args: @@ -155,7 +149,9 @@ def transform(self: TissueMasker, images: np.ndarray) -> np.ndarray: channels). """ - super().transform(images) + if not self.fitted: + msg = "Fit must be called before transform." + raise SyntaxError(msg) masks = [] for image in images: @@ -165,7 +161,7 @@ def transform(self: TissueMasker, images: np.ndarray) -> np.ndarray: mask = (grey < self.threshold).astype(bool) masks.append(mask) - return masks + return np.array(masks) class MorphologicalMasker(OtsuTissueMasker): @@ -206,7 +202,7 @@ class MorphologicalMasker(OtsuTissueMasker): """ def __init__( - self: TissueMasker, + self: MorphologicalMasker, *, mpp: float | tuple[float, float] | None = None, power: float | tuple[float, float] | None = None, @@ -250,18 +246,19 @@ def __init__( # Convert MPP to an integer kernel_size if mpp is not None: - mpp = np.array(mpp) - if mpp.size != 2: # noqa: PLR2004 - mpp = mpp.repeat(2) - kernel_size = np.max([32 / mpp, [1, 1]], axis=0) + mpp_array = np.array(mpp) + if mpp_array.size != 2: # noqa: PLR2004 + mpp_array = mpp_array.repeat(2) + kernel_size = np.max([32 / mpp_array, [1, 1]], axis=0) # Ensure kernel_size is a length 2 numpy array - kernel_size = np.array(kernel_size) - if kernel_size.size != 2: # noqa: PLR2004 - kernel_size = kernel_size.repeat(2) + kernel_size_array = np.array(kernel_size) + if kernel_size_array.size != 2: # noqa: PLR2004 + kernel_size_array = kernel_size_array.repeat(2) # Convert to an integer double/ pair - self.kernel_size = tuple(np.round(kernel_size).astype(int)) + self.kernel_size: tuple[int, int] + self.kernel_size = tuple(np.round(kernel_size_array).astype(int)) # Create structuring element for morphological operations self.kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, self.kernel_size) @@ -270,7 +267,7 @@ def __init__( if self.min_region_size is None: self.min_region_size = np.sum(self.kernel) - def transform(self: TissueMasker, images: np.ndarray) -> None: + def transform(self: MorphologicalMasker, images: np.ndarray) -> np.ndarray: """Create masks using the found threshold followed by morphological operations. Args: @@ -284,7 +281,9 @@ def transform(self: TissueMasker, images: np.ndarray) -> None: channels). """ - super().transform(images) + if not self.fitted: + msg = "Fit must be called before transform." + raise SyntaxError(msg) results = [] for image in images: @@ -304,4 +303,4 @@ def transform(self: TissueMasker, images: np.ndarray) -> None: mask = cv2.morphologyEx(mask, cv2.MORPH_DILATE, self.kernel) results.append(mask.astype(bool)) - return results + return np.array(results) diff --git a/tiatoolbox/utils/visualization.py b/tiatoolbox/utils/visualization.py index 3e7c9da46..e75b7376c 100644 --- a/tiatoolbox/utils/visualization.py +++ b/tiatoolbox/utils/visualization.py @@ -633,6 +633,7 @@ def __init__( # noqa: PLR0913 self.secondary_cmap = secondary_cmap self.blur_radius = blur_radius self.function_mapper = function_mapper + self.blur: ImageFilter.GaussianBlur | None if blur_radius > 0: self.blur = ImageFilter.GaussianBlur(blur_radius) self.edge_thickness = 0 From 23fb2a7a98a3b6257b0069f4a73b33c8db66aab1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 10:44:27 +0000 Subject: [PATCH 2/6] [pre-commit.ci] pre-commit autoupdate (#781) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.14 → v0.2.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.14...v0.2.0) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * :hammer: Fix settings for ruff Signed-off-by: Shan E Ahmed Raza <13048456+shaneahmed@users.noreply.github.com> * :bug: Fix SIM401 Use `kwargs.get("chunks", 10000)` instead of an `if` block Signed-off-by: Shan E Ahmed Raza <13048456+shaneahmed@users.noreply.github.com> * :bug: SIM401 Use `clinical_info.get(v, np.nan)` instead of an `if` block Signed-off-by: Shan E Ahmed Raza <13048456+shaneahmed@users.noreply.github.com> * :bug: Fix unnecessary SIM911 fix by `ruff`. Signed-off-by: Shan E Ahmed Raza <13048456+shaneahmed@users.noreply.github.com> --------- Signed-off-by: Shan E Ahmed Raza <13048456+shaneahmed@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Shan E Ahmed Raza <13048456+shaneahmed@users.noreply.github.com> --- .github/workflows/python-package.yml | 2 +- .pre-commit-config.yaml | 2 +- benchmarks/annotation_nquery.ipynb | 2 +- benchmarks/annotation_store.ipynb | 4 +- benchmarks/annotation_store_alloc.py | 10 ++-- examples/full-pipelines/slide-graph.ipynb | 2 +- pyproject.toml | 18 +++---- requirements/requirements_dev.txt | 2 +- tests/test_dsl.py | 62 +++++++++++----------- tests/test_wsireader.py | 4 +- tiatoolbox/annotation/storage.py | 4 +- tiatoolbox/utils/misc.py | 2 +- tiatoolbox/visualization/bokeh_app/main.py | 12 ++--- 13 files changed, 64 insertions(+), 62 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 2b61c7bb4..321316040 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -30,7 +30,7 @@ jobs: sudo apt update sudo apt-get install -y libopenslide-dev openslide-tools libopenjp2-7 libopenjp2-tools python -m pip install --upgrade pip - python -m pip install ruff==0.1.13 pytest pytest-cov pytest-runner + python -m pip install ruff==0.2.1 pytest pytest-cov pytest-runner pip install -r requirements/requirements.txt - name: Cache tiatoolbox static assets uses: actions/cache@v3 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bc0650353..935cf209b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -68,7 +68,7 @@ repos: language: python - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.1.14 + rev: v0.2.1 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] diff --git a/benchmarks/annotation_nquery.ipynb b/benchmarks/annotation_nquery.ipynb index 458ecbb22..64a58794a 100644 --- a/benchmarks/annotation_nquery.ipynb +++ b/benchmarks/annotation_nquery.ipynb @@ -71,7 +71,7 @@ "from shapely.geometry import Polygon\n", "\n", "sys.path.append(\"..\") # If running locally without pypi installed tiatoolbox\n", - "from tiatoolbox.annotation.storage import ( # noqa: E402\n", + "from tiatoolbox.annotation.storage import (\n", " Annotation,\n", " AnnotationStore,\n", " DictionaryStore,\n", diff --git a/benchmarks/annotation_store.ipynb b/benchmarks/annotation_store.ipynb index 128ad387c..6c8b83d65 100644 --- a/benchmarks/annotation_store.ipynb +++ b/benchmarks/annotation_store.ipynb @@ -207,8 +207,8 @@ "\n", "sys.path.append(\"..\") # If running locally without pypi installed tiatoolbox\n", "\n", - "from tiatoolbox import logger # noqa: E402\n", - "from tiatoolbox.annotation.storage import ( # noqa: E402\n", + "from tiatoolbox import logger\n", + "from tiatoolbox.annotation.storage import (\n", " Annotation,\n", " DictionaryStore,\n", " SQLiteStore,\n", diff --git a/benchmarks/annotation_store_alloc.py b/benchmarks/annotation_store_alloc.py index 41b85043f..d5b6df9cb 100644 --- a/benchmarks/annotation_store_alloc.py +++ b/benchmarks/annotation_store_alloc.py @@ -139,12 +139,12 @@ def __exit__(self: memray, *args: object) -> None: # Intentionally blank. -import numpy as np # noqa: E402 -import psutil # noqa: E402 -from shapely.geometry import Polygon # noqa: E402 -from tqdm import tqdm # noqa: E402 +import numpy as np +import psutil +from shapely.geometry import Polygon +from tqdm import tqdm -from tiatoolbox.annotation.storage import ( # noqa: E402 +from tiatoolbox.annotation.storage import ( Annotation, DictionaryStore, SQLiteStore, diff --git a/examples/full-pipelines/slide-graph.ipynb b/examples/full-pipelines/slide-graph.ipynb index de6f2b60f..54d1cdbde 100644 --- a/examples/full-pipelines/slide-graph.ipynb +++ b/examples/full-pipelines/slide-graph.ipynb @@ -397,7 +397,7 @@ "# https://docs.gdc.cancer.gov/Encyclopedia/pages/TCGA_Barcode/\n", "wsi_patient_codes = np.array([\"-\".join(v.split(\"-\")[:3]) for v in wsi_names])\n", "wsi_labels = np.array(\n", - " [clinical_info[v] if v in clinical_info else np.nan for v in wsi_patient_codes],\n", + " [clinical_info.get(v, np.nan) for v in wsi_patient_codes],\n", ")\n", "\n", "# * Filter the WSIs and paths that do not have labels\n", diff --git a/pyproject.toml b/pyproject.toml index dbf71f456..05463efe8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ search = 'TOOLBOX_VER: {current_version}' replace = 'TOOLBOX_VER: {new_version}' [tool.ruff] -select = [ +lint.select = [ "A", # flake8-builtins "B", # flake8-bugbear "D", # pydocstyle, need to enable for docstrings check. @@ -126,13 +126,13 @@ select = [ "SLOT", # flake8-slots "ASYNC", # flake8-async ] -ignore = [] +lint.ignore = [] # Allow Ruff to discover `*.ipynb` files. include = ["*.py", "*.pyi", "**/pyproject.toml", "*.ipynb"] # Allow autofix for all enabled rules (when `--fix`) is provided. -fixable = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"] -unfixable = [] +lint.fixable = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"] +lint.unfixable = [] # Exclude a variety of commonly ignored directories. exclude = [ @@ -149,27 +149,27 @@ exclude = [ ] # Ignore `F401` (import violations) in all `__init__.py` files. -per-file-ignores = {"__init__.py" = ["F401"], "tests/*" = ["T201", "PGH001", "SLF001", "S101", "PLR2004"], "benchmarks/*" = ["T201", "INP001"], "pre-commit/*" = ["T201", "INP001"], "tiatoolbox/cli/*" = ["PLR0913"]} +lint.per-file-ignores = {"__init__.py" = ["F401"], "tests/*" = ["T201", "PGH001", "SLF001", "S101", "PLR2004"], "benchmarks/*" = ["T201", "INP001"], "pre-commit/*" = ["T201", "INP001"], "tiatoolbox/cli/*" = ["PLR0913"]} # Same as Black. line-length = 88 # Allow unused variables when underscore-prefixed. -dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +lint.dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" # Minimum Python version 3.8. target-version = "py38" -[tool.ruff.mccabe] +[tool.ruff.lint.mccabe] # Unlike Flake8, default to a complexity level of 10. max-complexity = 14 # need to enable for docstrings check. -[tool.ruff.pydocstyle] +[tool.ruff.lint.pydocstyle] # Use Google-style docstrings. convention = "google" -[tool.ruff.pylint] +[tool.ruff.lint.pylint] max-args = 10 [tool.mypy] diff --git a/requirements/requirements_dev.txt b/requirements/requirements_dev.txt index 6911165c5..7c58e0703 100644 --- a/requirements/requirements_dev.txt +++ b/requirements/requirements_dev.txt @@ -12,7 +12,7 @@ pytest>=7.2.0 pytest-cov>=4.0.0 pytest-runner>=6.0 pytest-xdist[psutil] -ruff==0.1.13 # This will be updated by pre-commit bot to latest version +ruff==0.2.1 # This will be updated by pre-commit bot to latest version toml>=0.10.2 twine>=4.0.1 wheel>=0.37.1 diff --git a/tests/test_dsl.py b/tests/test_dsl.py index e09753556..ad811ac6e 100644 --- a/tests/test_dsl.py +++ b/tests/test_dsl.py @@ -101,7 +101,7 @@ class TestSQLite: @staticmethod def test_prop_or_prop() -> None: """Test OR operator between two prop accesses.""" - query = eval( # skipcq: PYL-W0123 # noqa: S307 + query = eval( # skipcq: PYL-W0123 "(props['int'] == 2) | (props['int'] == 3)", SQL_GLOBALS, {}, @@ -143,7 +143,7 @@ def test_number_binary_operations( """Check that binary operations between ints does not error.""" for op in BINARY_OP_STRINGS: query = f"2 {op} 2" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -159,7 +159,7 @@ def test_property_binary_operations( """Check that binary operations between properties does not error.""" for op in BINARY_OP_STRINGS: query = f"props['int'] {op} props['int']" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -175,7 +175,7 @@ def test_r_binary_operations( """Test right hand binary operations between numbers and properties.""" for op in BINARY_OP_STRINGS: query = f"2 {op} props['int']" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -191,7 +191,7 @@ def test_number_prefix_operations( """Test prefix operations on numbers.""" for op in PREFIX_OP_STRINGS: query = f"{op}1" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -207,7 +207,7 @@ def test_property_prefix_operations( """Test prefix operations on properties.""" for op in PREFIX_OP_STRINGS: query = f"{op}props['int']" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -222,7 +222,7 @@ def test_regex_nested_props( ) -> None: """Test regex on nested properties.""" query = "props['nesting']['fib'][4]" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -237,7 +237,7 @@ def test_regex_str_props( ) -> None: """Test regex on string properties.""" query = "regexp('Hello', props['string'])" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -252,7 +252,7 @@ def test_regex_str_str( ) -> None: """Test regex on string and string.""" query = "regexp('Hello', 'Hello world!')" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -267,7 +267,7 @@ def test_regex_props_str( ) -> None: """Test regex on property and string.""" query = "regexp(props['string'], 'Hello world!')" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -282,7 +282,7 @@ def test_regex_ignore_case( ) -> None: """Test regex with ignorecase flag.""" query = "regexp('hello', props['string'], re.IGNORECASE)" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -297,7 +297,7 @@ def test_regex_no_match( ) -> None: """Test regex with no match.""" query = "regexp('Yello', props['string'])" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -312,7 +312,7 @@ def test_has_key( ) -> None: """Test has_key function.""" query = "has_key(props, 'foo')" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -327,7 +327,7 @@ def test_is_none( ) -> None: """Test is_none function.""" query = "is_none(props['null'])" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -342,7 +342,7 @@ def test_is_not_none( ) -> None: """Test is_not_none function.""" query = "is_not_none(props['int'])" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -357,7 +357,7 @@ def test_nested_has_key( ) -> None: """Test nested has_key function.""" query = "has_key(props['dict'], 'a')" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -372,7 +372,7 @@ def test_list_sum( ) -> None: """Test sum function on a list.""" query = "sum(props['list'])" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -387,7 +387,7 @@ def test_abs( ) -> None: """Test abs function.""" query = "abs(props['neg'])" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -402,7 +402,7 @@ def test_not( ) -> None: """Test not operator.""" query = "not props['bool']" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -417,7 +417,7 @@ def test_props_int_keys( ) -> None: """Test props with int keys.""" query = "props['list'][1]" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -432,7 +432,7 @@ def test_props_get( ) -> None: """Test props.get function.""" query = "is_none(props.get('foo'))" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -447,7 +447,7 @@ def test_props_get_default( ) -> None: """Test props.get function with default.""" query = "props.get('foo', 42)" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -462,7 +462,7 @@ def test_in_list( ) -> None: """Test in operator for list.""" query = "1 in props.get('list')" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -478,7 +478,7 @@ def test_has_key_exception( """Test has_key function with exception.""" query = "has_key(1, 'a')" with pytest.raises(TypeError, match="(not iterable)|(Unsupported type)"): - _ = eval( # skipcq: PYL-W0123 # noqa: S307 + _ = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -492,7 +492,7 @@ def test_logical_and( ) -> None: """Test logical and operator.""" query = "props['bool'] & is_none(props['null'])" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -507,7 +507,7 @@ def test_logical_or( ) -> None: """Test logical or operator.""" query = "props['bool'] | (props['int'] < 2)" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -522,7 +522,7 @@ def test_nested_logic( ) -> None: """Test nested logical operators.""" query = "(props['bool'] | (props['int'] < 2)) & abs(props['neg'])" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -537,7 +537,7 @@ def test_contains_list( ) -> None: """Test contains operator for list.""" query = "1 in props['list']" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -552,7 +552,7 @@ def test_contains_dict( ) -> None: """Test contains operator for dict.""" query = "'a' in props['dict']" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -567,7 +567,7 @@ def test_contains_str( ) -> None: """Test contains operator for str.""" query = "'Hello' in props['string']" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, @@ -582,7 +582,7 @@ def test_key_with_period( ) -> None: """Test key with period.""" query = "props['dot.key']" - result = eval( # skipcq: PYL-W0123 # noqa: S307 + result = eval( # skipcq: PYL-W0123 query, eval_globals, eval_locals, diff --git a/tests/test_wsireader.py b/tests/test_wsireader.py index 390751b5d..76a5d3861 100644 --- a/tests/test_wsireader.py +++ b/tests/test_wsireader.py @@ -204,7 +204,7 @@ def read_bounds_level_consistency(wsi: WSIReader, bounds: IntBounds) -> None: # from interpolation when calculating the downsampled levels. This # adds some tolerance for the comparison. blurred = [cv2.GaussianBlur(img, (5, 5), cv2.BORDER_REFLECT) for img in resized] - as_float = [img.astype(np.float_) for img in blurred] + as_float = [img.astype(np.float64) for img in blurred] # Pair-wise check resolutions for mean squared error for i, a in enumerate(as_float): @@ -2646,7 +2646,7 @@ def test_read_rect_level_consistency(wsi: WSIReader) -> None: # from interpolation when calculating the downsampled levels. This # adds some tolerance for the comparison. blurred = [cv2.GaussianBlur(img, (5, 5), cv2.BORDER_REFLECT) for img in resized] - as_float = [img.astype(np.float_) for img in blurred] + as_float = [img.astype(np.float64) for img in blurred] # Pair-wise check resolutions for mean squared error for i, a in enumerate(as_float): diff --git a/tiatoolbox/annotation/storage.py b/tiatoolbox/annotation/storage.py index 9863e08d3..3fb786374 100644 --- a/tiatoolbox/annotation/storage.py +++ b/tiatoolbox/annotation/storage.py @@ -2028,7 +2028,9 @@ def transform( transformed_geoms = { key: transform(annotation.geometry) for key, annotation in self.items() } - self.patch_many(transformed_geoms.keys(), transformed_geoms.values()) + _keys = transformed_geoms.keys() + _values = transformed_geoms.values() + self.patch_many(_keys, _values) def __del__(self: AnnotationStore) -> None: """Implements destructor method. diff --git a/tiatoolbox/utils/misc.py b/tiatoolbox/utils/misc.py index 9d0c2de97..5164c7917 100644 --- a/tiatoolbox/utils/misc.py +++ b/tiatoolbox/utils/misc.py @@ -1327,7 +1327,7 @@ def dict_to_zarr( compressor = ( kwargs["compressor"] if "compressor" in kwargs else numcodecs.Zstd(level=1) ) - chunks = kwargs["chunks"] if "chunks" in kwargs else 10000 + chunks = kwargs.get("chunks", 10000) # ensure proper zarr extension save_path = save_path.parent.absolute() / (save_path.stem + ".zarr") diff --git a/tiatoolbox/visualization/bokeh_app/main.py b/tiatoolbox/visualization/bokeh_app/main.py index 608dc23a9..0f29a4aea 100644 --- a/tiatoolbox/visualization/bokeh_app/main.py +++ b/tiatoolbox/visualization/bokeh_app/main.py @@ -64,14 +64,14 @@ # GitHub actions seems unable to find TIAToolbox unless this is here sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent)) -from tiatoolbox import logger # noqa: E402 -from tiatoolbox.models.engine.nucleus_instance_segmentor import ( # noqa: E402 +from tiatoolbox import logger +from tiatoolbox.models.engine.nucleus_instance_segmentor import ( NucleusInstanceSegmentor, ) -from tiatoolbox.tools.pyramid import ZoomifyGenerator # noqa: E402 -from tiatoolbox.utils.visualization import random_colors # noqa: E402 -from tiatoolbox.visualization.ui_utils import get_level_by_extent # noqa: E402 -from tiatoolbox.wsicore.wsireader import WSIReader # noqa: E402 +from tiatoolbox.tools.pyramid import ZoomifyGenerator +from tiatoolbox.utils.visualization import random_colors +from tiatoolbox.visualization.ui_utils import get_level_by_extent +from tiatoolbox.wsicore.wsireader import WSIReader if TYPE_CHECKING: # pragma: no cover from bokeh.document import Document From b6a371ba671859280642b07666f5363c461e76e2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 10:41:45 +0000 Subject: [PATCH 3/6] [pre-commit.ci] pre-commit autoupdate (#784) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 24.1.1 → 24.2.0](https://github.com/psf/black/compare/24.1.1...24.2.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 935cf209b..ba7ff479f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -59,7 +59,7 @@ repos: - id: rst-directive-colons # Detect mistake of rst directive not ending with double colon. - id: rst-inline-touching-normal # Detect mistake of inline code touching normal text in rst. - repo: https://github.com/psf/black - rev: 24.1.1 # Replace with any tag/version: https://github.com/psf/black/tags + rev: 24.2.0 # Replace with any tag/version: https://github.com/psf/black/tags hooks: - id: black language_version: python3 # Should be a command that runs python3.+ From 5c00381797a392204ae8f4501445e628034462fc Mon Sep 17 00:00:00 2001 From: Shan E Ahmed Raza <13048456+shaneahmed@users.noreply.github.com> Date: Tue, 20 Feb 2024 10:02:04 +0000 Subject: [PATCH 4/6] :pushpin: Update minimum Python version to `3.9` (#786) - Update minimum Python version to `3.9` ToDo: - [x] Fix all errors - [x] Update docker containers - [x] Use `functools.cachedtools` - [x] Test docker containers --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: mostafajahanifar <74412979+mostafajahanifar@users.noreply.github.com> --- .github/workflows/docker-publish.yml | 12 +- .github/workflows/mypy-type-check.yml | 2 +- .github/workflows/pip-install.yml | 2 +- .github/workflows/python-package.yml | 2 +- README.md | 2 +- benchmarks/annotation_store.ipynb | 5375 +++++++++-------- benchmarks/annotation_store_alloc.py | 3 +- docker/{3.8 => 3.11}/Debian/Dockerfile | 2 +- docker/3.11/Ubuntu/Dockerfile | 30 + docker/3.12/Debian/Dockerfile | 14 + docker/3.12/Ubuntu/Dockerfile | 30 + docs/installation.rst | 2 +- examples/full-pipelines/slide-graph.ipynb | 5 +- pyproject.toml | 6 +- requirements/requirements.conda.yml | 2 +- requirements/requirements.dev.conda.yml | 2 +- requirements/requirements.win64.conda.yml | 2 +- requirements/requirements.win64.dev.conda.yml | 2 +- setup.py | 4 +- tests/test_annotation_stores.py | 11 +- tests/test_app_bokeh.py | 14 +- tests/test_docs.py | 5 +- tests/test_dsl.py | 5 +- tests/test_wsireader.py | 6 +- tiatoolbox/__init__.py | 12 +- tiatoolbox/annotation/storage.py | 5 +- tiatoolbox/cli/visualize.py | 8 +- tiatoolbox/data/__init__.py | 6 +- tiatoolbox/models/dataset/dataset_abc.py | 6 +- tiatoolbox/tools/pyramid.py | 4 +- tiatoolbox/tools/stainextract.py | 26 +- tiatoolbox/typing.py | 13 +- tiatoolbox/wsicore/wsimeta.py | 4 +- tiatoolbox/wsicore/wsireader.py | 7 +- 34 files changed, 2850 insertions(+), 2781 deletions(-) rename docker/{3.8 => 3.11}/Debian/Dockerfile (91%) create mode 100644 docker/3.11/Ubuntu/Dockerfile create mode 100644 docker/3.12/Debian/Dockerfile create mode 100644 docker/3.12/Ubuntu/Dockerfile diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 4f63c729e..4d486766b 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -15,8 +15,6 @@ jobs: fail-fast: true matrix: include: - - dockerfile: ./docker/3.8/Debian/Dockerfile - mtag: py3.8-debian - dockerfile: ./docker/3.9/Debian/Dockerfile mtag: py3.9-debian - dockerfile: ./docker/3.9/Ubuntu/Dockerfile @@ -25,7 +23,15 @@ jobs: mtag: py3.10-debian - dockerfile: ./docker/3.10/Ubuntu/Dockerfile mtag: py3.10-ubuntu - - dockerfile: ./docker/3.10/Ubuntu/Dockerfile + - dockerfile: ./docker/3.11/Debian/Dockerfile + mtag: py3.11-debian + - dockerfile: ./docker/3.11/Ubuntu/Dockerfile + mtag: py3.11-ubuntu + - dockerfile: ./docker/3.12/Debian/Dockerfile + mtag: py3.12-debian + - dockerfile: ./docker/3.12/Ubuntu/Dockerfile + mtag: py3.12-ubuntu + - dockerfile: ./docker/3.12/Ubuntu/Dockerfile mtag: latest permissions: contents: read diff --git a/.github/workflows/mypy-type-check.yml b/.github/workflows/mypy-type-check.yml index a22f339c5..1c026da9e 100644 --- a/.github/workflows/mypy-type-check.yml +++ b/.github/workflows/mypy-type-check.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.9", "3.10", "3.11", "3.12"] steps: diff --git a/.github/workflows/pip-install.yml b/.github/workflows/pip-install.yml index abdb11527..ffa6961c9 100644 --- a/.github/workflows/pip-install.yml +++ b/.github/workflows/pip-install.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: true matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.9", "3.10", "3.11", "3.12"] os: [ubuntu-22.04, windows-latest, macos-latest] steps: - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 321316040..9df1550c6 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -17,7 +17,7 @@ jobs: strategy: fail-fast: true matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v3 diff --git a/README.md b/README.md index 0c5de616d..da8c04f06 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ Prepare a computer as a convenient platform for further development of the Pytho 5. Create virtual environment for TIAToolbox using ```sh - $ conda create -n tiatoolbox-dev python=3.8 # select version of your choice + $ conda create -n tiatoolbox-dev python=3.9 # select version of your choice $ conda activate tiatoolbox-dev $ pip install -r requirements/requirements_dev.txt ``` diff --git a/benchmarks/annotation_store.ipynb b/benchmarks/annotation_store.ipynb index 6c8b83d65..882ab251c 100644 --- a/benchmarks/annotation_store.ipynb +++ b/benchmarks/annotation_store.ipynb @@ -1,2703 +1,2704 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "aqPkpRk-pT5q" - }, - "source": [ - "# Benchmarking Annotation Storage\n", - "\n", - "Click to open in: \\[[GitHub](https://github.com/TissueImageAnalytics/tiatoolbox/tree/develop/benchmarks/annotation_store.ipynb)\\]\\[[Colab](https://colab.research.google.com/github/TissueImageAnalytics/tiatoolbox/blob/develop/benchmarks/annotation_store.ipynb)\\]\\[[Kaggle](https://kaggle.com/kernels/welcome?src=https://github.com/TissueImageAnalytics/tiatoolbox/blob/develop/benchmarks/annotation_store.ipynb)\\]\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "BS0G58BPpT5s" - }, - "source": [ - "_In order to run this notebook on a Kaggle platform, 1) click the Kaggle URL 2) click on Settings on the right of the Kaggle screen, 3) log in to your Kaggle account, 4) tick \"Internet\" checkbox under Settings, to enable necessary downloads._\n", - "\n", - "**NOTE:** Some parts of this notebook require a lot of memory. Part 2 in particular may not run on memory constrained systems. The notebook will run well on an MacBook Air (M1, 2020) but will use a lot of swap. It may require >64GB of memory for second half to avoid using swap.\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "EjHQXjqrpT5s" - }, - "source": [ - "## About This Notebook\n", - "\n", - "Managing annotation, either created by hand or from model output, is a\n", - "common task in computational pathology. For a small number of\n", - "annotations this may be trivial. However, for large numbers of\n", - "annotations, it is often necessary to store the annotations in a more\n", - "structured format such as a database. This is because finding a desired\n", - "subset of annotations within a very large collection, for example over\n", - "one million cell boundary polygons derived from running HoVerNet on a\n", - "WSI, may be very slow if performed in a naive manner. In the toolbox, we\n", - "implement two storage method to make handling annotations easier:\n", - "`DictionaryStore` and `SQLiteStore`.\n", - "\n", - "### Storage Classes\n", - "\n", - "Both stores act as a key-value store where the key is the annotation ID\n", - "(as a string) and the value is the annotation. This follows the Python\n", - "[`MutableMapping`](https://docs.python.org/3/library/collections.abc.html#collections.abc.MutableMapping)\n", - "interface meaning that the stores can be used in the same way as a\n", - "regular Python dictionary (`dict`).\n", - "\n", - "The `DictionaryStore` is implemented internally using a Python\n", - "dictionary. It is a realtively simple class, operating with all\n", - "annotations in memory and using a simple scan method to search for\n", - "annotations. This works very well for a small number of annotations. In\n", - "contrast the `SQLiteStore` is implemented using a SQLite database\n", - "(either in memory or on disk), it is a more complex class making use of\n", - "an rtree index to efficiently spatially search for annotations. This is\n", - "much more suited to a very large number of annotations. However, they\n", - "both follow the same interface and can be used interchangeably for\n", - "almost all methods (`SQLiteStore` has some additional methods).\n", - "\n", - "### Provided Functionality (Mini Tutorial)\n", - "\n", - "The storage classes provide a lot of functionality including. This\n", - "includes all of the standard `MutableMapping` methods, as well as\n", - "some additional ones for querying the collection of annotations.\n", - "Below is a brief summary of the main functionality.\n", - "\n", - "#### Adding Annotations\n", - "\n", - "```python\n", - "from tiatoolbox.annotation.storage import Annotation, DictionaryStore, SQliteStore\n", - "from shapely.geometry import Polygon\n", - "\n", - "# Create a new store. If no path is given it is an in-memory store.\n", - "store = DictionaryStore()\n", - "\n", - "# An annotation is a shapely geometry and a JSON serializable dictionary\n", - "annotation = Annotation(Polygon.from_bounds(0, 0, 1, 1), {\"id\": \"1\"})\n", - "\n", - "# Add the annotation to the store in the same way as a dictionary\n", - "store[\"foo\"] = annotation\n", - "\n", - "# Bulk append is also supported. This will be faster in some contexts\n", - "# (e.g. for an SQLiteStore) than adding them one at a time.\n", - "# Here we add 100 simple box annotations.\n", - "# As we have not specified a set of keys to use, a new UUID is generated\n", - "# for each. The respective generated keys are also returned.\n", - "annotations = [\n", - " Annotation(Polygon.from_bounds(n, n, n + 1, n + 1), {\"id\": n}) for n in range(100)\n", - "]\n", - "keys = store.append_many(annotations)\n", - "```\n", - "\n", - "#### Removing Annotations\n", - "\n", - "```python\n", - "# Remove an annotation by key\n", - "del store[\"foo\"]\n", - "\n", - "# Bulk removal\n", - "keys = [\"1234-5676....\", \"...\"] # etc.\n", - "store.remove_many(keys)\n", - "```\n", - "\n", - "#### Querying Within a Region\n", - "\n", - "```python\n", - "# Find all annotations which intersect a polygon\n", - "search_region = Polygon.from_bounds(0, 0, 10, 10)\n", - "result = store.query(search_region)\n", - "\n", - "# Find all annotations which are contained within a polygon\n", - "search_region = Polygon.from_bounds(0, 0, 10, 10)\n", - "result = store.query(search_region, geometry_predicate=\"contains\")\n", - "```\n", - "\n", - "#### Querying Using A Predicate Statement\n", - "\n", - "```python\n", - "# 'props' is a provided shorthand to access the 'properties' dictionary\n", - "results = store.query(where=\"propd['id'] == 1\")\n", - "```\n", - "\n", - "#### Serializing and Deserializing\n", - "\n", - "```python\n", - "# Serialize the store to a GeoJSON string\n", - "json_string = store.to_geojson()\n", - "\n", - "# Serialize the store to a GeoJSON file\n", - "store.to_geojson(\"boxes.geojson\")\n", - "\n", - "# Deserialize a GeoJSON string into a store (even of a different type)\n", - "sqlitestore = SqliteStore.from_geojson(\"boxes.geojson\")\n", - "\n", - "# The above is an in-memory store. We can also now write this to disk\n", - "# as an SQLite database.\n", - "sqlitestore.dump(\"boxes.db\")\n", - "```\n", - "\n", - "### Benchmarking\n", - "\n", - "Here we evaluate the storage efficient and data querying performance of\n", - "the annotation store versus other common formats. We will evaluate some\n", - "common situations and use cases including:\n", - "\n", - "- Disk I/O (tested with an SSD)\n", - "- Querying the data for annotations within a box region\n", - "- Querying the data for annotations within a polygon region\n", - "- Querying the data with a predicate e.g. 'class=1'\n", - "\n", - "All saved output is from running this notebook on a 2020 M1 MacBook Air with 16GB RAM.\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "aov8ENq2pT5t" - }, - "source": [ - "## Imports\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "UoMpbDXopT5t" - }, - "outputs": [], - "source": [ - "\"\"\"Import modules required to run the Jupyter notebook.\"\"\"\n", - "\n", - "from __future__ import annotations\n", - "\n", - "# Clear logger to use tiatoolbox.logger\n", - "import logging\n", - "\n", - "if logging.getLogger().hasHandlers():\n", - " logging.getLogger().handlers.clear()\n", - "\n", - "import copy\n", - "import pickle\n", - "import sys\n", - "import tempfile\n", - "import timeit\n", - "import uuid\n", - "from pathlib import Path\n", - "from typing import TYPE_CHECKING, Any, Generator\n", - "\n", - "import numpy as np\n", - "from IPython.display import display\n", - "from matplotlib import patheffects\n", - "from matplotlib import pyplot as plt\n", - "from shapely import affinity\n", - "from shapely.geometry import MultiPolygon, Point, Polygon\n", - "from tqdm.auto import tqdm\n", - "\n", - "if TYPE_CHECKING:\n", - " from numbers import Number\n", - "\n", - "sys.path.append(\"..\") # If running locally without pypi installed tiatoolbox\n", - "\n", - "from tiatoolbox import logger\n", - "from tiatoolbox.annotation.storage import (\n", - " Annotation,\n", - " DictionaryStore,\n", - " SQLiteStore,\n", - ")\n", - "\n", - "plt.style.use(\"ggplot\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nW-UyVQOpT5u" - }, - "source": [ - "## Data Generation & Utility Functions\n", - "\n", - "Here we define some useful functions to generate some artificial data\n", - "and visualise results.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "N5xNW64UpT5v" - }, - "outputs": [], - "source": [ - "def cell_polygon(\n", - " xy: tuple[Number, Number],\n", - " n_points: int = 20,\n", - " radius: Number = 8,\n", - " noise: Number = 0.01,\n", - " eccentricity: tuple[Number, Number] = (1, 3),\n", - " direction: str = \"CCW\",\n", - " seed: int = 0,\n", - " *,\n", - " repeat_first: bool = True,\n", - ") -> Polygon:\n", - " \"\"\"Generate a fake cell boundary polygon.\n", - "\n", - " Borrowed from tiatoolbox unit tests.\n", - "\n", - " Cell boundaries are generated an ellipsoids with randomised eccentricity,\n", - " added noise, and a random rotation.\n", - "\n", - " Args:\n", - " xy (tuple(int)): The x,y centre point to generate the cell boundary around.\n", - " n_points (int): Number of points in the boundary. Defaults to 20.\n", - " radius (float): Radius of the points from the centre. Defaults to 10.\n", - " noise (float): Noise to add to the point locations. Defaults to 1.\n", - " eccentricity (tuple(float)): Range of values (low, high) to use for\n", - " randomised eccentricity. Defaults to (1, 3).\n", - " repeat_first (bool): Enforce that the last point is equal to the first.\n", - " direction (str): Ordering of the points. Defaults to \"CCW\". Valid options\n", - " are: counter-clockwise \"CCW\", and clockwise \"CW\".\n", - " seed: Seed for the random number generator. Defaults to 0.\n", - "\n", - " \"\"\"\n", - " rand_state = np.random.default_rng().__getstate__()\n", - " rng_seed = np.random.default_rng(seed)\n", - "\n", - " if repeat_first:\n", - " n_points -= 1\n", - "\n", - " # Generate points about an ellipse with random eccentricity\n", - " x, y = xy\n", - " alpha = np.linspace(0, 2 * np.pi - (2 * np.pi / n_points), n_points)\n", - " rx = radius * (rng_seed.random() + 0.5)\n", - " ry = rng_seed.uniform(*eccentricity) * radius - 0.5 * rx\n", - " x = rx * np.cos(alpha) + x + (rng_seed.random(n_points) - 0.5) * noise\n", - " y = ry * np.sin(alpha) + y + (rng_seed.random(n_points) - 0.5) * noise\n", - " boundary_coords = np.stack([x, y], axis=1).astype(int).tolist()\n", - "\n", - " # Copy first coordinate to the end if required\n", - " if repeat_first:\n", - " boundary_coords = [*boundary_coords, boundary_coords[0]]\n", - "\n", - " # Swap direction\n", - " if direction.strip().lower() == \"cw\":\n", - " boundary_coords = boundary_coords[::-1]\n", - "\n", - " polygon = Polygon(boundary_coords)\n", - "\n", - " # Add random rotation\n", - " angle = rng_seed.random() * 360\n", - " polygon = affinity.rotate(polygon, angle, origin=\"centroid\")\n", - "\n", - " # Restore the random state\n", - " np.random.default_rng().__setstate__(rand_state)\n", - "\n", - " return polygon" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "jyQEBhNIpT5v" - }, - "outputs": [], - "source": [ - "def cell_grid(\n", - " size: tuple[int, int] = (10, 10),\n", - " spacing: Number = 25,\n", - ") -> Generator[Polygon, None, None]:\n", - " \"\"\"Generate a grid of cell boundaries.\"\"\"\n", - " return (\n", - " cell_polygon(xy=np.multiply(ij, spacing), repeat_first=False, seed=n)\n", - " for n, ij in enumerate(np.ndindex(size))\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "VVjSum_9pT5v" - }, - "outputs": [], - "source": [ - "def plot_results(\n", - " experiments: list[list[Number]],\n", - " title: str,\n", - " capsize: int = 5,\n", - " **kwargs: dict[str, Any],\n", - ") -> None:\n", - " \"\"\"Plot the results of a benchmark.\n", - "\n", - " Uses the min for the bar height (see See\n", - " https://docs.python.org/2/library/timeit.html#timeit.Timer.repeat),\n", - " and plots a min-max error bar.\n", - "\n", - " \"\"\"\n", - " x = range(len(experiments))\n", - " color = [f\"C{x_i}\" for x_i in x]\n", - " plt.bar(\n", - " x=x,\n", - " height=[min(e) for e in experiments],\n", - " color=color,\n", - " yerr=[[0 for e in experiments], [max(e) - min(e) for e in experiments]],\n", - " capsize=capsize,\n", - " **kwargs,\n", - " )\n", - " for i, (runs, c) in enumerate(zip(experiments, color)):\n", - " plt.text(\n", - " i,\n", - " min(runs),\n", - " f\" {min(runs):.4f}s\",\n", - " ha=\"left\",\n", - " va=\"bottom\",\n", - " color=c,\n", - " zorder=10,\n", - " fontweight=\"bold\",\n", - " path_effects=[\n", - " patheffects.withStroke(linewidth=2, foreground=\"w\"),\n", - " ],\n", - " )\n", - " plt.title(title)\n", - " plt.hlines(\n", - " 0.5,\n", - " -0.5,\n", - " len(experiments) - 0.5,\n", - " linestyles=\"dashed\",\n", - " colors=\"black\",\n", - " alpha=0.5,\n", - " )\n", - " plt.yscale(\"log\")\n", - " plt.xlabel(\"Store Type\")\n", - " plt.ylabel(\"Time (s)\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "tHEUErSmpT5w" - }, - "source": [ - "## Display Some Generated Data\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "YUQmgohbpT5w", - "outputId": "1a0cdee1-e32d-41e9-fb9d-26c5ee572880" - }, - "outputs": [ + "cells": [ { - "data": { - "image/svg+xml": "", - "text/plain": [ - "" + "cell_type": "markdown", + "metadata": { + "id": "aqPkpRk-pT5q" + }, + "source": [ + "# Benchmarking Annotation Storage\n", + "\n", + "Click to open in: \\[[GitHub](https://github.com/TissueImageAnalytics/tiatoolbox/tree/develop/benchmarks/annotation_store.ipynb)\\]\\[[Colab](https://colab.research.google.com/github/TissueImageAnalytics/tiatoolbox/blob/develop/benchmarks/annotation_store.ipynb)\\]\\[[Kaggle](https://kaggle.com/kernels/welcome?src=https://github.com/TissueImageAnalytics/tiatoolbox/blob/develop/benchmarks/annotation_store.ipynb)\\]\n", + "\n" ] - }, - "metadata": {}, - "output_type": "display_data" }, { - "data": { - "image/svg+xml": "", - "text/plain": [ - "" + "cell_type": "markdown", + "metadata": { + "id": "BS0G58BPpT5s" + }, + "source": [ + "_In order to run this notebook on a Kaggle platform, 1) click the Kaggle URL 2) click on Settings on the right of the Kaggle screen, 3) log in to your Kaggle account, 4) tick \"Internet\" checkbox under Settings, to enable necessary downloads._\n", + "\n", + "**NOTE:** Some parts of this notebook require a lot of memory. Part 2 in particular may not run on memory constrained systems. The notebook will run well on an MacBook Air (M1, 2020) but will use a lot of swap. It may require >64GB of memory for second half to avoid using swap.\n", + "\n" ] - }, - "metadata": {}, - "output_type": "display_data" }, { - "data": { - "image/svg+xml": "", - "text/plain": [ - "" + "cell_type": "markdown", + "metadata": { + "id": "EjHQXjqrpT5s" + }, + "source": [ + "## About This Notebook\n", + "\n", + "Managing annotation, either created by hand or from model output, is a\n", + "common task in computational pathology. For a small number of\n", + "annotations this may be trivial. However, for large numbers of\n", + "annotations, it is often necessary to store the annotations in a more\n", + "structured format such as a database. This is because finding a desired\n", + "subset of annotations within a very large collection, for example over\n", + "one million cell boundary polygons derived from running HoVerNet on a\n", + "WSI, may be very slow if performed in a naive manner. In the toolbox, we\n", + "implement two storage method to make handling annotations easier:\n", + "`DictionaryStore` and `SQLiteStore`.\n", + "\n", + "### Storage Classes\n", + "\n", + "Both stores act as a key-value store where the key is the annotation ID\n", + "(as a string) and the value is the annotation. This follows the Python\n", + "[`MutableMapping`](https://docs.python.org/3/library/collections.abc.html#collections.abc.MutableMapping)\n", + "interface meaning that the stores can be used in the same way as a\n", + "regular Python dictionary (`dict`).\n", + "\n", + "The `DictionaryStore` is implemented internally using a Python\n", + "dictionary. It is a realtively simple class, operating with all\n", + "annotations in memory and using a simple scan method to search for\n", + "annotations. This works very well for a small number of annotations. In\n", + "contrast the `SQLiteStore` is implemented using a SQLite database\n", + "(either in memory or on disk), it is a more complex class making use of\n", + "an rtree index to efficiently spatially search for annotations. This is\n", + "much more suited to a very large number of annotations. However, they\n", + "both follow the same interface and can be used interchangeably for\n", + "almost all methods (`SQLiteStore` has some additional methods).\n", + "\n", + "### Provided Functionality (Mini Tutorial)\n", + "\n", + "The storage classes provide a lot of functionality including. This\n", + "includes all of the standard `MutableMapping` methods, as well as\n", + "some additional ones for querying the collection of annotations.\n", + "Below is a brief summary of the main functionality.\n", + "\n", + "#### Adding Annotations\n", + "\n", + "```python\n", + "from tiatoolbox.annotation.storage import Annotation, DictionaryStore, SQliteStore\n", + "from shapely.geometry import Polygon\n", + "\n", + "# Create a new store. If no path is given it is an in-memory store.\n", + "store = DictionaryStore()\n", + "\n", + "# An annotation is a shapely geometry and a JSON serializable dictionary\n", + "annotation = Annotation(Polygon.from_bounds(0, 0, 1, 1), {\"id\": \"1\"})\n", + "\n", + "# Add the annotation to the store in the same way as a dictionary\n", + "store[\"foo\"] = annotation\n", + "\n", + "# Bulk append is also supported. This will be faster in some contexts\n", + "# (e.g. for an SQLiteStore) than adding them one at a time.\n", + "# Here we add 100 simple box annotations.\n", + "# As we have not specified a set of keys to use, a new UUID is generated\n", + "# for each. The respective generated keys are also returned.\n", + "annotations = [\n", + " Annotation(Polygon.from_bounds(n, n, n + 1, n + 1), {\"id\": n}) for n in range(100)\n", + "]\n", + "keys = store.append_many(annotations)\n", + "```\n", + "\n", + "#### Removing Annotations\n", + "\n", + "```python\n", + "# Remove an annotation by key\n", + "del store[\"foo\"]\n", + "\n", + "# Bulk removal\n", + "keys = [\"1234-5676....\", \"...\"] # etc.\n", + "store.remove_many(keys)\n", + "```\n", + "\n", + "#### Querying Within a Region\n", + "\n", + "```python\n", + "# Find all annotations which intersect a polygon\n", + "search_region = Polygon.from_bounds(0, 0, 10, 10)\n", + "result = store.query(search_region)\n", + "\n", + "# Find all annotations which are contained within a polygon\n", + "search_region = Polygon.from_bounds(0, 0, 10, 10)\n", + "result = store.query(search_region, geometry_predicate=\"contains\")\n", + "```\n", + "\n", + "#### Querying Using A Predicate Statement\n", + "\n", + "```python\n", + "# 'props' is a provided shorthand to access the 'properties' dictionary\n", + "results = store.query(where=\"propd['id'] == 1\")\n", + "```\n", + "\n", + "#### Serializing and Deserializing\n", + "\n", + "```python\n", + "# Serialize the store to a GeoJSON string\n", + "json_string = store.to_geojson()\n", + "\n", + "# Serialize the store to a GeoJSON file\n", + "store.to_geojson(\"boxes.geojson\")\n", + "\n", + "# Deserialize a GeoJSON string into a store (even of a different type)\n", + "sqlitestore = SqliteStore.from_geojson(\"boxes.geojson\")\n", + "\n", + "# The above is an in-memory store. We can also now write this to disk\n", + "# as an SQLite database.\n", + "sqlitestore.dump(\"boxes.db\")\n", + "```\n", + "\n", + "### Benchmarking\n", + "\n", + "Here we evaluate the storage efficient and data querying performance of\n", + "the annotation store versus other common formats. We will evaluate some\n", + "common situations and use cases including:\n", + "\n", + "- Disk I/O (tested with an SSD)\n", + "- Querying the data for annotations within a box region\n", + "- Querying the data for annotations within a polygon region\n", + "- Querying the data with a predicate e.g. 'class=1'\n", + "\n", + "All saved output is from running this notebook on a 2020 M1 MacBook Air with 16GB RAM.\n", + "\n" ] - }, - "metadata": {}, - "output_type": "display_data" }, { - "data": { - "image/svg+xml": "", - "text/plain": [ - "" + "cell_type": "markdown", + "metadata": { + "id": "aov8ENq2pT5t" + }, + "source": [ + "## Imports\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "UoMpbDXopT5t" + }, + "outputs": [], + "source": [ + "\"\"\"Import modules required to run the Jupyter notebook.\"\"\"\n", + "\n", + "from __future__ import annotations\n", + "\n", + "# Clear logger to use tiatoolbox.logger\n", + "import logging\n", + "\n", + "if logging.getLogger().hasHandlers():\n", + " logging.getLogger().handlers.clear()\n", + "\n", + "import copy\n", + "import pickle\n", + "import sys\n", + "import tempfile\n", + "import timeit\n", + "import uuid\n", + "from pathlib import Path\n", + "from typing import TYPE_CHECKING, Any\n", + "\n", + "import numpy as np\n", + "from IPython.display import display\n", + "from matplotlib import patheffects\n", + "from matplotlib import pyplot as plt\n", + "from shapely import affinity\n", + "from shapely.geometry import MultiPolygon, Point, Polygon\n", + "from tqdm.auto import tqdm\n", + "\n", + "if TYPE_CHECKING:\n", + " from collections.abc import Generator\n", + " from numbers import Number\n", + "\n", + "sys.path.append(\"..\") # If running locally without pypi installed tiatoolbox\n", + "\n", + "from tiatoolbox import logger\n", + "from tiatoolbox.annotation.storage import (\n", + " Annotation,\n", + " DictionaryStore,\n", + " SQLiteStore,\n", + ")\n", + "\n", + "plt.style.use(\"ggplot\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "nW-UyVQOpT5u" + }, + "source": [ + "## Data Generation & Utility Functions\n", + "\n", + "Here we define some useful functions to generate some artificial data\n", + "and visualise results.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "N5xNW64UpT5v" + }, + "outputs": [], + "source": [ + "def cell_polygon(\n", + " xy: tuple[Number, Number],\n", + " n_points: int = 20,\n", + " radius: Number = 8,\n", + " noise: Number = 0.01,\n", + " eccentricity: tuple[Number, Number] = (1, 3),\n", + " direction: str = \"CCW\",\n", + " seed: int = 0,\n", + " *,\n", + " repeat_first: bool = True,\n", + ") -> Polygon:\n", + " \"\"\"Generate a fake cell boundary polygon.\n", + "\n", + " Borrowed from tiatoolbox unit tests.\n", + "\n", + " Cell boundaries are generated an ellipsoids with randomised eccentricity,\n", + " added noise, and a random rotation.\n", + "\n", + " Args:\n", + " xy (tuple(int)): The x,y centre point to generate the cell boundary around.\n", + " n_points (int): Number of points in the boundary. Defaults to 20.\n", + " radius (float): Radius of the points from the centre. Defaults to 10.\n", + " noise (float): Noise to add to the point locations. Defaults to 1.\n", + " eccentricity (tuple(float)): Range of values (low, high) to use for\n", + " randomised eccentricity. Defaults to (1, 3).\n", + " repeat_first (bool): Enforce that the last point is equal to the first.\n", + " direction (str): Ordering of the points. Defaults to \"CCW\". Valid options\n", + " are: counter-clockwise \"CCW\", and clockwise \"CW\".\n", + " seed: Seed for the random number generator. Defaults to 0.\n", + "\n", + " \"\"\"\n", + " rand_state = np.random.default_rng().__getstate__()\n", + " rng_seed = np.random.default_rng(seed)\n", + "\n", + " if repeat_first:\n", + " n_points -= 1\n", + "\n", + " # Generate points about an ellipse with random eccentricity\n", + " x, y = xy\n", + " alpha = np.linspace(0, 2 * np.pi - (2 * np.pi / n_points), n_points)\n", + " rx = radius * (rng_seed.random() + 0.5)\n", + " ry = rng_seed.uniform(*eccentricity) * radius - 0.5 * rx\n", + " x = rx * np.cos(alpha) + x + (rng_seed.random(n_points) - 0.5) * noise\n", + " y = ry * np.sin(alpha) + y + (rng_seed.random(n_points) - 0.5) * noise\n", + " boundary_coords = np.stack([x, y], axis=1).astype(int).tolist()\n", + "\n", + " # Copy first coordinate to the end if required\n", + " if repeat_first:\n", + " boundary_coords = [*boundary_coords, boundary_coords[0]]\n", + "\n", + " # Swap direction\n", + " if direction.strip().lower() == \"cw\":\n", + " boundary_coords = boundary_coords[::-1]\n", + "\n", + " polygon = Polygon(boundary_coords)\n", + "\n", + " # Add random rotation\n", + " angle = rng_seed.random() * 360\n", + " polygon = affinity.rotate(polygon, angle, origin=\"centroid\")\n", + "\n", + " # Restore the random state\n", + " np.random.default_rng().__setstate__(rand_state)\n", + "\n", + " return polygon" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "jyQEBhNIpT5v" + }, + "outputs": [], + "source": [ + "def cell_grid(\n", + " size: tuple[int, int] = (10, 10),\n", + " spacing: Number = 25,\n", + ") -> Generator[Polygon, None, None]:\n", + " \"\"\"Generate a grid of cell boundaries.\"\"\"\n", + " return (\n", + " cell_polygon(xy=np.multiply(ij, spacing), repeat_first=False, seed=n)\n", + " for n, ij in enumerate(np.ndindex(size))\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "VVjSum_9pT5v" + }, + "outputs": [], + "source": [ + "def plot_results(\n", + " experiments: list[list[Number]],\n", + " title: str,\n", + " capsize: int = 5,\n", + " **kwargs: dict[str, Any],\n", + ") -> None:\n", + " \"\"\"Plot the results of a benchmark.\n", + "\n", + " Uses the min for the bar height (see See\n", + " https://docs.python.org/2/library/timeit.html#timeit.Timer.repeat),\n", + " and plots a min-max error bar.\n", + "\n", + " \"\"\"\n", + " x = range(len(experiments))\n", + " color = [f\"C{x_i}\" for x_i in x]\n", + " plt.bar(\n", + " x=x,\n", + " height=[min(e) for e in experiments],\n", + " color=color,\n", + " yerr=[[0 for e in experiments], [max(e) - min(e) for e in experiments]],\n", + " capsize=capsize,\n", + " **kwargs,\n", + " )\n", + " for i, (runs, c) in enumerate(zip(experiments, color)):\n", + " plt.text(\n", + " i,\n", + " min(runs),\n", + " f\" {min(runs):.4f}s\",\n", + " ha=\"left\",\n", + " va=\"bottom\",\n", + " color=c,\n", + " zorder=10,\n", + " fontweight=\"bold\",\n", + " path_effects=[\n", + " patheffects.withStroke(linewidth=2, foreground=\"w\"),\n", + " ],\n", + " )\n", + " plt.title(title)\n", + " plt.hlines(\n", + " 0.5,\n", + " -0.5,\n", + " len(experiments) - 0.5,\n", + " linestyles=\"dashed\",\n", + " colors=\"black\",\n", + " alpha=0.5,\n", + " )\n", + " plt.yscale(\"log\")\n", + " plt.xlabel(\"Store Type\")\n", + " plt.ylabel(\"Time (s)\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "tHEUErSmpT5w" + }, + "source": [ + "## Display Some Generated Data\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "YUQmgohbpT5w", + "outputId": "1a0cdee1-e32d-41e9-fb9d-26c5ee572880" + }, + "outputs": [ + { + "data": { + "image/svg+xml": "", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": "", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": "", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": "", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "for n in range(4):\n", + " display(cell_polygon(xy=(0, 0), n_points=20, repeat_first=False, seed=n))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "APUNL2PtpT5w" + }, + "source": [ + "### Randomised Cell Boundaries\n", + "\n", + "Here we create a function to generate grid of cells for testing. It uses a fixed seed for reproducibility.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "SOpBKM7IpT5w" + }, + "source": [ + "### A Sample 5×5 Grid\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "2xA-oG4VpT5w", + "outputId": "caea51e4-8a27-4dd1-ed0d-c272b93d8bb7" + }, + "outputs": [ + { + "data": { + "image/svg+xml": "", + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "MultiPolygon(polygons=list(cell_grid(size=(5, 5), spacing=35)))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "b6S8vzFipT5w" + }, + "source": [ + "# Part 1: Small Scale Benchmarking of Annotation Storage\n", + "\n", + "Using the already defined data generation functions (`cell_polygon` and\n", + "`cell_grid`), we create some simple artificial cell boundaries by\n", + "creating a circle of points, adding some noise, scaling to introduce\n", + "eccentricity, and then rotating. We use 20 points per cell, which is a\n", + "reasonably high value for cell annotation. However, this can be\n", + "adjusted.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "UZMoLDvkpT5x" + }, + "source": [ + "## 1.1) Appending Annotations (In-Memory & Disk I/O)\n", + "\n", + "Here we test:\n", + "\n", + "1. A python dictionary based in-memory store (`DictionaryStore`)\n", + "1. An SQLite database based in-memory store (`SQLiteStore`)\n", + "\n", + "Both of these stores may operate in memory. The `SQLiteStore` may also\n", + "be backed by an on-disk file for datasets which are too large to fit in\n", + "memory. The `DictionaryStore` class can serialise/deserialise itself\n", + "to/from disk in a line delimited GeoJSON format (each line seperated\n", + "by `\\n` is a valid GeoJSON object)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "DZBiw_EepT5x" + }, + "outputs": [], + "source": [ + "# Convert to annotations (a dataclass pairing a geometry and (optional)\n", + "# key-value properties)\n", + "# Run time: ~2s\n", + "annotations = [\n", + " Annotation(polygon) for polygon in cell_grid(size=(100, 100), spacing=35)\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "LUVa03F2pT5x" + }, + "source": [ + "### 1.1.1) In Memory Append\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "7PzE7AhdpT5x", + "outputId": "974bb3d0-3290-4315-a6fc-3b7ca90072a6" + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Run time: ~5s\n", + "\n", + "# Time dictionary store\n", + "dict_runs = timeit.repeat(\n", + " \"dict_store.append_many(annotations)\",\n", + " setup=\"dict_store = DictionaryStore()\",\n", + " globals={\"DictionaryStore\": DictionaryStore, \"annotations\": annotations},\n", + " number=1,\n", + " repeat=3,\n", + ")\n", + "\n", + "# Time SQLite store\n", + "sqlite_runs = timeit.repeat(\n", + " \"sql_store.append_many(annotations)\",\n", + " setup=\"sql_store = SQLiteStore()\",\n", + " globals={\"SQLiteStore\": SQLiteStore, \"annotations\": annotations},\n", + " number=1,\n", + " repeat=3,\n", + ")\n", + "\n", + "# Plot the results\n", + "plot_results(\n", + " experiments=[dict_runs, sqlite_runs],\n", + " title=\"Time to Append 10,000 Annotations In Memory\",\n", + " tick_label=[\"DictionaryStore\", \"SQLiteStore\"],\n", + ")\n", + "plt.hlines(0.5, -0.5, 1.5, linestyles=\"dashed\", color=\"k\")\n", + "plt.xlim([-0.5, 1.5])\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "gU6PLE7wpT5x" + }, + "source": [ + "Note that inserting into the `SQLiteStore` is much slower than the\n", + "`DictionaryStore`. Appending to a `Dictionary` store simply requires\n", + "adding a memory reference to a dictionary. Therefore, this is a very\n", + "fast operation. On the other hand, for the `SQLiteStore`, the insertion\n", + "is slower because the data must be serialised for the database and the\n", + "R-Tree spatial index must also be updated. Updating the index is a\n", + "relatively expensive operation. However, this spatial index allows for\n", + "very fast queries of a very large set of annotations within a set of\n", + "spatial bounds.\n", + "\n", + "Insertion is typically only performed once for each\n", + "annotation, whereas queries may be performed many times on the\n", + "annotation set. Therefore, it makes sense to trade a more expensive\n", + "insertion for fast queries as the cost of insertion will be amortised\n", + "over a number of queries on the data. Additionally, data may be written\n", + "to the database from multiple threads or subprocesses (so long as a new\n", + "instance of `SQLiteStore` is created for each thread or subprocess to\n", + "attach to a database on disk) thus freeing up the main thread.\n", + "\n", + "For comparison, we also compare bulk insertion plus seralising to disk\n", + "as line-delimited GeoJSON from the `DictionaryStore` as this is the\n", + "default serialisation to disk method (`DictionaryStore.dump(file_path`).\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "t2q9QTCfpT5x", + "outputId": "2202c328-ba48-476b-8efa-662678d75135" + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Run time: ~10s\n", + "\n", + "setup = \"fp.truncate(0)\\nstore = Store(fp)\" # Clear the file\n", + "\n", + "# Time dictionary store\n", + "with tempfile.NamedTemporaryFile(\"w+\") as fp:\n", + " dict_runs = timeit.repeat(\n", + " (\"store.append_many(annotations)\\nstore.commit()\"),\n", + " setup=setup,\n", + " globals={\"Store\": DictionaryStore, \"annotations\": annotations, \"fp\": fp},\n", + " number=1,\n", + " repeat=3,\n", + " )\n", + "\n", + "# Time SQLite store\n", + "with tempfile.NamedTemporaryFile(\"w+b\") as fp:\n", + " sqlite_runs = timeit.repeat(\n", + " (\"store.append_many(annotations)\\nstore.commit()\"),\n", + " setup=setup,\n", + " globals={\"Store\": SQLiteStore, \"annotations\": annotations, \"fp\": fp},\n", + " number=1,\n", + " repeat=3,\n", + " )\n", + "\n", + "# Plot the results\n", + "plot_results(\n", + " experiments=[dict_runs, sqlite_runs],\n", + " title=\"Time to Append & Serialise 10,000 Annotations To Disk\",\n", + " tick_label=[\"DictionaryStore\", \"SQLiteStore\"],\n", + ")\n", + "plt.hlines(0.5, -0.5, 1.5, linestyles=\"dashed\", color=\"k\")\n", + "plt.xlim([-0.5, 1.5])\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "LKr6FmctpT5x" + }, + "source": [ + "Here we can see that when we include the serialisation to disk in the\n", + "benchmark, the time to insert is much more similar.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "V7WV8wNmpT5x" + }, + "source": [ + "## 1.2) Box Query\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "eul4PYZPpT5x", + "outputId": "a0131a72-f527-48b1-8aac-8cbccfced2ed" + }, + "outputs": [ + { + "data": { + "image/png": "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obZXX32VnTt3kpOTw4MPPsidd97Jddddx7Bhwxg/fjw+n48+ffpQv379qi5VRKTGqxbBM2bMmAtu79ChAx06dLC2GBERKVe1PdQmIiK/TgoeERGxlIJHREQspeARERFLKXhERMRSCh4REbGUgkdERCyl4BEREUspeERExFIKHhERsZSCR0RELKXgERERSyl4RETEUgoeERGxlIJHREQspeARERFLKXjKsWnTJqZPn17VZYiIVCvV4g6klSU5OZnk5OSqLkNEpFrRjEdERCyl4BEREUspeERExFIKHhERsZSCR0RELKXgERERSyl4RETEUgoeERGxlIJHREQspeARERFLKXhERMRSCh4REbGUgkdERCyl4BEREUspeERExFIKHhERsZSCR0RELKXgERERSyl4RETEUgoeERGxlKO8xuzsbFatWsWWLVs4fPgwbreb0NBQGjZsSPv27enduzeRkZFW1SoiItVAmcHzwQcfsHr1aq6++mquu+466tati8vlIj8/n+PHj7Nz506eeuopunfvzj333GNlzSIichkrM3hiYmKYOnUqQUFBpdoaN25M9+7dKSoqYtmyZZVaoIiIVC9lnuO5/vrrLxg6PxYcHMzAgQMveVG/Fps2bWL69OlVXYaISLVS7jme87Zv305CQgIJCQmcPXuW999/H8MwuPvuu4mOjq7kEqtOcnIyycnJVV2GiEi1UqFVbbNmzcIwSrq+++67eL1ebDabZgMiIvKTVWjGk5mZSXx8PF6vl++++4433ngDh8PByJEjK7s+ERGpZioUPC6Xi6ysLI4ePUq9evVwOp14PB48Hk9l1yciItVMhYJn4MCBjBs3Do/Hw9ChQwHYvXs3devWrczaRESkGqpQ8AwaNIhOnTphGAa1a9cGIDY2lgcffLBSixMRkeqnQsEDkJSUVO5jERGRiihzVdu4ceNYv359medxPB4P69at45lnnqm04kREpPopc8bz0EMPMX/+fGbOnEnjxo1JSkrC6XRSUFBAWloaBw4coG3btowePdrKekVE5DJnM03TLK9DVlYW33//PUeOHCEvL4+wsDAaNmxIu3btiIqKsqrOKnfixAnLx/Q+cLPlY4r17DMWVXUJNUZ8fDwZGRlVXUaNUdYpmYue44mOjqZnz56XvCAREamZdD8eERGxlIJHREQspeARERFLKXhERMRSFfoCqWmaLF26lLVr15KTk8Nf//pXdu7cSVZWFl27dq3sGkVEpBqp0Ixn/vz5LF++nL59+/qXIsbFxfHpp59WanEiIlL9VCh4Vq5cyVNPPUW3bt2w2WwAJCQkkJ6eXqnFiYhI9VOh4PH5fDidzoBtBQUFpbaJiIhcTIWC5+qrr+bdd9+luLgYKDnnM3/+fDp27FipxYmISPVToeC5//77yczMZOjQobjdbu6//35Onz7NPffcU9n1iYhINVOhVW2hoaE8+eSTZGVlkZGRQXx8PNHR0ZVcmoiIVEc/6Xs8wcHBxMbG4vP5yMzMJDMzs7LqEhGRaqpCM57vv/+et99+m9OnT5dqmz9//iUvSkREqq8KBc9bb73FbbfdRrdu3QgODq7smkREpBqrUPAUFxfTp08fDENX2BERkV+mQkly44038umnn3KRe8aJiIhcVIVmPNdeey3jx4/nk08+ISIiIqBt2rRplVLYr8GmTZvYvHkzI0eOrOpSRESqjQoFz+TJk2nZsiVdunSpUed4kpOTSU5OruoyRESqlQoFT3p6Oi+99JLO8YiIyC9WoSRJTk5m+/btlV2LiIjUABVe1TZx4kRatWpFVFRUQNvDDz9cKYWJiEj1VKHgqV+/PvXr16/sWkREpAaoUPDccccdlV2HiIjUEGUGz86dO2ndujVAued32rZte+mrEhGRaqvM4Jk1axaTJk0C4M0337xgH5vNVq2/xyMiIpeezSzncgRr1qyhe/fuVtbzq3XixAnLx/Q+cLPlY4r17DMWVXUJNUZ8fDwZGRlVXUaNkZSUdMHt5S6nnjFjRqUUIyIiNVe5waNrs4mIyKVW7qo2n8930S+OanGBiIj8FOUGT3FxMW+99VaZMx8tLhARkZ+q3OBxOp0KFhERuaR01U8REbGUFheIiIilyg2ed99916o6RESkhtChNhERsZSCR0RELKXgERERSyl4RETEUgoeERGxlIJHREQspeARERFLKXhERMRSCh4REbGUgkdERCyl4BEREUspeERExFLl3o+nOjt16hQLFy7E7XYzduzYqi5HRKTGsCx48vLyeOuttzh69Cg2m41Ro0bRvHnzn7yfN954gy1bthAVFcWkSZMC2rZt28Y777yDz+cjJSWFQYMGlbmfxMRERo0aVWofIiJSuSwLnnfeeYf27dszduxYPB4PhYWFAe3nzp0jODgYl8vl33by5Elq164d0K93794MHDiQ119/PWC7z+dj1qxZ/PGPfyQuLo5x48aRnJyMz+fjgw8+COg7atQooqKiLvFPKCIiFWFJ8Ljdbnbt2sVDDz1UMqjDgcMROPTOnTv56quvGDduHMHBwXz99dds3LiRcePGBfRr3bo16enppcbYt28ftWvXJjExEYCuXbuyceNGbr31Vp5++umfVfemTZvYvHkzI0eO/FnPFxGR0iwJnvT0dCIjI3njjTc4fPgwTZo0YejQoTidTn+fLl26kJ6ezquvvkqXLl1Yvnw5f/rTnyo8RmZmJnFxcf7HcXFxpKamltk/JyeHDz/8kEOHDvHxxx9z6623luqTnJxMcnJyhWsQEZGLs2RVm9fr5eDBg/Tv35+JEycSEhLCJ598UqrfLbfcQnBwMDNnzuSpp54KCKaLudBtum02W5n9IyIiGDFiBK+99toFQ0dERCqHJcETFxdHXFwcV1xxBQCdO3fm4MGDpfrt2rWLo0ePcs011/D3v//9J49x5swZ/+MzZ84QExPzywoXEZFLzpLgiY6OJi4ujhMnTgDwww8/UK9evYA+Bw8eZPr06TzxxBOMHj2a3Nxc/va3v1V4jKZNm5KWlkZ6ejoej4d169bpMJmIyK+QzbzQMapKcOjQId566y08Hg8JCQmMHj2a8PBwf/vu3bsJDQ2lQYMGAHg8HlasWEHfvn0D9vPqq6+yc+dOcnJyiIqK4s477+S6664DYMuWLcydOxefz0efPn0YPHjwJav/fGhayfvAzZaPKdazz1hU1SXUGPHx8WRkZFR1GTVGUlLSBbdbFjyXOwWPVBYFj3UUPNYqK3h0yRwREbGUgkdERCyl4BEREUspeERExFIKHhERsZSCR0RELKXgERERSyl4RETEUgoeERGxlIJHREQspeARERFLKXhERMRSCh4REbGUgkdERCyl4BEREUspeERExFIKHhERsZSCR0RELKXgERERSyl4RETEUgoeERGxlIJHREQspeARERFLKXhERMRSCh4REbGUgkdERCyl4BEREUspeERExFIKHhERsZSCR0RELKXgERERSzmquoDqbv78+aW2tWjRgvbt21NcXMzChQtLtbdp04a2bduS7/XxWVpmqfZ2UWG0iHCRXezly1NnS7V3iA6nabiTzCIPS9OzSrV3io2gYWgI6YXFrDx9rlR7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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Run time: ~20s\n", + "\n", + "# One time Setup\n", + "dict_store = DictionaryStore()\n", + "sql_store = SQLiteStore()\n", + "dict_store.append_many(annotations)\n", + "sql_store.append_many(annotations)\n", + "\n", + "rng = np.random.default_rng(123)\n", + "boxes = [\n", + " Polygon.from_bounds(x, y, 128, 128) for x, y in rng.integers(0, 1000, size=(100, 2))\n", + "]\n", + "stmt = \"for box in boxes:\\n _ = store.query(box)\"\n", + "\n", + "# Time dictionary store\n", + "dict_runs = timeit.repeat(\n", + " stmt,\n", + " globals={\"store\": dict_store, \"boxes\": boxes},\n", + " number=1,\n", + " repeat=10,\n", + ")\n", + "\n", + "# Time SQLite store\n", + "sqlite_runs = timeit.repeat(\n", + " stmt,\n", + " globals={\"store\": sql_store, \"boxes\": boxes},\n", + " number=1,\n", + " repeat=10,\n", + ")\n", + "\n", + "# Plot the results\n", + "plot_results(\n", + " experiments=[dict_runs, sqlite_runs],\n", + " title=\"100 Box Queries\",\n", + " tick_label=[\"DictionaryStore\", \"SQLiteStore\"],\n", + ")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "z9ntCgKapT5x" + }, + "source": [ + "Here we can see that the `SQLiteStore` is a bit faster. Addtionally,\n", + "difference in performance is more pronounced when there are more\n", + "annotations (as we will see later in this notebook) in the store or when\n", + "just returning keys:\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "vfGH6e4upT5x", + "outputId": "7cf8bf30-a4c9-4de5-9a5f-f9fd6cffc141" + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Run time: ~15s\n", + "\n", + "# One time Setup\n", + "dict_store = DictionaryStore()\n", + "sql_store = SQLiteStore()\n", + "dict_store.append_many(annotations)\n", + "sql_store.append_many(annotations)\n", + "\n", + "rng = np.random.default_rng(123)\n", + "boxes = [\n", + " Polygon.from_bounds(x, y, 128, 128) for x, y in rng.integers(0, 1000, size=(100, 2))\n", + "]\n", + "stmt = \"for box in boxes:\\n _ = store.iquery(box)\" # Just return the keys (uuids)\n", + "\n", + "# Time dictionary store\n", + "dict_runs = timeit.repeat(\n", + " stmt,\n", + " globals={\"store\": dict_store, \"boxes\": boxes},\n", + " number=1,\n", + " repeat=10,\n", + ")\n", + "\n", + "# Time SQLite store\n", + "sqlite_runs = timeit.repeat(\n", + " stmt,\n", + " globals={\"store\": sql_store, \"boxes\": boxes},\n", + " number=1,\n", + " repeat=10,\n", + ")\n", + "\n", + "# Plot the results\n", + "plot_results(\n", + " experiments=[dict_runs, sqlite_runs],\n", + " title=\"100 Box Queries (Key Lookup Only)\",\n", + " tick_label=[\"DictionaryStore\", \"SQLiteStore\"],\n", + ")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "xVQlsK1MpT5y" + }, + "source": [ + "## 1.3) Polygon Query\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "fnkdnKWRpT5y", + "outputId": "03ccc35c-df96-4d68-9d53-72ac835a9088" + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Run time: ~15s\n", + "\n", + "# One time Setup\n", + "dict_store = DictionaryStore()\n", + "sql_store = SQLiteStore()\n", + "dict_store.append_many(annotations)\n", + "sql_store.append_many(annotations)\n", + "\n", + "rng = np.random.default_rng(123)\n", + "query_polygons = [\n", + " Polygon(\n", + " [\n", + " (x, y),\n", + " (x + 128, y),\n", + " (x + 128, y + 128),\n", + " (x, y),\n", + " ],\n", + " )\n", + " for x, y in rng.integers(0, 1000, size=(100, 2))\n", + "]\n", + "stmt = \"for polygon in query_polygons:\\n _ = store.query(polygon)\"\n", + "\n", + "# Time dictionary store\n", + "dict_runs = timeit.repeat(\n", + " stmt,\n", + " globals={\"store\": dict_store, \"query_polygons\": query_polygons},\n", + " number=1,\n", + " repeat=10,\n", + ")\n", + "\n", + "# Time SQLite store\n", + "sqlite_runs = timeit.repeat(\n", + " stmt,\n", + " globals={\"store\": sql_store, \"query_polygons\": query_polygons},\n", + " number=1,\n", + " repeat=10,\n", + ")\n", + "\n", + "# Plot the results\n", + "plot_results(\n", + " experiments=[dict_runs, sqlite_runs],\n", + " title=\"100 Polygon Queries\",\n", + " tick_label=[\"DictionaryStore\", \"SQLiteStore\"],\n", + ")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "1k1xOgB5pT5y" + }, + "source": [ + "Here we can see that performing queries within a polygon region is about\n", + "10x faster with the `SQLiteStore` than with the `DictionaryStore`.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "iYFK95w1pT5y" + }, + "source": [ + "## 1.4) Predicate Query\n", + "\n", + "Here we query the whole annotation region but with a predicate to\n", + "select only annotations with the class label of 0. We also,\n", + "demonstrate how creating a database index can dramatically improve\n", + "the performance of queries.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "zNX4UG4BpT5y", + "outputId": "97444739-4aa5-42c7-bebc-84a022282ac7" + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYgAAAEmCAYAAAB4VQe4AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAsTAAALEwEAmpwYAAA6n0lEQVR4nO3dd3xUVcL/8c+UTGbSGwFC6B0UUFh6RxR7wy7iurZFBfcRVHT5waK4KiurKCpSBDsWXBGfZWUpIkWkGHov0gKkhzBpM3N+f2SZx5AbiC4hQL7v18uXzD3nnjlzL8x3zi3n2owxBhERkZPYq7oDIiJyblJAiIiIJQWEiIhYUkCIiIglBYSIiFhSQIiIiCUFhIiIWFJAyAVnzJgxNGnSpKq7UUpF+9SgQQOef/75s9Cjs693797cf//95b6Wc48C4gKxZMkSrr/+eurXr4/NZiv3S2blypV07doVt9tN7dq1GTlyJH6/v1Sd7du3c8UVVxAWFkZCQgIPP/wwx48fP20fjhw5wmOPPUaDBg1wuVzUqFGDgQMHkpKSciY+YoUNHz6cH3744ay+5+mc3Kfnn3+eBg0aVF2HgHvvvRebzYbNZsPpdFK/fn0efvhhMjIyzsr7z549mwkTJpyx9g4cOIDNZmPx4sVnrM3qTgFxgcjLy6NVq1a8/PLL1KpVy7LO/v376d+/P82bN2fNmjW89dZbTJ48mWeffbZUO/369cPpdLJ8+XI+/fRT5s2bxx/+8IdTvv/+/fvp0KEDy5cv56233mLnzp188803hISE0LlzZ+bNm3dGP6+VQCCA3+8nIiKChISESn+/X+Nc7BNAjx49SE1NZe/evUycOJEvvviCe+65x7KuMYbi4uIz9t5xcXFERUWdsfakEhi54NSvX98899xzZZaPHDnS1KlTx/j9/uCyN954w4SFhZm8vDxjjDGTJ082brfbZGdnB+vMnTvXAGb37t3lvue1115ratasaXJycsqUXXnllaZmzZrG6/UaY4wZPXq0ady4cak633//vQHMnj17gstWr15t+vfvb8LDw01CQoK58cYbzd69e4PlJ9r55JNPTPPmzY3D4TAbNmywbP/bb781Xbt2NW632yQlJZl7773XpKenB8s3btxoLr/8chMdHW3CwsJMixYtzHvvvVfu501OTjZTpkwJvr7nnnsMYHbs2BFcVq9ePTNp0qQyn/ndd981QKn/Ro8ebYwp2XejRo0yQ4cONbGxsSYxMdE88cQTxufzldsXY4x55plnTIsWLYzH4zHJycnmoYceKrUPrQwePNj069ev1LLnn3/e2O124/V6zbvvvmscDodZuHChadeunQkJCTFff/21KS4uNqNHjzYNGjQwoaGhplWrVubtt98u1c7evXvNFVdcYdxut6lbt66ZOHGi6dWrl/nDH/4QrHPya2NK/j62bNnSuFwuU6NGDXPzzTcHyz788EPTsWNHExUVZeLj481VV11ltm3bFiw/eZvWr18/WHa6/S/WNIKoRpYtW8bll1+O3f5/u33AgAF4vV5++umnYJ0uXboQHR0drHNinWXLllm2m5WVxTfffMOjjz5q+Ytw5MiRHDlyhPnz51e4r5s3b6ZXr1506dKF1atXs3DhQhwOB/3796egoCBY79ChQ7z55pvMmDGDzZs3U79+/TJtLVy4kOuvv57bb7+d9evX849//IO9e/dy4403Yv4zFdkdd9xBfHw8y5cvZ8OGDUyYMIHY2Nhy+9enTx8WLFgQfL1o0SJq1KgRXLZr1y727dtH3759y6x722238dRTT5GcnExqaiqpqakMHz48WP76669Tu3ZtVq5cycSJE3n11Vd57733Trm9PB4P77zzDps3b2bGjBksXryYoUOHnnKd8toJBAL4fD6gZFT25JNP8sorr7B161Y6derE/fffz+zZs5k8eTJbtmzh//2//8dTTz3FtGnTgJKRxo033khGRgaLFy9mzpw5zJkzh7Vr157yvUePHs1TTz3FkCFD2LBhA/PmzaNdu3bB8sLCQkaNGsXatWuZP38+DoeDq6++mqKiIoBg+1988QWpqamsWrUKqNj+l3JUcUBJJShvBNG0aVMzcuTIUsvy8vIMYD799FNjjDH9+/c3d9xxR5l1ExISzMsvv2z5fitXrjSAmT17tmV5RkaGAYLrV2QEMXjwYHPbbbeVqlNQUGA8Ho/58ssvg+3YbDbz888/l6p3cvu9evUyTz31VKk6P//8swHMTz/9ZIwxJioqyrz77ruW/bfy7rvvmsTERGOMMdu3bzcej8eMHTvW3HLLLcYYY9555x1Tu3btcvv03HPPlfqFe0L9+vXNtddeW2rZFVdcYW6//fYK980YY2bPnm1cLlep0eLJTh5BbNq0yTRq1Mh06tQp+BkBs2TJkmCd3bt3G5vNZrZs2VKqrb/85S+mbdu2xhhj5s+fb4BSv+6PHj1q3G53uSOIvLw843a7zfjx4yv8GU/8vVq6dKkxxpj9+/cbwCxatKhUvYrsf7HmrKpgknODzWYr9f+K1D2ZOc2vsBPrhYSEVLhfq1atYufOnURERJRaXlBQwI4dO4Kva9asSb169U7b1g8//MAbb7xRpmzHjh20a9eO4cOHc//99zNjxgx69+7Nddddx6WXXlpum/369ePo0aNs3LiRZcuW0b17dwYMGMDEiRMxxrBw4ULL0UNF/PJXM0CdOnXYs2fPKdeZPXs2r776Kjt37iQ3N5dAIEBRURGHDx8mKSmp3PUWL15MREQEfr+fwsJC+vXrx+TJk0vV+d3vfhf88+rVqzHG0KFDh1J1fD4fDocDKBn9JSQk0KxZs2B5jRo1aN68ebn92LRpEwUFBVx++eXl1klJSeEvf/kLKSkppKenB//e/fzzz3Tr1q3c9Sqy/8WaAqIaqV27NocPHy617MTrEye2a9euzf79+0vVKS4uJjMzs9yT382aNcNut7Nx40ZuvPHGMuUbN24M1gOw2+1lQuXkk5+BQIBBgwbx9NNPl2kvPj4++Ofw8HDLPp3c1lNPPcWgQYPKlJ34TKNGjeKuu+5i3rx5LFy4kBdeeIEnn3yy3KvB6tatS+PGjVmwYAHLly+nb9++tG/fHp/Px/r161m0aBEvvPDCaftmxeVylXpts9kIBALl1l+5ciW33HILI0eOZPz48cTGxvLDDz8wePDg4OGX8nTq1ImZM2fidDqpXbs2oaGhpcodDgdutzv4+kQ/li9fTlhYWJl+QskPhor84LBS3nper5fLL7+c7t27M3369OB+a9269Wk/Y0X2v1hTQFQj3bp14/333ycQCATPQ8ybN4+wsDAuueSSYJ1hw4aRm5sbPJ8wf/58AoFAub/SYmNjufrqq5k0aRLDhg0rcx7ihRdeICkpif79+wOQmJjI0aNH8fv9wV+dJx+f7tChA+vXr6dx48a/+cvml21t2rTptPchNGrUiCFDhjBkyBBefPFFxo8ff8p7Evr27cuCBQtYuXIlw4cPx26307NnT15//XWOHDlyyhGEy+Uqc3nxb7V06VISEhJK9fXzzz+v0Loej+dX3TPSvn17APbt28c111xjWad169akpaWxY8cOmjZtCkB6ejrbt28vM/I4oVWrVrjdbv71r39x8cUXlynfsmULaWlpjBs3jpYtWwIlIfXLHxongvXk7VrR/S9l6ST1BSIvL4+UlBRSUlKChxZSUlLYuXNnsM4f//hHcnJyeOCBB9i0aRNz5sxh1KhRPPbYY8Ff4nfeeScJCQnceeedrFu3jkWLFvHII49w22230bBhw3Lff9KkSTidTvr27cu8efPYv38/q1at4s4772TRokV89NFHwUNMffr0wev1MmrUKHbt2sVnn33GpEmTSrX3zDPPsGXLFu6++25+/PFH9uzZw6JFixg2bBi7d+/+Vdtm7NixfPXVV/zpT38iJSWFXbt2BS/dzc/PJy8vj0ceeYSFCxeyZ88efvrpJ+bNm0erVq1O2W7fvn355z//SWFhYfBwVN++fZk5cyYNGzY85X0ODRs25PDhw6xYsYL09HS8Xu+v+ky/1Lx5c9LS0pg2bRq7d+/mvffe48033/zN7Z1KkyZNuO+++3jggQd4//332blzJ+vWrWP69Om89NJLQMnht7Zt2wb3XUpKCnfddRdOZ/m/RyMiInjiiScYM2YMkyZNYvv27axbt46//vWvANSvX5/Q0FBef/11du3axYIFCxg2bFipHw8JCQlERETw7bffcvjwYbKysoDT7385hSo8/yFn0KJFi8pc5geYXr16laq3YsUK06VLFxMaGmpq1qxpnn766TKXUG7dutX079/feDweExcXZx588MHgZbCncvjwYfPII4+YevXqGYfDYQCTlJRktm/fXqbutGnTTMOGDY3b7TYDBgwwH3/8cZnLXNevX2+uu+46ExMTY9xut2ncuLF54IEHTEZGhjHG+mR3ecuXLFli+vXrZyIiIoKXsQ4bNswUFxeb/Px8c8cddwQv26xRo4a59dZbzb59+075eY8cOWJsNpu57rrrSvUZKHP55sl9KioqMnfccYeJjY0tc5nryRcY/OEPfyizH0/25z//2SQmJpqwsDBz5ZVXmo8++qjM9jyZ1WWuv3TiMteT+Xw+89JLL5nmzZubkJAQEx8fb3r27Bm80MEYY/bs2WP69+9vQkNDTZ06dcyrr7562stcA4GAefXVV02zZs1MSEiISUxMNAMHDgyWf/bZZ6ZJkyYmNDTUtGvXzixevNg4HI5SFxfMnDnTNGjQwDidzlIXAZxq/0v5bMboOi+pHN988w0DBw5kxIgRjB07tqq7IyK/kg4xSaW5+uqr+fbbb7Hb7ae9CkdEzj0aQYiIiCWNIERExJICQkRELJ3390GsXr2aNWvW8NBDD1V1V855RXt3UrhuVZnlYf2vwxFW9oazws3rKPjpB0xRESH1G+Pp3g+7q+RGqoINayhctxrjKyakQRM83fphDwkhUFhA0Y7NFO/dCX4/YT3644j7v1lMC7esp+CnH7A5QwjrcyXOGrpRSeRcdUGdgzh06FBVd6HKJSQkkJ6eXmZ5WFgYIau+J2vy+OAyk19y7X3SR/8mLb8geJesx+PBvWsL6aOH4qxTD2fNJArW/kB4/2uJe3w03mULyXjhSZx1G+KIS6Bw3SoirrmV6IeGU7R+NWl/fgRbqBtTkE/iK++SG18Lj8cDyxeQ9dpzuFq3I5CThT/tCDVffZ/c8ChsNhtutxuHw0EgEKC4uBiv13vKO4jPF+XtE6la2i8lTjUViw4xVRNFRUWE9rmSpE8XU+ez76j52vtgs+H+XXf8nrBSX8ROp5OiHZsBiLn/f0gY+zo2TxiF2zaVtLW95P+xQ56ixnNvgN1B0faNGGMIadaaOp8uInxA6Sk3QkNDyfvqY2xuD4kvvE38k+MwhQXkfvEe8fHxRGQdJf/tl8kZ+yeOvzYW57ofiYyMPEtbR0SsnPeHmKRifD4fR48eBUqmxsj/8kMwhsibB5GXl1eqbmFhITF9r8K75FuyZ0zEOW82YCPqtvsIBAKEX34d+csXkf3OBBzxNbB5wogceC8FBQWlpuj4JZvNhgkEMH4/prCAgLfkCXXFe7YDkPHiSLA7iLjyJvyZ6RTv30NI+66Vu1FE5JQ0gqhmnE4nrsJ8vAu+wdWsFY4WbUo9XwFKJmgLHMvF5B/HZndgs9vB7yOQlY7dbieQm0OgwAt2e8l/viIC2ZnBSfisjloWFBQQeeNd4Csm9YEbSR/zOACB4/8JJ7uDQN4xfKn7cdapR+R1t//XczCJyH9HI4hqJjw8nLwvP8AUFRJ50z3BOYDi4+Nx2cBgw+5ykTF1Av60I9R44W1CkupyeOjdZM+YRMT1d3Lsi/cJZKZT67UPcMQlkPrQzWTPfIOkK2/C7XYTEhLCL+dm9Xg8FBYWEn/59biataZ4xxbssfGk/+VxXE1KJl5L+PPfyPvmM4p2byPvn7PJ+3oWCa++XwVbSERO0AiiGrHb7XgcDvK++RRHrTqEdu6J1+vFbrfjLPBy4MZuZI4veT61I7ZkSu38Jd9SsPYHfIcP4IiNw2a3Y4+NA+D44nnkr16GP+0Ijth47HY70QQo+t/PKd65FQDvd/+CZf8mPj6ego1r8e3fgz02nrw5n0AgQPjl1wNQsGY5nm59iX1wOCH1G+M7ckgjCJEqphFENeLxeChYvRR7RBSRAwfjLSj8v7n77XacSXVxxCXg8/mIvO33+NKPcOyrjzCFBTjrNSbm/scpLCwk6s4HCWRlcmzWdExxESENmxLzwBPk5+fjyMog739Lppp2JtWlYPUyindtI+KKGwhkZ5L11ssEjuUSUr8x8U+Ow3Fx+5KH1WxeR+7HUwnkH8eRUIvYh0dQWFhYxVtMpHrTZa4XmFNduudyuYiOjsZut+Pz+cjKygpevRQTE4PL5cIYw7FjxwgEAkRGRhISEoLNZsPn8+H1ejl+/DihoaFEREQEp+/2+/14vV68Xi+xsbGW0zoXFBSUnP9wubDZbMF1ftme0+kMPhwnPz+fY8eOXRDPDNbllOcm7ZcSp7rM9bwfQehGuYorKioiLS3Nsiw7O7vMsoyMDMu6hYWF5f66z8zM/NX9KigoKHOiXESq3nkfEB06dCj3KVVnmv+B687K+1TULSu2sTIr77T1OsVG8FmX8p8HfLY5psyp6i6ISAWc9wFRnZ1LX/oicuHRVUwiImJJASEiIpYUECIiYkkBISIilhQQIiJiSQEhIiKWzvuAWL16NZMnT67qboiIXHDO+/sgzuaNciIi1cl5P4IQEZHKoYAQERFLCggREbGkgBAREUsKCBERsaSAEBERSwoIERGxpIAQERFLCggREbF03geEptoQEakcmmpDREQsnfcjCBERqRwKCBERsaSAEBERSwoIERGxpIAQERFLCggREbGkgBAREUsKCBERsaSAEBERSwoIERGxpIAQERFL531AaLI+EZHKocn6RETE0nk/ghARkcqhgBAREUsKCBERsaSAEBERSwoIERGxpIAQERFLCggREbGkgBAREUsKCBERsaSAEBERSwoIERGxpIAQERFLCggREbGkgBAREUvnfUDoeRAiIpVDz4MQERFL5/0IQkREKocCQkRELCkgRETEkgJCREQsKSBERMSSAkJERCwpIERExJICQkRELCkgRETEkgJCREQsKSBERMSS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sFkoxhjyjmfi8/mw2Wx4Ilw0iCm5D8Lv9wfvjagMFQqIW265pVLeXEQuTF/Pyq7qLpTyyhuD2bF79WnrNW3UgScenXkWenR6194WU+bGPGMMhYWFlXLPg5VyA2Lz5s20atUKgI0bN5bbwEUXXXTmeyUicgadK1/655tyA2LatGm88sorALz11luWdWw2m+6DEBG5QJUbEK+88gpLly6le/fuTJo06Wz2SUREzgGnvLFhypQpZ6sfIiJyjjllQGjuJRGR6uuUVzEFAoFTnqAGnaQWEblQnTIgiouLefvtt8sdSegktYjIheuUAeF2uxUAIiLVlGbfExERSzpJLSIilk4ZEO+9997Z6oeIiJxjdIhJREQsKSBERMSSAkJERCwpIERExJICQkRELCkgRETEkgJCREQsKSBERMSSAkJERCwpIERExJICQkRELCkgRETEkgJCREQsKSBERMSSAkJERCwpIERExJICQkRELCkgRETEkgJCREQsOau6A1YKCgqYOnUqTqeT1q1b06NHj6rukohItXPWAuLNN99k7dq1REdH88orrwSXp6Sk8O677xIIBOjXrx833HADP/74I507d6ZDhw78/e9/V0CIiFSBs3aIqXfv3jzzzDOllgUCAaZNm8YzzzzD3//+d5YtW8aBAwfIyMggISGhpIN2HQUTEakKZ+3bt1WrVkRERJRatnPnTmrVqkXNmjVxOp107dqVVatWER8fT0ZGBgDGmLPVRRER+YUqPQeRmZlJfHx88HV8fDw7duzgyiuvZPr06axdu5b27duXu/6///1v/v3vfwPw4osvBkcdleVIpbZefVT2fpJf78zvk+wz3F71cy78O6nSgLAaHdhsNtxuN0OGDDnt+pdddhmXXXZZ8HV6evoZ7Z9UDu2nc4/2ybnnbO2TpKSkcsuq9AD/Lw8lAWRkZBAbG1uFPRIRkROqNCAaN25MamoqR48exefzsXz5cjp06FCVXRIRkf84a4eYXn31VTZv3syxY8d4+OGHufXWW+nbty/33Xcf48aNIxAI0KdPH+rWrXu2uiQiIqdw1gLi8ccft1x+6aWXcumll56tboiISAXpJgMREbF03gfE6tWrmTx5clV3Q0TkgnNOzsX0a3To0EEntkVEKsF5P4IQEZHKoYAQERFLCggREbGkgBAREUsKCBERsaSAEBERSwoIERGxdN4H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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Run time: ~2m\n", + "\n", + "# Setup\n", + "labelled_annotations = copy.deepcopy(annotations)\n", + "for n, annotation in enumerate(labelled_annotations):\n", + " annotation.properties[\"class\"] = n % 10\n", + " annotation.properties[\"vector\"] = rng.integers(1, 4, 10).tolist()\n", + "\n", + "predicate = \"(props['class'] == ?) & (3 in props['vector'])\"\n", + "classes = rng.integers(0, 10, size=100)\n", + "stmt = \"for n in classes:\\n store.query(where=predicate.replace('?', str(n)))\"\n", + "\n", + "dict_store = DictionaryStore()\n", + "sql_store = SQLiteStore()\n", + "\n", + "dict_store.append_many(labelled_annotations)\n", + "sql_store.append_many(labelled_annotations)\n", + "\n", + "\n", + "# Time dictionary store\n", + "dict_runs = timeit.repeat(\n", + " stmt,\n", + " globals={\"store\": dict_store, \"predicate\": predicate, \"classes\": classes},\n", + " number=1,\n", + " repeat=10,\n", + ")\n", + "dict_result = dict_store.query(where=predicate.replace(\"?\", \"0\"))\n", + "\n", + "# Time SQLite store\n", + "sqlite_runs = timeit.repeat(\n", + " stmt,\n", + " globals={\"store\": sql_store, \"predicate\": predicate, \"classes\": classes},\n", + " number=1,\n", + " repeat=10,\n", + ")\n", + "sql_result = sql_store.query(where=predicate.replace(\"?\", \"0\"))\n", + "\n", + "\n", + "# Add an index\n", + "# Note: Indexes may not always speed up the query (sometimes they can\n", + "# actually slow it down), test to make sure.\n", + "sql_store.create_index(\"class_lookup\", \"props['class']\")\n", + "sql_store.create_index(\"has_3\", \"3 in props['vector']\")\n", + "\n", + "# Time SQLite store again\n", + "sqlite_index_runs = timeit.repeat(\n", + " stmt,\n", + " globals={\"store\": sql_store, \"predicate\": predicate, \"classes\": classes},\n", + " number=1,\n", + " repeat=10,\n", + ")\n", + "sql_index_result = sql_store.query(where=predicate.replace(\"?\", \"0\"))\n", + "\n", + "# # Validate the results against each other\n", + "# for a, b, c in zip(dict_result, sql_result, sql_index_result):\n", + "# assert a.geometry == b.geometry == c.geometry # noqa: ERA001\n", + "\n", + "# Plot the results\n", + "plot_results(\n", + " experiments=[dict_runs, sqlite_runs, sqlite_index_runs],\n", + " title=\"100 Queries with a Predicate\",\n", + " tick_label=[\"DictionaryStore\", \"SQLiteStore\", \"SQLiteStore\\n(with index)\"],\n", + ")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "gp8mq1TNpT5y" + }, + "source": [ + "### Polygon & Predicate Query\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Eu0hGvhdpT5y", + "outputId": "0d89174e-01e0-4e71-a9c3-e063ed30ca38" + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Run time: ~10s\n", + "\n", + "# Setup\n", + "labelled_annotations = copy.deepcopy(annotations)\n", + "for n, annotation in enumerate(labelled_annotations):\n", + " annotation.properties[\"class\"] = n % 10\n", + "\n", + "predicate = \"props['class'] == \"\n", + "classes = rng.integers(0, 10, size=50)\n", + "query_polygons = [\n", + " Polygon(\n", + " [\n", + " (x, y),\n", + " (x + 128, y),\n", + " (x + 128, y + 128),\n", + " (x, y),\n", + " ],\n", + " )\n", + " for x, y in rng.integers(0, 1000, size=(100, 2))\n", + "]\n", + "stmt = (\n", + " \"for n, poly in zip(classes, query_polygons):\\n\"\n", + " \" store.query(poly, where=predicate + str(n))\"\n", + ")\n", + "\n", + "dict_store = DictionaryStore()\n", + "sql_store = SQLiteStore()\n", + "\n", + "dict_store.append_many(labelled_annotations)\n", + "sql_store.append_many(labelled_annotations)\n", + "\n", + "\n", + "# Time dictionary store\n", + "dict_runs = timeit.repeat(\n", + " stmt,\n", + " globals={\n", + " \"store\": dict_store,\n", + " \"predicate\": predicate,\n", + " \"classes\": classes,\n", + " \"query_polygons\": query_polygons,\n", + " },\n", + " number=1,\n", + " repeat=10,\n", + ")\n", + "dict_result = dict_store.query(query_polygons[0], where=predicate + \"0\")\n", + "\n", + "# Time SQLite store\n", + "sqlite_runs = timeit.repeat(\n", + " stmt,\n", + " globals={\n", + " \"store\": sql_store,\n", + " \"predicate\": predicate,\n", + " \"classes\": classes,\n", + " \"query_polygons\": query_polygons,\n", + " },\n", + " number=1,\n", + " repeat=10,\n", + ")\n", + "sql_result = sql_store.query(query_polygons[0], where=predicate + \"0\")\n", + "\n", + "\n", + "# Check that the set difference of bounding boxes is empty i.e. all sets\n", + "# of results contain polygons which produce the same set of bounding\n", + "# boxes. This avoids being tripped up by slight varations in order or\n", + "# coordinate order between the results.\n", + "dict_set = {x.geometry.bounds for x in dict_result}\n", + "sql_set = {x.geometry.bounds for x in sql_result}\n", + "assert len(dict_set.difference(sql_set)) == 0 # noqa: S101\n", + "\n", + "# Plot the results\n", + "plot_results(\n", + " experiments=[dict_runs, sqlite_runs],\n", + " title=\"100 Queries with a Polygon and Predicate\",\n", + " tick_label=[\"DictionaryStore\", \"SQLiteStore\"],\n", + ")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "kJ8x5tJmpT5y" + }, + "source": [ + "### Complex Predicate Query\n", + "\n", + "Here we slightly increase the complexity of the predicate to show how\n", + "the complexity of a predicate can dramatically affect the performance\n", + "when handling many annotations.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "VHb4PqbHpT5y", + "outputId": "343b44c7-741d-4e11-9dd2-85f357ba6f32" + }, + "outputs": [ + { + "data": { + "image/png": "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Gzeu8fHV9vm7nTB47nANGjTApUuXbJZV3q4ckNCgQQNkZmba9CkrK0NeXl61gxaaNWsGSZJw4sQJDBw40K698iR1s2bNAACSJNmFVVlZmc1ti8WCkSNHYtq0aXbr8/Lysv5c3S/mteuaOnUqRo4caddW+ZimT5+OESNGYMuWLdi+fTvef/99vPbaa9WODmzYsCFCQ0Oxbds2xMfHo1evXujYsSPMZjOOHz+OHTt24P33379ubVVRq9U2txUKBSwWS7X9Dx48iCFDhiAuLg4ff/wxPDw8cODAATz99NPVngBv2rQpJElCUlJSla/Zta4NKCGE3bKq/rC4dtn1HkvluqtjsVjg5uaGw4cP27Vd+7zt3r0bSqUS586dg9FohFarrXG7ALB8+XJ07NgRbm5u8PHxsWu/dn+r7X5amz/srlXTfWbNmoUPPvgAs2fPRocOHeDi4oI5c+bg559/rnGdN/L83S443OIO161bN/z+++82v/hbtmyBk5MT2rdvb+2zf/9+6PV6a5/K+3Tr1q3K9Xp4eOChhx7CggULbO5X6f3330dAQAD69OkDAPD19UV2drbNMO7ExESb+4SHh+P48eMIDQ1FWFiYzT8PD48betzh4eFISkqyW09YWJjNX6pNmjTBhAkTsGHDBrzzzjtYuHBhjevt1asXtm3bhp07dyI2NhaSJCE6OhqfffYZLl++XOMRj1qtthvGfrP27t0Lb29vvPfee+jSpQuaNWt23e+DeHp6on///pg/fz4KCgrs2svKylBcXIywsDA4Ojpi165dNu27d+9G69atb0n9144a279/P1q2bFll3/DwcOTn58NoNNq9ln8/8l26dCl++OEH7Nq1CwaDAf/+979rVUtgYCDCwsKqDJ3q6rneftqqVSvs27fP5n7X3r5Wx44dsW/fvmq/xrB7927069cPzz77LNq3b4+wsDCbTwKAqvex2j5/t5X6/JyPalZYWCiOHj0qjh49Kho0aCAmTpwojh49ajPK6ty5c8LFxUWMHj1anDhxQmzatEl4enranP8oLCwUQUFB4qGHHhLHjh0T27dvF8HBwXafY1/r3LlzIjAwUHTs2FH8+uuv4ty5c+LQoUNi2LBhwtHRUezcudPa99SpU0KSJBEXFyfS09PF+vXrRUhIiM05nuTkZOHs7CyGDx8uDh48KM6cOSO2b98uJk2aJE6f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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Run time: ~1m\n", + "\n", + "# Setup\n", + "box = Polygon.from_bounds(0, 0, 1024, 1024)\n", + "labelled_annotations = copy.deepcopy(annotations)\n", + "for n, annotation in enumerate(labelled_annotations):\n", + " annotation.properties[\"class\"] = n % 4\n", + " annotation.properties[\"n\"] = n\n", + "\n", + "predicate = \"(props['n'] > 1000) & (props['n'] % 4 == 0) & (props['class'] == \"\n", + "targets = rng.integers(0, 4, size=100)\n", + "stmt = \"for n in targets:\\n store.query(box, where=predicate + str(n) + ')')\"\n", + "\n", + "dict_store = DictionaryStore()\n", + "sql_store = SQLiteStore()\n", + "\n", + "dict_store.append_many(labelled_annotations)\n", + "sql_store.append_many(labelled_annotations)\n", + "\n", + "\n", + "# Time dictionary store\n", + "dict_runs = timeit.repeat(\n", + " stmt,\n", + " globals={\n", + " \"store\": dict_store,\n", + " \"predicate\": predicate,\n", + " \"targets\": targets,\n", + " \"box\": box,\n", + " },\n", + " number=1,\n", + " repeat=10,\n", + ")\n", + "dict_result = dict_store.query(box, where=predicate + \"0)\")\n", + "\n", + "# Time SQLite store\n", + "sqlite_runs = timeit.repeat(\n", + " stmt,\n", + " globals={\n", + " \"store\": sql_store,\n", + " \"predicate\": predicate,\n", + " \"targets\": targets,\n", + " \"box\": box,\n", + " },\n", + " number=1,\n", + " repeat=10,\n", + ")\n", + "sql_result = sql_store.query(box, where=predicate + \"0)\")\n", + "\n", + "\n", + "# Check that the set difference of bounding boxes is empty i.e. all sets\n", + "# of results contain polygons which produce the same set of bounding\n", + "# boxes. This avoids being tripped up by slight varations in order or\n", + "# coordinate order between the results.\n", + "dict_set = {x.geometry.bounds for x in dict_result.values()}\n", + "sql_set = {x.geometry.bounds for x in sql_result.values()}\n", + "\n", + "assert len(dict_set.difference(sql_set)) == 0 # noqa: S101\n", + "\n", + "# Plot the results\n", + "plot_results(\n", + " experiments=[dict_runs, sqlite_runs],\n", + " title=\"100 Queries with a Complex Predicate\",\n", + " tick_label=[\"DictionaryStore\", \"SQLiteStore\"],\n", + ")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "CAT0KmS6pT5y" + }, + "source": [ + "# Part 2: Large Scale Dataset Benchmarking\n", + "\n", + "Here we generate some sets of anntations with five million items each\n", + "(in a 2237 x 2237 grid). One is a set of points, the other a set of\n", + "generated cell boundaries.\n", + "\n", + "The code to generate and write out the annotations to various formats is\n", + "included in the following cells. However, some of these take a very long\n", + "time to run. A pre-generated dataset is downloaded and then read from\n", + "disk instead to save time. However, you may uncomment the generation\n", + "code to replicate the original.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "nwH5zYFupT5y" + }, + "source": [ + "## 2.1) Points Dataset\n", + "\n", + "Here we generate a simple points data in a grid. The grid is 2237 x 2237\n", + "and contains over 5 million points. We also write this to disk in\n", + "various formats. Some formats take a long time and are commented out. A\n", + "summary of times for a consumer laptop are shown in a table at the end.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "2FjCL2jgpT5y" + }, + "outputs": [], + "source": [ + "# Generate some points with a little noise\n", + "# Run time: ~5s\n", + "points = np.array(\n", + " [\n", + " [x, y]\n", + " for x in np.linspace(0, 75_000, 2237)\n", + " for y in np.linspace(0, 75_000, 2237)\n", + " ],\n", + ")\n", + "# Add some noise between -1 and 1\n", + "rng_42 = np.random.default_rng(42)\n", + "points += rng_42.uniform(-1, 1, size=(2237**2, 2))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "DRWABSBVpT5z" + }, + "source": [ + "### 2.1.1) Writing To Disk\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "x76WbSFdpT52" + }, + "outputs": [], + "source": [ + "# Save as a simple Numpy array (.npy)\n", + "# Run time: <1s\n", + "np.save(\"points.npy\", points)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "dkKtM-DKpT52" + }, + "outputs": [], + "source": [ + "# Save as compressed NumPy archive (.npz)\n", + "# Run time: ~5s\n", + "np.savez_compressed(\"points.npz\", points)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "rbHdEIbPpT52" + }, + "source": [ + "Note that the above numpy format is missing the keys (UUIDs) of each point.\n", + "This may not be required in all cases. However, for the sake of comparison\n", + "we also generate a NumPy archive with keys included. We store the UUIDs\n", + "as integers to save space and for a fair comparison where the optimal\n", + "storage method is used in each case. Note however that UUIDs are too\n", + "large to be a standard C type and therefore are stored as an object\n", + "array.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "DbLm4l5tpT52" + }, + "outputs": [], + "source": [ + "# Generate UUIDs\n", + "# Run time: ~10s\n", + "keys = np.array([uuid.uuid4().int for _ in range(len(points))])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "zXuAqw0KpT52" + }, + "outputs": [], + "source": [ + "# Generate some UUIDs as keys\n", + "# Save in NumPy format (.npz)\n", + "# Run time: <1s\n", + "np.savez(\"uuid_points.npz\", keys=keys, coords=points)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "UAHAgPU4pT52" + }, + "outputs": [], + "source": [ + "# Save in compressed (zip) NumPy format (.npz)\n", + "# Run time: ~10s\n", + "np.savez_compressed(\"uuid_points_compressed.npz\", keys=keys, coords=points)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "j5wlDFYfpT52" + }, + "outputs": [], + "source": [ + "# Write to SQLite with SQLiteStore\n", + "# Run time: ~10m\n", + "points_sqlite_store = SQLiteStore(\"points.db\")\n", + "_ = points_sqlite_store.append_many(\n", + " annotations=(Annotation(Point(x, y)) for x, y in points),\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "tUekiEqspT53" + }, + "outputs": [], + "source": [ + "# Load a DictionaryStore into memory by copying from the SQLiteStore\n", + "# Run time: ~1m 30s\n", + "points_dict_store = DictionaryStore(Path(\"points.ndjson\"))\n", + "for key, value in points_sqlite_store.items():\n", + " points_dict_store[key] = value" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Uynntjq7pT53" + }, + "outputs": [], + "source": [ + "# Save as GeoJSON\n", + "# Run time: ~1m 30s\n", + "points_sqlite_store.to_geojson(\"points.geojson\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "4YMuggcgpT53" + }, + "outputs": [], + "source": [ + "# Save as ndjson\n", + "# Run time: ~1m 30s\n", + "# Spec: https://github.com/ndjson/ndjson-spec\n", + "points_sqlite_store.to_ndjson(\"points.ndjson\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "lW9NoCPwpT53" + }, + "source": [ + "### 2.1.2) Points Dataset Statistics Summary\n", + "\n", + "| Format | Write Time | Size |\n", + "| -----------------------------: | ---------: | -----: |\n", + "| SQLiteStore (.db) | 6m 20s | 893MB |\n", + "| ndjson | 1m 23s | 667 MB |\n", + "| GeoJSON | 1m 42s | 500 MB |\n", + "| NumPy + UUID (.npz) | 0.5s | 165 MB |\n", + "| NumPy + UUID Compressed (.npz) | 31s | 136 MB |\n", + "| NumPy (.npy) | 0.1s | 76 MB |\n", + "| NumPy Compressed (.npz) | 3.3s | 66 MB |\n", + "\n", + "Note that the points SQLite database is significantly larger than the\n", + "NumPy arrays on disk. The numpy array is much more storage efficient\n", + "partly because there is no R Tree index or unique identifier (UUID)\n", + "stored for each point. For a more fair comparison, another NumPy archive\n", + "(.npz) is created where the keys are stored along with the coordinates.\n", + "\n", + "Also note that although the compressed NumPy representation is much\n", + "smaller, it must be decompressed in memeory before it can be used. The\n", + "uncompressed versions may be memory mapped if their size exceeds the\n", + "available memory.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "a_3Gz5Q0pT53" + }, + "source": [ + "### 2.1.3) Simple Box Query\n", + "\n", + "Here we evaluate the performance of performing a simple box query on the\n", + "data. All points which are in the area between 128 and 256 in the x and\n", + "y coordinates are retrieved. It is assumed that the data is already in\n", + "memory for the NumPy formats. In reality this would not the be case for\n", + "the first query, all data would have to be read from disk, which is a\n", + "significan overhead. However, this cost is amortised across many\n", + "queries. To ensure the fairest possible comparison, it is assumed that\n", + "many queries will be performed, and that this data loading cost in\n", + "negligable.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "o9J0d6gdpT53" + }, + "outputs": [], + "source": [ + "box = Polygon.from_bounds(128, 128, 256, 256)\n", + "\n", + "# Time numpy\n", + "numpy_runs = timeit.repeat(\n", + " (\n", + " \"where = np.all([\"\n", + " \"points[:, 0] > 128,\"\n", + " \"points[:, 0] < 256,\"\n", + " \"points[:, 1] > 128,\"\n", + " \"points[:, 1] < 256\"\n", + " \"], 0)\\n\"\n", + " \"uuids = keys[where]\\n\"\n", + " \"result = points[where]\\n\"\n", + " ),\n", + " globals={\"keys\": keys, \"points\": points, \"np\": np},\n", + " number=1,\n", + " repeat=10,\n", + ")\n", + "\n", + "# Time SQLiteStore\n", + "sqlite_runs = timeit.repeat(\n", + " \"store.query(box)\",\n", + " globals={\"store\": points_sqlite_store, \"box\": box},\n", + " number=1,\n", + " repeat=10,\n", + ")\n", + "\n", + "# Time DictionaryStore\n", + "dict_runs = timeit.repeat(\n", + " \"store.query(box)\",\n", + " globals={\"store\": points_dict_store, \"box\": box},\n", + " number=1,\n", + " repeat=10,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "eX1qqUIipT53", + "outputId": "a4033a88-6b2d-4a55-f3f6-ba419ef748c0" + }, + "outputs": [ + { + "data": { + "image/png": "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/Tz77LMcP36c9evX8/TTT/PII4+U2M3kjoYNGwKwdu1arl27RlZWFqdOneKFF15g+/btnD9/nl27drFt2zaXz4VSiq1bt9K/f/8/fGyVSsUNp4iSFHfL6o0cDod68803VYMGDZRer1cNGzZ0+5bbG7322muqfv36zuWUlBR17733KrPZ7LzldsuWLapHjx7O22KjoqLUG2+8oRwOR6n154ZbFE0mk2revLl66623lN1ud+ZLT0933nLr5eXlcsvtyZMnlb+/v5o1a5bLcfft21e1b99eWa3WEvefl5enpk2bplq2bKm8vb0VoLRarVq7dm2RvLt371Zt27ZVRqNRtW7dWm3durXILbfJyclqzJgxqlatWspgMKhatWqpQYMGOR++K2lAvdDBgwcVoP7973+XWOcbbd68WZnNZmWxWJzr/vnPf6oGDRooo9GogoKCVKdOndSqVatuWlZx1/1Gnh4IV6roLbcxMTHF3nJ7s1udf8ud245vvOU2NDRUjRkzxq1bbm+0bds2BaizZ886140bN06Fh4crjUajhg8frhISEtTgwYNV7dq1lZeXl6pZs6YaOXKkSktLc27z448/qsDAQJWdnV1qnW83GqXkzX2iajt9+jSxsbE0btyYtWvXltuDaIXWrVvHoEGDuHDhAhEREW5t06tXLwYMGMD48ePLtnKizPTr149u3brxwgsvVHRVPEq6p0SVFxkZybZt2+jSpYvLnVllzWKxcPz4cV599VWGDh3qdsAAmDNnzi3d5SUql8zMTDp16lQlg760NIQoI1OmTOH111+nffv2rFmzptS7poS4XUjQEEII4TbpnhJCCOE2CRpCCCHcVi2mEbnx4a3qKjQ0tMiEcVDw9GtYSDCavFzX9QYvUrOyi0ynrdPpMJvNeDls2JOuoDMH4jAHcv36dXx9fTHptNiTroBOhzY8guycXLKysvDx8cHPzw+VcAGN0QcVFEJ6ejp2u53AwMCCF/9YsgCweRlJSUkpu5NRSZR0TUTFkutSoKSHV6ts0Ni3bx9xcXGMHj26oqtSqen1euxnTpI04XGX9QEjnsHQ6z6XoKHRaAgJCiJr6QekfPMZKi8PgKB/TCbknkFY43aS8J+XUdkFX/660BqEvjITfXgtDNevce3FUdjOnwHA2CGGoOdeJ08p8n/8muTFs3FkZaDx8qb26l/fCqfVavHy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nVmzZmGxWBgyZAiRkZFERkaWuv2XX37pEjTKOmAUBrfizJ49m3/+8580aNAAh8NBQkICAPHx8RiNxlv6EittP5XFiRMniIuLY/r06RgMBjIyMrDZbHzyySfk5OQwc+ZMtFotmzZt4s0332TatGlotcU31u+55x7n/zdv3kzdunVvGjSq2/kuicFgYM+ePQwaNAiz2ezx8vv378/AgQO5dOkS//rXv5g3b16J1/G3Ll26hMPh4NixY+Tm5mI0Govk+e25v52vRXEkaJShgIAARo0axeTJk3nooYc4evQo//3vf5k0aRK5ubksXLiQ06dPo9FoePDBBzl9+jRWq5WJEydSt25dnnnmGeevWKUUy5Yt4+DBgwA88MADdO7cmfj4eD7//HP8/f25ePEijRo14umnn0aj0bBq1Sri4uKwWq00btyYUaNGodFomDJlCo0bN+aXX36hZcuWbN68mZkzZ6LX67FYLEycOJGZM2eSkZFBUFAQAFqtljp16pCUlMT333+PVqtl27ZtjBgxgtDQUD744AMyMjIwm8089dRThIaGMnv2bPz8/Dh37hwNGzbknnvuYcGCBWRkZODt7c3o0aOLvCO7Il2/fh1/f38MBgMAZrOZvLw8Nm/ezPvvv+/8YunRowebNm3i8OHD3HHHHcWW9dlnn2E0GgkPD+f06dPMmjULLy8vpk6dyqVLl1iyZAm5ubnO8xUUFFTtzndJtFotvXr14ptvvuGvf/2rS9rs2bNp164dHTt2BH5t5cXHx/PZZ58REBDA+fPnad++PfXq1WPdunXOz1RERIRLWXXq1EGr1ZKSksKUKVOK/Qzo9a5fkdu3bycmJobLly+zb98+7r77bgCXz1R0dDRxcXEuyzVr1mT16tXYbDb8/f15+umnMZvNjB8/ntdff93Z8hk3bhxTp04tk2DpKRI0yliNGjVQSpGenu6yftWqVfj4+DBjxgwAsrKy6NixI+vXr3e2VG60Z88ezp07x1tvvUVGRgaTJ0+mWbNmAJw9e5a3336boKAgXn75ZX755ReaNm1K3759efDBBwF47733iIuLczbFLRYLr7zyCgDXrl1j//79tG/fnp07d9KhQwf0ej39+/dn/PjxNG/enDZt2tCtWzfCw8Pp3bs3RqORgQMHAjBt2jRiYmLo3r07P/74IwsXLuT5558HIDExkZdffhmtVsurr77Kk08+Sc2aNTl58iTz58/nX//6Vxmc9d/njjvuYNWqVYwbN45WrVrRuXNnfH19CQ0NxcfHxyVvo0aNuHTpUolBo1DhNX3ssceIjIzEZrM5z4/ZbGbnzp2sWLGCp556qtqd79L06dOHiRMnct9997m9zfnz53nnnXfw8/PjH//4B7GxsbzxxhusW7eO9evX8/jjj7vkP3nyJFqtltDQUFq0aFHsZ+C3du3axUsvvURCQgLr1693Bg1w/UzFxcW5LGdlZTF16lQ0Gg0//PADa9euZdiwYXTt2pVt27bRv39/Dh8+TP369St1wAAJGuWiuLuaDx8+7NIn6ufnV2oZx48fp0uXLmi1WgIDA2nevDmnT5/GZDIRFRVFSEgIAA0aNCApKYmmTZty5MgR1q5dS15eHllZWdStW9cZNDp37uwsu2fPnqxdu5b27duzadMmRo8eDcCDDz7I3XffzaFDh9i+fTs7duxgypQpRep28uRJnnvuOQBiYmJYvny5M61jx45otVpyc3P55ZdfePvtt51pNpvtJmeufBmNRqZPn86xY8eIj4/nnXfeYfDgwWg0Go/tIyEhgYsXL/Laa68B4HA4nK2L6na+S+Pj40NMTAzr1q3Dy8vLrW0iIyOd5zIiIoLWrVsDUK9ePY4cOeLM980337Bt2zZMJhPjx49Ho9GU+Bm40alTpzCbzYSFhRESEsIHH3xAVlaW87N742fqt8upqam8++67XL9+HZvNRnh4OFDQan3rrbfo378/mzZtokePHrdwliqGBI0ydvXqVbRaLQEBAVy+fNklzVNfRoXdKVDQtHc4HFitVhYsWMAbb7xBaGgon332GVar1ZnP29vb+f+mTZuyYMECjh49isPhoF69es60iIgIIiIiiI2NZeTIkWRmZt5S3Qr7fB0OB76+vsW2oioTrVZLixYtaNGiBfXq1eP777/n2rVr5OTkYDKZnPnOnj3r7CK5VXXq1GHq1KnFplW3812a/v3788ILL9C9e3fnOp1Oh8PhAAp+jN0YCG/8HGg0GueyRqNxblNYbmGrrVBpn4FCO3bs4PLly/z9738HICcnhz179hAbGwu4fqZ+u7xw4UIGDBhAdHS0s0sZIDQ0lICAAI4cOcLJkyd55pln3D9BFURuuS1DGRkZzJs3j759+xYJEK1bt2b9+vXO5aysLAD0en2xvwibNWvGrl27cDgcZGRkcOzYMaKiokrcd35+PlDQL5+bm8uePXtKrWtMTAwzZ850+aWzf/9+ZyspMTERrVaLr68vJpOJ3NxcZ77GjRuzc+dOoKDPt2nTpkXK9/HxITw8nF27dgEFH/hz586VWqfylpCQQGJionP53Llz1KpVi27durFkyRLnF8+WLVswGAxuD0wbjUZycnIAqFWrFhkZGZw4cQIo+PV/8eJFoPqd75vx8/OjU6dO/Pjjj851YWFhnDlzBoC9e/dit9s9tr/iPgOFHA4Hu3fv5j//+Q+zZ89m9uzZTJw4kR07drhVtsVicd4IsWXLFpe0nj178t5779GpUye3B+QrkrQ0PKxw0K3wjomuXbsyYMCAIvkeeOAB5s+fz4QJE9BqtTz44IN06NCB2NhYJk6cSMOGDV1+dbRv354TJ04wceJEAB599FECAwOLtF4K+fr6Ehsby4QJEwgPD7/pXVtdu3bl008/pUuXLs51W7duZcmSJXh5eaHT6Xj66afRarW0a9eOt99+m7179zJixAj+9re/8cEHH7B27VrnwGxxnnnmGebNm+ccEOzSpQsNGjS42SktN4U3J2RnZ6PT6YiIiGDUqFGYTCaWLl3KuHHjsFqtmM1mZ/80FFzzMWPGOMv57fXu3r078+bNcw6ET5gwgUWLFmGxWLDb7fTr14+6detWu/PtjgEDBrj8uIqNjeWtt95i8uTJtGrVqsiv+z+iuM9AoWPHjhEcHOxyB1zz5s2ZNWsW169fv2nZDz30EG+//TbBwcH86U9/IikpyZkWHR3NBx98cFt0TYFMIyL+Z/fu3ezdu5enn366oqtSqaWlpTF16lT69OkjcxRVMRX1GTh9+jRLlizh1VdfLdf9/l4SNAQLFy7kwIEDTJ48mVq1alV0dYQodxX1GVizZg0bNmzgmWeeKbabsTKSoCGEEMJtlX/URQghRKUhQUMIIYTbJGgIIYRwmwQNIYQQbpPnNES1d/z4cZYtW8bFixedEwUOHz6cqKgoNm/ezA8//OCc9qMsrV69mi+//BIoeJjMZrM5p9AICwtzmRJEiIoiQUNUaxaLhWnTpjFy5Eg6d+6MzWbj2LFjLlNS/BG3Mi32/fff75wWvzyDlRC3QoKGqNYKpw0pnK3Uy8vLOXPtpUuXmDdvHjabjcceewydTsfixYuxWCzO+/q9vb2JjY1l8ODBaLVa55d9ZGQkW7ZsoU+fPjzwwAOsWLGCXbt2YbPZuOuuu3j88cfdnohv7dq1nDhxwjlJIRQ8V6DVann88ced03IfPnyYhIQEWrRowVNPPeWcSO/EiRN8/PHHXLp0ibCwMB5//HFatGjhydMoqhEZ0xDVWs2aNdFqtbz//vscOHDAOQcYFEws+OSTT9K4cWOWLl3K4sWLgYIvbIvFwvvvv8+UKVPYunUrmzdvdm538uRJatSowfz587n//vtZvnw5iYmJvPXWW8yaNYvU1FRWrVrldh27du3Kzz//THZ2NlDQetm5cycxMTHOPFu2bGHs2LHMnTsXrVbLwoULgYLZVadNm8b999/PwoULeeyxx5gxYwYZGRl/4KyJ6kyChqjWfHx8ePXVV9FoNMydO5eRI0cyffp00tLSis3vcDjYuXMnQ4cOxWQyER4ezoABA9i6daszT1BQEPfeey86nQ6DwcAPP/zA8OHD8fPzw2Qycf/997s90V1heYUTVgIcPHgQf39/GjVq5MwTExNDvXr1MBqN/OUvf3FObrl161buvPNO2rZti1arpXXr1kRGRrJ///7fd8JEtSfdU6Laq1OnjnO668uXL/Pee++xePHiYt8BXfgK2ML3g0PBIHVqaqpz+ca0jIwM8vLymDRpknOdUsplqm53dOvWjQ0bNtCrVy+2bdvm0soAnO9TKdy/3W4nIyOD5ORkdu/e7Xy/ORS0VKR7SvxeEjSEuEHt2rXp3r0733//fbHpZrMZnU5HcnIyderUASA5ObnE93/7+/vj5eXlnOH097rrrruYP38+Fy5cIC4ujkcffdQlPSUlxfn/5ORkdDodZrOZkJAQunbt6jILrxB/hHRPiWrt8uXL/Pe//3V+6SYnJ7Njxw7+9Kc/ARAYGEhqaqrzHSdarZZOnTqxYsUKcnJyuHbtGl9//TVdu3YttnytVktsbCyLFy92vvI3NTXV+a53d3l5edGhQwdmzZpFVFSUS2sGYNu2bVy6dIm8vDw+++wz5xv8unbtSlxcHAcPHnS+nCs+Pt4lyAhxK6SlIao1k8nEyZMn+frrr7FYLPj4+NCuXTvnL/mWLVs6B8S1Wi0LFixgxIgRLFy4kH/84x94eXkRGxtb6rsQHnnkEVatWsX//d//kZmZSXBwML1796ZNmza3VNfCd4KPHTu2SFpMTAyzZ88mISGBZs2aOd+xERoayvPPP8+yZcuYOXMmWq2WqKgonnzyyVvatxCFZJZbIW4TycnJjB8/no8++ggfHx/n+ilTptC1a1fna0eFKEvSPSXEbcDhcPD111/TuXNnl4AhRHmToCFEJZebm8vw4cM5dOgQQ4YMqejqiGpOuqeEEEK4TVoaQggh3CZBQwghhNskaAghhHCbBA0hhBBuk6AhhBDCbf8fZakD0nF7LkQAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_results(\n", + " experiments=[dict_runs, sqlite_runs, numpy_runs],\n", + " title=\"Points Box Query (5 Million Points)\",\n", + " tick_label=[\"DictionaryStore\", \"SQLiteStore\", \"NumPy Array\"],\n", + ")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "aNU6FP90pT53" + }, + "source": [ + "Although the NumPy array is very space efficient on disk, it is not as\n", + "fast to query as the `SQLiteStore`. The `SQLiteStore` is likely faster\n", + "due to the use of the R tree index. Furthermore, the method used to\n", + "store the points in a NumPy array is limited in that it does not use\n", + "UUIDs, which makes merging two datasets more difficult as the indexes of\n", + "points no longer uniquely identify them. Additionally, only homogeneous\n", + "data such as two-dimentional coordinates can be practically stored in\n", + "this way. If the user would like to store variable length data\n", + "structures such as polygons, or even mix data types by storing both\n", + "points and polygons, then using raw NumPy arrays in this way can become\n", + "cumbersome and begins to offer little benefit in terms of storage\n", + "efficient or query performance.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "c766NXGPpT53" + }, + "source": [ + "### 2.1.4) Polygon Query\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "6jiMpRnxpT53" + }, + "outputs": [], + "source": [ + "big_triangle = Polygon(\n", + " shell=[ # noqa: S604\n", + " (1024, 1024),\n", + " (1024, 4096),\n", + " (4096, 4096),\n", + " (1024, 1024),\n", + " ],\n", + ")\n", + "\n", + "# Time SQLiteStore\n", + "sqlite_runs = timeit.repeat(\n", + " \"store.query(polygon)\",\n", + " globals={\"store\": points_sqlite_store, \"polygon\": big_triangle},\n", + " number=1,\n", + " repeat=10,\n", + ")\n", + "\n", + "# Time DictionaryStore\n", + "dict_runs = timeit.repeat(\n", + " \"store.query(polygon)\",\n", + " globals={\"store\": points_dict_store, \"polygon\": big_triangle},\n", + " number=1,\n", + " repeat=10,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Es2OQ5OdpT53", + "outputId": "b98176ee-7003-49f7-f5ca-62b08180b2ee" + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYgAAAEaCAYAAAAL7cBuAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAy3klEQVR4nO3dd3wUdeL/8deWJLtpJCGEQEJHqYeAAZSOFMEuCnbhkCLY8LCA5SeK3GFBDyxILyIcHjb0lFO50AMHQRBDFaRHIISwCam7O78/ctmva4YQhCQE3s/HI49HZj5TPjs7u+/9TPmMxTAMAxERkd+xVnQFRETk4qSAEBERUwoIERExpYAQERFTCggRETGlgBAREVMKiMtA165dGTx4cEVX47Lz2GOP8eijj5b7eseOHUvDhg19w3PmzMFut/uGly9fjsVi4dChQwDs27cPi8XC6tWry72uf8TAgQPp0aNHma9n//79REdHk5qaWubrulgpIC5yAwcOxGKxYLFYsNvt1KlTh4cffpgTJ05UdNXKVGZmJs8//zyNGjUiKCiIyMhI+vTpw/Llyyu6aqWyc+dOZs+ezQsvvOAbN3bsWN97+du/n3/+ucRl1a1bF4vFwjvvvFOsbOTIkVgsFr8vzKeeeop169aVuq61atUiNTWVdu3alXqeP+q328BqtRIXF8c999zD/v37S72MSZMm8c9//vOc1vvqq69St27dc5qnTp069O/fnxdffPGc5ruUKCAqgU6dOpGamsq+ffuYPHkyn3zyCQ8++GBFV6vMuFwuOnTowKJFi3j11VfZtWsXiYmJXHHFFXTv3p1Zs2aVeR0Mw6CgoOAPzz958mRuuOEGYmNj/cbXrVuX1NRUv7969eqddXm1a9dm+vTpfuNyc3P58MMPqVOnjt/40NBQoqOjS11Xm81GbGwsAQEBpZ7nfBRtg0OHDjFv3jw2btzIzTffjMfjKdX8VapUITIysoxrWWjw4MHMnz+ftLS0clnfxUYBUQkEBgYSGxtLfHw8t956KyNHjmTp0qXk5ORgGAZvvvkm9evXJzAwkAYNGvD3v//9jMuaPXs2ERERZGdn+41/+eWXqVevHkU31n///ff86U9/wuFw0KJFC1asWIHFYmH+/Pm+eXbu3MmNN95IaGgooaGh3HzzzX6/hosObaxZs4bWrVsTHBxMmzZtSE5OLvH1vvDCC+zevZtly5bRr18/6tSpQ8uWLZk8eTJDhw7lkUce4ciRI37r+K1Dhw5hsVj8Whs///wzd9xxBxEREURGRtKrVy+2bt1arK6JiYm0atWKoKAgpkyZgtVqZe3atX7LX7FiBVarlb1795rW3+v1snDhQm677bZiZUVfxr/9s9lsJW4PgLvvvpu9e/eyfv1637jFixcTGRlJly5d/Kb9/SGmszE7xFRW7y383zaoWbMm3bt3Z+zYsWzdutW3/Llz59K0aVOCgoKIj4/nhRdewO12++b//SGmouFp06ZRp04dwsPDufXWWzl+/Livri+++CL79+/3tV7Gjh0LwBdffEGrVq0IDg4mIiKCtm3b8sMPP/iW3bp1a6pXr87ixYtLvT0vJQqISsjpdOL1enG73bz//vu8+OKLjB49mpSUFJ5++mlGjx7NzJkzTee9++67sVgsfk10r9fL7NmzGTx4MBaLhcOHD3PLLbfQrl07Nm3axNtvv81f/vIXv+Xk5OTQq1cvcnNzWbFiBStWrCArK4vevXuTn5/vt+wxY8YwadIkNm3aRGRkJP379/f7wP+WYRh89NFH3HfffcV+GQM899xz5ObmntMH9ujRo3Ts2JGYmBhWrVrFunXraNSoEV27dvV9iRTV9ZlnnmHixIns2LGDe+65h549exb75T5jxgy6d+9O/fr1Tde3detWTp48Sdu2bYuVHTp0iPj4eOLj4+nTp0+x8DmTsLAw7r77br+6TJs2zfeeXUhl9d6eidPpBKCgoIB//etfDBo0iAceeICtW7cyceJE3nvvPV5++eUSl7FhwwYSExP517/+xdKlS9m8eTNPPfUUAHfddRfPPvss8fHxvlbbU089xa+//kq/fv245557SElJISkpiZEjRxb7wdGuXTsSExPP6TVdMgy5qA0YMMDo3r27bzglJcWoX7++0a5dO8MwDCM+Pt54+umn/eYZOXKkUa9ePd9wly5djIceesg3/NhjjxkdOnTwDS9dutSw2+3GkSNHDMMwjOeee86oU6eO4Xa7fdN88803BmB8+OGHhmEYxowZMwyn02kcP37cN82vv/5qOBwOY+7cuYZhGMbs2bMNwEhOTvZNk5SUZADGjh07TF/v0aNHDcB46623zrhNwsPDjREjRvjWYbPZ/MoPHjxoAEZiYqJhGIbx0ksv+bZXEa/Xa9SvX994++23/eq6cuVKv+k++eQTIzg42MjIyDAMwzBOnjxpOJ1O4+OPPz5j/T777DMDMLKzs/3Gf/3118aiRYuMLVu2GCtXrjTuuecew2q1Gt9+++0Zl2UYhlGnTh1j3Lhxxvr1642QkBDD5XIZ27dvNwICAoxff/212D7y0ksvGQ0aNPAN/34bJSYmGoBx8OBBwzAM45dffjEAY9WqVYZhlN17a1a3/fv3G23btjVq1apl5OfnGx07djT69evnN8/f//53w+FwGHl5eYZhFP9MDBgwwIiOjjZyc3N94/72t78ZsbGxvuFx48YZderU8Vvupk2bDMD45ZdfzlhfwzCMJ5980khISChxmkuVWhCVwPLlywkNDcXpdNK8eXPq16/PggULcLlcHDp0iM6dO/tN36VLF/bt21fsMFKRYcOGsWbNGrZt2wbA9OnTufHGG6lRowYA27Zto02bNn6HPq699lq/ZaSkpNC0aVO/Y93Vq1enUaNGpKSk+MZZLBauuuoq33BcXBxQ+KvejFGKviMNwzin4+UbNmwgOTnZd7gkNDSUsLAw9u3bx+7du/2mbdOmjd/wLbfcQpUqVViwYAEA8+fPJzQ0lFtvvfWM68vJyQEgKCjIb3yfPn3o378/LVq0oFOnTixYsICOHTvyxhtvlOp1tG3bliuuuIKFCxcybdo0br75ZqpXr16qec9FWb23Rfbu3UtoaCjBwcHUqVMHwzD47LPPCAgIICUlxXR/zs3NZc+ePWdcZpMmTfy2d1xc3Fnr0aJFC66//nqaN2/O7bffzqRJkzh48GCx6RwOh+89vdzYzz6JVLR27doxd+5c7HY7NWrU8H0QXC4XQLFDDGf7km3WrBkdO3ZkxowZjB49miVLlvD555/7TfP7ZZodxjAbZxiG33ir1eoXNEVlXq/XtG4xMTFERUXx008/mZYfPHiQzMxMrrzySt/yf+/3J5e9Xi/du3fn3XffLTZtlSpVfP/bbDYcDodfud1u56GHHmL69OkMHz6cGTNmMHDgQAIDA03rB1CtWjUATp48SdWqVc84HRQG76efflriNL81ZMgQpkyZwsGDB/noo49KPd+5Kov3tkitWrVYtmwZVquV2NhYgoODS1x30f5c0qG0378fFovlrJ8Dm83GN998w4YNG/j+++/55JNPGD16NP/85z+56aabfNOlp6f73tPLjVoQlYDT6aRhw4bUrVvX71dSeHg48fHxrFixwm/6lStXUq9evWIfvN8aNmwY8+bNY9q0acTGxtK7d29fWdOmTdmwYYPfVSVJSUl+8zdr1oyUlBS/qzuOHj3Krl27aNas2R9+rRaLhfvuu48FCxaYXvr417/+FYfDwV133QUUBorH4/H7tbhp0ya/eRISEkhJSSEuLo6GDRv6/ZXmgz9kyBC2bNnCBx98wJYtW856T0mrVq2wWCx+v7bP5IcffqBWrVpnna7I/fffz+7duwkNDaVnz56lnu9clNV7WyQgIICGDRtSv379Yvtos2bNTPdnp9N5xnM+pREYGGh6lZTFYqFt27Y899xzrFy5ki5dujB79my/abZu3UpCQsIfXndlpoCo5MaMGcM777zD9OnT2b17N1OnTmXKlCk899xzJc535513AjBu3Dgeeughv1/iI0aM4OjRowwfPpzt27eTmJjI888/D/zfr7h7772XatWqcdddd7Fp0yaSk5O5++67iYuL8315/1Hjxo3zXdK6ePFiDhw4wJYtW3jiiSeYNm0as2bN8v0yb9u2LWFhYYwePZrdu3ezdOlSXnnlFb/lPfroo3g8Hm677TZWrVrFvn37WL16Nc8//3ypThLXrl2b3r1788QTT9C1a1df6+VMqlatStu2bYt90f3lL3/hP//5D3v37mXz5s088sgjfPfdd4wcObLU2yY8PJzDhw+zdetW09bThVCW7+3ZjBkzhk8++YQJEyawa9cuPv74Y8aOHcuoUaNKbLWdTb169fj1119JSkoiLS2N7Oxs1q5dy7hx41i/fj0HDhxg2bJl/PjjjzRt2tQ3X2ZmJsnJydx4440X4uVVOgqISm748OG88sor/PWvf6Vp06a89tprTJgwgYceeqjE+RwOBw888ABut7vYtHFxcSxZsoS1a9fSsmVLnnjiCV599VXffFDYqvn2228JCgqic+fOdOnShZCQEJYuXXpeH2QoPOyzevVq+vfvz5gxY2jYsCEtW7Zk5syZJCUlcc899/imjYqKYuHChaxbt44WLVowbtw4Xn/9db/lVa9enaSkJKKjo+nbty+NGjXivvvuY//+/b7zLmczdOhQ8vPzGTp0aKmmHz58OB9++KHfuNTUVB588EGaNGlCr1692LlzJ99//z0333xzqZZZpEqVKoSFhZ3TPOeiLN/bs7nhhhuYNWsW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ESkbCggRETGlgBAREVMKCBERMaWAEBERUwoIERExpYAQERFTCggRETGlgBAREVMKCBERMaWAEBERUwoIERExpYAQERFTCggRETFV6QNCz4MQESkbeh6EiIiYqvQtCBERKRsKCBERMaWAEBERUwoIERExpYAQERFTCggRETGlgBAREVMKCBERMaWAEBERUwoIERExpYAQERFTCggRETGlgBAREVMKCBERMaWAEBERUwoIERExpYAQERFTCggRETGlgBAREVMKCBERMaWAEBERU5U+IDZu3MjUqVMruhoiIpcce0VX4HwlJCSQkJBQ0dUQEbnkVPoWhIiIlA0FhIiImFJAiIiIKQWEiIiYUkCIiIgpBYSIiJhSQIiIiCkFhIiImFJAiIiIKQWEiIiYUkCIiIgpBYSIiJhSQIiIiCkFhIiImFJAiIiIqRKfB+FyuVi5ciWbNm1i//79ZGdnExwcTJ06dWjZsiVdu3YlPDy8vOoqIiLl6IwBsWDBAlatWkWrVq247rrriIuLw+l0kpOTw+HDh9m2bRvPPvssHTt25L777ivPOouISDk4Y0BERkYyefJkAgICipXVq1ePjh07kp+fz3/+858yraCIiFSMMwZEnz59zjpzYGAgvXv3vqAVEhGRi0Opnkn9008/ERMTQ0xMDCdPnuSjjz7CarVy7733EhERUcZVFBGRilCqq5hmzpyJ1Vo46bx58/B4PFgsFqZOnVqmlRMRkYpTqhZEeno60dHReDwetmzZwvvvv4/dbmfYsGFlXT8REakgpQoIp9NJRkYGBw8eJD4+HofDgdvtxu12l3X9RESkgpQqIHr37s2YMWNwu90MHDgQgB07dhAXF1eWdRMRkQpUqoC47bbbaNu2LVarldjYWACioqJ4+OGHy7RyIiJScUoVEAA1a9YscVhERC4tZ7yKacyYMSQlJZ3xPIPb7Wbt2rU899xzZVY5ERGpOGdsQTzyyCMsWrSIGTNmUK9ePWrWrInD4SA3N5fU1FT27t1L8+bNGTFiRHnWV0REyonFMAyjpAkyMjL48ccfOXDgAKdPnyYkJIQ6derQokULqlSpUl71LJUjR46U+zo9Q24p93VK+bNNX1LRVbisREdHk5aWVtHVuCyUdLrgrOcgIiIi6Ny58wWtkIiIXPz0PAgRETGlgBAREVMKCBERMaWAEBERU6W6Uc4wDJYtW8aaNWvIzMzkzTffZNu2bWRkZNC+ffsLXqnc3FxmzJiB3W6nWbNmdOrU6YKvQ0RESlaqFsSiRYtITEykR48evkvPqlatyhdffFHqFb3//vsMHjyYUaNG+Y3fvHkzTzzxBI899hiff/45AP/973+55pprePjhh9m4cWOp1yEiIhdOqQJixYoVPPvss3To0AGLxQJATEwMx44dK/WKunbtWuyua6/Xy8yZM3nuued4++23WbNmDYcOHeLEiRNER0cXVtCqo2AiIhWhVIeYvF4vDofDb1xubm6xcSVp2rRpsUD5+eefiY2NpXr16gC0b9+eDRs2ULVqVU6cOEHdunUp6T6+77//nu+//x6ACRMm+EKlPB0t9zVKRaiIfetyZrfbtc0vAqUKiFatWjFv3jwGDBgAFJ6TWLRoEVdfffV5rTw9PZ2qVav6hqtWrcru3bvp06cPs2bNYtOmTSWuo0ePHvTo0cM3rDsvpaxo3ypfupO6/JzXndQADz74IO+++y4DBw7E7Xbz4IMP0qJFCx599NHzqphZ68BiseBwONTHk4hIBStVQAQHB/PMM8+QkZFBWloa0dHRREREnPfKiw4lFTlx4gSRkZHnvVwRETl/53QGODAwkKioKLxeL+np6aSnp5/Xyhs0aEBqairHjh3zdR+ekJBwXssUEZELo1QtiB9//JFp06Zx/PjxYmWLFi0q1Yr+/ve/s23bNjIzM3n44Yfp378/1113HYMGDWL8+PF4vV66detGrVq1zu0ViIhImShVQHzwwQfccccddOjQgcDAwD+0opEjR5qOb926Na1bt/5DywTYuHEjycnJDBs27A8vQ0REiitVQBQUFNCtW7eL8p6EhIQEHZYSESkDpfrGv/HGG/niiy9KvCdBREQuLaVqQbRr147x48fz+eefExYW5lf27rvvlknFRESkYpUqIN566y0aN27Mtdde+4fPQYiISOVSqoA4duwYr7322kV5DkJERMpGqb7xExIS+Omnn8q6LiIichEp9VVMr7/+Ok2aNKFKlSp+Zefb3cb50mWuIiJlo1QBUatWrYv2BjZd5ioiUjZKFRD9+vUr63qIiMhF5owBsW3bNpo2bQpQ4vmH5s2bX/haiYhIhTtjQMycOZOJEycCMGXKFNNpLBaL7oMQEblEWYwSbo9evXo1HTt2LM/6nJcjR46U+zo9Q24p93VK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HA5uueUWACZMmEDnzp3p2rUr//nPf5g1axbPPPMMAKmpqbz44otYrVZeeeUVhgwZQo0aNdi9ezczZszgpZdeKoOtfnm76qqrWLx4MU888QR/+tOfaN++PSEhIURHRxMcHOw3bf369Tl06NAZA6JI0b75wAMP0KBBA9xut+99Dg8PZ+3atSxcuJARI0Zov6lACohKyOzK5K1btzJy5EjfcGhoaInL2LFjBx06dMBqtRIREUHTpk3Zs2cPTqeThg0bUrVqVQDq1q3LsWPHaNy4MT/99BNLliwhLy+PrKwsatWq5QuI9u3b+5Z93XXXsWTJEtq2bUtiYiLDhg0D4M4776Rjx478+OOPrF69mjVr1jB27Nhiddu9ezdPPfUUAJ07d+ajjz7ylV1zzTVYrVZyc3PZuXMnb731lq/M7XafZcvJH+FwOHjttdfYvn07KSkpvP3229x+++1YLJYLto4jR45w8OBBxo0bB4DX6/W1GrTfVBwFRCVz9OhRrFYrVapU4fDhw35lF+oDW3QoAQqb9F6vl/z8fGbOnMnf/vY3oqOj+fjjj8nPz/dNFxQU5Pu/cePGzJw5k23btuH1eqldu7avLDY2ltjYWLp3787gwYPJzMw8p7o5HA6g8AskJCTEtHUkF57VaqVZs2Y0a9aM2rVr891333H8+HFycnJwOp2+6X755ReuueaaP7SO+Ph4xo8fb1qm/aZi6DLXSsTlcjF9+nR69+5dLAxatGjB0qVLfcNZWVkA2O12019ITZo0ISkpCa/Xi8vlYvv27TRs2PCM6y4oKAAKjz/n5uayfv36EuvauXNnJk2aRLdu3XzjNm3a5Gv9pKamYrVaCQkJwel0kpub65vuyiuvZO3atQCsXr2axo0bF1t+cHAwMTExJCUlAYWtqn379pVYJ/ljjhw5Qmpqqm9437591KxZky5dujB37ly8Xi8AK1asICAgoNQnjR0OBzk5OQDUrFkTl8vFrl27gMJf9QcPHgS031QktSAuckUnmYuuvujUqRM33XRTsenuuOMOZsyYwahRo7Bardx55520a9eO7t278/TTT1OvXj0ef/xx3/Rt27Zl165dPP300wDcf//9REREFGuVFAkJCaF79+6MGjWKmJiYs1491alTJ/7xj3/QoUMH37iVK1cyd+5cAgMDsdlsPPbYY1itVq6++mreeustNmzYwKBBg/jzn//MlClTWLJkie9ko5nHH3+c6dOn8+mnn+J2u+nQoQN169Y92yaVc1R0AcTp06ex2WzExsYydOhQnE4nH374IU888QT5+fmEh4czfvx434+X/Px8Hn74Yd9yfr/fdu3alenTp/tOUo8aNYrZs2eTnZ2Nx+PhhhtuoFatWtpvKpC62pAysW7dOjZs2MBjjz1W0VWRcpCRkcH48eO5/vrr1Y/SJUQBIRfcrFmz+OGHHxgzZgw1a9as6OqIyB+kgBAREVM6SS0iIqYUECIiYkoBISIiphQQIiJiSvdByGVjx44dzJ8/n4MHD/o6fRswYAANGzZk+fLlLFu2zNfVQ1n69NNP+eyzz4DCO3vdbjeBgYEAVKtWza8bCJGKpICQy0J2djYTJkxg8ODBtG/fHrfbzfbt2/26FTkf59KNdN++fX1dsJdnMImcKwWEXBaKuoro2LEjUPicjaIeRw8dOsT06dNxu9088MAD2Gw25syZQ3Z2tu+ejqCgILp3787tt9+O1Wr1fbE3aNCAFStWcP3113PHHXewcOFCkpKScLvdtGnThoEDB/paB2ezZMkSdu3a5etwDgrvKbFarQwcONDXrfrWrVs5cuQIzZo1Y8SIEb6OGXft2sW8efM4dOgQ1apVY+DAgTRr1uxCbka5zOgchFwWatSogdVq5d133+WHH37w9VUFhZ3EDRkyhCuvvJIPP/yQOXPmAIVfztnZ2bz77ruMHTuWlStXsnz5ct98u3fvpnr16syYMYO+ffvy0UcfkZqayhtvvMHkyZNJT09n8eLFpa5jp06d2LJlC6dPnwYKWyVr166lc+fOvmlWrFjB8OHDmTp1KlarlVmzZgGQnp7OhAkT6Nu3L7NmzeKBBx5g4sSJuFyu89hqcrlTQMhlITg4mFdeeQWLxcLUqVMZPHgwr732GhkZGabTe71e1q5dy7333ovT6SQmJoabbrqJlStX+qaJjIykT58+2Gw2AgICWLZsGQMGDCA0NBSn00nfvn1Zs2ZNqesYGRnp60QRYPPmzYSFhVG/fn3fNJ07d6Z27do4HA7uvvtuX4eLK1eupFWrVrRu3Rqr1UqLFi1o0KABmzZt+mMbTAQdYpLLSHx8PI888ggAhw8f5p133mHOnDl+z9EoUvRYzaLnK0PhCeT09HTf8G/LXC4XeXl5jB492jfOMAxfT6el1aVLF7799lt69OjBqlWr/FoPgO85HUXr93g8uFwu0tLSWLdune/50FDYAtEhJjkfCgi5LMXFxdG1a1e+++470/Lw8HBsNhtpaWnEx8cDkJaWdsbnJ4eFhREYGMhbb7111mcsl6RNmzbMmDGDAwcOkJyczP333+9XfuLECd//aWlp2Gw2wsPDqVq1Kp06dfLrPVXkfOkQk1wWDh8+zJdffun7gk1LS2PNmjVcccUVAERERJCenu57dobVauXaa69l4cKF5OTkcPz4cb766is6depkunyr1Ur37t2ZM2eO73Gw6enpvmd+l1ZgYCDt2rVj8uTJNGzY0K+VArBq1SoOHTpEXl4eH3/8se9JaZ06dSI5OZnNmzf7HvCUkpLiFygi50otCLksOJ1Odu/ezVdffUV2djbBwcFcffXVvl/ozZs3952stlqtzJw5k0GDBjFr1iweffRRAgMD6d69u98DkH7vvvvuY/HixTz//PNkZmYSFRVFz549admy5TnVteiZysOHDy9W1rlzZ9577z2OHDlCkyZNfM88iI6O5plnnmH+/PlMmjQJq9VKw4YNGTJkyDmtW+S31JuryEUmLS2NkSNHMm3aNIKDg33jx44dS6dOnejevXsF1k4uJzrEJHIR8Xq9fPXVV7Rv394vHEQqggJC5CKRm5vLgAED+PHHH+nfv39FV0dEh5hERMScWhAiImJKASEiIqYUECIiYkoBISIiphQQIiJi6v8DrmD9WcQUuyAAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_results(\n", + " experiments=[dict_runs, sqlite_runs],\n", + " title=\"Polygon Query (5 Million Points)\",\n", + " tick_label=[\"DictionaryStore\", \"SQLiteStore\"],\n", + ")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "HUBEmZDMpT53" + }, + "source": [ + "## 2.2) Cell Boundary Polygons Dataset\n", + "\n", + "Here we generate a much larger and more complex polygon dataset. This\n", + "consistes of a grid of over 5 million generated cell boundary like\n", + "polygons.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "xhCr_TDVpT53", + "outputId": "c02b7a20-6ab1-4cae-b6bb-fb5c6d94cd12" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 5004169/5004169 [10:04<00:00, 8277.35it/s] \n" + ] + } + ], + "source": [ + "# Generate a grid of 5 million cell boundary polygons (2237 x 2237)\n", + "# Run time: ~10m\n", + "rng_42 = np.random.default_rng(42)\n", + "\n", + "cell_polygons = [\n", + " Annotation(geometry=polygon, properties={\"class\": rng_42.integers(0, 4)})\n", + " for polygon in tqdm(cell_grid(size=(2237, 2237), spacing=35), total=2237**2)\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "21RgwKtgpT54" + }, + "source": [ + "### 2.2.1) Write To Formats For Comparison\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "CDVLMRUtpT54" + }, + "outputs": [], + "source": [ + "# Write to an SQLiteStore on disk (SSD for recorded times here)\n", + "# Run time: ~30m\n", + "cell_sqlite_store = SQLiteStore(\"cells.db\")\n", + "_ = cell_sqlite_store.append_many(annotations=cell_polygons)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "6Fb4tQHVpT54", + "outputId": "fba12c47-e0cb-44fd-ca95-35c38454c9cc" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + } + ], + "source": [ + "# Create a copy as an in memory DictionaryStore\n", + "# Run time: ~5m\n", + "cell_dict_store = DictionaryStore()\n", + "for key, value in tqdm( # Show a nice progress bar\n", + " cell_sqlite_store.items(),\n", + " total=len(cell_sqlite_store),\n", + " leave=False,\n", + " position=0,\n", + "):\n", + " cell_dict_store[key] = value" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "wXOOuGWypT54", + "outputId": "e2fb300e-e5b8-4459-b172-249cda363b50" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 5004169/5004169 [01:26<00:00, 58002.74it/s]\n" + ] + } + ], + "source": [ + "# Transform into a numpy array\n", + "# Run Time: ~1m\n", + "cell_polygons_np = np.array(\n", + " [np.array(a.geometry.exterior.coords) for a in tqdm(cell_polygons)],\n", + " dtype=object,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "yv9VgW9TpT54" + }, + "outputs": [], + "source": [ + "# Create an Nx4 index of (xmin, ymin, xmax, ymax) as a simple spatial\n", + "# index to speed up the numpy query.\n", + "# Run time: ~1m\n", + "min_max_index = np.array(\n", + " [(*np.min(coords, 0), *np.max(coords, 0)) for coords in cell_polygons_np],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "nFmHxwBwpT54" + }, + "outputs": [], + "source": [ + "# Write to GeoJSON\n", + "# Run time: ~10m\n", + "\n", + "cell_dict_store.to_geojson(\"cells.geojson\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "2UH6WdmipT54" + }, + "outputs": [], + "source": [ + "# Write to line delimited JSON (ndjson)\n", + "# Run time: ~10m\n", + "\n", + "cell_dict_store.to_ndjson(\"cells.ndjson\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "fw6wg5gapT54", + "outputId": "61a32277-fb8d-4bdc-be28-b379cb0a23eb" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "cells.ndjson : 40.82% ( 8.82 GiB => 3.60 GiB, cells.ndjson.zstd) \n" + ] + } + ], + "source": [ + "# Zstandard compression of ndjson to demonstrate how well it compresses.\n", + "# Gzip may also be used but is slower to compress.\n", + "# Run time: ~1m\n", + "! zstd -f -k cells.ndjson -o cells.ndjson.zstd" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "rzGC65zhpT55", + "outputId": "75ad772b-5641-4d64-ae16-7d50206e1b85" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "cells.db : 75.87% ( 4.87 GiB => 3.69 GiB, cells.db.zstd) \n" + ] + } + ], + "source": [ + "# Zstandard compression of sqlite to demonstrate how well it compresses.\n", + "# Gzip may also be used but is slower to compress.\n", + "# Run time: ~20s\n", + "! zstd -f -k cells.db -o cells.db.zstd" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "xT0KZLxdpT55" + }, + "outputs": [], + "source": [ + "# Write as a pickle (list)\n", + "# Run time: ~2m\n", + "with Path(\"cells.pickle\").open(\"wb\") as fh:\n", + " pickle.dump(cell_polygons, fh)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "-TAWGEu9pT55" + }, + "outputs": [], + "source": [ + "# Write as a pickle (dict)\n", + "# Run time: ~15m\n", + "with Path(\"cells-dict.pickle\").openI(\"wb\") as fh:\n", + " pickle.dump(cell_dict_store._rows, fh) # noqa: SLF001" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "I-W4o3GepT55" + }, + "outputs": [], + "source": [ + "# Write dictionary store to a pickle\n", + "# Run time: ~20m\n", + "with Path(\"cells.pickle\").open(\"wb\") as fh:\n", + " pickle.dump(cell_dict_store, fh)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "dALe8k0BpT55" + }, + "outputs": [], + "source": [ + "# Write as numpy object array (similar to writing out with pickle),\n", + "# Numpy cannot handle ragged arrays and therefore dtype must be object.\n", + "# Run time: ~30m\n", + "np.save(\"cells.npy\", np.asanyarray(cell_polygons_np, dtype=object))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "hOrGS0HgpT55" + }, + "outputs": [], + "source": [ + "# Create UUIDs, and get the class labels for each cell boundary\n", + "# Run time: ~2m\n", + "_uuids = [str(uuid.uuid4) for _ in cell_polygons]\n", + "_cls = [x.properties[\"class\"] for x in cell_polygons]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Fs2cz8lVpT55" + }, + "outputs": [], + "source": [ + "# Write as NumPy archive (.npz) with uuid and min_max_index\n", + "# Run time: ~40m\n", + "np.savez(\n", + " \"cells.npz\",\n", + " uuids=_uuids,\n", + " polygons=cell_polygons_np,\n", + " min_max_index=min_max_index,\n", + " cls=_cls,\n", + ")\n", + "\n", + "del _uuids, _cls" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "4gOTqc03pT55" + }, + "source": [ + "### 2.2.2) Time To Write Summary Statistics\n", + "\n", + "The following is a summary of the time required to write each format to\n", + "disk and the total disk space occupied by the final output.\n", + "\n", + "Note that some of these formats, such as GeoJSON compress well with\n", + "schemes such as gzip and zstd, reducing the disk space by approximately\n", + "half. Statistics for zstd compressed data is also reported below. It\n", + "should be noted that the data must be decompressed to be usable.\n", + "However, for gzip and zstd, this may be done in a streaming fashion from\n", + "disk.\n", + "\n", + "| Format | Write Time | Size |\n", + "| ----------------: | ---------: | -----: |\n", + "| SQLiteStore (.db) | 33m 48.4s | 4.9 GB |\n", + "| GeoJSON | 11m 32.9s | 8.9 GB |\n", + "| ndjson | 9m 0.9s | 8.8 GB |\n", + "| pickle | 1m 2.9s | 1.8 GB |\n", + "| zstd (SQLite) | 18.2s | 3.7 GB |\n", + "| zstd (ndjson) | 43.7s | 3.6 GB |\n", + "| NumPy (.npy) | 50.3s | 1.8 GB |\n", + "| NumPy (.npz) | 55.3s | 2.6 GB |\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "wS3sGpnWpT55" + }, + "source": [ + "### 2.2.3) Box Query\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "MKvKfkyvpT55" + }, + "outputs": [], + "source": [ + "# Run time: ~5m\n", + "\n", + "# Setup\n", + "xmin, ymin, xmax, ymax = 128, 12, 256, 256\n", + "box = Polygon.from_bounds(xmin, ymin, xmax, ymax)\n", + "\n", + "\n", + "# Time DictionaryStore\n", + "dict_runs = timeit.repeat(\n", + " \"store.query(box)\",\n", + " globals={\"store\": cell_dict_store, \"box\": box},\n", + " number=1,\n", + " repeat=3,\n", + ")\n", + "\n", + "# Time SQLite store\n", + "sqlite_runs = timeit.repeat(\n", + " \"store.query(box)\",\n", + " globals={\"store\": cell_sqlite_store, \"box\": box},\n", + " number=1,\n", + " repeat=3,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "0Yo14C3kpT55", + "outputId": "764bc28b-3072-4887-ea88-4c88ffcefb5f" + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Plot results\n", + "plot_results(\n", + " experiments=[dict_runs, sqlite_runs],\n", + " title=\"Box Query (5 Million Polygons)\",\n", + " tick_label=[\n", + " \"DictionaryStore\",\n", + " \"SQLiteStore\",\n", + " ],\n", + ")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ExF-fOGQpT56" + }, + "source": [ + "### 2.2.4) Polygon Query\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "PcxKapqNpT56" + }, + "outputs": [], + "source": [ + "# Run Time: 35s\n", + "\n", + "# Setup\n", + "big_triangle = Polygon(\n", + " shell=[ # noqa: S604\n", + " (1024, 1024),\n", + " (1024, 4096),\n", + " (4096, 4096),\n", + " (1024, 1024),\n", + " ],\n", + ")\n", + "\n", + "\n", + "# Time DictionaryStore\n", + "dict_runs = timeit.repeat(\n", + " \"store.query(polygon)\",\n", + " globals={\"store\": cell_dict_store, \"polygon\": big_triangle},\n", + " number=1,\n", + " repeat=3,\n", + ")\n", + "\n", + "# Time SQLite store\n", + "sqlite_runs = timeit.repeat(\n", + " \"store.query(polygon)\",\n", + " globals={\"store\": cell_sqlite_store, \"polygon\": big_triangle},\n", + " number=1,\n", + " repeat=3,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "vqHA50DQpT56", + "outputId": "7e837f4c-ada9-400f-b5f3-c59430b137f3" + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Plot results\n", + "plot_results(\n", + " experiments=[dict_runs, sqlite_runs],\n", + " title=\"Polygon Query (5 Million Polygons)\",\n", + " tick_label=[\n", + " \"DictionaryStore\",\n", + " \"SQLiteStore\",\n", + " ],\n", + ")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "6m-E5AwapT56" + }, + "source": [ + "### 2.2.5) Predicate Query\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "whEn34rOpT56" + }, + "outputs": [], + "source": [ + "# Run Time: ~10m\n", + "\n", + "# Setup\n", + "xmin, ymin, xmax, ymax = 128, 12, 256, 256\n", + "box = Polygon.from_bounds(xmin, ymin, xmax, ymax)\n", + "predicate = \"props['class'] == 0\"\n", + "\n", + "# Time DictionaryStore\n", + "dict_runs = timeit.repeat(\n", + " \"store.query(box, predicate)\",\n", + " globals={\"store\": cell_dict_store, \"box\": box, \"predicate\": predicate},\n", + " number=1,\n", + " repeat=3,\n", + ")\n", + "\n", + "# Time SQLiteStore\n", + "sqlite_runs = timeit.repeat(\n", + " \"store.query(box, where=predicate)\",\n", + " globals={\"store\": cell_sqlite_store, \"box\": box, \"predicate\": predicate},\n", + " number=1,\n", + " repeat=3,\n", + ")\n", + "\n", + "np_stmt = f\"\"\"\n", + "polygons = [\n", + " polygon\n", + " for polygon in tqdm(cell_polygons_np)\n", + " if np.all([\n", + " np.max(polygon, 0) >= ({xmin}, {ymin}), np.min(polygon, 0) <= ({xmax}, {ymax})\n", + " ])\n", + "]\n", + "\"\"\"\n", + "\n", + "# Time numpy\n", + "numpy_runs = timeit.repeat(\n", + " np_stmt,\n", + " globals={\"cell_polygons_np\": cell_polygons_np, \"np\": np, \"tqdm\": lambda x: x},\n", + " number=1,\n", + " repeat=3,\n", + ")\n", + "\n", + "# Time shapely\n", + "shapely_runs = timeit.repeat(\n", + " \"polygons = [box.intersects(ann.geometry) for ann in cell_polygons]\",\n", + " globals={\"box\": box, \"cell_polygons\": cell_polygons},\n", + " number=1,\n", + " repeat=3,\n", + ")\n", + "\n", + "# Time box indexed numpy\n", + "numpy_index_runs = timeit.repeat(\n", + " \"in_box = np.all(min_max_index[:, :2] <= (xmax, ymax), 1) \"\n", + " \"& np.all(min_max_index[:, 2:] >= (xmin, ymin), 1)\\n\"\n", + " \"polygons = [p for p, w in zip(cell_polygons, in_box) if w]\",\n", + " globals={\n", + " \"min_max_index\": min_max_index,\n", + " \"xmin\": xmin,\n", + " \"ymin\": ymin,\n", + " \"xmax\": xmax,\n", + " \"ymax\": ymax,\n", + " \"np\": np,\n", + " \"cell_polygons\": cell_polygons,\n", + " },\n", + " number=1,\n", + " repeat=3,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "oRxJTg7BpT56", + "outputId": "d235e51a-5109-486e-b779-fe39e5f6ee33" + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAZMAAAF2CAYAAACrlXVQAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAsTAAALEwEAmpwYAABZaUlEQVR4nO3dd3hUZfrw8e+Znl5JgNBBSmApIdI7UbCtgIi7NpR3VdBVUdbG6q67LisWwFXEBqhYWFd/YEFYFKRJh4j03iGQXiaT6ef9I2aWIQkEk0zL/bkuLplzzpy5n0PMPU9XVFVVEUIIIWpB4+8AhBBCBD9JJkIIIWpNkokQQohak2QihBCi1iSZCCGEqDVJJkIIIWpNkokQQohak2QiGox77rkHRVE8f2JiYujbty9Lly71yec7HA5efvllunbtSlhYGNHR0QwePJhFixb55POFqE+STESDMnDgQLKyssjKymLTpk2kpaUxatQojhw5Uq+f63A4uO6665gxYwaTJ09m7969bNq0iWHDhnHbbbfx/PPP1+vnV7Db7T75HNEAqUI0EOPHj1eHDx/uday4uFgF1EWLFnkdu//++9XExETVaDSqPXv2VJcvX66qqqparVa1e/fu6s033+y53mKxqJ07d1bHjRtX7WfPmDFDBdRNmzZVOjd9+nRVURR127Ztqqqq6qpVq1RAPXXqlNd1Wq1Wff/99z2vz507p44fP15NTExUIyMj1X79+qlr1qzxnK+4z5IlS9T+/furRqNRff3119XIyEj1k08+8br3sWPHVEVR1FWrVlVbBiEuRWomosGy2+289957GI1G0tLSPMcnTJjA8uXL+fjjj/npp5/o378/N954I/v378doNPLZZ5+xcuVKZs+eDcAjjzyCxWLh3XffrfazPvroI4YPH07v3r0rnXv00UcJCwvjk08+qXHsZWVlDB06lJKSEpYtW8ZPP/3E9ddfzzXXXMO+ffu8rp0yZQpPPvkk+/btY/To0dx+++289957XtfMmzePdu3aMXjw4BrHIIQXf2czIXxl/PjxqlarVSMiItSIiAhVURQ1IiJC/eyzzzzXHDp0SAXUb7/91uu9PXr0UO+9917P6w8++EA1Go3qc889p+r1enXz5s2X/OywsDD1kUceqfb8b37zG/X6669XVbVmNZP3339fTUlJUR0Oh9c1Q4cOVR999FGv+yxYsMDrmu3bt6uAevDgQVVVVdXpdKrNmjVTX3755UuWQYhL0fk3lQnhW7179+bDDz8EwGw289133zF+/HhiYmIYMWIEe/fuBWDQoEFe7xs0aBAbN270vB4/fjxLly7lhRdeYPr06fTq1avWsen1+hpfu3XrVs6dO0dsbKzXcZvNRlhYmNexi2NLS0sjPT2duXPn8tJLL7Fs2TLOnz/P+PHjf3XsQkgyEQ1KWFgY7dq187zu3r07K1euZNq0aYwYMaLa96mqiqIontdms5nMzEy0Wi0HDx687Od26NCB3bt3V3nOarVy5MgRRo4cCYBGo/F8ZgWXy4Xb7fa8drvddOrUicWLF1e6X3h4uNfriIiIStdMnDiRqVOn8o9//IO5c+cyatQokpKSLlsOIaojfSaiwdPpdFgsFgA6d+4MwNq1a72uWbduneccwKRJk9Bqtfzwww98/PHH/Pvf/77kZ9x111388MMPbN68udK5f/3rX5SVlXH33XcDeH6pnz171nPNjh07vJJLeno6R48eJTo6mnbt2nn9adq06WXL/Lvf/Q6r1co777zDt99+y3333XfZ9whxSX5uZhPCZ8aPH68OHDhQzcrKUrOystTDhw+rb775pqrVatV//OMfnutuvfVWtWXLlup///tfdd++feojjzyi6vV6dd++faqqqupHH32kGo1G9aefflJVVVVfffVVNTo6Wj169Gi1n22329Xhw4erSUlJ6vz589WjR4+qe/fuVZ9//nlVp9Op06dP91zrcDjUli1bqiNHjlT37dunrlu3Th04cKCqKIqnz6SsrEzt3Lmzmp6eri5fvlw9duyYumnTJvWf//ynunjxYlVVq+97qfDggw+qBoNBbdOmjep2u2vxZIVQVUkmosEYP368Cnj+hIWFqampqeorr7yiulwuz3VFRUWeocEGg8FraPChQ4fUqKgo9fXXX/dc73a71ZEjR6q9evVS7XZ7tZ9vs9nU6dOnq126dFGNRqMKqBqNRv36668rXbtp0yY1LS1NNZlMateuXdW1a9dWGhqcm5urTpw4UW3atKmq1+vVpk2bqqNGjVIzMzNVVb18MtmxY4cKqP/85z+v6DkKURVFVWWnRSH84ciRIwwfPpz27dvz9ddfYzKZfPr5S5cuZdSoUZw8eZLGjRv79LNF6JE+EyH8pG3btqxbt47+/ft7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KwwdOpSePXtetmnv5ZdfJjw8nDZt2ngl21AhySRANKTtXs+ePVspkUB5/9DChQuDuqnkpptu8vy9rKyMpUuXsmrVKvr16+d17kLR0dGePoNg9Nhjj5GQkOCpVa9evZrNmzfTqFGjalcCCPYyV7iSfWvy8/P585//XM8R+Y8kkwCRmppKYWGhZ9RPu3btQnaRwOr2gHC73RgMhqAvt9lsZsmSJaxbt47Bgwfz0ksvVdnUU2HcuHG8/fbbdOnSxesba7Csnvzee+/x3HPPAeWLHS5cuJB7772X48eP88477zBlypRK7wn2Mleo2LemW7duXi0KVW0d0b59e8+uoqFIkkmA2LBhAx9//LFnW9f58+dz1113heSquj179uTtt9/mnnvuwWQyAeXDJj/88EN69Ojh5+hq56OPPmLLli0MHz6cGTNmeMp3KatWreLs2bM4nU6vppJg+cXqdrs9yXLDhg0MHz6cPn360KdPH5544okq3xPsZa5wJfvW7N+/n9WrV5OUlIRer0dVVRRFCbp+oupIMgkQixcv5sUXX/R8Ky8uLuaFF14IyWRy5513snDhQh566CESExNRFIWcnBwGDx7M7bff7u/wamXJkiXodDoWLVrE4sWLPccrfnFUtZzKiRMnmDFjhi/DrFNutxuXy4VWq2X37t3cf//9XueqEuxlrrBlyxZmz55do31rKrYoDlWSTAKE2+32at6JjIwM2S1Bjx8/zo033shtt93GuXPn2L17N5mZmTidTqxW6yWbhALdZ599dsXvueqqqzh9+vRld+oLVP379+f5558nKioKg8FAp06dgPKdBasb/hrsZa7QsmVLSktLa9Q0G8wDDWpCkkmA6N69O9OmTaN///5AeXNBsDf5VKeijd1gMGA2m/nqq68u28Yeyg4cOMCaNWuCtvljzJgxdOnShcLCQs+WvVD+Benee++t8j3BXuYKNdm3psKLL77oGfXmcDjIzs6madOmzJw505ch1xuZtBhANm3axIEDB1BVldTUVHr16uXvkOrFE088wSuvvALA3LlziY6O9oz6ufBcQ5GTk1Pl8Yo1nUJRqJT54j1LKlT0fV7K0aNHWbFihVezYDCTmkkAqei0DHW/po09FFksFsLDw70WPAx1oVbmmiSN6rRp0+aSa7YFG0kmfvbcc8/xwgsveO1/AZfusA12v6aNPRS9/vrr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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Run Time: ~5s\n", + "\n", + "# Plot results\n", + "plot_results(\n", + " experiments=[dict_runs, sqlite_runs, numpy_runs, shapely_runs, numpy_index_runs],\n", + " title=\"Box Query\",\n", + " tick_label=[\n", + " \"DictionaryStore\",\n", + " \"SQLiteStore\",\n", + " \"NumPy\\n(Simple Loop)\",\n", + " \"Shapely\\n(Simple Loop)\",\n", + " \"NumPy\\n(With Bounds Index)\",\n", + " ],\n", + ")\n", + "plt.xticks(rotation=90)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "LJiGGkespT56" + }, + "source": [ + "## 2.3) Size vs Approximate Lower Bound\n", + "\n", + "Here we calculate an estimated lower bound on file size by finding the\n", + "the Shannon entropy of each file. This tells us the theoretical minimum\n", + "number of bits per byte. The lowest lower bound is then used as an\n", + "estimate of the minimum file size possible to store the annotation data.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "0IO10faZpT56", + "outputId": "033c2530-072a-4aa5-cf34-c2298e90d86f" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " " + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Approximate Lower Bound Size: 3.60 GB\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\r" + ] + } + ], + "source": [ + "# Run Time: ~5m\n", + "\n", + "\n", + "# Files to consider containing keys, geometry, and properties.\n", + "# Files which are missing keys e.g. cells.pickle are excluded\n", + "# for a fair comparison.\n", + "file_names = [\n", + " \"cells-dicionary-store.pickle\",\n", + " \"cells-dict.pickle\",\n", + " \"cells.db\",\n", + " \"cells.db.zstd\",\n", + " \"cells.geojson\",\n", + " \"cells.ndjson\",\n", + " \"cells.ndjson.zstd\",\n", + "]\n", + "\n", + "\n", + "def human_readible_bytes(byte_count: int) -> tuple[int, str]:\n", + " \"\"\"Convert bytes to human readble size and suffix.\"\"\"\n", + " byte_count_ref = 1024\n", + " for suffix in [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"]:\n", + " if byte_count < byte_count_ref:\n", + " return byte_count, suffix\n", + " byte_count /= byte_count_ref\n", + " return byte_count, \"PB\"\n", + "\n", + "\n", + "def shannon_entropy(\n", + " fp: Path,\n", + " sample_size: int = 1e9, # 1GiB\n", + " stride: int = 7,\n", + " skip: int = 1e5, # 100KiB\n", + ") -> float:\n", + " \"\"\"Calculate the Shannon entropy of a file from a sample.\n", + "\n", + " The first `skip` bytes are skipped to avoid sampling low entropy\n", + " (highly ordered) parts which commonly occur at the beginning e.g.\n", + " headers.\n", + "\n", + " Args:\n", + " fp: File path to calculate entropy of.\n", + " sample_size: Number of bytes to sample from the file.\n", + " stride: Number of bytes to skip between samples.\n", + " skip: Number of bytes to skip before sampling.\n", + " \"\"\"\n", + " npmmap = np.memmap(Path(fp), dtype=np.uint8, mode=\"r\")\n", + " values, counts = np.unique(\n", + " npmmap[int(skip) : int(skip + (sample_size * stride)) : int(stride)],\n", + " return_counts=True,\n", + " )\n", + " total = np.sum(counts)\n", + " frequencies = {v: 0 for v in range(256)}\n", + " for v, x in zip(values, counts):\n", + " frequencies[v] = x / total\n", + " frequency_array = np.array(list(frequencies.values()))\n", + " epsilon = 1e-16\n", + " return -np.sum(frequency_array * np.log2(frequency_array + epsilon))\n", + "\n", + "\n", + "# Find the min across all of the representations for the lowest lower\n", + "# bound.\n", + "bytes_lower_bounds = {\n", + " path: (\n", + " shannon_entropy(Path(path)) / 8 * len(np.memmap(path, dtype=np.uint8, mode=\"r\"))\n", + " )\n", + " for path in tqdm(\n", + " [Path.cwd() / name for name in file_names],\n", + " position=0,\n", + " leave=False,\n", + " )\n", + "}\n", + "\n", + "lowest_bytes_lower_bound = min(bytes_lower_bounds.values())\n", + "\n", + "size, suffix = human_readible_bytes(lowest_bytes_lower_bound)\n", + "logger.info(\"Approximate Lower Bound Size: %2f %s\", size, suffix)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "chwB3zeupT56" + }, + "source": [ + "### Plot Results\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "cu5jkrVppT56", + "outputId": "bb36aea5-d5d7-4560-a853-d2a8afba0eac" + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Get file sizes\n", + "file_sizes = {\n", + " path: path.stat().st_size for path in [Path.cwd() / name for name in file_names]\n", + "}\n", + "\n", + "# Sort by size\n", + "file_sizes = dict(sorted(file_sizes.items(), key=lambda x: x[1]))\n", + "\n", + "# Plot\n", + "plt.bar(\n", + " x=range(len(file_sizes)),\n", + " height=file_sizes.values(),\n", + " tick_label=[p.name for p in file_sizes],\n", + " color=[f\"C{i}\" for i in range(len(file_sizes))],\n", + ")\n", + "plt.xlabel(\"File Name\")\n", + "plt.ylabel(\"Bytes\")\n", + "plt.xticks(rotation=90)\n", + "plt.hlines(\n", + " y=lowest_bytes_lower_bound,\n", + " xmin=-0.5,\n", + " xmax=len(file_sizes) - 0.5,\n", + " linestyles=\"dashed\",\n", + " color=\"black\",\n", + " label=\"Approximate Bytes Lower Bound\",\n", + ")\n", + "plt.legend()\n", + "plt.tight_layout()\n", + "plt.title(\"Polygon Annotation File Sizes\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "gmuEWlImpT57" + }, + "source": [ + "The SQLite representation (4.9GB) appears to be quite compact compared\n", + "with GeoJSON and ndjson. Although not as compact as a dictionary pickle\n", + "or Zstandard compressed ndjson, it offers a good compromise between\n", + "compactness and read performance.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Yhe5rMXPpT57" + }, + "source": [ + "# 3: Extra Bits\n", + "\n", + "## 3.1) Space Saving\n", + "\n", + "A lot of space can be saved by rounding the coordinates to the nearest\n", + "integer when storing them. Below we make a copy of the dataset with all\n", + "coordinates rounded.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "H2Jsc0repT57", + "outputId": "d2ca9eff-b67d-4bfc-ad5a-57c87bc6a7da" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 10008338/10008338 [51:00<00:00, 3270.16it/s] \n" + ] + } + ], + "source": [ + "# Run Time: ~50m\n", + "! rm integer-cells.db\n", + "int_cell_sqlite_store = SQLiteStore(\"integer-cells.db\")\n", + "\n", + "# We use batches of 1000 to speed up appending\n", + "batch = {}\n", + "batch_size = 1000\n", + "for key, annotation in tqdm(cell_sqlite_store.items(), total=len(cell_sqlite_store)):\n", + " geometry = Polygon(np.array(annotation.geometry.exterior.coords).round())\n", + " rounded_annotation = Annotation(geometry, annotation.properties)\n", + " batch[key] = rounded_annotation\n", + " if len(batch) >= batch_size:\n", + " int_cell_sqlite_store.append_many(batch.values(), batch.keys())\n", + " batch = {}\n", + "_ = int_cell_sqlite_store.append_many(batch.values(), batch.keys())" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "U6aooIROpT57" + }, + "source": [ + "Here the database size is reduced to 2.9GB, down from 4.9GB.\n", + "Additionally, when using integer coordinates, the database compresses\n", + "much better. Zstandard can compress to approximately 60% of the\n", + "original size (and 35% of the floating point coordinate\n", + "database size). This may be done for archival purposes.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Q3TJ8XX4pT57", + "outputId": "b99d1af7-4c68-4394-cf9a-8bb2b64471a0" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "integer-cells.db : 60.58% ( 2.86 GiB => 1.73 GiB, integer-cells.db.zstd) \n" + ] + } + ], + "source": [ + "# Run time: ~15s\n", + "! zstd -f -k integer-cells.db -o integer-cells.db.zstd" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "alFRiIAbpT57" + }, + "source": [ + "With higher (slower) compression settings the space can be further\n", + "reduced for long term storage.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "nVFqovfPpT57", + "outputId": "0948bbe6-4252-4c93-eab7-8e3be4e98235" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "integer-cells.db : 51.22% ( 2.86 GiB => 1.47 GiB, integer-cells.db.19.zstd) \n" + ] + } + ], + "source": [ + "# Run time: ~20m\n", + "! zstd -f -k -19 --long integer-cells.db -o integer-cells.db.19.zstd" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "C3voJ43OpT57" + }, + "source": [ + "## 3.2) Feature Comparison Summary\n", + "\n", + "Here we briefly summarise some of the positives and negatives of each format and construct a comparison matrix.\n", + "\n", + "**GeoJSON**\n", + "\n", + "*Positives*\n", + "\n", + "- Simple, based JSON which is well known.\n", + "- Well defined with a public specification.\n", + "- Popular format for geometry, many tools which work with it.\n", + "- Fast to write.\n", + "\n", + "*Negatives*\n", + "\n", + "- Requires loading the whole file into memory for parsing. Some\n", + " specialised parsers can, in some situations, reduce or avoid this but\n", + " it is not possible in general.\n", + "- Not a very compact representation.\n", + "\n", + "**ndjson (One GeoJSON Feature Per Line)**\n", + "\n", + "*Positives*\n", + "\n", + "- Simple.\n", + "- Better to parse than JSON/GeoJSON. Each line can be parsed\n", + " independently.\n", + "- Many tools to parse JSON lines.\n", + "- Fast to write.\n", + "\n", + "*Negatives*\n", + "\n", + "- Not a very compact representation.\n", + "- Requires loading the whole dataset from disk before querying OR\n", + " scanning through and reparsing each line for each query.\n", + "- Amending annotations can be tricky. The easiest way is to blank out a\n", + " line and append a modified copy each time. This could end up\n", + " fragmenting the file and wasting a lot of space. More complex methods\n", + " could be developed to reduce fragmenting the file.\n", + "\n", + "**pickle**\n", + "\n", + "*Positives*\n", + "\n", + "- Fast to write.\n", + "\n", + "*Negatives*\n", + "\n", + "- Vulnerable to arbitrary code execution when loading from disk.\n", + "- Requires loading the whole dataset into memory for querying.\n", + "\n", + "**SQLite (SQLiteStore Flavour)**\n", + "\n", + "*Positives*\n", + "\n", + "- Very fast to query (uses an R-TREE index to accelerate\n", + " spatial queries).\n", + "- Does not require loading data into memory before querying.\n", + "- Possible to index property lookups.\n", + "\n", + "*Negatives*\n", + "\n", + "- Not the most compact representation on disk.\n", + "\n", + "### Feature Matrix\n", + "\n", + "| Format | Size On-Disk | Size In-Memory | Partial Reads | Serialization | Query Performance |\n", + "| ----------: | :----------- | :------------- | :------------ | :------------ | :---------------- |\n", + "| SQLiteStore | Medium | Small | Yes | Slow | Fast |\n", + "| GeoJSON | Large | Large | No | Fast | Slow |\n", + "| ndjson | Large | Large | Yes | Fast | Medium |\n", + "| pickle | Small | Medium | No | Medium | Slow |\n", + "\n" ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "for n in range(4):\n", - " display(cell_polygon(xy=(0, 0), n_points=20, repeat_first=False, seed=n))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "APUNL2PtpT5w" - }, - "source": [ - "### Randomised Cell Boundaries\n", - "\n", - "Here we create a function to generate grid of cells for testing. It uses a fixed seed for reproducibility.\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "SOpBKM7IpT5w" - }, - "source": [ - "### A Sample 5×5 Grid\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "2xA-oG4VpT5w", - "outputId": "caea51e4-8a27-4dd1-ed0d-c272b93d8bb7" - }, - "outputs": [ - { - "data": { - "image/svg+xml": "", - "text/plain": [ - "" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "MultiPolygon(polygons=list(cell_grid(size=(5, 5), spacing=35)))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "b6S8vzFipT5w" - }, - "source": [ - "# Part 1: Small Scale Benchmarking of Annotation Storage\n", - "\n", - "Using the already defined data generation functions (`cell_polygon` and\n", - "`cell_grid`), we create some simple artificial cell boundaries by\n", - "creating a circle of points, adding some noise, scaling to introduce\n", - "eccentricity, and then rotating. We use 20 points per cell, which is a\n", - "reasonably high value for cell annotation. However, this can be\n", - "adjusted.\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "UZMoLDvkpT5x" - }, - "source": [ - "## 1.1) Appending Annotations (In-Memory & Disk I/O)\n", - "\n", - "Here we test:\n", - "\n", - "1. A python dictionary based in-memory store (`DictionaryStore`)\n", - "1. An SQLite database based in-memory store (`SQLiteStore`)\n", - "\n", - "Both of these stores may operate in memory. The `SQLiteStore` may also\n", - "be backed by an on-disk file for datasets which are too large to fit in\n", - "memory. The `DictionaryStore` class can serialise/deserialise itself\n", - "to/from disk in a line delimited GeoJSON format (each line seperated\n", - "by `\\n` is a valid GeoJSON object)\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "DZBiw_EepT5x" - }, - "outputs": [], - "source": [ - "# Convert to annotations (a dataclass pairing a geometry and (optional)\n", - "# key-value properties)\n", - "# Run time: ~2s\n", - "annotations = [\n", - " Annotation(polygon) for polygon in cell_grid(size=(100, 100), spacing=35)\n", - "]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "LUVa03F2pT5x" - }, - "source": [ - "### 1.1.1) In Memory Append\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "7PzE7AhdpT5x", - "outputId": "974bb3d0-3290-4315-a6fc-3b7ca90072a6" - }, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Run time: ~5s\n", - "\n", - "# Time dictionary store\n", - "dict_runs = timeit.repeat(\n", - " \"dict_store.append_many(annotations)\",\n", - " setup=\"dict_store = DictionaryStore()\",\n", - " globals={\"DictionaryStore\": DictionaryStore, \"annotations\": annotations},\n", - " number=1,\n", - " repeat=3,\n", - ")\n", - "\n", - "# Time SQLite store\n", - "sqlite_runs = timeit.repeat(\n", - " \"sql_store.append_many(annotations)\",\n", - " setup=\"sql_store = SQLiteStore()\",\n", - " globals={\"SQLiteStore\": SQLiteStore, \"annotations\": annotations},\n", - " number=1,\n", - " repeat=3,\n", - ")\n", - "\n", - "# Plot the results\n", - "plot_results(\n", - " experiments=[dict_runs, sqlite_runs],\n", - " title=\"Time to Append 10,000 Annotations In Memory\",\n", - " tick_label=[\"DictionaryStore\", \"SQLiteStore\"],\n", - ")\n", - "plt.hlines(0.5, -0.5, 1.5, linestyles=\"dashed\", color=\"k\")\n", - "plt.xlim([-0.5, 1.5])\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gU6PLE7wpT5x" - }, - "source": [ - "Note that inserting into the `SQLiteStore` is much slower than the\n", - "`DictionaryStore`. Appending to a `Dictionary` store simply requires\n", - "adding a memory reference to a dictionary. Therefore, this is a very\n", - "fast operation. On the other hand, for the `SQLiteStore`, the insertion\n", - "is slower because the data must be serialised for the database and the\n", - "R-Tree spatial index must also be updated. Updating the index is a\n", - "relatively expensive operation. However, this spatial index allows for\n", - "very fast queries of a very large set of annotations within a set of\n", - "spatial bounds.\n", - "\n", - "Insertion is typically only performed once for each\n", - "annotation, whereas queries may be performed many times on the\n", - "annotation set. Therefore, it makes sense to trade a more expensive\n", - "insertion for fast queries as the cost of insertion will be amortised\n", - "over a number of queries on the data. Additionally, data may be written\n", - "to the database from multiple threads or subprocesses (so long as a new\n", - "instance of `SQLiteStore` is created for each thread or subprocess to\n", - "attach to a database on disk) thus freeing up the main thread.\n", - "\n", - "For comparison, we also compare bulk insertion plus seralising to disk\n", - "as line-delimited GeoJSON from the `DictionaryStore` as this is the\n", - "default serialisation to disk method (`DictionaryStore.dump(file_path`).\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "t2q9QTCfpT5x", - "outputId": "2202c328-ba48-476b-8efa-662678d75135" - }, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Run time: ~10s\n", - "\n", - "setup = \"fp.truncate(0)\\nstore = Store(fp)\" # Clear the file\n", - "\n", - "# Time dictionary store\n", - "with tempfile.NamedTemporaryFile(\"w+\") as fp:\n", - " dict_runs = timeit.repeat(\n", - " (\"store.append_many(annotations)\\nstore.commit()\"),\n", - " setup=setup,\n", - " globals={\"Store\": DictionaryStore, \"annotations\": annotations, \"fp\": fp},\n", - " number=1,\n", - " repeat=3,\n", - " )\n", - "\n", - "# Time SQLite store\n", - "with tempfile.NamedTemporaryFile(\"w+b\") as fp:\n", - " sqlite_runs = timeit.repeat(\n", - " (\"store.append_many(annotations)\\nstore.commit()\"),\n", - " setup=setup,\n", - " globals={\"Store\": SQLiteStore, \"annotations\": annotations, \"fp\": fp},\n", - " number=1,\n", - " repeat=3,\n", - " )\n", - "\n", - "# Plot the results\n", - "plot_results(\n", - " experiments=[dict_runs, sqlite_runs],\n", - " title=\"Time to Append & Serialise 10,000 Annotations To Disk\",\n", - " tick_label=[\"DictionaryStore\", \"SQLiteStore\"],\n", - ")\n", - "plt.hlines(0.5, -0.5, 1.5, linestyles=\"dashed\", color=\"k\")\n", - "plt.xlim([-0.5, 1.5])\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "LKr6FmctpT5x" - }, - "source": [ - "Here we can see that when we include the serialisation to disk in the\n", - "benchmark, the time to insert is much more similar.\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "V7WV8wNmpT5x" - }, - "source": [ - "## 1.2) Box Query\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "eul4PYZPpT5x", - "outputId": "a0131a72-f527-48b1-8aac-8cbccfced2ed" - }, - "outputs": [ - { - "data": { - "image/png": "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obZXX32VnTt3kpOTw4MPPsidd97Jddddx7Bhwxg/fjw+n48+ffpQv379qi5VRKTGqxbBM2bMmAtu79ChAx06dLC2GBERKVe1PdQmIiK/TgoeERGxlIJHREQspeARERFLKXhERMRSCh4REbGUgkdERCyl4BEREUspeERExFIKHhERsZSCR0RELKXgERERSyl4RETEUgoeERGxlIJHREQspeARERFLKXjKsWnTJqZPn17VZYiIVCvV4g6klSU5OZnk5OSqLkNEpFrRjEdERCyl4BEREUspeERExFIKHhERsZSCR0RELKXgERERSyl4RETEUgoeERGxlIJHREQspeARERFLKXhERMRSCh4REbGUgkdERCyl4BEREUspeERExFIKHhERsZSCR0RELKXgERERSyl4RETEUgoeERGxlKO8xuzsbFatWsWWLVs4fPgwbreb0NBQGjZsSPv27enduzeRkZFW1SoiItVAmcHzwQcfsHr1aq6++mquu+466tati8vlIj8/n+PHj7Nz506eeuopunfvzj333GNlzSIichkrM3hiYmKYOnUqQUFBpdoaN25M9+7dKSoqYtmyZZVaoIiIVC9lnuO5/vrrLxg6PxYcHMzAgQMveVG/Fps2bWL69OlVXYaISLVS7jme87Zv305CQgIJCQmcPXuW999/H8MwuPvuu4mOjq7kEqtOcnIyycnJVV2GiEi1UqFVbbNmzcIwSrq+++67eL1ebDabZgMiIvKTVWjGk5mZSXx8PF6vl++++4433ngDh8PByJEjK7s+ERGpZioUPC6Xi6ysLI4ePUq9evVwOp14PB48Hk9l1yciItVMhYJn4MCBjBs3Do/Hw9ChQwHYvXs3devWrczaRESkGqpQ8AwaNIhOnTphGAa1a9cGIDY2lgcffLBSixMRkeqnQsEDkJSUVO5jERGRiihzVdu4ceNYv359medxPB4P69at45lnnqm04kREpPopc8bz0EMPMX/+fGbOnEnjxo1JSkrC6XRSUFBAWloaBw4coG3btowePdrKekVE5DJnM03TLK9DVlYW33//PUeOHCEvL4+wsDAaNmxIu3btiIqKsqrOKnfixAnLx/Q+cLPlY4r17DMWVXUJNUZ8fDwZGRlVXUaNUdYpmYue44mOjqZnz56XvCAREamZdD8eERGxlIJHREQspeARERFLKXhERMRSFfoCqWmaLF26lLVr15KTk8Nf//pXdu7cSVZWFl27dq3sGkVEpBqp0Ixn/vz5LF++nL59+/qXIsbFxfHpp59WanEiIlL9VCh4Vq5cyVNPPUW3bt2w2WwAJCQkkJ6eXqnFiYhI9VOh4PH5fDidzoBtBQUFpbaJiIhcTIWC5+qrr+bdd9+luLgYKDnnM3/+fDp27FipxYmISPVToeC5//77yczMZOjQobjdbu6//35Onz7NPffcU9n1iYhINVOhVW2hoaE8+eSTZGVlkZGRQXx8PNHR0ZVcmoiIVEc/6Xs8wcHBxMbG4vP5yMzMJDMzs7LqEhGRaqpCM57vv/+et99+m9OnT5dqmz9//iUvSkREqq8KBc9bb73FbbfdRrdu3QgODq7smkREpBqrUPAUFxfTp08fDENX2BERkV+mQkly44038umnn3KRe8aJiIhcVIVmPNdeey3jx4/nk08+ISIiIqBt2rRplVLYr8GmTZvYvHkzI0eOrOpSRESqjQoFz+TJk2nZsiVdunSpUed4kpOTSU5OruoyRESqlQoFT3p6Oi+99JLO8YiIyC9WoSRJTk5m+/btlV2LiIjUABVe1TZx4kRatWpFVFRUQNvDDz9cKYWJiEj1VKHgqV+/PvXr16/sWkREpAaoUPDccccdlV2HiIjUEGUGz86dO2ndujVAued32rZte+mrEhGRaqvM4Jk1axaTJk0C4M0337xgH5vNVq2/xyMiIpeezSzncgRr1qyhe/fuVtbzq3XixAnLx/Q+cLPlY4r17DMWVXUJNUZ8fDwZGRlVXUaNkZSUdMHt5S6nnjFjRqUUIyIiNVe5waNrs4mIyKVW7qo2n8930S+OanGBiIj8FOUGT3FxMW+99VaZMx8tLhARkZ+q3OBxOp0KFhERuaR01U8REbGUFheIiIilyg2ed99916o6RESkhtChNhERsZSCR0RELKXgERERSyl4RETEUgoeERGxlIJHREQspeARERFLKXhERMRSCh4REbGUgkdERCyl4BEREUspeERExFLl3o+nOjt16hQLFy7E7XYzduzYqi5HRKTGsCx48vLyeOuttzh69Cg2m41Ro0bRvHnzn7yfN954gy1bthAVFcWkSZMC2rZt28Y777yDz+cjJSWFQYMGlbmfxMRERo0aVWofIiJSuSwLnnfeeYf27dszduxYPB4PhYWFAe3nzp0jODgYl8vl33by5Elq164d0K93794MHDiQ119/PWC7z+dj1qxZ/PGPfyQuLo5x48aRnJyMz+fjgw8+COg7atQooqKiLvFPKCIiFWFJ8Ljdbnbt2sVDDz1UMqjDgcMROPTOnTv56quvGDduHMHBwXz99dds3LiRcePGBfRr3bo16enppcbYt28ftWvXJjExEYCuXbuyceNGbr31Vp5++umfVfemTZvYvHkzI0eO/FnPFxGR0iwJnvT0dCIjI3njjTc4fPgwTZo0YejQoTidTn+fLl26kJ6ezquvvkqXLl1Yvnw5f/rTnyo8RmZmJnFxcf7HcXFxpKamltk/JyeHDz/8kEOHDvHxxx9z6623luqTnJxMcnJyhWsQEZGLs2RVm9fr5eDBg/Tv35+JEycSEhLCJ598UqrfLbfcQnBwMDNnzuSpp54KCKaLudBtum02W5n9IyIiGDFiBK+99toFQ0dERCqHJcETFxdHXFwcV1xxBQCdO3fm4MGDpfrt2rWLo0ePcs011/D3v//9J49x5swZ/+MzZ84QExPzywoXEZFLzpLgiY6OJi4ujhMnTgDwww8/UK9evYA+Bw8eZPr06TzxxBOMHj2a3Nxc/va3v1V4jKZNm5KWlkZ6ejoej4d169bpMJmIyK+QzbzQMapKcOjQId566y08Hg8JCQmMHj2a8PBwf/vu3bsJDQ2lQYMGAHg8HlasWEHfvn0D9vPqq6+yc+dOcnJyiIqK4s477+S6664DYMuWLcydOxefz0efPn0YPHjwJav/fGhayfvAzZaPKdazz1hU1SXUGPHx8WRkZFR1GTVGUlLSBbdbFjyXOwWPVBYFj3UUPNYqK3h0yRwREbGUgkdERCyl4BEREUspeERExFIKHhERsZSCR0RELKXgERERSyl4RETEUgoeERGxlIJHREQspeARERFLKXhERMRSCh4REbGUgkdERCyl4BEREUspeERExFIKHhERsZSCR0RELKXgERERSyl4RETEUgoeERGxlIJHREQspeARERFLKXhERMRSCh4REbGUgkdERCyl4BEREUspeERExFIKHhERsZSCR0RELKXgERERSzmquoDqbv78+aW2tWjRgvbt21NcXMzChQtLtbdp04a2bduS7/XxWVpmqfZ2UWG0iHCRXezly1NnS7V3iA6nabiTzCIPS9OzSrV3io2gYWgI6YXFrDx9rlR7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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Run time: ~20s\n", - "\n", - "# One time Setup\n", - "dict_store = DictionaryStore()\n", - "sql_store = SQLiteStore()\n", - "dict_store.append_many(annotations)\n", - "sql_store.append_many(annotations)\n", - "\n", - "rng = np.random.default_rng(123)\n", - "boxes = [\n", - " Polygon.from_bounds(x, y, 128, 128) for x, y in rng.integers(0, 1000, size=(100, 2))\n", - "]\n", - "stmt = \"for box in boxes:\\n _ = store.query(box)\"\n", - "\n", - "# Time dictionary store\n", - "dict_runs = timeit.repeat(\n", - " stmt,\n", - " globals={\"store\": dict_store, \"boxes\": boxes},\n", - " number=1,\n", - " repeat=10,\n", - ")\n", - "\n", - "# Time SQLite store\n", - "sqlite_runs = timeit.repeat(\n", - " stmt,\n", - " globals={\"store\": sql_store, \"boxes\": boxes},\n", - " number=1,\n", - " repeat=10,\n", - ")\n", - "\n", - "# Plot the results\n", - "plot_results(\n", - " experiments=[dict_runs, sqlite_runs],\n", - " title=\"100 Box Queries\",\n", - " tick_label=[\"DictionaryStore\", \"SQLiteStore\"],\n", - ")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "z9ntCgKapT5x" - }, - "source": [ - "Here we can see that the `SQLiteStore` is a bit faster. Addtionally,\n", - "difference in performance is more pronounced when there are more\n", - "annotations (as we will see later in this notebook) in the store or when\n", - "just returning keys:\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "vfGH6e4upT5x", - "outputId": "7cf8bf30-a4c9-4de5-9a5f-f9fd6cffc141" - }, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Run time: ~15s\n", - "\n", - "# One time Setup\n", - "dict_store = DictionaryStore()\n", - "sql_store = SQLiteStore()\n", - "dict_store.append_many(annotations)\n", - "sql_store.append_many(annotations)\n", - "\n", - "rng = np.random.default_rng(123)\n", - "boxes = [\n", - " Polygon.from_bounds(x, y, 128, 128) for x, y in rng.integers(0, 1000, size=(100, 2))\n", - "]\n", - "stmt = \"for box in boxes:\\n _ = store.iquery(box)\" # Just return the keys (uuids)\n", - "\n", - "# Time dictionary store\n", - "dict_runs = timeit.repeat(\n", - " stmt,\n", - " globals={\"store\": dict_store, \"boxes\": boxes},\n", - " number=1,\n", - " repeat=10,\n", - ")\n", - "\n", - "# Time SQLite store\n", - "sqlite_runs = timeit.repeat(\n", - " stmt,\n", - " globals={\"store\": sql_store, \"boxes\": boxes},\n", - " number=1,\n", - " repeat=10,\n", - ")\n", - "\n", - "# Plot the results\n", - "plot_results(\n", - " experiments=[dict_runs, sqlite_runs],\n", - " title=\"100 Box Queries (Key Lookup Only)\",\n", - " tick_label=[\"DictionaryStore\", \"SQLiteStore\"],\n", - ")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "xVQlsK1MpT5y" - }, - "source": [ - "## 1.3) Polygon Query\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "fnkdnKWRpT5y", - "outputId": "03ccc35c-df96-4d68-9d53-72ac835a9088" - }, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Run time: ~15s\n", - "\n", - "# One time Setup\n", - "dict_store = DictionaryStore()\n", - "sql_store = SQLiteStore()\n", - "dict_store.append_many(annotations)\n", - "sql_store.append_many(annotations)\n", - "\n", - "rng = np.random.default_rng(123)\n", - "query_polygons = [\n", - " Polygon(\n", - " [\n", - " (x, y),\n", - " (x + 128, y),\n", - " (x + 128, y + 128),\n", - " (x, y),\n", - " ],\n", - " )\n", - " for x, y in rng.integers(0, 1000, size=(100, 2))\n", - "]\n", - "stmt = \"for polygon in query_polygons:\\n _ = store.query(polygon)\"\n", - "\n", - "# Time dictionary store\n", - "dict_runs = timeit.repeat(\n", - " stmt,\n", - " globals={\"store\": dict_store, \"query_polygons\": query_polygons},\n", - " number=1,\n", - " repeat=10,\n", - ")\n", - "\n", - "# Time SQLite store\n", - "sqlite_runs = timeit.repeat(\n", - " stmt,\n", - " globals={\"store\": sql_store, \"query_polygons\": query_polygons},\n", - " number=1,\n", - " repeat=10,\n", - ")\n", - "\n", - "# Plot the results\n", - "plot_results(\n", - " experiments=[dict_runs, sqlite_runs],\n", - " title=\"100 Polygon Queries\",\n", - " tick_label=[\"DictionaryStore\", \"SQLiteStore\"],\n", - ")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "1k1xOgB5pT5y" - }, - "source": [ - "Here we can see that performing queries within a polygon region is about\n", - "10x faster with the `SQLiteStore` than with the `DictionaryStore`.\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "iYFK95w1pT5y" - }, - "source": [ - "## 1.4) Predicate Query\n", - "\n", - "Here we query the whole annotation region but with a predicate to\n", - "select only annotations with the class label of 0. We also,\n", - "demonstrate how creating a database index can dramatically improve\n", - "the performance of queries.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "zNX4UG4BpT5y", - "outputId": "97444739-4aa5-42c7-bebc-84a022282ac7" - }, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYgAAAEmCAYAAAB4VQe4AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAsTAAALEwEAmpwYAAA6n0lEQVR4nO3dd3xUVcL/8c+UTGbSGwFC6B0UUFh6RxR7wy7iurZFBfcRVHT5waK4KiurKCpSBDsWXBGfZWUpIkWkGHov0gKkhzBpM3N+f2SZx5AbiC4hQL7v18uXzD3nnjlzL8x3zi3n2owxBhERkZPYq7oDIiJyblJAiIiIJQWEiIhYUkCIiIglBYSIiFhSQIiIiCUFhIiIWFJAyAVnzJgxNGnSpKq7UUpF+9SgQQOef/75s9Cjs693797cf//95b6Wc48C4gKxZMkSrr/+eurXr4/NZiv3S2blypV07doVt9tN7dq1GTlyJH6/v1Sd7du3c8UVVxAWFkZCQgIPP/wwx48fP20fjhw5wmOPPUaDBg1wuVzUqFGDgQMHkpKSciY+YoUNHz6cH3744ay+5+mc3Kfnn3+eBg0aVF2HgHvvvRebzYbNZsPpdFK/fn0efvhhMjIyzsr7z549mwkTJpyx9g4cOIDNZmPx4sVnrM3qTgFxgcjLy6NVq1a8/PLL1KpVy7LO/v376d+/P82bN2fNmjW89dZbTJ48mWeffbZUO/369cPpdLJ8+XI+/fRT5s2bxx/+8IdTvv/+/fvp0KEDy5cv56233mLnzp188803hISE0LlzZ+bNm3dGP6+VQCCA3+8nIiKChISESn+/X+Nc7BNAjx49SE1NZe/evUycOJEvvviCe+65x7KuMYbi4uIz9t5xcXFERUWdsfakEhi54NSvX98899xzZZaPHDnS1KlTx/j9/uCyN954w4SFhZm8vDxjjDGTJ082brfbZGdnB+vMnTvXAGb37t3lvue1115ratasaXJycsqUXXnllaZmzZrG6/UaY4wZPXq0ady4cak633//vQHMnj17gstWr15t+vfvb8LDw01CQoK58cYbzd69e4PlJ9r55JNPTPPmzY3D4TAbNmywbP/bb781Xbt2NW632yQlJZl7773XpKenB8s3btxoLr/8chMdHW3CwsJMixYtzHvvvVfu501OTjZTpkwJvr7nnnsMYHbs2BFcVq9ePTNp0qQyn/ndd981QKn/Ro8ebYwp2XejRo0yQ4cONbGxsSYxMdE88cQTxufzldsXY4x55plnTIsWLYzH4zHJycnmoYceKrUPrQwePNj069ev1LLnn3/e2O124/V6zbvvvmscDodZuHChadeunQkJCTFff/21KS4uNqNHjzYNGjQwoaGhplWrVubtt98u1c7evXvNFVdcYdxut6lbt66ZOHGi6dWrl/nDH/4QrHPya2NK/j62bNnSuFwuU6NGDXPzzTcHyz788EPTsWNHExUVZeLj481VV11ltm3bFiw/eZvWr18/WHa6/S/WNIKoRpYtW8bll1+O3f5/u33AgAF4vV5++umnYJ0uXboQHR0drHNinWXLllm2m5WVxTfffMOjjz5q+Ytw5MiRHDlyhPnz51e4r5s3b6ZXr1506dKF1atXs3DhQhwOB/3796egoCBY79ChQ7z55pvMmDGDzZs3U79+/TJtLVy4kOuvv57bb7+d9evX849//IO9e/dy4403Yv4zFdkdd9xBfHw8y5cvZ8OGDUyYMIHY2Nhy+9enTx8WLFgQfL1o0SJq1KgRXLZr1y727dtH3759y6x722238dRTT5GcnExqaiqpqakMHz48WP76669Tu3ZtVq5cycSJE3n11Vd57733Trm9PB4P77zzDps3b2bGjBksXryYoUOHnnKd8toJBAL4fD6gZFT25JNP8sorr7B161Y6derE/fffz+zZs5k8eTJbtmzh//2//8dTTz3FtGnTgJKRxo033khGRgaLFy9mzpw5zJkzh7Vr157yvUePHs1TTz3FkCFD2LBhA/PmzaNdu3bB8sLCQkaNGsXatWuZP38+DoeDq6++mqKiIoBg+1988QWpqamsWrUKqNj+l3JUcUBJJShvBNG0aVMzcuTIUsvy8vIMYD799FNjjDH9+/c3d9xxR5l1ExISzMsvv2z5fitXrjSAmT17tmV5RkaGAYLrV2QEMXjwYHPbbbeVqlNQUGA8Ho/58ssvg+3YbDbz888/l6p3cvu9evUyTz31VKk6P//8swHMTz/9ZIwxJioqyrz77ruW/bfy7rvvmsTERGOMMdu3bzcej8eMHTvW3HLLLcYYY9555x1Tu3btcvv03HPPlfqFe0L9+vXNtddeW2rZFVdcYW6//fYK980YY2bPnm1cLlep0eLJTh5BbNq0yTRq1Mh06tQp+BkBs2TJkmCd3bt3G5vNZrZs2VKqrb/85S+mbdu2xhhj5s+fb4BSv+6PHj1q3G53uSOIvLw843a7zfjx4yv8GU/8vVq6dKkxxpj9+/cbwCxatKhUvYrsf7HmrKpgknODzWYr9f+K1D2ZOc2vsBPrhYSEVLhfq1atYufOnURERJRaXlBQwI4dO4Kva9asSb169U7b1g8//MAbb7xRpmzHjh20a9eO4cOHc//99zNjxgx69+7Nddddx6WXXlpum/369ePo0aNs3LiRZcuW0b17dwYMGMDEiRMxxrBw4ULL0UNF/PJXM0CdOnXYs2fPKdeZPXs2r776Kjt37iQ3N5dAIEBRURGHDx8mKSmp3PUWL15MREQEfr+fwsJC+vXrx+TJk0vV+d3vfhf88+rVqzHG0KFDh1J1fD4fDocDKBn9JSQk0KxZs2B5jRo1aN68ebn92LRpEwUFBVx++eXl1klJSeEvf/kLKSkppKenB//e/fzzz3Tr1q3c9Sqy/8WaAqIaqV27NocPHy617MTrEye2a9euzf79+0vVKS4uJjMzs9yT382aNcNut7Nx40ZuvPHGMuUbN24M1gOw2+1lQuXkk5+BQIBBgwbx9NNPl2kvPj4++Ofw8HDLPp3c1lNPPcWgQYPKlJ34TKNGjeKuu+5i3rx5LFy4kBdeeIEnn3yy3KvB6tatS+PGjVmwYAHLly+nb9++tG/fHp/Px/r161m0aBEvvPDCaftmxeVylXpts9kIBALl1l+5ciW33HILI0eOZPz48cTGxvLDDz8wePDg4OGX8nTq1ImZM2fidDqpXbs2oaGhpcodDgdutzv4+kQ/li9fTlhYWJl+QskPhor84LBS3nper5fLL7+c7t27M3369OB+a9269Wk/Y0X2v1hTQFQj3bp14/333ycQCATPQ8ybN4+wsDAuueSSYJ1hw4aRm5sbPJ8wf/58AoFAub/SYmNjufrqq5k0aRLDhg0rcx7ihRdeICkpif79+wOQmJjI0aNH8fv9wV+dJx+f7tChA+vXr6dx48a/+cvml21t2rTptPchNGrUiCFDhjBkyBBefPFFxo8ff8p7Evr27cuCBQtYuXIlw4cPx26307NnT15//XWOHDlyyhGEy+Uqc3nxb7V06VISEhJK9fXzzz+v0Loej+dX3TPSvn17APbt28c111xjWad169akpaWxY8cOmjZtCkB6ejrbt28vM/I4oVWrVrjdbv71r39x8cUXlynfsmULaWlpjBs3jpYtWwIlIfXLHxongvXk7VrR/S9l6ST1BSIvL4+UlBRSUlKChxZSUlLYuXNnsM4f//hHcnJyeOCBB9i0aRNz5sxh1KhRPPbYY8Ff4nfeeScJCQnceeedrFu3jkWLFvHII49w22230bBhw3Lff9KkSTidTvr27cu8efPYv38/q1at4s4772TRokV89NFHwUNMffr0wev1MmrUKHbt2sVnn33GpEmTSrX3zDPPsGXLFu6++25+/PFH9uzZw6JFixg2bBi7d+/+Vdtm7NixfPXVV/zpT38iJSWFXbt2BS/dzc/PJy8vj0ceeYSFCxeyZ88efvrpJ+bNm0erVq1O2W7fvn355z//SWFhYfBwVN++fZk5cyYNGzY85X0ODRs25PDhw6xYsYL09HS8Xu+v+ky/1Lx5c9LS0pg2bRq7d+/mvffe48033/zN7Z1KkyZNuO+++3jggQd4//332blzJ+vWrWP69Om89NJLQMnht7Zt2wb3XUpKCnfddRdOZ/m/RyMiInjiiScYM2YMkyZNYvv27axbt46//vWvANSvX5/Q0FBef/11du3axYIFCxg2bFipHw8JCQlERETw7bffcvjwYbKysoDT7385hSo8/yFn0KJFi8pc5geYXr16laq3YsUK06VLFxMaGmpq1qxpnn766TKXUG7dutX079/feDweExcXZx588MHgZbCncvjwYfPII4+YevXqGYfDYQCTlJRktm/fXqbutGnTTMOGDY3b7TYDBgwwH3/8cZnLXNevX2+uu+46ExMTY9xut2ncuLF54IEHTEZGhjHG+mR3ecuXLFli+vXrZyIiIoKXsQ4bNswUFxeb/Px8c8cddwQv26xRo4a59dZbzb59+075eY8cOWJsNpu57rrrSvUZKHP55sl9KioqMnfccYeJjY0tc5nryRcY/OEPfyizH0/25z//2SQmJpqwsDBz5ZVXmo8++qjM9jyZ1WWuv3TiMteT+Xw+89JLL5nmzZubkJAQEx8fb3r27Bm80MEYY/bs2WP69+9vQkNDTZ06dcyrr7562stcA4GAefXVV02zZs1MSEiISUxMNAMHDgyWf/bZZ6ZJkyYmNDTUtGvXzixevNg4HI5SFxfMnDnTNGjQwDidzlIXAZxq/0v5bMboOi+pHN988w0DBw5kxIgRjB07tqq7IyK/kg4xSaW5+uqr+fbbb7Hb7ae9CkdEzj0aQYiIiCWNIERExJICQkRELJ3390GsXr2aNWvW8NBDD1V1V855RXt3UrhuVZnlYf2vwxFW9oazws3rKPjpB0xRESH1G+Pp3g+7q+RGqoINayhctxrjKyakQRM83fphDwkhUFhA0Y7NFO/dCX4/YT3644j7v1lMC7esp+CnH7A5QwjrcyXOGrpRSeRcdUGdgzh06FBVd6HKJSQkkJ6eXmZ5WFgYIau+J2vy+OAyk19y7X3SR/8mLb8geJesx+PBvWsL6aOH4qxTD2fNJArW/kB4/2uJe3w03mULyXjhSZx1G+KIS6Bw3SoirrmV6IeGU7R+NWl/fgRbqBtTkE/iK++SG18Lj8cDyxeQ9dpzuFq3I5CThT/tCDVffZ/c8ChsNhtutxuHw0EgEKC4uBiv13vKO4jPF+XtE6la2i8lTjUViw4xVRNFRUWE9rmSpE8XU+ez76j52vtgs+H+XXf8nrBSX8ROp5OiHZsBiLn/f0gY+zo2TxiF2zaVtLW95P+xQ56ixnNvgN1B0faNGGMIadaaOp8uInxA6Sk3QkNDyfvqY2xuD4kvvE38k+MwhQXkfvEe8fHxRGQdJf/tl8kZ+yeOvzYW57ofiYyMPEtbR0SsnPeHmKRifD4fR48eBUqmxsj/8kMwhsibB5GXl1eqbmFhITF9r8K75FuyZ0zEOW82YCPqtvsIBAKEX34d+csXkf3OBBzxNbB5wogceC8FBQWlpuj4JZvNhgkEMH4/prCAgLfkCXXFe7YDkPHiSLA7iLjyJvyZ6RTv30NI+66Vu1FE5JQ0gqhmnE4nrsJ8vAu+wdWsFY4WbUo9XwFKJmgLHMvF5B/HZndgs9vB7yOQlY7dbieQm0OgwAt2e8l/viIC2ZnBSfisjloWFBQQeeNd4Csm9YEbSR/zOACB4/8JJ7uDQN4xfKn7cdapR+R1t//XczCJyH9HI4hqJjw8nLwvP8AUFRJ50z3BOYDi4+Nx2cBgw+5ykTF1Av60I9R44W1CkupyeOjdZM+YRMT1d3Lsi/cJZKZT67UPcMQlkPrQzWTPfIOkK2/C7XYTEhLCL+dm9Xg8FBYWEn/59biataZ4xxbssfGk/+VxXE1KJl5L+PPfyPvmM4p2byPvn7PJ+3oWCa++XwVbSERO0AiiGrHb7XgcDvK++RRHrTqEdu6J1+vFbrfjLPBy4MZuZI4veT61I7ZkSu38Jd9SsPYHfIcP4IiNw2a3Y4+NA+D44nnkr16GP+0Ijth47HY70QQo+t/PKd65FQDvd/+CZf8mPj6ego1r8e3fgz02nrw5n0AgQPjl1wNQsGY5nm59iX1wOCH1G+M7ckgjCJEqphFENeLxeChYvRR7RBSRAwfjLSj8v7n77XacSXVxxCXg8/mIvO33+NKPcOyrjzCFBTjrNSbm/scpLCwk6s4HCWRlcmzWdExxESENmxLzwBPk5+fjyMog739Lppp2JtWlYPUyindtI+KKGwhkZ5L11ssEjuUSUr8x8U+Ow3Fx+5KH1WxeR+7HUwnkH8eRUIvYh0dQWFhYxVtMpHrTZa4XmFNduudyuYiOjsZut+Pz+cjKygpevRQTE4PL5cIYw7FjxwgEAkRGRhISEoLNZsPn8+H1ejl+/DihoaFEREQEp+/2+/14vV68Xi+xsbGW0zoXFBSUnP9wubDZbMF1ftme0+kMPhwnPz+fY8eOXRDPDNbllOcm7ZcSp7rM9bwfQehGuYorKioiLS3Nsiw7O7vMsoyMDMu6hYWF5f66z8zM/NX9KigoKHOiXESq3nkfEB06dCj3KVVnmv+B687K+1TULSu2sTIr77T1OsVG8FmX8p8HfLY5psyp6i6ISAWc9wFRnZ1LX/oicuHRVUwiImJJASEiIpYUECIiYkkBISIilhQQIiJiSQEhIiKWzvuAWL16NZMnT67qboiIXHDO+/sgzuaNciIi1cl5P4IQEZHKoYAQERFLCggREbGkgBAREUsKCBERsaSAEBERSwoIERGxpIAQERFLCggREbF03geEptoQEakcmmpDREQsnfcjCBERqRwKCBERsaSAEBERSwoIERGxpIAQERFLCggREbGkgBAREUsKCBERsaSAEBERSwoIERGxpIAQERFL531AaLI+EZHKocn6RETE0nk/ghARkcqhgBAREUsKCBERsaSAEBERSwoIERGxpIAQERFLCggREbGkgBAREUsKCBERsaSAEBERSwoIERGxpIAQERFLCggREbGkgBAREUvnfUDoeRAiIpVDz4MQERFL5/0IQkREKocCQkRELCkgRETEkgJCREQsKSBERMSSAkJERCwpIERExJICQkRELCkgRETEkgJCREQsKSBERMSS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sFkoxhjyjmfi8/mw2Wx4Ilw0iCm5D8Lv9wfvjagMFQqIW265pVLeXEQuTF/Pyq7qLpTyyhuD2bF79WnrNW3UgScenXkWenR6194WU+bGPGMMhYWFlXLPg5VyA2Lz5s20atUKgI0bN5bbwEUXXXTmeyUicgadK1/655tyA2LatGm88sorALz11luWdWw2m+6DEBG5QJUbEK+88gpLly6le/fuTJo06Wz2SUREzgGnvLFhypQpZ6sfIiJyjjllQGjuJRGR6uuUVzEFAoFTnqAGnaQWEblQnTIgiouLefvtt8sdSegktYjIheuUAeF2uxUAIiLVlGbfExERSzpJLSIilk4ZEO+9997Z6oeIiJxjdIhJREQsKSBERMSSAkJERCwpIERExJICQkRELCkgRETEkgJCREQsKSBERMSSAkJERCwpIERExJICQkRELCkgRETEkgJCREQsKSBERMSSAkJERCwpIERExJICQkRELCkgRETEkgJCREQsOau6A1YKCgqYOnUqTqeT1q1b06NHj6rukohItXPWAuLNN99k7dq1REdH88orrwSXp6Sk8O677xIIBOjXrx833HADP/74I507d6ZDhw78/e9/V0CIiFSBs3aIqXfv3jzzzDOllgUCAaZNm8YzzzzD3//+d5YtW8aBAwfIyMggISGhpIN2HQUTEakKZ+3bt1WrVkRERJRatnPnTmrVqkXNmjVxOp107dqVVatWER8fT0ZGBgDGmLPVRRER+YUqPQeRmZlJfHx88HV8fDw7duzgyiuvZPr06axdu5b27duXu/6///1v/v3vfwPw4osvBkcdleVIpbZefVT2fpJf78zvk+wz3F71cy78O6nSgLAaHdhsNtxuN0OGDDnt+pdddhmXXXZZ8HV6evoZ7Z9UDu2nc4/2ybnnbO2TpKSkcsuq9AD/Lw8lAWRkZBAbG1uFPRIRkROqNCAaN25MamoqR48exefzsXz5cjp06FCVXRIRkf84a4eYXn31VTZv3syxY8d4+OGHufXWW+nbty/33Xcf48aNIxAI0KdPH+rWrXu2uiQiIqdw1gLi8ccft1x+6aWXcumll56tboiISAXpJgMREbF03gfE6tWrmTx5clV3Q0TkgnNOzsX0a3To0EEntkVEKsF5P4IQEZHKoYAQERFLCggREbGkgBAREUsKCBERsaSAEBERSwoIERGxdN4HhG6UExGpHLpRTkRELJ33IwgREakcCggREbGkgBAREUsKCBERsaSAEBERSwoIERGxpIAQERFL531A6EY5EZHKoRvlRETE0nk/ghARkcqhgBAREUsKCBERsaSAEBERSwoIERGxpIAQERFLCggREbGkgBAREUsKCBERsXTeB4Sm2hARqRyaakNERCyd9yMIERGpHAoIERGxpIAQERFLCggREbGkgBAREUsKCBERsaSAEBERSwoIERGxpIAQERFLCggREbF03k+1cSbMmjWrzLLmzZvTrl07iouLmT17NgDmQHqwvFVUGK2jwsj3B5ibmllm/TbR4TSP9JBb7OdfR7LKlF8aE0HjCDeZRT4WHM0uU94xLpL6YaEcLSzmu7ScMuXd4qNI8rg4lF/EsozcMuW9akSTGBrCz95Cfsw8Vqa8X2IMcS4nu/IKWJudV6b8ipqxRIU42HYsn/U5x8uUX1M7Do/DzqZcL5tzvWXKb0iKJ8RuY132cbbn5Zcqs82axW233QbAqlWr2L17d6lyp9PJzTffDMCKFSvYt29fqXK32831118PwJIlS0hfvbZUuSM0nNiLewCQs+1Hio+V3j/OsChiWnUFIHvzcnze0tsvJDKO6OYdAcja8D3+wtKf3xVdg6im7QHITFlEwFdYqjw0tjaRjdsCkLF2PibgL93/hLpENGgNQPrqeZzMU7MB4XVbEPD7yPzp32XKw5KaEJbUBH9RAVnrF5cpD09ujqdWQ3z5eWRvWlqmPKJea9yJdSk+nkPOlhUAzHKuC5Z37tyZ+vXrc/ToURYtWlRm/e7du1OnTh0OHjzI0qVl2+/Tpw/gIi1jPzt2rS5T3qZVbyIiYjl8dA+796aUKb/k4svweCI5mLqDn/dvLFPeod0AXC4P+w9uYf/BrWXKO7W/BocjhL37NnDo8M4y5V073gjArj0/cSRtb6kyh91Jpw7XArB95yrSMw+UKneFuOlwyZUAbNm+gqzsw6XKPe4ILmnTH4BNW74n51h6qfKI8BjatO4DwPpNi8g7nl2qPDoygdYtS/7u/u///i/HjpX+t1u7dm169uwJwFdffUVBQQFA8N/TmXbejyA0WZ+ISOWwGWNMVXfiTDl06FCltu9/4LpKbb+6cEyZc0bbu/7Dsr8i5df56q4WZ7S9r2dln9H2qqNrb4s5K++TlJRUbtl5P4IQEZHKoYAQERFLCggREbGkgBAREUsKCBERsaSAEBERSwoIERGxpIAQERFLF9SNciIicuZoBHGBefrpp6u6C3IS7ZNzk/bL6SkgRETEkgJCREQsKSAuMJdddllVd0FOon1ybtJ+OT2dpBYREUsaQYiIiCUFhIiIWNIjR3+j2267jXr16uH3+3E4HPTq1YurrroKu93Orl27+O6777jvvvvKXX/27NncdNNNwdd//vOfef75589G18vYvn07M2bMoLi4GJ/PR5cuXbj11lvZtGkTTqeT5s2bV0m/KtPs2bNZunQpdrsdm83Ggw8+SMOGDfnggw9Ys2YNAHXq1OH+++8nISEBgEGDBvH++++Xaufbb78lNDSUXr16sXjxYtq0aUNcXNwp37s6bu+K0D45Bxn5Te6+++7gn7Ozs83YsWPNrFmzftP6Z4PP5yu3bOjQoWbPnj3GGGP8fr/Zv3+/McaYWbNmma+++uqMvc+5Ytu2beaZZ54xRUVFxhhjcnJyTEZGhpk5c6Z58803jd/vN8YYs3DhQjNixIjg69Pts9GjR5udO3ee9v2r2/auCO2Tc5NGEGdAdHQ0Dz74ICNHjuSWW25h8+bNfP311zz99NMUFBQwffp0du3ahc1mY+DAgezatYuioiJGjBhB3bp1GTp0aPCXkDGGDz74gJSUFABuvvlmunbtyqZNm/jss8+IjIxk//79NGrUiMceewybzcbnn3/OmjVrKCoqolmzZjz44IPYbDbGjBlDs2bN2LZtGxdddBGLFy/mtddew+l04vV6GTFiBK+99hq5ubnExsYCYLfbSU5O5ujRo8yfPx+73c7333/PfffdR0JCAm+99Ra5ublERUUxZMgQEhISmDRpEhEREezdu5eGDRty+eWXM23aNHJzcwkNDeWhhx6iTp06VbiHSsvKyiIyMpKQkBAAoqKiKCwsZPHixbzxxhvY7SVHXvv06cOiRYvYsGEDbdu2tWzr008/xe12k5iYyK5du5g4cSIul4tx48Zx4MABZs6cSUFBQXB7xcbGVrvtXRHaJ+cmBcQZUrNmTYwx5OTklFr++eefExYWxiuvvAJAXl4enTt3Zt68eYwfP75MOytXrmTv3r2MHz+e3NxcRo4cScuWLQHYs2cPEyZMIDY2llGjRrFt2zZatGjBgAEDGDhwIACvv/46a9asoUOHDgB4vV7+8pe/AJCWlsbatWvp2LEjy5cvp1OnTjidTq6++moef/xxWrVqRbt27ejVqxeJiYn0798ft9vNddeVPIv7xRdfpGfPnvTu3ZuFCxcyffp0nnzySQBSU1MZNWoUdrudsWPH8sADD1C7dm127NjB1KlTGT16dCVs9d+mbdu2fP755wwbNoyLL76Yrl27Eh4eTkJCAmFhYaXqNmrUiAMHDpT7ZXTCiX06aNAgGjdujM/nC26fqKgoli9fzscff8yQIUOq3fauCO2Tc5MC4gwyFlcMb9iwgccffzz4OiIi4pRtbN26lW7dumG324mJiaFVq1bs2rULj8dDkyZNiI+PB6BBgwYcPXqUFi1asHHjRubMmUNhYSF5eXnUrVs3GBBdu3YNtt23b1/mzJlDx44dWbRoEQ899BAAAwcOpHv37qxfv56lS5eybNkyxowZU6ZvO3bsYPjw4QD07NmTDz/8MFjWuXNn7HY7BQUFbNu2jQkTJgTLfD7fabbc2eV2u3nppZfYsmULmzZt4u9//zs33ngjNpvtjL3HoUOH2L9/P8899xwAgUAg+Au1um3vitA+OTcpIM6QI0eOYLfbiY6O5uDBg6XKztRf8hPDbygZBgcCAYqKipg2bRp//etfSUhI4NNPP6WoqChYLzQ0NPjnFi1aMG3aNDZv3kwgEKBevXrBslq1alGrVi369evH/fffz7Fjx35V39xuN1Dyjy48PNxydHQusdvttG7dmtatW1OvXj3mz59PWloa+fn5eDyeYL09e/bQuXPn3/QeycnJjBs3zrKsum3vitA+OffoMtczIDc3lylTpjBgwIAyYdCmTRvmzZsXfJ2XlweA0+m0/FXRsmVLVqxYQSAQIDc3ly1bttCkSZNy37u4uBgoOWZbUFDAypUrT9nXnj178tprr9GnT5/gsrVr1wZHP6mpqdjtdsLDw/F4PBQUFATrNWvWjOXLlwOwdOlSWrRoUab9sLAwEhMTWbFiBVAyqtq7d+8p+3S2HTp0iNTU1ODrvXv3kpSURK9evZg5cyaBQACA7777jpCQkApfweJ2u8nPzwcgKSmJ3Nxctm/fDpT8gty/fz9Q/bZ3RWifnJs0gviNTpxkPnGZa48ePbjmmmvK1Lv55puZOnUqTzzxBHa7nYEDB9KpUyf69evHiBEjaNiwIUOHDg3W79ixI9u3b2fEiBEA3H333cTExJQZlZwQHh5Ov379eOKJJ0hMTKRx48an7HePHj345JNP6NatW3DZkiVLmDlzJi6XC4fDwWOPPYbdbqd9+/ZMmDCBVatWcd999/H73/+et956izlz5gRP0FkZOnQoU6ZMYfbs2fh8Prp160aDBg1Ot0nPmhMXDhw/fhyHw0GtWrV48MEH8Xg8vP/++wwbNoyioiKioqIYN25cMPSLiop4+OGHg+2cvL979+7NlClTgidEn3jiCd599128Xi9+v5+rrrqKunXrVrvtXRHaJ+cmTbVRzfzwww+sWrWKxx57rKq7ck7Lzs5m3LhxXHHFFZqz5xyhfXL2KSCqkenTp/PTTz8xcuRIkpKSqro7InKOU0CIiIglnaQWERFLCggRqXJFRUWMHj06eLXS6fz5z38G4OjRoyxdujS4fPHixUybNu2067/99tscOHDgV/Vx0KBBv6r+Cfv27WPSpEm/ad2qpoAQkSq3cOFCOnXqFJxS43ROTGyZlpZWKiAq6uGHHyY5OflXr/db1KtXj8zMTNLT08/K+51JCggRqXJLly4N3v0/depUVq9eDcD48eN58803gZIQ+eSTT4D/+zX/0UcfsWXLFkaMGMHcuXOBknmdxo0bx9ChQ/nggw8s32/MmDHs2rUr2NbHH3/MiBEjePbZZ8nOzgZKRifPPvssI0eODL7vCXPmzGHkyJEMHz6cTz/9FIAff/yR5557DmMMWVlZDBs2LNhW+/btWbZs2ZnYVGeVAkJEqpTP5+PIkSMkJiYCJTeLbtmyBYDMzMzgPUBbt24tc2PanXfeScuWLRk/fnzwHoi9e/fypz/9ib/97W8sX778tL/cCwsLadq0KePHj6dly5YsWLAAgHfffZfLL7+cv/71r8TExATrr1u3jtTUVF544QVefvlldu/ezebNm+nYsSPR0dH861//YvLkydxyyy3B9Ro1ahT8TOcTBYSIVKnc3FzCw8ODr1u2bMnWrVs5cOAAycnJREdHk5WVxfbt2yt0B/VFF11EWFgYLpeL5OTk0waE0+mkffv2QMkXeVpaGgDbtm0L3lDas2fPYP1169axfv16nnzySZ566ikOHjzI4cOHAbjvvvv48ssvcTqddO/ePbjOic9wvtGd1CJSpVwuV3DKGIC4uDjy8vJISUmhZcuW5OXlsWLFCtxud6k5mcpz8pxlfr//lPUdDkfwzuyT65c3j9oNN9xA//79yyzPzMzEbreTk5NDIBAInlMpLi7G5XKdtu/nGo0gRKRKRUREBCeePKFZs2Z88803tGrVipYtW/L1119bznvk8XiCcy2dac2bNw+eN/jlifC2bduyaNGi4BxNmZmZ5OTk4Pf7eeuttxg6dCh16tQJnhOBkrmm6tatWyn9rEwaQYhIlWvTpg1bt26lTZs2QMlhpvXr11OrVi0SEhLIy8sLPhfll+rVq4fD4WDEiBH06tXrtNPp/xq///3vee211/jnP/9Jp06dgsvbtm3LwYMHefbZZ4GSCQEfe+wx5s+fT4sWLWjZsiUNGjRg5MiRXHrppSQnJ7Np0yYuvfTSM9a3s0V3UotIlduzZw9z5869IOcIKy4uZsyYMYwdOxaHw1HV3flVdIhJRKpcw4YNad26dYVvlDufpKenc+edd5534QAaQYiISDk0ghAREUsKCBERsaSAEBERS7rMVaqNrVu38sEHH7B//37sdjvJyckMHjyYJk2asHjxYhYsWMBzzz1X6f2YPXs2X375JVDygHufzxe8iapGjRpMmDCh0vsgUhEKCKkWvF4vL774Ivfffz9du3bF5/OxZcuWUnfd/jdOPJu8Im666SZuuukmgLMaTCK/lgJCqoXU1FSA4Pw4LpeLtm3bAnDgwAGmTJmCz+dj0KBBOBwOZsyYgdfrDT6mNTQ0lH79+nHjjTdit9uDX+yNGzfmu+++44orruDmm2/m448/ZsWKFfh8Pn73u99x7733VniKhTlz5rB9+3aGDx8eXDZ9+nTsdjv33nsvY8aMoVmzZmzYsIFDhw7RunVrhgwZErw5bPv27bz33nscOHCAGjVqcO+999K6deszuRmlmtE5CKkWateujd1u54033uCnn34iLy8vWJacnMwDDzxAs2bNeP/995kxYwZQ8uXs9Xp54403GDNmDEuWLGHx4sXB9Xbs2EHNmjWZOnUqN910Ex9++CGpqamMHz+eiRMnkpmZyeeff17hPvbo0YN169Zx/PhxoGRUsnz58lITxX333Xf88Y9/ZPLkydjtdqZPnw6UTPfw4osvctNNNzF9+nQGDRrEK6+8Qm5u7n+x1aS6U0BItRAWFsbYsWOx2WxMnjyZ+++/n5deeik4X//JAoEAy5cv584778Tj8ZCYmMg111zDkiVLgnViY2O58sorcTgchISEsGDBAgYPHkxERAQej4ebbrrpVz0DIDY2lpYtW7JixQoAUlJSiIyMpFGjRsE6PXv2pF69erjdbm6//XZWrFhBIBBgyZIlXHLJJVx66aXY7XbatGlD48aNWbt27W/bYCLoEJNUI8nJyTzyyCMAHDx4kNdff50ZM2bw+OOPl6mbm5uLz+cjISEhuKxGjRpkZmYGX/+yLDc3l8LCQp5++ungMmPMr74zuFevXnz77bdcdtllfP/996VGDwDx8fGl3t/v95Obm0t6ejo//PADa9asCZb7/X4dYpL/igJCqqU6derQu3dv5s+fb1keFRWFw+EgPT09+GjK9PR04uLiLOtHRkbicrmYMGFCuXUq4ne/+x1Tp05l3759rFmzhrvvvrtUeUZGRvDP6enpOBwOoqKiiI+Pp0ePHjz88MO/+b1FTqZDTFItHDx4kK+//jr4BZuens6yZcto2rQpADExMWRmZuLz+YCS5wJ06dKFjz/+mPz8fNLS0pg7dy49evSwbN9ut9OvXz9mzJhBTk4OUHJeICUl5Vf10+Vy0alTJyZOnEiTJk1KjVIAvv/+ew4cOEBhYSGffvopnTt3xm6306NHD9asWUNKSkpw6uxNmzaVChSRX0sjCKkWPB4PO3bsYO7cuXi9XsLCwmjfvn3wF/pFF10UPFltt9uZNm0a9913H9OnT+fRRx/F5XLRr18/+vTpU+573HXXXXz++ec8++yzHDt2jLi4OPr370+7du1+VV979+7NwoUL+eMf/1imrGfPnkyaNIlDhw7RsmVLhgwZApQcbnryySf54IMPeO2117Db7TRp0oQHHnjgV723yC9psj6Rc0x6ejqPP/4477zzDmFhYcHlY8aMoUePHvTr168KeyfViQ4xiZxDAoEAc+fOpWvXrqXCQaQqKCBEzhEFBQUMHjyY9evXc+utt1Z1d0R0iElERKxpBCEiIpYUECIiYkkBISIilhQQIiJiSQEhIiKWFBAiImLp/wN5WKv7QimWrgAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Run time: ~2m\n", - "\n", - "# Setup\n", - "labelled_annotations = copy.deepcopy(annotations)\n", - "for n, annotation in enumerate(labelled_annotations):\n", - " annotation.properties[\"class\"] = n % 10\n", - " annotation.properties[\"vector\"] = rng.integers(1, 4, 10).tolist()\n", - "\n", - "predicate = \"(props['class'] == ?) & (3 in props['vector'])\"\n", - "classes = rng.integers(0, 10, size=100)\n", - "stmt = \"for n in classes:\\n store.query(where=predicate.replace('?', str(n)))\"\n", - "\n", - "dict_store = DictionaryStore()\n", - "sql_store = SQLiteStore()\n", - "\n", - "dict_store.append_many(labelled_annotations)\n", - "sql_store.append_many(labelled_annotations)\n", - "\n", - "\n", - "# Time dictionary store\n", - "dict_runs = timeit.repeat(\n", - " stmt,\n", - " globals={\"store\": dict_store, \"predicate\": predicate, \"classes\": classes},\n", - " number=1,\n", - " repeat=10,\n", - ")\n", - "dict_result = dict_store.query(where=predicate.replace(\"?\", \"0\"))\n", - "\n", - "# Time SQLite store\n", - "sqlite_runs = timeit.repeat(\n", - " stmt,\n", - " globals={\"store\": sql_store, \"predicate\": predicate, \"classes\": classes},\n", - " number=1,\n", - " repeat=10,\n", - ")\n", - "sql_result = sql_store.query(where=predicate.replace(\"?\", \"0\"))\n", - "\n", - "\n", - "# Add an index\n", - "# Note: Indexes may not always speed up the query (sometimes they can\n", - "# actually slow it down), test to make sure.\n", - "sql_store.create_index(\"class_lookup\", \"props['class']\")\n", - "sql_store.create_index(\"has_3\", \"3 in props['vector']\")\n", - "\n", - "# Time SQLite store again\n", - "sqlite_index_runs = timeit.repeat(\n", - " stmt,\n", - " globals={\"store\": sql_store, \"predicate\": predicate, \"classes\": classes},\n", - " number=1,\n", - " repeat=10,\n", - ")\n", - "sql_index_result = sql_store.query(where=predicate.replace(\"?\", \"0\"))\n", - "\n", - "# # Validate the results against each other\n", - "# for a, b, c in zip(dict_result, sql_result, sql_index_result):\n", - "# assert a.geometry == b.geometry == c.geometry # noqa: ERA001\n", - "\n", - "# Plot the results\n", - "plot_results(\n", - " experiments=[dict_runs, sqlite_runs, sqlite_index_runs],\n", - " title=\"100 Queries with a Predicate\",\n", - " tick_label=[\"DictionaryStore\", \"SQLiteStore\", \"SQLiteStore\\n(with index)\"],\n", - ")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gp8mq1TNpT5y" - }, - "source": [ - "### Polygon & Predicate Query\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Eu0hGvhdpT5y", - "outputId": "0d89174e-01e0-4e71-a9c3-e063ed30ca38" - }, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAY0AAAEaCAYAAADtxAsqAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAsTAAALEwEAmpwYAAA3sUlEQVR4nO3dd3gU1eI+8HdnN5vd9N4IJIFICwhcIkjohCCKoiACgghfpKugIhJUHrkIFryg9EsvKooiCOKVn0hCDSAkQCCU0AKBhFRSN5tsOb8/crOXNYUBUyC8n+fhedg5Z2bOzk723TPljEIIIUBERCSDVNcNICKihwdDg4iIZGNoEBGRbAwNIiKSjaFBRESyMTSIiEg2hgbds1mzZiE4OLium2FFbpsCAwMxZ86cWmhR9ejRowfGjBlT1814IDyI+51CocA333xT6ev6iKEh0/79+/H8888jICAACoWi0i+eo0ePIiwsDBqNBr6+vpgxYwZMJpNVncTERDz11FOws7ODh4cHJkyYgMLCwru2IS0tDW+++SYCAwOhVqvh6emJQYMG4eTJk9XxFmV79913ceTIkVpd5938tU1z5sxBYGBg3TUIwKhRo6BQKKBQKKBSqRAQEIAJEyYgKyurTttVnwUGBlq2uUajQfPmzTFv3jyYzeZaWX9qaioGDRpUbcv75ptvoFAoqm151YGhIVNBQQFatmyJefPmwcfHp8I6ycnJiIiIQLNmzRAbG4vly5djxYoV+OCDD6yWEx4eDpVKhZiYGPzwww/YtWsXXnvttSrXn5ycjNDQUMTExGD58uW4dOkSfv31V9jY2ODJJ5/Erl27qvX9VsRsNsNkMsHBwQEeHh41vr578SC2CQC6du2K1NRUJCUlYdGiRfjpp5/w6quv1nWz6rXp06cjNTUV586dw4QJExAZGYn58+dXWNdgMKA672/28fGBRqOptuU9kATds4CAAPHxxx+Xmz5jxgzRoEEDYTKZLNOWLFki7OzsREFBgRBCiBUrVgiNRiNycnIsdXbu3CkAiCtXrlS6zueee054e3uL3NzccmVPP/208Pb2FjqdTgghxEcffSSaNGliVefAgQMCgLh69apl2vHjx0VERISwt7cXHh4eYsCAASIpKclSXrac77//XjRr1kwolUpx+vTpCpf/+++/i7CwMKHRaISfn58YNWqUyMzMtJSfOXNG9OnTRzg7Ows7OzvRvHlzsXHjxkrfr7+/v1i1apXl9auvvioAiIsXL1qmNWrUSCxdurTce163bp0AYPXvo48+EkKUfnYzZ84UkydPFq6ursLLy0tMnTpVGI3GStsihBDvv/++aN68udBqtcLf31+MHz/e6jOsyMiRI0V4eLjVtDlz5ghJkoROpxNms1l88cUXIigoSNjY2IjGjRuLL7/80qp+9+7dxWuvvSaEEGLt2rXC2dlZFBYWWtWZNWuWCAwMFGazWQghxO7du0WrVq2Era2taN26tdi7d68AIL7++mvLPOfPnxfPPPOMsLe3F/b29uLZZ5+12rbr1q0TSqVSHDx4ULRr105otVoRGhoqjh8/XuV7jo2NFX379hWenp7C3t5ehIaGit9++82qjpzPQK/XiwkTJggnJyfh4uIiJkyYICIjI8vtd39V0d9m7969xZNPPimE+N9nsmjRIhEQECAUCoXIz88Xt27dEiNHjhQeHh7CwcFBhIWFiX379lktJyoqSrRu3dqyXaOiospt17++zs/PF1OmTBH+/v5CrVaLgIAAMXfuXEt5VftVdHR0uf145MiRlnkXLVokmjVrJmxtbUVwcLCYM2eOMBgMVW6f6sCeRjU6dOgQ+vTpA0n632bt27cvdDodTpw4YanTqVMnODs7W+qUzXPo0KEKl3v79m38+uuveOONN+Dk5FSufMaMGUhLS8Pu3btlt/Xs2bPo3r07OnXqhOPHjyMqKgpKpRIRERHQ6/WWeikpKVi2bBnWr1+Ps2fPIiAgoNyyoqKi8Pzzz2Po0KGIj4/Hzz//jKSkJAwYMMDyK+7ll1+Gu7s7YmJicPr0aSxYsACurq6Vtq9nz57Ys2eP5XV0dDQ8PT0t0y5fvozr16+jV69e5eYdMmQIpk+fDn9/f6SmpiI1NRXvvvuupXzx4sXw9fXF0aNHsWjRInz11VfYuHFjldtLq9Vi5cqVOHv2LNavX4+9e/di8uTJVc5T2XLMZjOMRiOWLVuGmTNnIjIyEgkJCZg2bRoiIyOxZs2aCucdOnQoFAoFfvzxR8s0s9mMdevWYcyYMVAoFLh58yb69++Pjh07Ii4uDl9++SXeeecdq+UUFRWhT58+0Ov12LdvH/bt24eCggL07dsXJSUlVsueMWMGFi5ciLi4OLi6umLw4MEwGo2Vvr+8vDwMHToUe/fuRVxcHJ566in0798fiYmJVvXu9hlERkbip59+wsaNG3H48GHY29tj6dKl97Sty2i1WhgMBsvrP//8E1FRUfj5559x6tQpCCHQs2dP5Ofn47fffsOJEyfwzDPPICIiAufOnQNQ+nfw7LPPon379oiLi8P8+fMxZcqUKtcrhMCzzz6LHTt2YPHixTh37hw2btwIT09Pq7ZVtl+FhYVhyZIlAGDZjxcuXAig9PzOv/71L3z66ac4d+4cFi5ciBUrVuCf//znfW2je1LjsVQPVdbTeOyxx8SMGTOsphUUFAgA4ocffhBCCBERESFefvnlcvN6eHiIefPmVbi+o0ePCgBi69atFZZnZWUJAJb55fQ0Ro4cKYYMGWJVR6/XC61WK7Zt22ZZjkKhENeuXbOq99fld+/eXUyfPt2qzrVr1wQAceLECSGEEE5OTmLdunUVtr8i69atE15eXkIIIRITE4VWqxWzZ88WL730khBCiJUrVwpfX99K2/Txxx+LgICAcssNCAgQzz33nNW0p556SgwdOlR224QQYuvWrUKtVlv1Kv/qrz2NhIQE0bhxY9GxY0chRGlvatq0aVbzvPXWWyIoKMjy+s6ehhBCvPnmm6Jz586W17t27RIqlUqkpKQIIUp/uQYEBFj9av/tt9+sfgGvXr1aaLVakZGRYalz69YtodFoxIYNG4QQ/+utxcbGWuocPnxYABDnz5+XsYX+5/HHHxdz5syxvL7bZ1BQUCBsbW3FypUrreq0b9/+nnoaJpNJ7Ny5U6jVasv+OXLkSOHs7Czy8/Mt86xbt040aNCg3K/0nj17iilTpgghhPjggw9Eo0aNrOr88ssvVfY0/vjjDwFAHDt2rMo23+mv+9XXX38t/vo1XVhYKLRabbke3IYNG4Szs7Psdd0v9jRqWNlJLDknsyqrI+5yzLVsPhsbG9ntOnbsGLZt2wYHBwfLP3d3d+j1ely8eNFSz9vbG40aNbrrsr766iurZbVs2RIALMt69913MWbMGPTo0QOzZs1CXFxclcsMDw9Heno6zpw5g6ioKHTp0gV9+/ZFdHQ0hBCIioqqsJchR9u2ba1eN2jQAGlpaVXOs3XrVnTr1g1+fn5wcHDA8OHDUVJSglu3blU53969e+Hg4ACtVotWrVqhcePG2LRpE/Ly8nDjxg1069bNqn737t2RlJQEnU5X4fLGjx+PQ4cO4ezZswCAVatWoV+/fvD19QVQ2oN84oknoFQqLfN06tTJahkJCQlo2bKl1Tkgb29vNGvWDAkJCZZpCoUCbdq0sbxu0KABAFS5rTIyMjBp0iQ0b94cLi4ucHBwQEJCAq5du2ZVr6rP4PLlyyguLkZYWJhVnS5dulS63jt9/PHHcHBwgEajwcCBAzFy5EjMmjXLUt6iRQs4ODhYXh87dgy3bt2ytLfs34EDByz779mzZ9GhQweoVCrZ7YmNjYWrqytCQ0MrrXM/+1VCQgKKiorw4osvWrV3/PjxyM3NRUZGxt020d+iunsVksvX17fch132uuzkua+vL5KTk63qGAwGZGdnV3qCvWnTppAkCWfOnMGAAQPKlZ85c8ZSDwAkSSoXNHd2z4HSQw8jRoxAZGRkueW5u7tb/m9vb19hm/66rOnTp2PEiBHlysre08yZMzF8+HDs2rULUVFR+OSTT/Dee+9VehVaw4YN0aRJE+zZswcxMTHo1asX2rdvD6PRiPj4eERHR+OTTz65a9sqolarrV4rFIoqr645evQoXnrpJcyYMQNffPEFXF1dceTIEYwcOdLqcE5FOnbsiA0bNkClUsHX1xe2trYASg/jlK37Tnf7gRASEoIuXbpg9erViIyMxI4dO/Dzzz+Xez9Vva5smhDCarokSVbhU1ZW1bYaNWoUrl+/jnnz5iEoKAharRZDhw4tt52q+gzKtsH9XjX0+uuvY9KkSdBoNPDz87M6XAyU36fNZjNatGiBbdu2lVuWnZ2dpU1ytutfVVXnfversu30448/Wv7m7+Tm5nbXdv0d7GlUo86dO2P37t1Wf1S7du2CnZ0d2rVrZ6lz+PBhy5cGAMs8nTt3rnC5rq6u6NevH5YuXWo1X5lPPvkEfn5+iIiIAAB4eXkhPT3d6lLfv/6yDw0NRXx8PJo0aYLg4GCrf1Wda6hIaGgoEhISyi0nODjY6hdd48aNMWnSJGzZsgWzZ8/G8uXLq1xur169sGfPHuzduxfh4eGQJAndunXD4sWLkZaWVmVPQ61Wl7vU+X4dPHgQHh4emDNnDjp27IimTZvixo0bsubVarUIDg5GYGCgJTAAwMnJCf7+/ti3b59V/f379yMoKMjyZVWR8ePHY+PGjVi5ciV8fHzQt29fS1nLli1x7Ngxq/d++PBhq/lDQkKQkJCAzMxMy7S0tDQkJiYiJCRE1vuqzP79+zFp0iT0798frVu3hq+vL65cuXJPywgODoZarS53ji8mJkbW/G5ubggODoa/v3+5wKhIaGgorly5Aicnp3L7r5+fH4DSbXb06FGr7Xrw4MEql9u+fXtkZ2fj+PHjFZbL2a/KwvXO9YaEhECj0eDKlSsV/s3dGfQ1gaEhU0FBAU6ePImTJ09auo8nT57EpUuXLHUmTpyI3NxcjB07FgkJCdixYwdmzpyJN9980/LrZtiwYfDw8MCwYcNw6tQpREdH4/XXX8eQIUMQFBRU6fqXLl0KlUqFXr16YdeuXUhOTsaxY8cwbNgwREdHY9OmTZbDUz179oROp8PMmTNx+fJl/Pjjj+VOIr7//vs4d+4cXnnlFfz555+4evUqoqOjMWXKlHv+I589eza2b9+Ot99+GydPnsTly5ctlxEXFRWhoKAAr7/+OqKionD16lWcOHECu3btshzCqkyvXr3w22+/obi4GP/4xz8s0zZs2ICgoKAq78MICgrCrVu3cPjwYWRmZlZ6uEeOZs2aISMjA2vWrMGVK1ewceNGLFu27L6XV2bGjBlYvHgxVq1ahYsXL2LFihVYvnw53n///SrnK7sP4OOPP8Zrr71m9cU4adIkpKWlYeLEiTh37hyio6Mtl3yX/eodNmwYPD09MWTIEMTFxSE2NhZDhw5FgwYNMGTIkL/1npo1a4Zvv/0Wp0+fxsmTJ/Hyyy/fc3jb29tjwoQJ+PDDD7Fjxw5cuHAB7733Hs6fP/+32laZ4cOHIygoCP369cPvv/+OpKQkHD16FJ9++qmlFzdx4kRkZGRg3LhxOHfuHPbs2WN1KX1FevXqha5du2LIkCHYvn07rl69ikOHDmH16tUA5O1XZd8JO3bsQEZGBgoKCuDg4ID3338f77//PpYsWYILFy4gISEB33//PaZPn179G+ivavysST1R0eVvAET37t2t6h0+fFh06tRJ2NraCm9vbxEZGVnucs7z58+LiIgIodVqhZubmxg3bpzlktyq3Lp1S7z++uuiUaNGQqlUCgDCz89PJCYmlqu7Zs0aERQUJDQajejbt6/47rvvyl1yGx8fL/r37y9cXFyERqMRTZo0EWPHjhVZWVlCiIpPqFc2ff/+/SI8PFw4ODhYLqmdMmWKMBgMoqioSLz88ssiMDBQ2NraCk9PTzF48GBx/fr1Kt9vWlqaUCgUon///lZtBmB1criiNpWUlIiXX35ZuLq6lrvk9q8XMbz22mvlPse/+vDDD4WXl5ews7MTTz/9tNi0aVO57flXFV1yeyez2SzmzZsnAgMDhUqlEkFBQVVecnunt956S0iSJJKTk8uV7d69W4SEhAi1Wi1at25tORG+ZcsWS53z58+Lp59+2nLJbb9+/Sq85PZOycnJAoCIjo6u9D3Fx8eLTp06CY1GIwICAsTSpUtFeHi41aWicj4DnU4nxo0bJ5ycnISTk5MYO3bsfV9ye6fKPpPMzEwxYcIE4efnJ2xsbISfn5944YUXRFxcnKXOH3/8IVq1aiXUarUICQkRe/bsueslt3l5eeKNN94QPj4+wsbGRgQGBopPP/3UUi5nv5oyZYrw8vISCoXCajuuXr1atGnTRtja2goXFxfRoUMHsWzZsiq3T3VQ/PeN0kPo119/xaBBgzBt2jTMnj27rptDtWjw4MEoKirCL7/8cte6+/fvR/fu3REfH4/WrVvXQuuoPuOJ8IdYWXd6z549uHr1apWHt6h+uH37Ng4cOIBt27ZVel/O8uXL0aZNG/j5+eHs2bN4++230bFjRwYGVQv2NIgeIoGBgcjKysLkyZMxd+7cCutERkZi06ZNSEtLg4+PDyIiIvD5559bXRVHdL8YGkREJBuvniIiItkYGkREJNsjcSI8JSWlrpvwSPDw8LC6YexOWq0WjgqBwl9/hOHaFaibhcC+7wDk6EtQXFxsqadSqeAmjMjf8b31/B26oTjwMWi1Wpjij6EoZi9gNsKuZz9ILR5HTk4O3O210B89AEPSJQiTEY7PDsZtlS1MJhPc3d1hOLIP+thDgKSCfZ/nIQKDkZ2dXZObhKpRVfsXVa+ymxorwp4G1TiFQgEne3tkTB+H/K2lTzXL3bAUWXOmlRu1V6FQwHQ7CwU/b0LRoSjoTxyB/sQRGNNT4OjoCMPB3cj8aApMWekw3c5GxvsTYIo7DEdHRxiTLiNn1Xzo9v6Ggp83wZiZBoVCAUdHR+h3bkbWZ5EwFxXBmHIN6dNGQ3H5PLRaLezt7eHu7g4vLy94eHjAxcWlxu+qJXpYPRI9DapbGo0GxccOwHgjCU4vj4XzK+OR/dVsFO7eAfPVRKjdvCsca8d5xATYtm4PpbsXFEolTCYTCvf8CgBwnz4XEMDNwT2Qt2UD3EM7A4+1hN+mP5C7fgnyt2ywLEetViNrz3+gUNvC/b25MKal4NbYAcjf+jU8Zs6H/tQx5H6/BqbMdEgODrDr+Qwce/dHTk5ObW0ioocGQ4NqnFKphOH6VQCAyj8ABoMBKr+GAADj9atQelbcFc5eMKt0fk9vuE//FLYtHodCXTp+kzHlBvDfC/+MyUmQJAm30tMrfN6IEAIKtRrCUAJTVjqMKaUDRhqSS9t0e/FcKLR2cP6/N2G+nQVhMj5wj9gkelDw8BTVOIVCARgNf50IABBGAyRJgkqlglKphBACSndPeMycD59V2+A6+UOYMtJwe9lnAACnIaMhOTgh7a0RSHtnJKBSQZQUW77kK7qCXK/Xw2n4eChsNUh97Xlk/vNtQFJC/Ld3I7m6w3jzGgp/3w5TVjo07cNkDXRH9ChiT4NqnMlkgo2Pf+n/szJgY2ODgqzSMf9Vvv6wd3CAMe82FFot4OIGpSRB5emDwsJC2Ef0R87K+TCllw4xb9u8NXzXbEdJ0kVIdg5Inz4W6sdCYDAYYGdnB1tbW9w5NKG9vT2Ki4uhbd8Jvut+gSHpEiStPdLeGQnblqXPivD4cD50Ub+i5PJ55P+yGYV/7IT3ursPz0H0KGJoUI3T6/VwDOsJae1XKPjle8BkRGHUf6DyD4A6pB2Evgi3JgyCpkNXeH70JfK+XwPDzWtQB7dAVsJJCH0RNF1Kh40vPnsK+lPHoHR1h27f/4PQFcLhucEwGAxwMBYjf/0aFCecBAAU/LIZNg2D4DJiIoqOx8BwNRGSgyMKf98BAHB4pnS02PytX8PGPxDazuEouXIBpow0y6EvIrLG0KAaZzaboTMLeM5ZitxvV6Jw9w5oQ8Pg9MoE5BcWwlFtA9s2HaAOegwAoA5pi+Lzp1Gw80cotHZwGDAcTkNGIz8/H7Y2auiPx8CUkwWVrz88PvoKyrYdkJubC7WhBIaky5DsHWHbpgPMBQUwJCcBABQ2NiiKiYIpLxc2jRrDc+4yiOAWpc+7NpuR/8v3ELpCKD284TJ6CvR3XAZMRP/zSAwjwvs0asfdrqPXaDSwt7eHUqmE0WhEYWEhiouL4ejoCLVaDSEECgsLoVKpYGtrC5VKBSEEDAYDCgoKYDQa4ejoCFtbW0iSVBpGOp3lWRnOzs5Wj+Mso9froVQqoVarIUkSTCYTdDodioqKoFarYWdnBxsbG8vT4/R6PQoKCmpsO9H94X0ataeq+zTqbWgcP34csbGxGD9+PEOjBsyfPx8LFiy4a7133nkHU6dOrYUWUX3H0Kg9VYVGvT08FRoaWuUD3WuaaWz/Olt3bTAnygti847vYDq/7+4VH1LKVTvquglEtarehgbVrHea+uGdppX/GiGi+okXoxMRkWwMDSIiko2hQUREsjE0iIhINoYGERHJxtAgIiLZGBpERCQbQ4OIiGTjzX1V2Lx5c7lpzZo1Q9u2bWEwGLB169Zy5SEhIWjVqhWKTGbsTC3//OnHne3RzFGLPIMJ/y/tdrnyf7g4oImDBtklRuxJzylX3sHNEQF2tkgvNmBfRm658s7uTvDTqpFSVIJDWXnlyrt7OsPL1gbXdMX4Mzu/XHm4lwvc1CpcLtAjLqf8+EtPebvCyUaJC/lFiM8tLFf+rK8btEoJCXk6nM3TlSt/wc8dNpICp3IKkVhQVK78JX8PAMDx2wW4Wqi3KlMqFBjYwB0AcCQrH8lF1oMK2koS+vu5AQAOZObhlt76aYAOKiWe9nEFAOzNyEVGsfUzPlxsVIjwdgEA7E7LQY7BaFXuaWuDHp7OAIDfbt1GgdEExR37iK+vL7p16wYA2L59O/R66/Y3atQInTp1AgD89NNPpYMl3qFx48Z44oknAPzNfa+oCDt2lL9TvU2bNmjevDny8vLw22+/lStv3749goNLn5u+e/fucuVPPvkkAgICkJ6ejujo6HLlXbp0QYMGDXDz5k0cPHiwXHnPnj3h5eWFa9eu4ciRI+XKIyIi4ObmhkuXLiE2NrZc+SuvvAIAOH/+PE6dOlWuvH///tBqtThz5gwSEhLKlQ8cOBA2NjY4efIkLly4UK58yJAhAIBjx47hypUrVmUqlQovvvgiAODw4cO4fv26VblGo8Hzzz8PANi/fz9SU1Otyh0dHfHMM88AAKKjo5Genm5V7urqij59+gAAfv/9d9y+bf3d4OXlhZ49ewIA/vOf/yA/3/pvt6J9r+z9VLd629M4fvw4VqxYUdfNICKqV+rtgIV3qosBC+v72FNUimNP1R4OWFh7qhqwsN72NIiIqPoxNIiISDaGBhERycbQICIi2RgaREQkG0ODiIhkY2gQEZFsDA0iIpKNoUFERLIxNIiISDaGBhERycbQICIi2RgaREQkW70NDQ6NTkRU/ertQ5hCQ0MRGhpa180gIqpX6m1Pg4iIqh9Dg4iIZGNoEBGRbAwNIiKSjaFBRESyMTSIiEg2hgYREcnG0CAiItkYGkREJBtDg4iIZGNoEBGRbAwNIiKSjaFBRESyMTSIiEg2hgYREcnG0CAiItkYGkREJFu9DQ0+7pWIqPrxca9ERCRbve1pEBFR9WNoEBGRbAwNIiKSjaFBRESyMTSIiEg2hgYREcnG0CAiItkYGkREJBtDg4iIZGNoEBGRbAwNIiKSjaFBRESyMTSIiEg2hgYREcnG0CAiItkYGkREJBtDg4iIZGNoEBGRbAwNIiKSjaFBRESyMTSIiEg2hgYREcnG0CAiItkYGkREJBtDg4iIZKu3oXH8+HGsWLGirptBRFSvqOq6ATUlNDQUoaGhdd0MIqJ6pd72NIiIqPoxNIiISLYqD0/l5eVh//79iIuLw7Vr16DT6WBnZ4eAgAC0bdsWPXr0gJOTU221lYiI6lilobFp0yYcOHAA7dq1Q69evdCgQQNotVoUFRXh5s2bOHv2LKZPn44uXbpg+PDhtdlmIiKqI5WGhqurKxYtWgQbG5tyZUFBQejSpQtKSkoQFRVVow0kIqIHR6Wh8fTTT991ZrVajb59+1Zrg4iI6MEl65LbM2fOwMvLC15eXrh9+za+/fZbSJKEYcOGwcXFpYabSEREDwpZV0+tWbMGklRadePGjTCZTFAoFLx5jojoESOrp5GdnQ0PDw+YTCacOnUKy5Ytg0qlwvjx42u6fURE9ACRFRparRY5OTlITk6Gv78/NBoNjEYjjEZjTbePiIgeILJCo2/fvpgxYwaMRiNGjRoFADh//jwaNGhQk20jIqIHjKzQeOGFF9ChQwdIkgQfHx8AgJubGyZMmFCjjSMiogeL7AEL/fz8qnxNRET1X6VXT82YMQOHDx+u9LyF0WhETEwM3n///RprHBERPVgq7Wm8/vrr2Lx5M1avXo2goCD4+flBo9FAr9cjNTUVV65cQatWrTBp0qTabC8REdUhhRBCVFUhJycH8fHxuH79OgoLC2Fvb4+AgAA8/vjjcHZ2rq12/i0pKSm1vk7T2P61vk6qfcpVO+q6CY8MDw8PZGZm1nUzHglVnX646zkNFxcXdOvWrVobREREDyc+T4OIiGRjaBARkWwMDSIiko2hQUREssm6uU8IgT179uDQoUPIz8/Hv/71L5w9exY5OTkICwur6TYSEdEDQlZPY/PmzYiOjkbv3r0tl7y5u7tj+/btNdo4IiJ6sMgKjX379mH69Ono3LkzFAoFAMDLywvp6ek12jgiInqwyAoNs9kMjUZjNU2v15ebRkRE9Zus0GjXrh02btwIg8EAoPQcx+bNm9G+ffsabRwRET1YZIXGq6++iuzsbIwaNQo6nQ6vvvoqMjIyMHz48JpuHxERPUBkXT1lZ2eH9957Dzk5OcjMzISHhwdcXFxquGlERPSguaf7NNRqNdzc3GA2m5GdnY3s7OyaahcRET2AZPU04uPjsXLlSmRkZJQr27x5c7U3ioiIHkyyQuPf//43XnzxRXTu3Blqtbqm20RERA8oWaFhMBjQs2dPSBJHHSEiepTJSoF+/fph+/btuMvzmoiIqJ6T1dPo2LEj5s6di59//hmOjo5WZUuWLKmRhhER0YNHVmgsWLAAzZs3R6dOnXhOg4joESYrNNLT0/H555/znAYR0SNOVgqEhobizJkzNd0WIiJ6wMm+emrevHlo0aIFnJ2drcreeOONGmkYERE9eGSFRsOGDdGwYcOabgsRET3gZIXGSy+9VNPtICKih0CloXH27Fm0bNkSAKo8n9GqVavqbxURET2QKg2NNWvWYP78+QCA5cuXV1hHoVDU6n0aaWlp2Lp1K3Q6HaZOnVpr6yUiolIKUcVt3gcPHkSXLl2qZUXLli1DXFwcnJ2dLWEEACdPnsS6detgNpsRHh6OF1544a7Lmj9//j2FRkpKyv00+W8xje1f6+uk2qdctaOum/DI8PDwQGZmZl0345Hg5+dXaVmV5zRWrVpVbaHRo0cP9O3bF0uXLrVMM5vNWLNmDT788EO4u7tjxowZCA0NhdlsxqZNm6zmnzhxYrkrt4iIqHZVGRrVOdZUy5YtkZ6ebjXt0qVL8PHxgbe3NwAgLCwMx44dw4ABAxAZGXnf6/rjjz/wxx9/AAA+++wzeHh43H/D71Nara+R6kJd7FuPKpVKxe39AKgyNMxm811v6vs7J8Kzs7Ph7u5uee3u7o6LFy9WWj8/Px/fffcdkpKSsG3bNgwYMKDCer1790bv3r0tr9mlpZrCfav28PBU7bnvw1MGgwH//ve/K+1x/N0T4RUtV6FQVFrf0dER48aNu+/1ERHR31NlaGg0mhq9Osrd3R1ZWVmW11lZWXB1da2x9RER0d9TpyMQNmnSBKmpqUhPT4fRaERMTAxCQ0PrsklERFSFWjsR/tVXX+Hs2bPIz8/HhAkTMHjwYPTq1QujR4/G3LlzYTab0bNnTw5XQkT0AKvyPo36gvdpUE3hfRq1hyfCa09VJ8Lr7QMyjh8/jhUrVtR1M4iI6hVZAxY+jEJDQ3l+hIiomtXbngYREVU/hgYREcnG0CAiItkYGkREJBtDg4iIZGNoEBGRbPU2NHifBhFR9eN9GkREJFu97WkQEVH1Y2gQEZFsDA0iIpKNoUFERLIxNIiISDaGBhERycbQICIi2eptaPDmPiKi6seb+4iISLZ629MgIqLqx9AgIiLZGBpERCQbQ4OIiGRjaBARkWwMDSIiko2hQUREsjE0iIhItnobGrwjnIio+vGOcCIikq3e9jSIiKj6MTSIiEg2hgYREcnG0CAiItkYGkREJBtDg4iIZGNoEBGRbAwNIiKSjaFBRESyMTSIiEi2ehsaHHuKiKj6cewpIiKSrd72NIiIqPoxNIiISDaGBhERycbQICIi2RgaREQkG0ODiIhkY2gQEZFsDA0iIpKNoUFERLIxNIiISDaGBhERycbQICIi2RgaREQkW70NDQ6NTkRU/Tg0OhERyVZvexpERFT9GBpERCQbQ4OIiGRjaBARkWwMDSIikq3eXj1FRI8ejUYDlUoFs9mMoqIiCCEqrKdQKKDVaiFJEoxGI/R6PQBAkiTY2tpCqVQCAIxGI4qLiy3LkSQJGo0GkiTBYDCguLjYarm2trawsbGBEMIyb33D0CCih55SqYSbmxviUwtw8mYWGrrYoXuwJwryciv8Yndwcsa+S1lIztGhTQMXtPH1RE5ODuydXHDwSjZu5BRBKSkQ7GmPJxp64nZ2FtRqNWztHBF9MQNp+cXoEOCKZp5OyMrKgkKhgJubGy5mFeHPS9lQSRLa+bsg0FmD3NzcOtoqNYOhQUQPPWdnZyw9mIRvjyejoYsWKbl6NPdxxMohbVGSmWHpKSgUCjg6OWP85pM4dysffs4a/PvgVQwLbYjJ3Rrjeo4en/x+Hg1d7ZCSW4Q8vRFD2/vjjc4BMEHCq98cR0puEbwcbLH84BW83q0xhjzuDaVSic+jLmF7fCoC3exgp1YiNvk2Pn2mGVQqFezs7KBUKi09kKKiIphMpjreaveHoUFEDzWlUol8owLfx91AO38XrBjaDisOXcWaw0k4cCUb7b210Ol0AEoPXx28ko0zqXkY3SkQEzoHYeLmE9gcdwPDQxuhgbMWf7zRFSqlhNTcIvRfeRhxyTmwtW2Kn07exLVsHab2egyD/+GPYev/xLrD1/BSO39cyizE9vhUDG7nj3fDH4NCoYDeYIISZtg4uODruBu4nq2DrUpCaz9nPNfSE5mZmXW85e4PT4QT0UNNpVIhMb0AJrNASx9HmEwmtPRxAgCcvZVnOT9RVjfhVh4AWOq28HGCySxwMSMfxUWFuF1kwNd/XsOX0ZdgZ6PE8NCGlmUBQIivE8wmE5p7O0JnMOFatg4HL5cGwJWsAryw6jDGfx+HC+kFsLGxwcK9l/D98WQ0ctXCSWODo0nZkKSH96v34W05ERFKT04XlhgBADZKCWazGWqlAgBQWGyy+oIurWuqsG5BsREKhQK5RQZEJ2Yg7kYOTEIgv9j432VZr8NGKVnmKyg2/ff/JowJC8LVrEJM3RYPvcEEg8kMMwSKDGY85uWAyIjSQ1YPq4e35UREAEwmE7wdNQCAnCID1Go1snUGAIC3ky3s7e1hZ2cHoPSchrejbWld3V/qOmpgZ2eHYAcJa18JRVGJCf3+fQiL913G4Hb+lnXc1pVA7e2IHF2JZT6v/y7z6ZY+eK6VL44mZeP/nUvDrXw9pvR4DK52asSn5OKnkzehsZGwbcyTkKTS8HnYsKdBRA81g8GAFl72aOCswf5LGdh3KQM/x9+EpAB6N/MCALy45ggGrj4CoHSapAC2nbqJ/Zcysf9SBvycNWjl64QDl7Ow7dRNxCbfxs6EVBSWGOFur4ZCoUCfFt4AgB/ibiAqMR3Hrt9GC29HNHLVomdTTygVChy/fhuJ6fk4dysfzhoV/Jy0SEjNQ0Rzb3zyXAgimnshT29EdpHhoT1ExZ4GET3UhBDQF+kw59kQzPsjEe9uOw0vB1t82LcFPLWl92HY2ShhFqX3XXhqJczs2wLLD1zB1G3xaO7tiPd6N4WkAApLjFi87zLyi41QSgqE+DjhnV6PIS8vD4+5azC112NYeyQJB69koW0DZ0yPaIbc3Fx4aTX4oG9zLD9wGcM3HEOgmx0+f7411CoJZ1Jz8cOJG9AbzLBVSRjUtgH8HNXIzHg4L8VViMrufqlHUlJSan2dprH9a32dVPuUq3bUdRPqpfnz52PBggV3rffOO+9g6tSpAAB7e3vY29vDKBSwkYCioiLk5+fDwcHBcnhKp9OhoKAAjo6O0Gq1MJgBlUKgsLAQer0ezs7OsLGxQYkJUKsUMJtMKCwshE6ngyRJcHR0hK1GA6MZUMKMgoICFBUVlV7K+99lmoQCyv8us6SkBE5OTlCpVNAbzdCoJJSUlCAvLw9Go7FGt+Hf4efnV2kZQ6OGMDQeDQyN2jFo0CDY2Njgu+++u2tdhUJR6Z3gcuvebRn3so6/M09dqSo0eHiK6CH0/Lfn67oJNerm7xuQ+sfGctMbNGhg9dq396to0GdkbTWr1m0f3ryum1BOvQ2N48ePIzY2FuPHj6/rphDRPWrQZ2S9DoOHWb0NDT7ulYio+j2c13wREVGdYGgQEZFsDA0iIpKNoUFERLIxNIiISDaGBhERycbQICIi2R6JYUSIiKh6sKdB1SYyMrKum0D1GPevBwNDg4iIZGNoEBGRbAwNqja9e/eu6yZQPcb968HAE+FERCQbexpERCQbQ4OIiGSrt8/TqM+GDBmCRo0awWQyQalUonv37njmmWcgSRIuX76Mffv2YfTo0ZXOv3XrVgwcONDy+sMPP8ScOXNqo+nlJCYmYv369TAYDDAajejUqRMGDx6MhIQEqFQqNGvWrE7aRfJs3boVBw8ehCRJUCgUGDduHIKCgvDNN98gNjYWQOnT9saMGQMPDw8AwIgRI/D1119bLef333+Hra0tunfvjr179+Lxxx+Hm5tblevmvlM3GBoPIbVajS+++AIAkJubi0WLFkGn02Hw4MFo0qQJmjRpUuX827ZtswqNmg6MsnCryNKlS/H2228jMDAQZrPZ8jz3hIQEaDSae/rDr2o9VP0SExMRGxuLzz//HDY2NsjLy4PRaMSmTZtQVFSEhQsXQpIkREdHY968efjss88gSRUf3OjTp4/l/3v37kXDhg3vGhrcd+oGQ+Mh5+zsjHHjxmHGjBl46aWXcPbsWfzyyy+IjIyEXq/H2rVrcfnyZSgUCgwaNAiXL19GSUkJpk2bhoYNG2Ly5MmWX35CCHzzzTc4efIkAODFF19EWFgYEhIS8OOPP8LR0RHJyclo3Lgx3nzzTSgUCmzZsgWxsbEoKSlB06ZNMW7cOCgUCsyaNQtNmzbFhQsX0KpVK+zduxcLFy6ESqWCTqfDtGnTsHDhQuTl5cHV1RUAIEkS/P39kZ6ejt27d0OSJBw4cACjR4+Gh4cHli9fjry8PDg5OWHSpEnw8PDA0qVL4eDggKSkJAQFBaFPnz5Ys2YN8vLyYGtri/Hjx5d7rjRVj9u3b8PR0RE2NjYAACcnJxQXF2Pv3r1YsmSJJSB69uyJ6OhonD59Gm3atKlwWT/88AM0Gg28vLxw+fJlLFq0CGq1GnPnzsWNGzewYcMG6PV6y2fv6urKfaeOMDTqAW9vbwghkJubazV9y5YtsLOzw/z58wEABQUFePLJJ7Fr1y5LT+VOR48eRVJSEr744gvk5eVhxowZaNGiBQDg6tWrWLBgAVxdXTFz5kxcuHABzZs3R9++fTFo0CAAwOLFixEbG2t5zK5Op8M///lPAEBGRgbi4uLQoUMHxMTEoGPHjlCpVOjXrx/eeusttGzZEm3btkX37t3h5eWFiIgIaDQa9O/fHwDw2WefoVu3bujRoweioqKwdu1avPfeewCA1NRUzJw5E5IkYfbs2Rg7dix8fX1x8eJFrF69Gh999FENbHVq06YNtmzZgilTpqB169YICwuDvb09PDw8YGdnZ1W3cePGuHHjRqWhUaZs/xwxYgSaNGkCo9Fo+aydnJwQExOD7777DpMmTeK+U0cYGvVERVdOnz59Gm+99ZbltYODQ5XLOH/+PDp37gxJkuDi4oKWLVvi8uXL0Gq1CA4Ohru7OwAgMDAQ6enpaN68Oc6cOYMdO3aguLgYBQUFaNiwoSU0wsLCLMvu1asXduzYgQ4dOiA6Ohrjx48HAAwaNAhdunRBfHw8Dh48iEOHDmHWrFnl2nbx4kW8++67AIBu3brh22+/tZQ9+eSTkCQJer0eFy5cwIIFCyxlRqPxLluO7pdGo8Hnn3+Oc+fOISEhAV9++SUGDBgAhUJRbetISUlBcnIyPv74YwCA2Wy29C6479QNhkY9kJaWBkmS4OzsjJs3b1qVVdcfcNkhCKD0UIDZbEZJSQnWrFmDTz/9FB4eHvjhhx9QUlJiqWdra2v5f/PmzbFmzRqcPXsWZrMZjRo1spT5+PjAx8cH4eHhGDNmDPLz8++pbRqNBkDpF4q9vX2FvSiqGZIkISQkBCEhIWjUqBF2796NjIwMFBUVQavVWupdvXoVTz755H2tw9/fH3Pnzq2wjPtO7eMltw+5vLw8rFq1Cn379i0XEI8//jh27dpleV1QUAAAUKlUFf6KatGiBQ4fPgyz2Yy8vDycO3cOwcHBla7bYDAAKD2WrdfrcfTo0Srb2q1bNyxcuBA9e/a0TIuLi7P0klJTUyFJEuzt7aHVaqHX6y31mjZtipiYGADAwYMH0bx583LLt7Ozg5eXFw4fPgygtPeVlJRUZZvo/qWkpCA1NdXyOikpCX5+fujevTs2bNgAs9kMANi3bx9sbGxkn5jWaDQoKioCAPj5+SEvLw+JiYkASn/9JycnA+C+U1fY03gIlZ3ILrvio2vXrnj22WfL1XvxxRexevVqTJ06FZIkYdCgQejYsSPCw8Mxbdo0BAUFYfLkyZb6HTp0QGJiIqZNmwYAeOWVV+Di4lKu91LG3t4e4eHhmDp1Kry8vO561VbXrl3x/fffo3PnzpZp+/fvx4YNG6BWq6FUKvHmm29CkiS0b98eCxYswLFjxzB69Gj83//9H5YvX44dO3ZYTmZWZPLkyVi1ahW2bt0Ko9GIzp07IzAw8G6blO5D2YUWhYWFUCqV8PHxwbhx46DVavH1119jypQpKCkpgZOTE+bOnWv5UVNSUoIJEyZYlvPXfbdHjx5YtWqV5UT41KlTsW7dOuh0OphMJjzzzDNo2LAh9506wmFEqNYcOXIEx44dw5tvvlnXTaFakpOTg7lz5+Kpp57i2FH1BEODasXatWtx4sQJzJgxA35+fnXdHCK6TwwNIiKSjSfCiYhINoYGERHJxtAgIiLZGBpERCQb79OgR9758+fxzTffIDk52TLw3ciRIxEcHIy9e/diz549lmEsatLWrVuxbds2AKV3KBuNRqjVagCAp6en1RAXRHWFoUGPNJ1Oh88++wxjxoxBWFgYjEYjzp07ZzVsyt9xL0NuDxw40DJkfW2GFdG9YGjQI61sGIwuXboAKH1WSdlIrDdu3MCqVatgNBoxYsQIKJVKrF+/HjqdznLfia2tLcLDwzFgwABIkmT5sm/SpAn27duHp556Ci+++CK+++47HD58GEajEU888QRGjRpl6UXczY4dO5CYmGgZdA8ove9FkiSMGjXKMgz96dOnkZKSgpCQEEyaNMkyQGViYiI2btyIGzduwNPTE6NGjUJISEh1bkZ6hPCcBj3SfH19IUkSlixZghMnTljG5wJKB8obO3YsmjZtiq+//hrr168HUPqFrdPpsGTJEsyaNQv79+/H3r17LfNdvHgR3t7eWL16NQYOHIhvv/0Wqamp+OKLL7Bo0SJkZ2djy5YtstvYtWtXnDp1CoWFhQBKey8xMTHo1q2bpc6+ffswceJErFixApIkYe3atQCA7OxsfPbZZxg4cCDWrl2LESNGYP78+cjLy/sbW40eZQwNeqTZ2dlh9uzZUCgUWLFiBcaMGYPPP/8cOTk5FdY3m82IiYnBsGHDoNVq4eXlhWeffRb79++31HF1dcXTTz8NpVIJGxsb7NmzByNHjoSDgwO0Wi0GDhyIQ4cOyW6jq6urZTBJADh58iQcHR3RuHFjS51u3bqhUaNG0Gg0GDp0qGXgyf3796Ndu3b4xz/+AUmS8Pjjj6NJkyaIi4u7vw1GjzwenqJHnr+/P15//XUAwM2bN7F48WKsX7/e6lkkZcoeaVr2vGug9CR1dna25fWdZXl5eSguLkZkZKRlmhDCMgKsXN27d8fvv/+O3r1748CBA1a9DACWZ52Urd9kMiEvLw+ZmZk4cuSI5XndQGlPhYen6H4xNIju0KBBA/To0QO7d++usNzJyQlKpRKZmZnw9/cHAGRmZlb6PGtHR0eo1WosWLDgrs+8rsoTTzyB1atX4/r164iNjcUrr7xiVZ6VlWX5f2ZmJpRKJZycnODu7o6uXbtajSpL9Hfw8BQ90m7evIlffvnF8qWbmZmJQ4cO4bHHHgMAuLi4IDs72/L8EUmS0KlTJ3z33XcoKipCRkYGdu7cia5du1a4fEmSEB4ejvXr11sex5udnW15DrtcarUaHTt2xKJFixAcHGzVmwGAAwcO4MaNGyguLsYPP/xgeSJd165dERsbi5MnT1oenJWQkGAVMkT3gj0NeqRptVpcvHgRO3fuhE6ng52dHdq3b2/5Jd+qVSvLCXFJkrBmzRqMHj0aa9euxRtvvAG1Wo3w8HCrB0v91fDhw7FlyxZ88MEHyM/Ph5ubGyIiItC2bdt7amvZM64nTpxYrqxbt25YunQpUlJS0KJFC8szIzw8PPDee+/hm2++wcKFCyFJEoKDgzF27Nh7WjdRGY5yS/SQyMzMxFtvvYWVK1fCzs7OMn3WrFno2rUrwsPD67B19Kjg4Smih4DZbMbOnTsRFhZmFRhEtY2hQfSA0+v1GDlyJOLj4zF48OC6bg494nh4ioiIZGNPg4iIZGNoEBGRbAwNIiKSjaFBRESyMTSIiEi2/w9qri8lsEQzPQAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Run time: ~10s\n", - "\n", - "# Setup\n", - "labelled_annotations = copy.deepcopy(annotations)\n", - "for n, annotation in enumerate(labelled_annotations):\n", - " annotation.properties[\"class\"] = n % 10\n", - "\n", - "predicate = \"props['class'] == \"\n", - "classes = rng.integers(0, 10, size=50)\n", - "query_polygons = [\n", - " Polygon(\n", - " [\n", - " (x, y),\n", - " (x + 128, y),\n", - " (x + 128, y + 128),\n", - " (x, y),\n", - " ],\n", - " )\n", - " for x, y in rng.integers(0, 1000, size=(100, 2))\n", - "]\n", - "stmt = (\n", - " \"for n, poly in zip(classes, query_polygons):\\n\"\n", - " \" store.query(poly, where=predicate + str(n))\"\n", - ")\n", - "\n", - "dict_store = DictionaryStore()\n", - "sql_store = SQLiteStore()\n", - "\n", - "dict_store.append_many(labelled_annotations)\n", - "sql_store.append_many(labelled_annotations)\n", - "\n", - "\n", - "# Time dictionary store\n", - "dict_runs = timeit.repeat(\n", - " stmt,\n", - " globals={\n", - " \"store\": dict_store,\n", - " \"predicate\": predicate,\n", - " \"classes\": classes,\n", - " \"query_polygons\": query_polygons,\n", - " },\n", - " number=1,\n", - " repeat=10,\n", - ")\n", - "dict_result = dict_store.query(query_polygons[0], where=predicate + \"0\")\n", - "\n", - "# Time SQLite store\n", - "sqlite_runs = timeit.repeat(\n", - " stmt,\n", - " globals={\n", - " \"store\": sql_store,\n", - " \"predicate\": predicate,\n", - " \"classes\": classes,\n", - " \"query_polygons\": query_polygons,\n", - " },\n", - " number=1,\n", - " repeat=10,\n", - ")\n", - "sql_result = sql_store.query(query_polygons[0], where=predicate + \"0\")\n", - "\n", - "\n", - "# Check that the set difference of bounding boxes is empty i.e. all sets\n", - "# of results contain polygons which produce the same set of bounding\n", - "# boxes. This avoids being tripped up by slight varations in order or\n", - "# coordinate order between the results.\n", - "dict_set = {x.geometry.bounds for x in dict_result}\n", - "sql_set = {x.geometry.bounds for x in sql_result}\n", - "assert len(dict_set.difference(sql_set)) == 0 # noqa: S101\n", - "\n", - "# Plot the results\n", - "plot_results(\n", - " experiments=[dict_runs, sqlite_runs],\n", - " title=\"100 Queries with a Polygon and Predicate\",\n", - " tick_label=[\"DictionaryStore\", \"SQLiteStore\"],\n", - ")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "kJ8x5tJmpT5y" - }, - "source": [ - "### Complex Predicate Query\n", - "\n", - "Here we slightly increase the complexity of the predicate to show how\n", - "the complexity of a predicate can dramatically affect the performance\n", - "when handling many annotations.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "VHb4PqbHpT5y", - "outputId": "343b44c7-741d-4e11-9dd2-85f357ba6f32" - }, - "outputs": [ - { - "data": { - "image/png": "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Gzeu8fHV9vm7nTB47nANGjTApUuXbJZV3q4ckNCgQQNkZmba9CkrK0NeXl61gxaaNWsGSZJw4sQJDBw40K698iR1s2bNAACSJNmFVVlZmc1ti8WCkSNHYtq0aXbr8/Lysv5c3S/mteuaOnUqRo4caddW+ZimT5+OESNGYMuWLdi+fTvef/99vPbaa9WODmzYsCFCQ0Oxbds2xMfHo1evXujYsSPMZjOOHz+OHTt24P33379ubVVRq9U2txUKBSwWS7X9Dx48iCFDhiAuLg4ff/wxPDw8cODAATz99NPVngBv2rQpJElCUlJSla/Zta4NKCGE3bKq/rC4dtn1HkvluqtjsVjg5uaGw4cP27Vd+7zt3r0bSqUS586dg9FohFarrXG7ALB8+XJ07NgRbm5u8PHxsWu/dn+r7X5amz/srlXTfWbNmoUPPvgAs2fPRocOHeDi4oI5c+bg559/rnGdN/L83S443OIO161bN/z+++82v/hbtmyBk5MT2rdvb+2zf/9+6PV6a5/K+3Tr1q3K9Xp4eOChhx7CggULbO5X6f3330dAQAD69OkDAPD19UV2drbNMO7ExESb+4SHh+P48eMIDQ1FWFiYzT8PD48betzh4eFISkqyW09YWJjNX6pNmjTBhAkTsGHDBrzzzjtYuHBhjevt1asXtm3bhp07dyI2NhaSJCE6OhqfffYZLl++XOMRj1qtthvGfrP27t0Lb29vvPfee+jSpQuaNWt23e+DeHp6on///pg/fz4KCgrs2svKylBcXIywsDA4Ojpi165dNu27d+9G69atb0n9144a279/P1q2bFll3/DwcOTn58NoNNq9ln8/8l26dCl++OEH7Nq1CwaDAf/+979rVUtgYCDCwsKqDJ3q6rneftqqVSvs27fP5n7X3r5Wx44dsW/fvmq/xrB7927069cPzz77LNq3b4+wsDCbTwKAqvex2j5/t5X6/JyPalZYWCiOHj0qjh49Kho0aCAmTpwojh49ajPK6ty5c8LFxUWMHj1anDhxQmzatEl4enranP8oLCwUQUFB4qGHHhLHjh0T27dvF8HBwXafY1/r3LlzIjAwUHTs2FH8+uuv4ty5c+LQoUNi2LBhwtHRUezcudPa99SpU0KSJBEXFyfS09PF+vXrRUhIiM05nuTkZOHs7CyGDx8uDh48KM6cOSO2b98uJk2aJE6f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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Run time: ~1m\n", - "\n", - "# Setup\n", - "box = Polygon.from_bounds(0, 0, 1024, 1024)\n", - "labelled_annotations = copy.deepcopy(annotations)\n", - "for n, annotation in enumerate(labelled_annotations):\n", - " annotation.properties[\"class\"] = n % 4\n", - " annotation.properties[\"n\"] = n\n", - "\n", - "predicate = \"(props['n'] > 1000) & (props['n'] % 4 == 0) & (props['class'] == \"\n", - "targets = rng.integers(0, 4, size=100)\n", - "stmt = \"for n in targets:\\n store.query(box, where=predicate + str(n) + ')')\"\n", - "\n", - "dict_store = DictionaryStore()\n", - "sql_store = SQLiteStore()\n", - "\n", - "dict_store.append_many(labelled_annotations)\n", - "sql_store.append_many(labelled_annotations)\n", - "\n", - "\n", - "# Time dictionary store\n", - "dict_runs = timeit.repeat(\n", - " stmt,\n", - " globals={\n", - " \"store\": dict_store,\n", - " \"predicate\": predicate,\n", - " \"targets\": targets,\n", - " \"box\": box,\n", - " },\n", - " number=1,\n", - " repeat=10,\n", - ")\n", - "dict_result = dict_store.query(box, where=predicate + \"0)\")\n", - "\n", - "# Time SQLite store\n", - "sqlite_runs = timeit.repeat(\n", - " stmt,\n", - " globals={\n", - " \"store\": sql_store,\n", - " \"predicate\": predicate,\n", - " \"targets\": targets,\n", - " \"box\": box,\n", - " },\n", - " number=1,\n", - " repeat=10,\n", - ")\n", - "sql_result = sql_store.query(box, where=predicate + \"0)\")\n", - "\n", - "\n", - "# Check that the set difference of bounding boxes is empty i.e. all sets\n", - "# of results contain polygons which produce the same set of bounding\n", - "# boxes. This avoids being tripped up by slight varations in order or\n", - "# coordinate order between the results.\n", - "dict_set = {x.geometry.bounds for x in dict_result.values()}\n", - "sql_set = {x.geometry.bounds for x in sql_result.values()}\n", - "\n", - "assert len(dict_set.difference(sql_set)) == 0 # noqa: S101\n", - "\n", - "# Plot the results\n", - "plot_results(\n", - " experiments=[dict_runs, sqlite_runs],\n", - " title=\"100 Queries with a Complex Predicate\",\n", - " tick_label=[\"DictionaryStore\", \"SQLiteStore\"],\n", - ")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "CAT0KmS6pT5y" - }, - "source": [ - "# Part 2: Large Scale Dataset Benchmarking\n", - "\n", - "Here we generate some sets of anntations with five million items each\n", - "(in a 2237 x 2237 grid). One is a set of points, the other a set of\n", - "generated cell boundaries.\n", - "\n", - "The code to generate and write out the annotations to various formats is\n", - "included in the following cells. However, some of these take a very long\n", - "time to run. A pre-generated dataset is downloaded and then read from\n", - "disk instead to save time. However, you may uncomment the generation\n", - "code to replicate the original.\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nwH5zYFupT5y" - }, - "source": [ - "## 2.1) Points Dataset\n", - "\n", - "Here we generate a simple points data in a grid. The grid is 2237 x 2237\n", - "and contains over 5 million points. We also write this to disk in\n", - "various formats. Some formats take a long time and are commented out. A\n", - "summary of times for a consumer laptop are shown in a table at the end.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "2FjCL2jgpT5y" - }, - "outputs": [], - "source": [ - "# Generate some points with a little noise\n", - "# Run time: ~5s\n", - "points = np.array(\n", - " [\n", - " [x, y]\n", - " for x in np.linspace(0, 75_000, 2237)\n", - " for y in np.linspace(0, 75_000, 2237)\n", - " ],\n", - ")\n", - "# Add some noise between -1 and 1\n", - "rng_42 = np.random.default_rng(42)\n", - "points += rng_42.uniform(-1, 1, size=(2237**2, 2))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "DRWABSBVpT5z" - }, - "source": [ - "### 2.1.1) Writing To Disk\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "x76WbSFdpT52" - }, - "outputs": [], - "source": [ - "# Save as a simple Numpy array (.npy)\n", - "# Run time: <1s\n", - "np.save(\"points.npy\", points)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "dkKtM-DKpT52" - }, - "outputs": [], - "source": [ - "# Save as compressed NumPy archive (.npz)\n", - "# Run time: ~5s\n", - "np.savez_compressed(\"points.npz\", points)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "rbHdEIbPpT52" - }, - "source": [ - "Note that the above numpy format is missing the keys (UUIDs) of each point.\n", - "This may not be required in all cases. However, for the sake of comparison\n", - "we also generate a NumPy archive with keys included. We store the UUIDs\n", - "as integers to save space and for a fair comparison where the optimal\n", - "storage method is used in each case. Note however that UUIDs are too\n", - "large to be a standard C type and therefore are stored as an object\n", - "array.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "DbLm4l5tpT52" - }, - "outputs": [], - "source": [ - "# Generate UUIDs\n", - "# Run time: ~10s\n", - "keys = np.array([uuid.uuid4().int for _ in range(len(points))])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "zXuAqw0KpT52" - }, - "outputs": [], - "source": [ - "# Generate some UUIDs as keys\n", - "# Save in NumPy format (.npz)\n", - "# Run time: <1s\n", - "np.savez(\"uuid_points.npz\", keys=keys, coords=points)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "UAHAgPU4pT52" - }, - "outputs": [], - "source": [ - "# Save in compressed (zip) NumPy format (.npz)\n", - "# Run time: ~10s\n", - "np.savez_compressed(\"uuid_points_compressed.npz\", keys=keys, coords=points)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "j5wlDFYfpT52" - }, - "outputs": [], - "source": [ - "# Write to SQLite with SQLiteStore\n", - "# Run time: ~10m\n", - "points_sqlite_store = SQLiteStore(\"points.db\")\n", - "_ = points_sqlite_store.append_many(\n", - " annotations=(Annotation(Point(x, y)) for x, y in points),\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "tUekiEqspT53" - }, - "outputs": [], - "source": [ - "# Load a DictionaryStore into memory by copying from the SQLiteStore\n", - "# Run time: ~1m 30s\n", - "points_dict_store = DictionaryStore(Path(\"points.ndjson\"))\n", - "for key, value in points_sqlite_store.items():\n", - " points_dict_store[key] = value" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Uynntjq7pT53" - }, - "outputs": [], - "source": [ - "# Save as GeoJSON\n", - "# Run time: ~1m 30s\n", - "points_sqlite_store.to_geojson(\"points.geojson\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "4YMuggcgpT53" - }, - "outputs": [], - "source": [ - "# Save as ndjson\n", - "# Run time: ~1m 30s\n", - "# Spec: https://github.com/ndjson/ndjson-spec\n", - "points_sqlite_store.to_ndjson(\"points.ndjson\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "lW9NoCPwpT53" - }, - "source": [ - "### 2.1.2) Points Dataset Statistics Summary\n", - "\n", - "| Format | Write Time | Size |\n", - "| -----------------------------: | ---------: | -----: |\n", - "| SQLiteStore (.db) | 6m 20s | 893MB |\n", - "| ndjson | 1m 23s | 667 MB |\n", - "| GeoJSON | 1m 42s | 500 MB |\n", - "| NumPy + UUID (.npz) | 0.5s | 165 MB |\n", - "| NumPy + UUID Compressed (.npz) | 31s | 136 MB |\n", - "| NumPy (.npy) | 0.1s | 76 MB |\n", - "| NumPy Compressed (.npz) | 3.3s | 66 MB |\n", - "\n", - "Note that the points SQLite database is significantly larger than the\n", - "NumPy arrays on disk. The numpy array is much more storage efficient\n", - "partly because there is no R Tree index or unique identifier (UUID)\n", - "stored for each point. For a more fair comparison, another NumPy archive\n", - "(.npz) is created where the keys are stored along with the coordinates.\n", - "\n", - "Also note that although the compressed NumPy representation is much\n", - "smaller, it must be decompressed in memeory before it can be used. The\n", - "uncompressed versions may be memory mapped if their size exceeds the\n", - "available memory.\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "a_3Gz5Q0pT53" - }, - "source": [ - "### 2.1.3) Simple Box Query\n", - "\n", - "Here we evaluate the performance of performing a simple box query on the\n", - "data. All points which are in the area between 128 and 256 in the x and\n", - "y coordinates are retrieved. It is assumed that the data is already in\n", - "memory for the NumPy formats. In reality this would not the be case for\n", - "the first query, all data would have to be read from disk, which is a\n", - "significan overhead. However, this cost is amortised across many\n", - "queries. To ensure the fairest possible comparison, it is assumed that\n", - "many queries will be performed, and that this data loading cost in\n", - "negligable.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "o9J0d6gdpT53" - }, - "outputs": [], - "source": [ - "box = Polygon.from_bounds(128, 128, 256, 256)\n", - "\n", - "# Time numpy\n", - "numpy_runs = timeit.repeat(\n", - " (\n", - " \"where = np.all([\"\n", - " \"points[:, 0] > 128,\"\n", - " \"points[:, 0] < 256,\"\n", - " \"points[:, 1] > 128,\"\n", - " \"points[:, 1] < 256\"\n", - " \"], 0)\\n\"\n", - " \"uuids = keys[where]\\n\"\n", - " \"result = points[where]\\n\"\n", - " ),\n", - " globals={\"keys\": keys, \"points\": points, \"np\": np},\n", - " number=1,\n", - " repeat=10,\n", - ")\n", - "\n", - "# Time SQLiteStore\n", - "sqlite_runs = timeit.repeat(\n", - " \"store.query(box)\",\n", - " globals={\"store\": points_sqlite_store, \"box\": box},\n", - " number=1,\n", - " repeat=10,\n", - ")\n", - "\n", - "# Time DictionaryStore\n", - "dict_runs = timeit.repeat(\n", - " \"store.query(box)\",\n", - " globals={\"store\": points_dict_store, \"box\": box},\n", - " number=1,\n", - " repeat=10,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "eX1qqUIipT53", - "outputId": "a4033a88-6b2d-4a55-f3f6-ba419ef748c0" - }, - "outputs": [ - { - "data": { - "image/png": "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/Tz77LMcP36c9evX8/TTT/PII4+U2M3kjoYNGwKwdu1arl27RlZWFqdOneKFF15g+/btnD9/nl27drFt2zaXz4VSiq1bt9K/f/8/fGyVSsUNp4iSFHfL6o0cDod68803VYMGDZRer1cNGzZ0+5bbG7322muqfv36zuWUlBR17733KrPZ7LzldsuWLapHjx7O22KjoqLUG2+8oRwOR6n154ZbFE0mk2revLl66623lN1ud+ZLT0933nLr5eXlcsvtyZMnlb+/v5o1a5bLcfft21e1b99eWa3WEvefl5enpk2bplq2bKm8vb0VoLRarVq7dm2RvLt371Zt27ZVRqNRtW7dWm3durXILbfJyclqzJgxqlatWspgMKhatWqpQYMGOR++K2lAvdDBgwcVoP7973+XWOcbbd68WZnNZmWxWJzr/vnPf6oGDRooo9GogoKCVKdOndSqVatuWlZx1/1Gnh4IV6roLbcxMTHF3nJ7s1udf8ud245vvOU2NDRUjRkzxq1bbm+0bds2BaizZ886140bN06Fh4crjUajhg8frhISEtTgwYNV7dq1lZeXl6pZs6YaOXKkSktLc27z448/qsDAQJWdnV1qnW83GqXkzX2iajt9+jSxsbE0btyYtWvXltuDaIXWrVvHoEGDuHDhAhEREW5t06tXLwYMGMD48ePLtnKizPTr149u3brxwgsvVHRVPEq6p0SVFxkZybZt2+jSpYvLnVllzWKxcPz4cV599VWGDh3qdsAAmDNnzi3d5SUql8zMTDp16lQlg760NIQoI1OmTOH111+nffv2rFmzptS7poS4XUjQEEII4TbpnhJCCOE2CRpCCCHcVi2mEbnx4a3qKjQ0tMiEcVDw9GtYSDCavFzX9QYvUrOyi0ynrdPpMJvNeDls2JOuoDMH4jAHcv36dXx9fTHptNiTroBOhzY8guycXLKysvDx8cHPzw+VcAGN0QcVFEJ6ejp2u53AwMCCF/9YsgCweRlJSUkpu5NRSZR0TUTFkutSoKSHV6ts0Ni3bx9xcXGMHj26oqtSqen1euxnTpI04XGX9QEjnsHQ6z6XoKHRaAgJCiJr6QekfPMZKi8PgKB/TCbknkFY43aS8J+XUdkFX/660BqEvjITfXgtDNevce3FUdjOnwHA2CGGoOdeJ08p8n/8muTFs3FkZaDx8qb26l/fCqfVavHy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nVmzZmGxWBgyZAiRkZFERkaWuv2XX37pEjTKOmAUBrfizJ49m3/+8580aNAAh8NBQkICAPHx8RiNxlv6EittP5XFiRMniIuLY/r06RgMBjIyMrDZbHzyySfk5OQwc+ZMtFotmzZt4s0332TatGlotcU31u+55x7n/zdv3kzdunVvGjSq2/kuicFgYM+ePQwaNAiz2ezx8vv378/AgQO5dOkS//rXv5g3b16J1/G3Ll26hMPh4NixY+Tm5mI0Govk+e25v52vRXEkaJShgIAARo0axeTJk3nooYc4evQo//3vf5k0aRK5ubksXLiQ06dPo9FoePDBBzl9+jRWq5WJEydSt25dnnnmGeevWKUUy5Yt4+DBgwA88MADdO7cmfj4eD7//HP8/f25ePEijRo14umnn0aj0bBq1Sri4uKwWq00btyYUaNGodFomDJlCo0bN+aXX36hZcuWbN68mZkzZ6LX67FYLEycOJGZM2eSkZFBUFAQAFqtljp16pCUlMT333+PVqtl27ZtjBgxgtDQUD744AMyMjIwm8089dRThIaGMnv2bPz8/Dh37hwNGzbknnvuYcGCBWRkZODt7c3o0aOLvCO7Il2/fh1/f38MBgMAZrOZvLw8Nm/ezPvvv+/8YunRowebNm3i8OHD3HHHHcWW9dlnn2E0GgkPD+f06dPMmjULLy8vpk6dyqVLl1iyZAm5ubnO8xUUFFTtzndJtFotvXr14ptvvuGvf/2rS9rs2bNp164dHTt2BH5t5cXHx/PZZ58REBDA+fPnad++PfXq1WPdunXOz1RERIRLWXXq1EGr1ZKSksKUKVOK/Qzo9a5fkdu3bycmJobLly+zb98+7r77bgCXz1R0dDRxcXEuyzVr1mT16tXYbDb8/f15+umnMZvNjB8/ntdff93Z8hk3bhxTp04tk2DpKRI0yliNGjVQSpGenu6yftWqVfj4+DBjxgwAsrKy6NixI+vXr3e2VG60Z88ezp07x1tvvUVGRgaTJ0+mWbNmAJw9e5a3336boKAgXn75ZX755ReaNm1K3759efDBBwF47733iIuLczbFLRYLr7zyCgDXrl1j//79tG/fnp07d9KhQwf0ej39+/dn/PjxNG/enDZt2tCtWzfCw8Pp3bs3RqORgQMHAjBt2jRiYmLo3r07P/74IwsXLuT5558HIDExkZdffhmtVsurr77Kk08+Sc2aNTl58iTz58/nX//6Vxmc9d/njjvuYNWqVYwbN45WrVrRuXNnfH19CQ0NxcfHxyVvo0aNuHTpUolBo1DhNX3ssceIjIzEZrM5z4/ZbGbnzp2sWLGCp556qtqd79L06dOHiRMnct9997m9zfnz53nnnXfw8/PjH//4B7GxsbzxxhusW7eO9evX8/jjj7vkP3nyJFqtltDQUFq0aFHsZ+C3du3axUsvvURCQgLr1693Bg1w/UzFxcW5LGdlZTF16lQ0Gg0//PADa9euZdiwYXTt2pVt27bRv39/Dh8+TP369St1wAAJGuWiuLuaDx8+7NIn6ufnV2oZx48fp0uXLmi1WgIDA2nevDmnT5/GZDIRFRVFSEgIAA0aNCApKYmmTZty5MgR1q5dS15eHllZWdStW9cZNDp37uwsu2fPnqxdu5b27duzadMmRo8eDcCDDz7I3XffzaFDh9i+fTs7duxgypQpRep28uRJnnvuOQBiYmJYvny5M61jx45otVpyc3P55ZdfePvtt51pNpvtJmeufBmNRqZPn86xY8eIj4/nnXfeYfDgwWg0Go/tIyEhgYsXL/Laa68B4HA4nK2L6na+S+Pj40NMTAzr1q3Dy8vLrW0iIyOd5zIiIoLWrVsDUK9ePY4cOeLM980337Bt2zZMJhPjx49Ho9GU+Bm40alTpzCbzYSFhRESEsIHH3xAVlaW87N742fqt8upqam8++67XL9+HZvNRnh4OFDQan3rrbfo378/mzZtokePHrdwliqGBI0ydvXqVbRaLQEBAVy+fNklzVNfRoXdKVDQtHc4HFitVhYsWMAbb7xBaGgon332GVar1ZnP29vb+f+mTZuyYMECjh49isPhoF69es60iIgIIiIiiI2NZeTIkWRmZt5S3Qr7fB0OB76+vsW2oioTrVZLixYtaNGiBfXq1eP777/n2rVr5OTkYDKZnPnOnj3r7CK5VXXq1GHq1KnFplW3812a/v3788ILL9C9e3fnOp1Oh8PhAAp+jN0YCG/8HGg0GueyRqNxblNYbmGrrVBpn4FCO3bs4PLly/z9738HICcnhz179hAbGwu4fqZ+u7xw4UIGDBhAdHS0s0sZIDQ0lICAAI4cOcLJkyd55pln3D9BFURuuS1DGRkZzJs3j759+xYJEK1bt2b9+vXO5aysLAD0en2xvwibNWvGrl27cDgcZGRkcOzYMaKiokrcd35+PlDQL5+bm8uePXtKrWtMTAwzZ850+aWzf/9+ZyspMTERrVaLr68vJpOJ3NxcZ77GjRuzc+dOoKDPt2nTpkXK9/HxITw8nF27dgEFH/hz586VWqfylpCQQGJionP53Llz1KpVi27durFkyRLnF8+WLVswGAxuD0wbjUZycnIAqFWrFhkZGZw4cQIo+PV/8eJFoPqd75vx8/OjU6dO/Pjjj851YWFhnDlzBoC9e/dit9s9tr/iPgOFHA4Hu3fv5j//+Q+zZ89m9uzZTJw4kR07drhVtsVicd4IsWXLFpe0nj178t5779GpUye3B+QrkrQ0PKxw0K3wjomuXbsyYMCAIvkeeOAB5s+fz4QJE9BqtTz44IN06NCB2NhYJk6cSMOGDV1+dbRv354TJ04wceJEAB599FECAwOLtF4K+fr6Ehsby4QJEwgPD7/pXVtdu3bl008/pUuXLs51W7duZcmSJXh5eaHT6Xj66afRarW0a9eOt99+m7179zJixAj+9re/8cEHH7B27VrnwGxxnnnmGebNm+ccEOzSpQsNGjS42SktN4U3J2RnZ6PT6YiIiGDUqFGYTCaWLl3KuHHjsFqtmM1mZ/80FFzzMWPGOMv57fXu3r078+bNcw6ET5gwgUWLFmGxWLDb7fTr14+6detWu/PtjgEDBrj8uIqNjeWtt95i8uTJtGrVqsiv+z+iuM9AoWPHjhEcHOxyB1zz5s2ZNWsW169fv2nZDz30EG+//TbBwcH86U9/IikpyZkWHR3NBx98cFt0TYFMIyL+Z/fu3ezdu5enn366oqtSqaWlpTF16lT69OkjcxRVMRX1GTh9+jRLlizh1VdfLdf9/l4SNAQLFy7kwIEDTJ48mVq1alV0dYQodxX1GVizZg0bNmzgmWeeKbabsTKSoCGEEMJtlX/URQghRKUhQUMIIYTbJGgIIYRwmwQNIYQQbpPnNES1d/z4cZYtW8bFixedEwUOHz6cqKgoNm/ezA8//OCc9qMsrV69mi+//BIoeJjMZrM5p9AICwtzmRJEiIoiQUNUaxaLhWnTpjFy5Eg6d+6MzWbj2LFjLlNS/BG3Mi32/fff75wWvzyDlRC3QoKGqNYKpw0pnK3Uy8vLOXPtpUuXmDdvHjabjcceewydTsfixYuxWCzO+/q9vb2JjY1l8ODBaLVa55d9ZGQkW7ZsoU+fPjzwwAOsWLGCXbt2YbPZuOuuu3j88cfdnohv7dq1nDhxwjlJIRQ8V6DVann88ced03IfPnyYhIQEWrRowVNPPeWcSO/EiRN8/PHHXLp0ibCwMB5//HFatGjhydMoqhEZ0xDVWs2aNdFqtbz//vscOHDAOQcYFEws+OSTT9K4cWOWLl3K4sWLgYIvbIvFwvvvv8+UKVPYunUrmzdvdm538uRJatSowfz587n//vtZvnw5iYmJvPXWW8yaNYvU1FRWrVrldh27du3Kzz//THZ2NlDQetm5cycxMTHOPFu2bGHs2LHMnTsXrVbLwoULgYLZVadNm8b999/PwoULeeyxx5gxYwYZGRl/4KyJ6kyChqjWfHx8ePXVV9FoNMydO5eRI0cyffp00tLSis3vcDjYuXMnQ4cOxWQyER4ezoABA9i6daszT1BQEPfeey86nQ6DwcAPP/zA8OHD8fPzw2Qycf/997s90V1heYUTVgIcPHgQf39/GjVq5MwTExNDvXr1MBqN/OUvf3FObrl161buvPNO2rZti1arpXXr1kRGRrJ///7fd8JEtSfdU6Laq1OnjnO668uXL/Pee++xePHiYt8BXfgK2ML3g0PBIHVqaqpz+ca0jIwM8vLymDRpknOdUsplqm53dOvWjQ0bNtCrVy+2bdvm0soAnO9TKdy/3W4nIyOD5ORkdu/e7Xy/ORS0VKR7SvxeEjSEuEHt2rXp3r0733//fbHpZrMZnU5HcnIyderUASA5ObnE93/7+/vj5eXlnOH097rrrruYP38+Fy5cIC4ujkcffdQlPSUlxfn/5ORkdDodZrOZkJAQunbt6jILrxB/hHRPiWrt8uXL/Pe//3V+6SYnJ7Njxw7+9Kc/ARAYGEhqaqrzHSdarZZOnTqxYsUKcnJyuHbtGl9//TVdu3YttnytVktsbCyLFy92vvI3NTXV+a53d3l5edGhQwdmzZpFVFSUS2sGYNu2bVy6dIm8vDw+++wz5xv8unbtSlxcHAcPHnS+nCs+Pt4lyAhxK6SlIao1k8nEyZMn+frrr7FYLPj4+NCuXTvnL/mWLVs6B8S1Wi0LFixgxIgRLFy4kH/84x94eXkRGxtb6rsQHnnkEVatWsX//d//kZmZSXBwML1796ZNmza3VNfCd4KPHTu2SFpMTAyzZ88mISGBZs2aOd+xERoayvPPP8+yZcuYOXMmWq2WqKgonnzyyVvatxCFZJZbIW4TycnJjB8/no8++ggfHx/n+ilTptC1a1fna0eFKEvSPSXEbcDhcPD111/TuXNnl4AhRHmToCFEJZebm8vw4cM5dOgQQ4YMqejqiGpOuqeEEEK4TVoaQggh3CZBQwghhNskaAghhHCbBA0hhBBuk6AhhBDCbf8fZakD0nF7LkQAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "plot_results(\n", - " experiments=[dict_runs, sqlite_runs, numpy_runs],\n", - " title=\"Points Box Query (5 Million Points)\",\n", - " tick_label=[\"DictionaryStore\", \"SQLiteStore\", \"NumPy Array\"],\n", - ")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "aNU6FP90pT53" - }, - "source": [ - "Although the NumPy array is very space efficient on disk, it is not as\n", - "fast to query as the `SQLiteStore`. The `SQLiteStore` is likely faster\n", - "due to the use of the R tree index. Furthermore, the method used to\n", - "store the points in a NumPy array is limited in that it does not use\n", - "UUIDs, which makes merging two datasets more difficult as the indexes of\n", - "points no longer uniquely identify them. Additionally, only homogeneous\n", - "data such as two-dimentional coordinates can be practically stored in\n", - "this way. If the user would like to store variable length data\n", - "structures such as polygons, or even mix data types by storing both\n", - "points and polygons, then using raw NumPy arrays in this way can become\n", - "cumbersome and begins to offer little benefit in terms of storage\n", - "efficient or query performance.\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "c766NXGPpT53" - }, - "source": [ - "### 2.1.4) Polygon Query\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "6jiMpRnxpT53" - }, - "outputs": [], - "source": [ - "big_triangle = Polygon(\n", - " shell=[ # noqa: S604\n", - " (1024, 1024),\n", - " (1024, 4096),\n", - " (4096, 4096),\n", - " (1024, 1024),\n", - " ],\n", - ")\n", - "\n", - "# Time SQLiteStore\n", - "sqlite_runs = timeit.repeat(\n", - " \"store.query(polygon)\",\n", - " globals={\"store\": points_sqlite_store, \"polygon\": big_triangle},\n", - " number=1,\n", - " repeat=10,\n", - ")\n", - "\n", - "# Time DictionaryStore\n", - "dict_runs = timeit.repeat(\n", - " \"store.query(polygon)\",\n", - " globals={\"store\": points_dict_store, \"polygon\": big_triangle},\n", - " number=1,\n", - " repeat=10,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Es2OQ5OdpT53", - "outputId": "b98176ee-7003-49f7-f5ca-62b08180b2ee" - }, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYgAAAEaCAYAAAAL7cBuAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAy3klEQVR4nO3dd3wUdeL/8deWJLtpJCGEQEJHqYeAAZSOFMEuCnbhkCLY8LCA5SeK3GFBDyxILyIcHjb0lFO50AMHQRBDFaRHIISwCam7O78/ctmva4YQhCQE3s/HI49HZj5TPjs7u+/9TPmMxTAMAxERkd+xVnQFRETk4qSAEBERUwoIERExpYAQERFTCggRETGlgBAREVMKiMtA165dGTx4cEVX47Lz2GOP8eijj5b7eseOHUvDhg19w3PmzMFut/uGly9fjsVi4dChQwDs27cPi8XC6tWry72uf8TAgQPp0aNHma9n//79REdHk5qaWubrulgpIC5yAwcOxGKxYLFYsNvt1KlTh4cffpgTJ05UdNXKVGZmJs8//zyNGjUiKCiIyMhI+vTpw/Llyyu6aqWyc+dOZs+ezQsvvOAbN3bsWN97+du/n3/+ucRl1a1bF4vFwjvvvFOsbOTIkVgsFr8vzKeeeop169aVuq61atUiNTWVdu3alXqeP+q328BqtRIXF8c999zD/v37S72MSZMm8c9//vOc1vvqq69St27dc5qnTp069O/fnxdffPGc5ruUKCAqgU6dOpGamsq+ffuYPHkyn3zyCQ8++GBFV6vMuFwuOnTowKJFi3j11VfZtWsXiYmJXHHFFXTv3p1Zs2aVeR0Mw6CgoOAPzz958mRuuOEGYmNj/cbXrVuX1NRUv7969eqddXm1a9dm+vTpfuNyc3P58MMPqVOnjt/40NBQoqOjS11Xm81GbGwsAQEBpZ7nfBRtg0OHDjFv3jw2btzIzTffjMfjKdX8VapUITIysoxrWWjw4MHMnz+ftLS0clnfxUYBUQkEBgYSGxtLfHw8t956KyNHjmTp0qXk5ORgGAZvvvkm9evXJzAwkAYNGvD3v//9jMuaPXs2ERERZGdn+41/+eWXqVevHkU31n///ff86U9/wuFw0KJFC1asWIHFYmH+/Pm+eXbu3MmNN95IaGgooaGh3HzzzX6/hosObaxZs4bWrVsTHBxMmzZtSE5OLvH1vvDCC+zevZtly5bRr18/6tSpQ8uWLZk8eTJDhw7lkUce4ciRI37r+K1Dhw5hsVj8Whs///wzd9xxBxEREURGRtKrVy+2bt1arK6JiYm0atWKoKAgpkyZgtVqZe3atX7LX7FiBVarlb1795rW3+v1snDhQm677bZiZUVfxr/9s9lsJW4PgLvvvpu9e/eyfv1637jFixcTGRlJly5d/Kb9/SGmszE7xFRW7y383zaoWbMm3bt3Z+zYsWzdutW3/Llz59K0aVOCgoKIj4/nhRdewO12++b//SGmouFp06ZRp04dwsPDufXWWzl+/Livri+++CL79+/3tV7Gjh0LwBdffEGrVq0IDg4mIiKCtm3b8sMPP/iW3bp1a6pXr87ixYtLvT0vJQqISsjpdOL1enG73bz//vu8+OKLjB49mpSUFJ5++mlGjx7NzJkzTee9++67sVgsfk10r9fL7NmzGTx4MBaLhcOHD3PLLbfQrl07Nm3axNtvv81f/vIXv+Xk5OTQq1cvcnNzWbFiBStWrCArK4vevXuTn5/vt+wxY8YwadIkNm3aRGRkJP379/f7wP+WYRh89NFH3HfffcV+GQM899xz5ObmntMH9ujRo3Ts2JGYmBhWrVrFunXraNSoEV27dvV9iRTV9ZlnnmHixIns2LGDe+65h549exb75T5jxgy6d+9O/fr1Tde3detWTp48Sdu2bYuVHTp0iPj4eOLj4+nTp0+x8DmTsLAw7r77br+6TJs2zfeeXUhl9d6eidPpBKCgoIB//etfDBo0iAceeICtW7cyceJE3nvvPV5++eUSl7FhwwYSExP517/+xdKlS9m8eTNPPfUUAHfddRfPPvss8fHxvlbbU089xa+//kq/fv245557SElJISkpiZEjRxb7wdGuXTsSExPP6TVdMgy5qA0YMMDo3r27bzglJcWoX7++0a5dO8MwDCM+Pt54+umn/eYZOXKkUa9ePd9wly5djIceesg3/NhjjxkdOnTwDS9dutSw2+3GkSNHDMMwjOeee86oU6eO4Xa7fdN88803BmB8+OGHhmEYxowZMwyn02kcP37cN82vv/5qOBwOY+7cuYZhGMbs2bMNwEhOTvZNk5SUZADGjh07TF/v0aNHDcB46623zrhNwsPDjREjRvjWYbPZ/MoPHjxoAEZiYqJhGIbx0ksv+bZXEa/Xa9SvX994++23/eq6cuVKv+k++eQTIzg42MjIyDAMwzBOnjxpOJ1O4+OPPz5j/T777DMDMLKzs/3Gf/3118aiRYuMLVu2GCtXrjTuuecew2q1Gt9+++0Zl2UYhlGnTh1j3Lhxxvr1642QkBDD5XIZ27dvNwICAoxff/212D7y0ksvGQ0aNPAN/34bJSYmGoBx8OBBwzAM45dffjEAY9WqVYZhlN17a1a3/fv3G23btjVq1apl5OfnGx07djT69evnN8/f//53w+FwGHl5eYZhFP9MDBgwwIiOjjZyc3N94/72t78ZsbGxvuFx48YZderU8Vvupk2bDMD45ZdfzlhfwzCMJ5980khISChxmkuVWhCVwPLlywkNDcXpdNK8eXPq16/PggULcLlcHDp0iM6dO/tN36VLF/bt21fsMFKRYcOGsWbNGrZt2wbA9OnTufHGG6lRowYA27Zto02bNn6HPq699lq/ZaSkpNC0aVO/Y93Vq1enUaNGpKSk+MZZLBauuuoq33BcXBxQ+KvejFGKviMNwzin4+UbNmwgOTnZd7gkNDSUsLAw9u3bx+7du/2mbdOmjd/wLbfcQpUqVViwYAEA8+fPJzQ0lFtvvfWM68vJyQEgKCjIb3yfPn3o378/LVq0oFOnTixYsICOHTvyxhtvlOp1tG3bliuuuIKFCxcybdo0br75ZqpXr16qec9FWb23Rfbu3UtoaCjBwcHUqVMHwzD47LPPCAgIICUlxXR/zs3NZc+ePWdcZpMmTfy2d1xc3Fnr0aJFC66//nqaN2/O7bffzqRJkzh48GCx6RwOh+89vdzYzz6JVLR27doxd+5c7HY7NWrU8H0QXC4XQLFDDGf7km3WrBkdO3ZkxowZjB49miVLlvD555/7TfP7ZZodxjAbZxiG33ir1eoXNEVlXq/XtG4xMTFERUXx008/mZYfPHiQzMxMrrzySt/yf+/3J5e9Xi/du3fn3XffLTZtlSpVfP/bbDYcDodfud1u56GHHmL69OkMHz6cGTNmMHDgQAIDA03rB1CtWjUATp48SdWqVc84HRQG76efflriNL81ZMgQpkyZwsGDB/noo49KPd+5Kov3tkitWrVYtmwZVquV2NhYgoODS1x30f5c0qG0378fFovlrJ8Dm83GN998w4YNG/j+++/55JNPGD16NP/85z+56aabfNOlp6f73tPLjVoQlYDT6aRhw4bUrVvX71dSeHg48fHxrFixwm/6lStXUq9evWIfvN8aNmwY8+bNY9q0acTGxtK7d29fWdOmTdmwYYPfVSVJSUl+8zdr1oyUlBS/qzuOHj3Krl27aNas2R9+rRaLhfvuu48FCxaYXvr417/+FYfDwV133QUUBorH4/H7tbhp0ya/eRISEkhJSSEuLo6GDRv6/ZXmgz9kyBC2bNnCBx98wJYtW856T0mrVq2wWCx+v7bP5IcffqBWrVpnna7I/fffz+7duwkNDaVnz56lnu9clNV7WyQgIICGDRtSv379Yvtos2bNTPdnp9N5xnM+pREYGGh6lZTFYqFt27Y899xzrFy5ki5dujB79my/abZu3UpCQsIfXndlpoCo5MaMGcM777zD9OnT2b17N1OnTmXKlCk899xzJc535513AjBu3Dgeeughv1/iI0aM4OjRowwfPpzt27eTmJjI888/D/zfr7h7772XatWqcdddd7Fp0yaSk5O5++67iYuL8315/1Hjxo3zXdK6ePFiDhw4wJYtW3jiiSeYNm0as2bN8v0yb9u2LWFhYYwePZrdu3ezdOlSXnnlFb/lPfroo3g8Hm677TZWrVrFvn37WL16Nc8//3ypThLXrl2b3r1788QTT9C1a1df6+VMqlatStu2bYt90f3lL3/hP//5D3v37mXz5s088sgjfPfdd4wcObLU2yY8PJzDhw+zdetW09bThVCW7+3ZjBkzhk8++YQJEyawa9cuPv74Y8aOHcuoUaNKbLWdTb169fj1119JSkoiLS2N7Oxs1q5dy7hx41i/fj0HDhxg2bJl/PjjjzRt2tQ3X2ZmJsnJydx4440X4uVVOgqISm748OG88sor/PWvf6Vp06a89tprTJgwgYceeqjE+RwOBw888ABut7vYtHFxcSxZsoS1a9fSsmVLnnjiCV599VXffFDYqvn2228JCgqic+fOdOnShZCQEJYuXXpeH2QoPOyzevVq+vfvz5gxY2jYsCEtW7Zk5syZJCUlcc899/imjYqKYuHChaxbt44WLVowbtw4Xn/9db/lVa9enaSkJKKjo+nbty+NGjXivvvuY//+/b7zLmczdOhQ8vPzGTp0aKmmHz58OB9++KHfuNTUVB588EGaNGlCr1692LlzJ99//z0333xzqZZZpEqVKoSFhZ3TPOeiLN/bs7nhhhuYNWsW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ESkbCggRETGlgBAREVMKCBERMaWAEBERUwoIERExpYAQERFTCggRETGlgBAREVMKCBERMaWAEBERUwoIERExpYAQERFTCggRETFV6QNCz4MQESkbeh6EiIiYqvQtCBERKRsKCBERMaWAEBERUwoIERExpYAQERFTCggRETGlgBAREVMKCBERMaWAEBERUwoIERExpYAQERFTCggRETGlgBAREVMKCBERMaWAEBERUwoIERExpYAQERFTCggRETGlgBAREVMKCBERMaWAEBERU5U+IDZu3MjUqVMruhoiIpcce0VX4HwlJCSQkJBQ0dUQEbnkVPoWhIiIlA0FhIiImFJAiIiIKQWEiIiYUkCIiIgpBYSIiJhSQIiIiCkFhIiImFJAiIiIKQWEiIiYUkCIiIgpBYSIiJhSQIiIiCkFhIiImFJAiIiIqRKfB+FyuVi5ciWbNm1i//79ZGdnExwcTJ06dWjZsiVdu3YlPDy8vOoqIiLl6IwBsWDBAlatWkWrVq247rrriIuLw+l0kpOTw+HDh9m2bRvPPvssHTt25L777ivPOouISDk4Y0BERkYyefJkAgICipXVq1ePjh07kp+fz3/+858yraCIiFSMMwZEnz59zjpzYGAgvXv3vqAVEhGRi0Opnkn9008/ERMTQ0xMDCdPnuSjjz7CarVy7733EhERUcZVFBGRilCqq5hmzpyJ1Vo46bx58/B4PFgsFqZOnVqmlRMRkYpTqhZEeno60dHReDwetmzZwvvvv4/dbmfYsGFlXT8REakgpQoIp9NJRkYGBw8eJD4+HofDgdvtxu12l3X9RESkgpQqIHr37s2YMWNwu90MHDgQgB07dhAXF1eWdRMRkQpUqoC47bbbaNu2LVarldjYWACioqJ4+OGHy7RyIiJScUoVEAA1a9YscVhERC4tZ7yKacyYMSQlJZ3xPIPb7Wbt2rU899xzZVY5ERGpOGdsQTzyyCMsWrSIGTNmUK9ePWrWrInD4SA3N5fU1FT27t1L8+bNGTFiRHnWV0REyonFMAyjpAkyMjL48ccfOXDgAKdPnyYkJIQ6derQokULqlSpUl71LJUjR46U+zo9Q24p93VK+bNNX1LRVbisREdHk5aWVtHVuCyUdLrgrOcgIiIi6Ny58wWtkIiIXPz0PAgRETGlgBAREVMKCBERMaWAEBERU6W6Uc4wDJYtW8aaNWvIzMzkzTffZNu2bWRkZNC+ffsLXqnc3FxmzJiB3W6nWbNmdOrU6YKvQ0RESlaqFsSiRYtITEykR48evkvPqlatyhdffFHqFb3//vsMHjyYUaNG+Y3fvHkzTzzxBI899hiff/45AP/973+55pprePjhh9m4cWOp1yEiIhdOqQJixYoVPPvss3To0AGLxQJATEwMx44dK/WKunbtWuyua6/Xy8yZM3nuued4++23WbNmDYcOHeLEiRNER0cXVtCqo2AiIhWhVIeYvF4vDofDb1xubm6xcSVp2rRpsUD5+eefiY2NpXr16gC0b9+eDRs2ULVqVU6cOEHdunUp6T6+77//nu+//x6ACRMm+EKlPB0t9zVKRaiIfetyZrfbtc0vAqUKiFatWjFv3jwGDBgAFJ6TWLRoEVdfffV5rTw9PZ2qVav6hqtWrcru3bvp06cPs2bNYtOmTSWuo0ePHvTo0cM3rDsvpaxo3ypfupO6/JzXndQADz74IO+++y4DBw7E7Xbz4IMP0qJFCx599NHzqphZ68BiseBwONTHk4hIBStVQAQHB/PMM8+QkZFBWloa0dHRREREnPfKiw4lFTlx4gSRkZHnvVwRETl/53QGODAwkKioKLxeL+np6aSnp5/Xyhs0aEBqairHjh3zdR+ekJBwXssUEZELo1QtiB9//JFp06Zx/PjxYmWLFi0q1Yr+/ve/s23bNjIzM3n44Yfp378/1113HYMGDWL8+PF4vV66detGrVq1zu0ViIhImShVQHzwwQfccccddOjQgcDAwD+0opEjR5qOb926Na1bt/5DywTYuHEjycnJDBs27A8vQ0REiitVQBQUFNCtW7eL8p6EhIQEHZYSESkDpfrGv/HGG/niiy9KvCdBREQuLaVqQbRr147x48fz+eefExYW5lf27rvvlknFRESkYpUqIN566y0aN27Mtdde+4fPQYiISOVSqoA4duwYr7322kV5DkJERMpGqb7xExIS+Omnn8q6LiIichEp9VVMr7/+Ok2aNKFKlSp+Zefb3cb50mWuIiJlo1QBUatWrYv2BjZd5ioiUjZKFRD9+vUr63qIiMhF5owBsW3bNpo2bQpQ4vmH5s2bX/haiYhIhTtjQMycOZOJEycCMGXKFNNpLBaL7oMQEblEWYwSbo9evXo1HTt2LM/6nJcjR46U+zo9Q24p93VK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HA5uueUWACZMmEDnzp3p2rUr//nPf5g1axbPPPMMAKmpqbz44otYrVZeeeUVhgwZQo0aNdi9ezczZszgpZdeKoOtfnm76qqrWLx4MU888QR/+tOfaN++PSEhIURHRxMcHOw3bf369Tl06NAZA6JI0b75wAMP0KBBA9xut+99Dg8PZ+3atSxcuJARI0Zov6lACohKyOzK5K1btzJy5EjfcGhoaInL2LFjBx06dMBqtRIREUHTpk3Zs2cPTqeThg0bUrVqVQDq1q3LsWPHaNy4MT/99BNLliwhLy+PrKwsatWq5QuI9u3b+5Z93XXXsWTJEtq2bUtiYiLDhg0D4M4776Rjx478+OOPrF69mjVr1jB27Nhiddu9ezdPPfUUAJ07d+ajjz7ylV1zzTVYrVZyc3PZuXMnb731lq/M7XafZcvJH+FwOHjttdfYvn07KSkpvP3229x+++1YLJYLto4jR45w8OBBxo0bB4DX6/W1GrTfVBwFRCVz9OhRrFYrVapU4fDhw35lF+oDW3QoAQqb9F6vl/z8fGbOnMnf/vY3oqOj+fjjj8nPz/dNFxQU5Pu/cePGzJw5k23btuH1eqldu7avLDY2ltjYWLp3787gwYPJzMw8p7o5HA6g8AskJCTEtHUkF57VaqVZs2Y0a9aM2rVr891333H8+HFycnJwOp2+6X755ReuueaaP7SO+Ph4xo8fb1qm/aZi6DLXSsTlcjF9+nR69+5dLAxatGjB0qVLfcNZWVkA2O12019ITZo0ISkpCa/Xi8vlYvv27TRs2PCM6y4oKAAKjz/n5uayfv36EuvauXNnJk2aRLdu3XzjNm3a5Gv9pKamYrVaCQkJwel0kpub65vuyiuvZO3atQCsXr2axo0bF1t+cHAwMTExJCUlAYWtqn379pVYJ/ljjhw5Qmpqqm9437591KxZky5dujB37ly8Xi8AK1asICAgoNQnjR0OBzk5OQDUrFkTl8vFrl27gMJf9QcPHgS031QktSAuckUnmYuuvujUqRM33XRTsenuuOMOZsyYwahRo7Bardx55520a9eO7t278/TTT1OvXj0ef/xx3/Rt27Zl165dPP300wDcf//9REREFGuVFAkJCaF79+6MGjWKmJiYs1491alTJ/7xj3/QoUMH37iVK1cyd+5cAgMDsdlsPPbYY1itVq6++mreeustNmzYwKBBg/jzn//MlClTWLJkie9ko5nHH3+c6dOn8+mnn+J2u+nQoQN169Y92yaVc1R0AcTp06ex2WzExsYydOhQnE4nH374IU888QT5+fmEh4czfvx434+X/Px8Hn74Yd9yfr/fdu3alenTp/tOUo8aNYrZs2eTnZ2Nx+PhhhtuoFatWtpvKpC62pAysW7dOjZs2MBjjz1W0VWRcpCRkcH48eO5/vrr1Y/SJUQBIRfcrFmz+OGHHxgzZgw1a9as6OqIyB+kgBAREVM6SS0iIqYUECIiYkoBISIiphQQIiJiSvdByGVjx44dzJ8/n4MHD/o6fRswYAANGzZk+fLlLFu2zNfVQ1n69NNP+eyzz4DCO3vdbjeBgYEAVKtWza8bCJGKpICQy0J2djYTJkxg8ODBtG/fHrfbzfbt2/26FTkf59KNdN++fX1dsJdnMImcKwWEXBaKuoro2LEjUPicjaIeRw8dOsT06dNxu9088MAD2Gw25syZQ3Z2tu+ejqCgILp3787tt9+O1Wr1fbE3aNCAFStWcP3113PHHXewcOFCkpKScLvdtGnThoEDB/paB2ezZMkSdu3a5etwDgrvKbFarQwcONDXrfrWrVs5cuQIzZo1Y8SIEb6OGXft2sW8efM4dOgQ1apVY+DAgTRr1uxCbka5zOgchFwWatSogdVq5d133+WHH37w9VUFhZ3EDRkyhCuvvJIPP/yQOXPmAIVfztnZ2bz77ruMHTuWlStXsnz5ct98u3fvpnr16syYMYO+ffvy0UcfkZqayhtvvMHkyZNJT09n8eLFpa5jp06d2LJlC6dPnwYKWyVr166lc+fOvmlWrFjB8OHDmTp1KlarlVmzZgGQnp7OhAkT6Nu3L7NmzeKBBx5g4sSJuFyu89hqcrlTQMhlITg4mFdeeQWLxcLUqVMZPHgwr732GhkZGabTe71e1q5dy7333ovT6SQmJoabbrqJlStX+qaJjIykT58+2Gw2AgICWLZsGQMGDCA0NBSn00nfvn1Zs2ZNqesYGRnp60QRYPPmzYSFhVG/fn3fNJ07d6Z27do4HA7uvvtuX4eLK1eupFWrVrRu3Rqr1UqLFi1o0KABmzZt+mMbTAQdYpLLSHx8PI888ggAhw8f5p133mHOnDl+z9EoUvRYzaLnK0PhCeT09HTf8G/LXC4XeXl5jB492jfOMAxfT6el1aVLF7799lt69OjBqlWr/FoPgO85HUXr93g8uFwu0tLSWLdune/50FDYAtEhJjkfCgi5LMXFxdG1a1e+++470/Lw8HBsNhtpaWnEx8cDkJaWdsbnJ4eFhREYGMhbb7111mcsl6RNmzbMmDGDAwcOkJyczP333+9XfuLECd//aWlp2Gw2wsPDqVq1Kp06dfLrPVXkfOkQk1wWDh8+zJdffun7gk1LS2PNmjVcccUVAERERJCenu57dobVauXaa69l4cKF5OTkcPz4cb766is6depkunyr1Ur37t2ZM2eO73Gw6enpvmd+l1ZgYCDt2rVj8uTJNGzY0K+VArBq1SoOHTpEXl4eH3/8se9JaZ06dSI5OZnNmzf7HvCUkpLiFygi50otCLksOJ1Odu/ezVdffUV2djbBwcFcffXVvl/ozZs3952stlqtzJw5k0GDBjFr1iweffRRAgMD6d69u98DkH7vvvvuY/HixTz//PNkZmYSFRVFz549admy5TnVteiZysOHDy9W1rlzZ9577z2OHDlCkyZNfM88iI6O5plnnmH+/PlMmjQJq9VKw4YNGTJkyDmtW+S31JuryEUmLS2NkSNHMm3aNIKDg33jx44dS6dOnejevXsF1k4uJzrEJHIR8Xq9fPXVV7Rv394vHEQqggJC5CKRm5vLgAED+PHHH+nfv39FV0dEh5hERMScWhAiImJKASEiIqYUECIiYkoBISIiphQQIiJi6v8DrmD9WcQUuyAAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "plot_results(\n", - " experiments=[dict_runs, sqlite_runs],\n", - " title=\"Polygon Query (5 Million Points)\",\n", - " tick_label=[\"DictionaryStore\", \"SQLiteStore\"],\n", - ")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "HUBEmZDMpT53" - }, - "source": [ - "## 2.2) Cell Boundary Polygons Dataset\n", - "\n", - "Here we generate a much larger and more complex polygon dataset. This\n", - "consistes of a grid of over 5 million generated cell boundary like\n", - "polygons.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "xhCr_TDVpT53", - "outputId": "c02b7a20-6ab1-4cae-b6bb-fb5c6d94cd12" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "100%|██████████| 5004169/5004169 [10:04<00:00, 8277.35it/s] \n" - ] - } - ], - "source": [ - "# Generate a grid of 5 million cell boundary polygons (2237 x 2237)\n", - "# Run time: ~10m\n", - "rng_42 = np.random.default_rng(42)\n", - "\n", - "cell_polygons = [\n", - " Annotation(geometry=polygon, properties={\"class\": rng_42.integers(0, 4)})\n", - " for polygon in tqdm(cell_grid(size=(2237, 2237), spacing=35), total=2237**2)\n", - "]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "21RgwKtgpT54" - }, - "source": [ - "### 2.2.1) Write To Formats For Comparison\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "CDVLMRUtpT54" - }, - "outputs": [], - "source": [ - "# Write to an SQLiteStore on disk (SSD for recorded times here)\n", - "# Run time: ~30m\n", - "cell_sqlite_store = SQLiteStore(\"cells.db\")\n", - "_ = cell_sqlite_store.append_many(annotations=cell_polygons)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "6Fb4tQHVpT54", - "outputId": "fba12c47-e0cb-44fd-ca95-35c38454c9cc" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - " \r" - ] - } - ], - "source": [ - "# Create a copy as an in memory DictionaryStore\n", - "# Run time: ~5m\n", - "cell_dict_store = DictionaryStore()\n", - "for key, value in tqdm( # Show a nice progress bar\n", - " cell_sqlite_store.items(),\n", - " total=len(cell_sqlite_store),\n", - " leave=False,\n", - " position=0,\n", - "):\n", - " cell_dict_store[key] = value" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "wXOOuGWypT54", - "outputId": "e2fb300e-e5b8-4459-b172-249cda363b50" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "100%|██████████| 5004169/5004169 [01:26<00:00, 58002.74it/s]\n" - ] - } - ], - "source": [ - "# Transform into a numpy array\n", - "# Run Time: ~1m\n", - "cell_polygons_np = np.array(\n", - " [np.array(a.geometry.exterior.coords) for a in tqdm(cell_polygons)],\n", - " dtype=object,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "yv9VgW9TpT54" - }, - "outputs": [], - "source": [ - "# Create an Nx4 index of (xmin, ymin, xmax, ymax) as a simple spatial\n", - "# index to speed up the numpy query.\n", - "# Run time: ~1m\n", - "min_max_index = np.array(\n", - " [(*np.min(coords, 0), *np.max(coords, 0)) for coords in cell_polygons_np],\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "nFmHxwBwpT54" - }, - "outputs": [], - "source": [ - "# Write to GeoJSON\n", - "# Run time: ~10m\n", - "\n", - "cell_dict_store.to_geojson(\"cells.geojson\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "2UH6WdmipT54" - }, - "outputs": [], - "source": [ - "# Write to line delimited JSON (ndjson)\n", - "# Run time: ~10m\n", - "\n", - "cell_dict_store.to_ndjson(\"cells.ndjson\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "fw6wg5gapT54", - "outputId": "61a32277-fb8d-4bdc-be28-b379cb0a23eb" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "cells.ndjson : 40.82% ( 8.82 GiB => 3.60 GiB, cells.ndjson.zstd) \n" - ] - } - ], - "source": [ - "# Zstandard compression of ndjson to demonstrate how well it compresses.\n", - "# Gzip may also be used but is slower to compress.\n", - "# Run time: ~1m\n", - "! zstd -f -k cells.ndjson -o cells.ndjson.zstd" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "rzGC65zhpT55", - "outputId": "75ad772b-5641-4d64-ae16-7d50206e1b85" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "cells.db : 75.87% ( 4.87 GiB => 3.69 GiB, cells.db.zstd) \n" - ] - } - ], - "source": [ - "# Zstandard compression of sqlite to demonstrate how well it compresses.\n", - "# Gzip may also be used but is slower to compress.\n", - "# Run time: ~20s\n", - "! zstd -f -k cells.db -o cells.db.zstd" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "xT0KZLxdpT55" - }, - "outputs": [], - "source": [ - "# Write as a pickle (list)\n", - "# Run time: ~2m\n", - "with Path(\"cells.pickle\").open(\"wb\") as fh:\n", - " pickle.dump(cell_polygons, fh)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "-TAWGEu9pT55" - }, - "outputs": [], - "source": [ - "# Write as a pickle (dict)\n", - "# Run time: ~15m\n", - "with Path(\"cells-dict.pickle\").openI(\"wb\") as fh:\n", - " pickle.dump(cell_dict_store._rows, fh) # noqa: SLF001" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "I-W4o3GepT55" - }, - "outputs": [], - "source": [ - "# Write dictionary store to a pickle\n", - "# Run time: ~20m\n", - "with Path(\"cells.pickle\").open(\"wb\") as fh:\n", - " pickle.dump(cell_dict_store, fh)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "dALe8k0BpT55" - }, - "outputs": [], - "source": [ - "# Write as numpy object array (similar to writing out with pickle),\n", - "# Numpy cannot handle ragged arrays and therefore dtype must be object.\n", - "# Run time: ~30m\n", - "np.save(\"cells.npy\", np.asanyarray(cell_polygons_np, dtype=object))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "hOrGS0HgpT55" - }, - "outputs": [], - "source": [ - "# Create UUIDs, and get the class labels for each cell boundary\n", - "# Run time: ~2m\n", - "_uuids = [str(uuid.uuid4) for _ in cell_polygons]\n", - "_cls = [x.properties[\"class\"] for x in cell_polygons]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Fs2cz8lVpT55" - }, - "outputs": [], - "source": [ - "# Write as NumPy archive (.npz) with uuid and min_max_index\n", - "# Run time: ~40m\n", - "np.savez(\n", - " \"cells.npz\",\n", - " uuids=_uuids,\n", - " polygons=cell_polygons_np,\n", - " min_max_index=min_max_index,\n", - " cls=_cls,\n", - ")\n", - "\n", - "del _uuids, _cls" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "4gOTqc03pT55" - }, - "source": [ - "### 2.2.2) Time To Write Summary Statistics\n", - "\n", - "The following is a summary of the time required to write each format to\n", - "disk and the total disk space occupied by the final output.\n", - "\n", - "Note that some of these formats, such as GeoJSON compress well with\n", - "schemes such as gzip and zstd, reducing the disk space by approximately\n", - "half. Statistics for zstd compressed data is also reported below. It\n", - "should be noted that the data must be decompressed to be usable.\n", - "However, for gzip and zstd, this may be done in a streaming fashion from\n", - "disk.\n", - "\n", - "| Format | Write Time | Size |\n", - "| ----------------: | ---------: | -----: |\n", - "| SQLiteStore (.db) | 33m 48.4s | 4.9 GB |\n", - "| GeoJSON | 11m 32.9s | 8.9 GB |\n", - "| ndjson | 9m 0.9s | 8.8 GB |\n", - "| pickle | 1m 2.9s | 1.8 GB |\n", - "| zstd (SQLite) | 18.2s | 3.7 GB |\n", - "| zstd (ndjson) | 43.7s | 3.6 GB |\n", - "| NumPy (.npy) | 50.3s | 1.8 GB |\n", - "| NumPy (.npz) | 55.3s | 2.6 GB |\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "wS3sGpnWpT55" - }, - "source": [ - "### 2.2.3) Box Query\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "MKvKfkyvpT55" - }, - "outputs": [], - "source": [ - "# Run time: ~5m\n", - "\n", - "# Setup\n", - "xmin, ymin, xmax, ymax = 128, 12, 256, 256\n", - "box = Polygon.from_bounds(xmin, ymin, xmax, ymax)\n", - "\n", - "\n", - "# Time DictionaryStore\n", - "dict_runs = timeit.repeat(\n", - " \"store.query(box)\",\n", - " globals={\"store\": cell_dict_store, \"box\": box},\n", - " number=1,\n", - " repeat=3,\n", - ")\n", - "\n", - "# Time SQLite store\n", - "sqlite_runs = timeit.repeat(\n", - " \"store.query(box)\",\n", - " globals={\"store\": cell_sqlite_store, \"box\": box},\n", - " number=1,\n", - " repeat=3,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "0Yo14C3kpT55", - "outputId": "764bc28b-3072-4887-ea88-4c88ffcefb5f" - }, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Plot results\n", - "plot_results(\n", - " experiments=[dict_runs, sqlite_runs],\n", - " title=\"Box Query (5 Million Polygons)\",\n", - " tick_label=[\n", - " \"DictionaryStore\",\n", - " \"SQLiteStore\",\n", - " ],\n", - ")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ExF-fOGQpT56" - }, - "source": [ - "### 2.2.4) Polygon Query\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "PcxKapqNpT56" - }, - "outputs": [], - "source": [ - "# Run Time: 35s\n", - "\n", - "# Setup\n", - "big_triangle = Polygon(\n", - " shell=[ # noqa: S604\n", - " (1024, 1024),\n", - " (1024, 4096),\n", - " (4096, 4096),\n", - " (1024, 1024),\n", - " ],\n", - ")\n", - "\n", - "\n", - "# Time DictionaryStore\n", - "dict_runs = timeit.repeat(\n", - " \"store.query(polygon)\",\n", - " globals={\"store\": cell_dict_store, \"polygon\": big_triangle},\n", - " number=1,\n", - " repeat=3,\n", - ")\n", - "\n", - "# Time SQLite store\n", - "sqlite_runs = timeit.repeat(\n", - " \"store.query(polygon)\",\n", - " globals={\"store\": cell_sqlite_store, \"polygon\": big_triangle},\n", - " number=1,\n", - " repeat=3,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "vqHA50DQpT56", - "outputId": "7e837f4c-ada9-400f-b5f3-c59430b137f3" - }, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Plot results\n", - "plot_results(\n", - " experiments=[dict_runs, sqlite_runs],\n", - " title=\"Polygon Query (5 Million Polygons)\",\n", - " tick_label=[\n", - " \"DictionaryStore\",\n", - " \"SQLiteStore\",\n", - " ],\n", - ")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "6m-E5AwapT56" - }, - "source": [ - "### 2.2.5) Predicate Query\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "whEn34rOpT56" - }, - "outputs": [], - "source": [ - "# Run Time: ~10m\n", - "\n", - "# Setup\n", - "xmin, ymin, xmax, ymax = 128, 12, 256, 256\n", - "box = Polygon.from_bounds(xmin, ymin, xmax, ymax)\n", - "predicate = \"props['class'] == 0\"\n", - "\n", - "# Time DictionaryStore\n", - "dict_runs = timeit.repeat(\n", - " \"store.query(box, predicate)\",\n", - " globals={\"store\": cell_dict_store, \"box\": box, \"predicate\": predicate},\n", - " number=1,\n", - " repeat=3,\n", - ")\n", - "\n", - "# Time SQLiteStore\n", - "sqlite_runs = timeit.repeat(\n", - " \"store.query(box, where=predicate)\",\n", - " globals={\"store\": cell_sqlite_store, \"box\": box, \"predicate\": predicate},\n", - " number=1,\n", - " repeat=3,\n", - ")\n", - "\n", - "np_stmt = f\"\"\"\n", - "polygons = [\n", - " polygon\n", - " for polygon in tqdm(cell_polygons_np)\n", - " if np.all([\n", - " np.max(polygon, 0) >= ({xmin}, {ymin}), np.min(polygon, 0) <= ({xmax}, {ymax})\n", - " ])\n", - "]\n", - "\"\"\"\n", - "\n", - "# Time numpy\n", - "numpy_runs = timeit.repeat(\n", - " np_stmt,\n", - " globals={\"cell_polygons_np\": cell_polygons_np, \"np\": np, \"tqdm\": lambda x: x},\n", - " number=1,\n", - " repeat=3,\n", - ")\n", - "\n", - "# Time shapely\n", - "shapely_runs = timeit.repeat(\n", - " \"polygons = [box.intersects(ann.geometry) for ann in cell_polygons]\",\n", - " globals={\"box\": box, \"cell_polygons\": cell_polygons},\n", - " number=1,\n", - " repeat=3,\n", - ")\n", - "\n", - "# Time box indexed numpy\n", - "numpy_index_runs = timeit.repeat(\n", - " \"in_box = np.all(min_max_index[:, :2] <= (xmax, ymax), 1) \"\n", - " \"& np.all(min_max_index[:, 2:] >= (xmin, ymin), 1)\\n\"\n", - " \"polygons = [p for p, w in zip(cell_polygons, in_box) if w]\",\n", - " globals={\n", - " \"min_max_index\": min_max_index,\n", - " \"xmin\": xmin,\n", - " \"ymin\": ymin,\n", - " \"xmax\": xmax,\n", - " \"ymax\": ymax,\n", - " \"np\": np,\n", - " \"cell_polygons\": cell_polygons,\n", - " },\n", - " number=1,\n", - " repeat=3,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "oRxJTg7BpT56", - "outputId": "d235e51a-5109-486e-b779-fe39e5f6ee33" - }, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAZMAAAF2CAYAAACrlXVQAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAsTAAALEwEAmpwYAABZaUlEQVR4nO3dd3hUZfrw8e+Znl5JgNBBSmApIdI7UbCtgIi7NpR3VdBVUdbG6q67LisWwFXEBqhYWFd/YEFYFKRJh4j03iGQXiaT6ef9I2aWIQkEk0zL/bkuLplzzpy5n0PMPU9XVFVVEUIIIWpB4+8AhBBCBD9JJkIIIWpNkokQQohak2QihBCi1iSZCCGEqDVJJkIIIWpNkokQQohak2QiGox77rkHRVE8f2JiYujbty9Lly71yec7HA5efvllunbtSlhYGNHR0QwePJhFixb55POFqE+STESDMnDgQLKyssjKymLTpk2kpaUxatQojhw5Uq+f63A4uO6665gxYwaTJ09m7969bNq0iWHDhnHbbbfx/PPP1+vnV7Db7T75HNEAqUI0EOPHj1eHDx/uday4uFgF1EWLFnkdu//++9XExETVaDSqPXv2VJcvX66qqqparVa1e/fu6s033+y53mKxqJ07d1bHjRtX7WfPmDFDBdRNmzZVOjd9+nRVURR127Ztqqqq6qpVq1RAPXXqlNd1Wq1Wff/99z2vz507p44fP15NTExUIyMj1X79+qlr1qzxnK+4z5IlS9T+/furRqNRff3119XIyEj1k08+8br3sWPHVEVR1FWrVlVbBiEuRWomosGy2+289957GI1G0tLSPMcnTJjA8uXL+fjjj/npp5/o378/N954I/v378doNPLZZ5+xcuVKZs+eDcAjjzyCxWLh3XffrfazPvroI4YPH07v3r0rnXv00UcJCwvjk08+qXHsZWVlDB06lJKSEpYtW8ZPP/3E9ddfzzXXXMO+ffu8rp0yZQpPPvkk+/btY/To0dx+++289957XtfMmzePdu3aMXjw4BrHIIQXf2czIXxl/PjxqlarVSMiItSIiAhVURQ1IiJC/eyzzzzXHDp0SAXUb7/91uu9PXr0UO+9917P6w8++EA1Go3qc889p+r1enXz5s2X/OywsDD1kUceqfb8b37zG/X6669XVbVmNZP3339fTUlJUR0Oh9c1Q4cOVR999FGv+yxYsMDrmu3bt6uAevDgQVVVVdXpdKrNmjVTX3755UuWQYhL0fk3lQnhW7179+bDDz8EwGw289133zF+/HhiYmIYMWIEe/fuBWDQoEFe7xs0aBAbN270vB4/fjxLly7lhRdeYPr06fTq1avWsen1+hpfu3XrVs6dO0dsbKzXcZvNRlhYmNexi2NLS0sjPT2duXPn8tJLL7Fs2TLOnz/P+PHjf3XsQkgyEQ1KWFgY7dq187zu3r07K1euZNq0aYwYMaLa96mqiqIontdms5nMzEy0Wi0HDx687Od26NCB3bt3V3nOarVy5MgRRo4cCYBGo/F8ZgWXy4Xb7fa8drvddOrUicWLF1e6X3h4uNfriIiIStdMnDiRqVOn8o9//IO5c+cyatQokpKSLlsOIaojfSaiwdPpdFgsFgA6d+4MwNq1a72uWbduneccwKRJk9Bqtfzwww98/PHH/Pvf/77kZ9x111388MMPbN68udK5f/3rX5SVlXH33XcDeH6pnz171nPNjh07vJJLeno6R48eJTo6mnbt2nn9adq06WXL/Lvf/Q6r1co777zDt99+y3333XfZ9whxSX5uZhPCZ8aPH68OHDhQzcrKUrOystTDhw+rb775pqrVatV//OMfnutuvfVWtWXLlup///tfdd++feojjzyi6vV6dd++faqqqupHH32kGo1G9aefflJVVVVfffVVNTo6Wj169Gi1n22329Xhw4erSUlJ6vz589WjR4+qe/fuVZ9//nlVp9Op06dP91zrcDjUli1bqiNHjlT37dunrlu3Th04cKCqKIqnz6SsrEzt3Lmzmp6eri5fvlw9duyYumnTJvWf//ynunjxYlVVq+97qfDggw+qBoNBbdOmjep2u2vxZIVQVUkmosEYP368Cnj+hIWFqampqeorr7yiulwuz3VFRUWeocEGg8FraPChQ4fUqKgo9fXXX/dc73a71ZEjR6q9evVS7XZ7tZ9vs9nU6dOnq126dFGNRqMKqBqNRv36668rXbtp0yY1LS1NNZlMateuXdW1a9dWGhqcm5urTpw4UW3atKmq1+vVpk2bqqNGjVIzMzNVVb18MtmxY4cKqP/85z+v6DkKURVFVWWnRSH84ciRIwwfPpz27dvz9ddfYzKZfPr5S5cuZdSoUZw8eZLGjRv79LNF6JE+EyH8pG3btqxbt47+/ft7jRSrbxaLhf379/P3v/+d22+/XRKJqBNSMxGigXn++ef5xz/+Qa9evfjyyy9lFJeoE0GVTLZs2UJmZibFxcWMGDGCbt26+TskIYQQBEAymTNnDpmZmcTExDBjxgzP8R07dvD+++/jdrsZPnw4o0aN8pwzm8189NFHTJo0yQ8RCyGEuJjf+0yGDBnC1KlTvY653W7mzZvH1KlTmTVrFuvXr+f06dOe84sWLbrkBDMhhBC+5fdkkpqaSmRkpNexw4cP07hxY5KTk9HpdPTr14+tW7eiqioff/wx3bt3p02bNn6KWAghxMUCcjmV/Px8EhISPK8TEhI4dOgQy5YtY9euXVgsFs6dO8e1115b5ftXrFjBihUrAJg+fXrQ7eGg0+lwOp3+DsOnpMwNg5Q5eBgMhiu6PiCTSVXdOIqicP3113P99ddf9v0ZGRlkZGR4Xufm5tZpfPUtMTEx6GKuLSlzwyBlDh41WZbnQn5v5qpKQkICeXl5ntd5eXnExcVd0T22bdvGO++8U9ehCSGEqEJAJpO2bduSlZVFdnY2TqeTDRs2kJ6efkX3SE9P54EHHqinCIUQQlzI781cr732Gnv37qWkpISJEycybtw4hg0bxoQJE5g2bRput5uhQ4fSvHnzK7rvtm3b2L59uyQUIYTwAb/PM/GFC5fyDgbB2sZaG1Lmqmm1WqKjo9FoNKiqSklJCYqiEB4ejk6nQ1EU3G43FouFsrKySu9XFIWIiAiMRiMajQaHw4HZbPZ0CIeFhREeHo5Go8HpdGI2m3E4HISHh2M0GtHpyr9vXngOyvdMCQ8PR1GUSvesbZlDTbCW+Ur7TPxeM6kvUjMRoSAuLo6ftxVyPquMlOYRdOgSgcFgYuOa8+ScL8PhcBMdYyCtdyLR0XqKi4u93h8fH8/pEzb27szCWuaiRatIul2dQElJARERERTlq2xcfY6SYgdNm0WQ1jsRJcKBxaxh6485FOTbURRo3DSc9L6JlFqKiIyMJPuck3UrzmIpddKidSQ9rk7EXFoYdCMnRd0J2WSSnp5+xf0sQgSSyMhIzp2x8dOWXFQVwsJ1KEoEbrfK6ZOltOsQjdOpsiszj3NnLdx531UoSolnNGRYWBi52U5WfHualm0iadU2io1rzmMpdTL42iaUWVx8u+gwUTF62lwVzY6tuRQV2LlxbEtOHivEbnfTvlMMZ09b2LuzALvdzbCRTSnIs7Fs8UkSk0y0ahPFjm15lJqdDMpIwmaz/bLTowZFUXG5XJjN5iprTSK0hGwyESKY6XQ6DPpw1nx/lK5pCfy8/X+jGzUalVvvbI3VVkZ4eDjZ58o4d8aCpdSFRqPB5XIB5XvKnzpuBuA3aQmkNI9g/65CDu0vov+wxhzYU4jLpdKzTyPadYghP9fG8SMlFBXYuKpDNO06RGKz2ejQOZYF7xykIM+Goijs21UIQO+BSTRrEcm5sxaOHChmwLAmFOUrfPOfo5QUO9BooFnLSK65sakkkwYgZJOJNHOJQDJjxgxmzpx52esef/xxpkyZQlxcHD/+cJ74RCOdfhPrSSYajYacnBwAoqKiKMi3k3u+jKTGYUREaii1uDz3crvdhEeU/y9+6pgZvV5DcbEdVQVzsYOiwvImqYhIPQ6Hg4io8muLihy4sWCz2YiPj2fvz4UAtOsYDUBxUfn7IqN+eV+kHiijpMjOrp/ycTlVxt3dFqfDTX6erfYPTwSFkE0m0swlAsmUKVOYMmWK5/XYsWPR6/UsXLiw0rUmk4mc83YOHyjmhjEtMJvLO7adDjcup5bIyEh0Oh0FeW6Wf32c6FgD197UjKKiIjQajWfmss1mo2OXOM6cLOXn7Xns+ikPg1GL0+FC0ShoNArwv0nCFUNxNJryjvuEhAR2bC0gc3MunbvH0blbDHa7/YL34fV+jUYhMcnEyWNmvvniOPGJJq7qGOPpxBehTf6VhQgwiqJQUuTA7VL55vMTnuPHj5SAAiNuas6RA0X8sPwsTZqGc81NzTAatWh1EWi1Ws6espZ3mqdEYjDouG5UC+x2F6jw5b+Po9UqxMYZiEswAlBcaKdpswiKC8prHLFxRsLCI1i3MosDewrp1T+J7lcnoKoqGo2G2HgDHIGiAjuxcZEUFTrQahWiYvSk921Ei9aRZJ8r4+DeIlZ/d5amzcLRarWe5jcRmiSZCBFgXC4XLVrHcvO4VgCYzQ5WLj1DsxYR9OqXhM3qYsXSMwCUmh18/Z/jAAwdkUJikp51K4+hKAq3/792lJodrF5+lrgEI+ezyijItzFweBNsNhvtO8Xw05ZcNv+Yzanj5vJO/Y4xREbp2b+7gAN7CtFqFQ4fKOLwgSJMYVpuGtuKzt3i2b0jn/Wrsti/20RejpXf9IjHYNCyYfU5XG6ViAgdqlpey9HpNahlIT8DocEL2WQifSYiWNntdlyuPHRGLRqNhpS4aH7TI57EZBNRMVpUVeE3PeIrvc9gLF/QomOXOBSlvM/EYNASl2ikpMhBfKKRPgOTSEjSk5eXR0REBGN+35rdO/IpLnbQf0hj2neOxmw2ExtvrPQZekP5XBQUG2Nub8OeHfnlI8OuaULb9lHYbDaaNo/gxNEScs5baZRsot+QZFAcuN1unzw74T8yaTEABeskp9poaGW+VJ/JxSomKKqqisViwWAwoNfrK13ncrlwOByYTCYArFYrUD5EuGLSo9Vq9RyH8hFfF05aLC0tRVVVIiIi0Ggqr7ZktVqx2+0YDAavSYulpaWeWPV6PYqioKoqNpvNayRXQ/t3huAts0xaFCLEWCwWr9dlZWWXHGp78cTBS00kdDgcFBUVVTpuNpsvGZPdbq/yvhVJRTQ8kkyEqMY3nxXW3b3++ybffjen0vGUlBSv1zdc+yA3jXyozj73ptti6+xeQlxKyCYT6TMRgeSmkQ/VaZIQItCEbDKReSZCCOE7AbmfiRBCiOAiyUQIIUStSTIRQghRayGbTGQPeCGE8B3pgA8yFTvnGQwGtFotUD7mv2KuQMX5sLAwtFotbrcbm81GcXExF85PDQsLIzIystL9LRYLVquViIgI9Hq9Z+JaSUkJNpuNuLg4z+de6OIJcxeS/SyECH0hm0xCVVhYGMbTRylZ/AnOMydRUUmeuYBipXwl18TEROzrvifvy09xnjuNNi6B+Eeew9ikhdfM54iICIpm/AXHySOeY9rYROL//jpGoxHHd19StHE1rqJ8jKndCf/D47jdbpRjB8l94x+V4kqY8nei2nbk/JN/wG3+325/hnapRD70jCeZVCQnWV5DiNAiySTIKIqCKy8XTWQ0bpsV17kzQHmNIyIiAvv6leTP+Aum3oOIvm0CroI80Fb9z+w8dwbVaiWs7xAANBFRqKqKoig4z51B36I1tiXb0CU2BsqXGtdEx2Dq0cdzj9Lvv0YtK0MTE1d+z1PH0SY0wtjtagB0jVM8y3NERkRAcSGq240mNhGbw0FBQUE9PSkhhC9JMgkyVqsV49UDiOk3FOdzD/+STMqZTCYK/7sIJSKS6LF34y4qJLzfULTxiRScO1fl/TThkegap6Br1BhTen9Kf1l7KWL8H9E7HZiXfO651uFwYImMRf/7+8vXXzp9DPOXnxI+eASuqFiUX5YY10THomvSDH1KC4zdemG12Qg3F3F+8p24srMAUIwmmrz/DRqNRmopQoQASSZBxuVykZ+fT0JCQqVzer0e+8E9qA4HOc8+BPzS9PXXWYS1al/lukmugjyKP30Pd3Eh+rYdafTye5jNdmw2G8nRUZWur7hHQkICJYs+BiDqlrsoMZuJi/uldnL6BEUL5qCWmjH1GkjiczMo2bQaV3YWSS/PRdeiNfb9u1AMRlSLtdJnCCGCjySTEKKqKooxDNVmI3nWAtDpOXf/GEq+WEDsX2Z6mrAcDgcajYbEZ19Fm9AI1eUif/rTlG1cjf2nzRg6dMVmq367Vb1ej7aoAMva7zF264XSoi22nBw0Gg3Jb3yKEpeA4nKS/eR9WLesw3HiCMbOaShGI9lP/gFtUhNMPXpj7NzDa89yIUTwCtmhwaFKq9WSkJBQaQnyxMRENBoN+lbtANDExqONLd+PQlXd6PV6TMf2o1mzjPgwE1rV7UksqqKgiS6vVaguJ2FhYSQmJnrd32AwkJCQgEajISIigpKvPgW3i6gxd2I2m8tHjpUUozGacDqdqFodmshfajZOB4b2qTT5cCmJf/sXpvR+lC7/Esua5RiNxnp+YkIIXwjZmkmoLvRoNBpxbVlH/ifvePofzj96J8au6cQ/9AzR4+4lZ9d2cl+YUr7NHRB53RgASlctw7LyW8J6DUQJi+DsvTdi7NQNVBXbnp/QJqdg7N4bt05HyVsvYdudCYBtdybnH7yNmLsmEdH1akxuF/nLv0Tfqh367r0oyM5Gr9fjyDpFztMPYOjUFbe5BMfhfRg6dEHfuj2l/12E5ceV6Ju1wnnyGAC65KbYpb9EiJAgm2MFoEttpmMymYgszMWaudHruK5JM5S0fiiKgubMCSyr/wtaLWG9B0GbDjidTjR7fsJx8giRI8eAKQzr1h+x79uJaitDl9KS8KHXU+JyYzQaUbf9iPO893MzpffHlZyCLuccZVvWYkztjq1Za8xmMxqNhkYx0Vg3r8V+eB+4XOhbtiVs8AisbhVjcQGWdStw5pxDYzRh7H412q5Xe8oZiBsI1eUS9P7izyXoZ8yYwcyZMy973eOPP86UKVN8EJF/BOLPdk1c6eZYkkwC0OV++Cp2xruQqqqUlZXhdpcng4rmI7vdjtVqRaPREBYW5mnastvtmEwmz654LpeLsrIyXC4XWq0Wk8mE8svclQoulwur1erZuc/tdntt3KTRaDCZTOh0Os/1FTEZDAbPREtVVXE4HAG/A58kk7p1JbtLhpJA/NmuCdlp0Qdc9/22zu5168YDbC649K52AL3jIvm8bwcASi5zreWXPxdyAY4LXmvf+7raHftcLtcld8y7eOe/ChcnlwtVtzOfECI0SDLxs4oEIYQQwUxGcwkhhKg1SSZCCCFqTZKJEEKIWpNkIoQQotaCqgP+/PnzLFq0CIvFEtLj0oUQItj4vWYyZ84c/vCHP1RKDjt27ODRRx/l4Ycf5ssvvwQgOTmZSZMm+SFKIYQQl+L3ZDJkyBCmTp3qdcztdjNv3jymTp3KrFmzWL9+PadPn/ZThEIIIS7H78kkNTW10vaxhw8fpnHjxiQnJ6PT6ejXrx9bt271U4RCCCEuJyD7TC7eryMhIYFDhw5RUlLCwoULOX78OIsXL2b06NFVvn/FihWsWLECgOnTp1daAbe2ztfp3fyjrp9Jbel0uoCLCQr9HUCtBdIzrVi6J5Bi8oXA/NmuewGZTKpaLkxRFKKiorj//vsv+/6MjAwyMjI8r4NxXZz6FmjPJFjXLwp0gfRMHQ4Her2+2pg0Gk2lrRWgfCmei38nKIqCXq/3rAPncDhwOBxe11x4P5fLhdPp9Ly3Yu05u93uOQ7lWzwYDAYAbDZbnewCGqw/21e6Npffm7mqkpCQQF5enud1Xl6eZxe/mtq2bRvvvPNOXYcmhKgn8fHx5Ofnk52d7flTUlJCbGys13U6nY6EhATMZjMHDx7k0KFDqKpKQkKCZ3FSRVFISEggNzeX7OxsIiMj0Wq1hIWFkZCQwJkzZzhw4AAGg4G4uDgURSE6Oprw8HBOnjzJ8ePHiYmJITo62g9PIjgFZM2kbdu2ZGVlkZ2dTXx8PBs2bOCRRx65onukp6eTnp5eTxEKIeqaVqvliy++oKioyHOsefPm3HfffV7X6fV6du7cyRdffEF0dDRmsxm3282YMWNITU2luLiY6OhoMjMzWbx4MQCTJk0iOjoak8nEG2+8QW5uLkajka+++orf//73dOrUiXPnzvHee+95aiZfffUVDzzwACZT+YZvJpPJs+q10+mkrKysylaUhsrvyeS1115j7969lJSUMHHiRMaNG8ewYcOYMGEC06ZNw+12M3ToUJo3b35F9w3VzbGECHXdunXjpptuAsqbqi5uvoLyJpjHH3+cxMRETp06xVtvvcWWLVvo3r07RqMRm83G0qVLadKkCVlZ5ZvIhYeHs2HDBnJycrj11lvp0qULL7/8MkuXLqVz586sWrUKm83G5MmTURSF6dOn8/3333P33XdjNpvZvn07+fn5GI1GWrduTatWrSgoKPDpswlkfk8mkydPrvJ4WloaaWlpv/q+UjMRIjgdOHCA48ePEx8fT0ZGBo0bN/Y6b7VaiYqKwmazUVJS4uk3qWiSio6O5oMPPqBr164YjUZPMtHr9Zw8eRKAVq1aodVqadq0KYcOHcJsNnu2Y9DpdJ7mslOnTgGwaNEijh49Svfu3SkqKiIzM5M2bdrU/8MIIgHZZyKEaHhUVSU1NZVrr72WXr16cfr0aT788ENUVUWr1XpdV1JSgtFoxG638/HHHxMTE8NNN92ERqNh+/bt5OXlccMNN3jdX6PRePbb0ev1uN1uTyKyWCyeL6/vvvuup7+1Yl8fi8WCTqcjNjaWnj17Mm7cOK+YRADUTOqLNHMJEVx0Oh0333wzZWVlGAwG8vLyyMzMJCsri9atW3tqC2azGb1eT05ODh9++CGRkZHce++9REZGoigK27dvR6fT8e9//5vs7GwAli5dys0330xMTAxQnhyio6M9tZGYmBgaN25MXFwcR48eJTo6mu+++46oqCgAbr75ZlasWMGmTZsoLCykUaNGPProo354SoErZGsm6enpkkiECBI6nY7c3Fy+//57cnNzOXbsGEeOHPGMytLpdMyYMYMFCxYQExNDTk4O7733Hg6Hg/T0dA4fPsyePXvQarW0a9eOJk2aoNfrPdtba7VaFEWhc+fOAGzatImDBw9y+vRp2rRpQ1hYGLm5uWi1WtLS0igsLKS0tJQePXoAUFRUxA033MBjjz1GamoqOTk52O32SttnN2RSMxFCBAStVsv27dtZuXIlAGFhYYwZM4bw8HBUVaW4uJiIiAgAzp07h8vlAmDZsmUAREVF0b17dwYNGuR5/7fffsuPP/7IiBEjSExMJDExkX79+rF582Y2bdpESkoKo0aNoqysjLy8PD744ANUVcVgMDBgwAD69u2L0+lk27Zt7N69GygfdtynTx9PU5kop6gNYGzb2bNn6/R+dbkHvL9o3/va3yF4CcSJXd98VujvEGrtptti/R2Cx9ixY9Hr9SxcuLDK8xVDd202Gy6Xi4iICKxWK8XFxcTGxmIymYDyDniDwVBlrcBisXiGFhuNRs8cEpfLRU5ODlqt1tNRb7PZCA8Pp7S0FKvVSnx8PFDejBYZGYnL5aK4uBiDwUB0dDQOh4OysjIiIiJQVZWioiKvCY/VCcSf7Zq40kmLIVszEUIEl+LiYoqLiz1Jwmw2e879miG4NpuNc+fOeR1zOp3k5+ejKAqKong62KF8tQBFUdBoNOTl5XnmkJSVlVFWVuY5Z7VaZX5JFSSZCCE8Xn/99Tq7148//sj69esrHU9JSfF63b9/fwYMGFAnn1nTyc2qqlaZEFRV9TSfXck5EcLJRPpMhPCvAQMG1FmSEIEvZJOJTFoUQgjfkXFtQgghak2SiRBCiFoL2WQiS9ALIYTvSJ+JEEKIWgvZmokQQgjfkWQihBCi1iSZCCGEqLWQTSbSAS+EEL4jHfBCCCFqLWRrJkIIIXxHkokQQohak2QihBCi1iSZCCGEqDVJJkIIIWpNkokQQohaC9lkIvNMhBDCd2SeiRBCiFoL2ZqJEEII35FkIoQQotYkmQghhKg1SSZCCCFqTZKJEEKIWpNkIoQQotYkmQghhKi1oJpnYrVamTt3Ljqdjs6dOzNw4EB/hySEEIIASCZz5swhMzOTmJgYZsyY4Tm+Y8cO3n//fdxuN8OHD2fUqFFs2bKFPn36kJ6ezqxZsySZCCFEgPB7M9eQIUOYOnWq1zG32828efOYOnUqs2bNYv369Zw+fZq8vDwSExMB0Gj8HroQQohfXLJmUlxczNq1a8nMzOTEiRNYLBbCw8Np2bIl3bt3Z8iQIURHR9cqgNTUVLKzs72OHT58mMaNG5OcnAxAv3792Lp1KwkJCeTl5dGqVStUVa3V5wohhKg71SaTTz/9lHXr1tGjRw+GDRtGSkoKYWFhlJWVcebMGfbu3ctTTz3FgAEDuOOOO+o0qPz8fBISEjyvExISOHToENdddx3z588nMzOTnj17Vvv+FStWsGLFCgCmT5/uqc3UlfN1ejf/qOtnUls6nS7gYoJCfwdQa4H3TOtXIJY3MH+26161ySQuLo7XX38dvV5f6Vzr1q0ZMGAAdrudH374oc6DqqrWoSgKJpOJBx988LLvz8jIICMjw/M6Nze3TuMLBYH2TBITEwMuplDQ0J5pIJY3WH+2mzZtekXXV9vxcN1111WZSC5kMBgYOXLkFX1gTVQ0Z1XIy8sjLi7uiu4hS9ALIYTv1KgXe/fu3Z5+jYKCAmbPns2cOXMoLCysl6Datm1LVlYW2dnZOJ1ONmzYcMXLyaenp/PAAw/US3xCCFGVGTNmkJKS4vXHaDRWOnbhyNVQUaNkMm/ePM/oqQULFuByuVAUpU6++b/22ms8++yznD17lokTJ/LDDz+g1WqZMGEC06ZN47HHHqNv3740b978iu4rNRMhhK9NmTKFM2fOeP707duXQYMGeR07c+YMU6ZM8Xeoda5G80zy8/NJTEzE5XLx888/M2fOHHQ6XZ188588eXKVx9PS0khLS/vV95XNsYQQwndqlEzCwsIoLCzk1KlTNGvWDJPJhNPpxOl01nd8QgghgkCNksnIkSN55plncDqd3HPPPQDs37+flJSU+oytVrZt28b27dul30QIIXygRslk1KhR9OrVC41GQ+PGjQGIj49n4sSJ9RpcbUgzlxBC+E6N1+a6eMzxlY5BFkIIEbqqHc31zDPPsHHjxmr7RSqG7F68rlagkNFcQgjhO9XWTB566CE+++wz5s6dS+vWrWnatCkmkwmr1UpWVhZHjx6lS5cuNZqR7g/SzCWEEL5TbTJp1qwZU6ZMobCwkJ07d3Ly5ElKSkqIiIhg0KBB/PGPfyQmJsaXsfrcZ599VulYhw4d+A3gcKt8eTav0vnU6HA6R4dT5nKzJCu/0vmuMRF0iAqj2OFi+fmCSufTYiNpG2ki3+5kZXZhpfO94qNoGW4k2+ZgTU5RpfP9E6JpGmbgbJmd9XnFlc4PbhRDklHPiRMn2LRpU6Xz11xzDfHx8Rw+fJjt27dXOn/dddcRHR3N/v37+fnnnyud/+1vf0tYWBi7d+9mz549lc6PGTMGvV7Pjh07OHDggOd4VFQUJSUl3HbbbQBs3bqVo0ePer1Xp9Nxyy23ALBx40ZOnjzpdd5kMnHzzTcDsHbtWrKysrzOR0VFcf311wOwatWqSguMxsXFce211wLw3XffsWHLGa/zMVGJdO5Uvu3BTzu/p8xq9n5/bGM6te8LwLaflmF3WL3OJ8Y3o327qwHYvO0bXG7vWn9yo1a0bd0DgA1bFnOxpo3b0arFb3C5HGzevqTS+eYpHWme0gm7vYxtO/4LgIVwz/lu3brRsWNHiouLWbZsWaX3V6x3Z7FYOHLkSOX7N29ObGwsZrOZY8eOVTrfsmVLoqOjKS4u5sSJE5XOt27dmsjISM/I0Iu1bduW8PBw8vLyOHv2bKXz7du3x2g0kpOTw7lz5yqd79ixI8AV/+xVqI+fvezsbLRaLUuXLr2in72CAu/fDUlJSQwdOhSApUuXUlJS4nW+SZMmDBo0CICvvvoKq9XqKY+vXLbPJDY21hNkMJHRXEKIYBEZGYnBYLjsdAuNRkNSUhImk4nCwkIcDgdQnsy0Wq3nXnq9Ho1Gg9vt9nqvVqtFVVXP5yiKUmk7D7fb/atWZVfUBrCWe1XfcmrDdd9v6/R+/qB972t/h+AlEBfD++azQn+HUGs33RZ7Rde//vrr9ROIjzzyyCP+DsHL2LFj0ev1LFy4sMrzWq2W2NhYDJSBNRs1PAWrQ6GoqMjrF7perycuLg6t2wLWbNAYcJuSKbXYUBSFSKMKbof3zRUtFqeekpISYmJiMOpcKGXnQGsqf29pGZHhehSnxettqi6CnLwizxYgNeX3nRaFEKKhiouLQ3vmS1yn/wuqC7QmTO3uQo3uQVHR/5qxTSYTmqzvcB27oOndEEdUl8dRIlvg2vMaat5P3jePbInhN8+REB+P5uwS3KeXgdsOgNL8BiJa3AI563EdnOf1Nk2Xx9Hpml1xWSSZCCGEHxiNRvS2M7hOfYuSmI6m7R24dr2K+/DHhPXqTslFzVSYGqHp8jhKVDvUsytwn1iE+/RStB0nomk5GrXpNQCohXtRTy1Bie2ETq/HnbcD98mvUBoPQtPm9+VJy16ES1VRfrm1psMDYCjvA1ciW6CzuHE7bVC4G+yFoItAiW6LYmpUbXlCdu9bGRoshAhker0etWA3AEpiOhjiUOJ/A64yKDnqtQVIWVkZ9qjfUKS0wGx1Q/RV5ScUPWVlZeRZIyhwN4aYTlB8GBQtmqbX4nQ6UXM2l19riMd9dCFq9kYIS8Jut3vur57/EfXcarDlgS6CyMhI1L3/wr3/bdw5m3EfX4T7xJeXLE+NaiaqqrJy5UrWr19PSUkJr776Knv37qWwsJB+/frV/On5kAwNFkIEMo1Gg2r/pSlLG4bD4UCnDUMFsBehMf5vpXSn00l+fj4RERFE6u249i8AfRSaFr/FYrFgt9sxGo0opSdwF+1HSeqHXYkElwuttbwvUs3ZiBLRHPeRT1CKjxDeaRJusx4lvjsYE1ALdqHmbEHjLEXb7DqcpSchojmaFqNQIluAcum6R41qJp999hmrVq0iIyPD00makJDAV199dcUPUAghRPmoKUUfVf7CVYZOp0N12cpfG6KJiIggPj6euLg4DAYD0dHRRGuLcO14AdwOtN2mYiUSm638PZGRkeX9IoCm2XWYzebyzzBElx9rewfa1IchLBk1bzuqqqJJ6ouS+ij2Zrei7fJ4eVy5meXXtxoLtgLcu17CtfEh3McXXbI8NUoma9as4amnnqJ///4oSnkrW1JSUqWx0kIIIWrG4XCgxHUGQM3fiaLaUQv3gMaIEtUGvWJHf2g2xpwVJCYmEuE6i+vnf4LqRNPuDnBZMakFxMbGotfrMbiLUXO2oMR1wWlsgs1mw+l0osR0KP9AezGqyw7OMjDEoigK7txtKLYcTCYjatFBAE/yUWLao+09E23f2RDZCvX8ukuWp0bNXG63G5PJ5HXMarVWOiaEEKJmbDYbzuiWaJoMRc1ahSt7Q3lfR9vbsbsNGChDLdgFujAA3Pk7yvtTXODe86/ym0S3Q5f6VHmt5Mz/ASpKs+sxm8sn1JaWlhKeNAhytuI+OB+OfgpuF5r2EwBQc7fj3vsGoAAqGOLRtBwDgGvnK+C2gdYEtjyUpEt3adQomfTo0YMFCxYwfvz48gBUlc8++8wzY1YIIcSVKygoILbl7ehTRqKWnUOJaoXNbaKwoIDEhDi0PaeBNgyn04k25Vq4+Be6xoDN4UCr1aJJyYAmQ3AZkinLyQHKKwKFxaXEdH4SrfUUqrMMJbIFFrsGW34+MVf9P7TNr0e15qHoI3GHN6fEYkNbWkp4zxdQS0+By4ZijEcNv/Rw4Rolk7vvvpvZs2dzzz334HQ6ufvuu+natSt//OMff90T9AGZAS+ECHROp5Pc3NxfZqw3x1lgw+Uqn0SYk5uPVmtAVZ24XOXLsiiKoYp7lHfi63Q6wICr2Hvyr81mIzsnB4MhGkWJwZFX6hlybLVa0etNaDTNcdvcOEvzPZMlI5o2RYnt5LmPwqXVKJmEh4fz5JNPUlhYSG5uLomJicTGxtbkrX4jo7mEEDWRdPiZOrvX39/fwQsf7Kx0/OKNBJ+7pyt/ubd7nXxmdrsXAS67FMuFQ4EvVLEkS21d0aRFg8FAfHw8breb/PzyRQzj4+PrJBAhhAh2f7m3e50liWBTo2Syc+dO3n33XXJ+aYe7UFUr6wohhGhYapRM3n77bW655Rb69++PwVC5zU4IIUTDVqNk4nA4GDp0aKWlioUQQgio4aTFG264ga+++upXrXEvhBAi9NWoZtK7d2+mTZvGl19+SVRUlNe52bNn10tgtSVDg4UQwndqlExmzpxJx44d6du3b9D0mcjQYCGE8J0aJZPs7Gxeeukl6TMRQghRpRplh/T0dHbv3l3fsQghhAhSNR7N9fLLL9OpUydiYmK8zgXykipCCCF8o0bJpHnz5jRv3vzyFwohhGiQapRMbr311vqOQwghRBCrNpns3buX1NRUgEv2l3Tp0qXuoxJCCBFUqk0m8+bNY8aMGQC89dZbVV6jKErAzjMRQgjhO9UmkxkzZvDjjz8yYMAA3nzzTV/GVK3z58+zaNEiLBYLU6ZM8Xc4QgghfnHJocHvvfdenX3QnDlz+MMf/lApCezYsYNHH32Uhx9+mC+//PKS90hOTmbSpEl1FpMQQoi6cckO+Lpci2vIkCGMHDnSq5bjdruZN28ezz77LAkJCTzzzDOkp6fjdrv59NNPvd4/adKkSsOShRBCBIZLJhO3233ZyYo17YBPTU0lOzvb69jhw4dp3LgxycnJAPTr14+tW7cyevRonn766RrdVwghhP9dMpk4HA7efvvtamsote2Az8/PJyEhwfM6ISGBQ4cOVXt9SUkJCxcu5Pjx4yxevJjRo0dXed2KFStYsWIFANOnTycxMfFXx1iV83V6N/+o62dSWzqdLuBigkJ/B1BrgfdM69evKu/huo/DlwLl3/iSycRkMtXraK2qkpSiVL9tfVRUFPfff/9l75uRkUFGRobndW5u7q8LMIQF2jNJTEwMuJhCQUN7pr+mvEn1EIcv1de/cdOmTa/oer+u3JiQkEBeXp7ndV5eHnFxcXVy723btvHOO+/Uyb2EEEJc2iWTSX1vhtW2bVuysrLIzs7G6XSyYcOGOls2Pj09XfYyEUIIH7lkM9eCBQvq7INee+019u7dS0lJCRMnTmTcuHEMGzaMCRMmMG3aNNxuN0OHDq2zNcBkcywhhPCdGq3NVRcmT55c5fG0tDTS0tLq/PNkcywhhPAd2e1KCCFErYVsMpEOeCGE8B2fNXP5mjRzCSGE74RszUQIIYTvhGwykWYuIYTwHWnmEkIIUWshWzMRQgjhOyGbTKSZSwghfEeauYQQQtRayNZMhBBC+I4kEyGEELUWsslE+kyEEMJ3pM9ECCFErYVszUQIIYTvSDIRQghRa5JMhBBC1JokEyGEELUWsslERnMJIYTvyGguIYQQtRayNRMhhBC+I8lECCFErUkyEUIIUWuSTIQQQtSaJBMhhBC1FrLJRIYGCyGE78jQYCGEELUWsjUTIYQQviPJRAghRK1JMhFCCFFrkkyEEELUmiQT4XMzZswgJSXF64/RaKx0bMaMGf4OVQhRQyE7mksErilTpjBlyhTP67Fjx6LX61m4cKEfoxJC1IbUTIQQQtSaJBMhhBC1FlTNXFu2bCEzM5Pi4mJGjBhBt27d/B2SEEIIfJhM5syZQ2ZmJjExMV4dqzt27OD999/H7XYzfPhwRo0aVe09evXqRa9evTCbzXz00UeSTIQQIkD4LJkMGTKEkSNH8uabb3qOud1u5s2bx7PPPktCQgLPPPMM6enpuN1uPv30U6/3T5o0iZiYGAAWLVrEiBEjfBW6EEKIy/BZMklNTSU7O9vr2OHDh2ncuDHJyckA9OvXj61btzJ69GiefvrpSvdQVZVPPvmE7t2706ZNm2o/a8WKFaxYsQKA6dOnk5iYWIclgfN1ejf/qOtnUht6vR5FUQIqpnKF/g6g1gLvmdavX1Xew3Ufhy8Fyr+xX/tM8vPzSUhI8LxOSEjg0KFD1V6/bNkydu3ahcVi4dy5c1x77bVVXpeRkUFGRobndW5ubt0FHSIC6Zk4HA70en1AxRQqGtoz/TXlTaqHOHypvv6NmzZtekXX+zWZqKpa6ZiiKNVef/3113P99dfX6N7btm1j+/btPPDAA786PiGEEDXj12SSkJBAXl6e53VeXh5xcXF1cm9Zgl4IIXzHr/NM2rZtS1ZWFtnZ2TidTjZs2CAJQAghgpDPaiavvfYae/fupaSkhIkTJzJu3DiGDRvGhAkTmDZtGm63m6FDh9K8efM6+Txp5hJCCN/xWTKZPHlylcfT0tJIS0ur88+TZi4hhPCdkF1ORfaAF0II3wmq5VSuhNRMhBDCd0K2ZiKEEMJ3QjaZSDOXEEL4jjRzCSGEqLWQrZkIIYTwnZBNJtLMJYQQviPNXEIIIWotZGsmQgghfEeSiRBCiFqTZCKEEKLWQjaZSAe8EEL4jnTACyGEqLWQrZkIIYTwHUkmQgghak2SiRBCiFoL2WQiHfBCCOE70gEvgoLRaCQyMhK9Xg9AWVkZJSUluN3uSteGh4cTGRmJRqPB7XZjsVgwm80AREREEBER4TlXWlpKaWkpBoOB8PBw9Ho9iqKgqiopzR2cOVUKQEqLCLr1TKBxSjiKAkUFdn7ensehfUW+ewhCBLCQTSYidBiNRpz6CJ5bdpB1R3KJC9dz59UtGds1mdzcXK9ro6KiOFrk4rUlO9h3roQ2iRE8PLgtXZNiUFWV/fkO/vXVTxzKNnNVUiSPDmlHx/hoTCYTb6w7xtYTBZhtTkamJjP4qjjOnCqlXcdoOvdPZM66I6z5MheXqpLeIo5pI1M5tK8InU4hMkqPqoK5xIHLpfrpSQnhPyHbzCVCR1RUFC+vPMjaw7k8Mbw9nZvEMGvVIX7OMhMWFua5TqPRoDWE8dTXu8kx2/jbDZ1wut08/dVunBoDisHEU1/tosTq4O83pGK2OXn6q12gN6LVarE6XHRNiSGr2EpxmfOXe0KfwclM/r+fWXs4l8lD2zFjdFf6tIrH5VTpNziZ39/Xjt6/bUK/UU25a1J7ho5o6q9HJYTfSDIRAU1RFGxuhbWHc0ltEsWY7ilM6NMSgG/3nMNoNHquNRgMbD9dSF6pnRGdkhnRqTGjujbF4nCx5nAO64/mUWx1cmOXJlzbKZkbuzSmyOpkw7F87HY7jw9uzeiu3omgSbMIjhRYOJJbyo1dmhCm1+JwuRnbPYWoaD1qso4b393IrfM2c8vcTQx9Yy3tOsX49BkJEQikmUsENK1Wy7liGwAJ4Qbsdjtx4QYAzhVb0Wq1XtdmFVnKr43wvjaryIpJX35t/EXnzhVZsdlsWK1W4H81HYDYOAP7z5cA8FnmadbFmDhbZKVLk2je/X0aqw7lUGJz8vmE3jSONvHzmSKU+nscQgQsqZmIgKaqKuGG8iTgcKtoNBqcrvJO93CDDo1GQ0REhKdz3nOtq/xaxwXXVpxzuiruo/5yTltlRz6Aw+Em0lj+voFtE/nq/n6M7Z7C7qxitp0soG/reEx6DbfO38zN727ku/3nsbvchIVrq7yfEKFKaiYioLlcLpIiDcSE6TmSawaNhoPZ5SOz2ieVJ5DNWVbOFJZx59Ut6JBUnhQOZpeg0+m8rg37pWZSfq4ZB7NLfjkXRViYiaioKApyLZ7Pbt8pls7d4j3XVSSVSGP5/zZajUJa81iWTRrAvnPFLN93nq92ZdG/Tfmor2OHSnzwhIQIDCGbTLZt28b27dt54IEH/B2KqCW7zcp9/Vrx6spD3P7BFrJLbMSF67m1ewpOp5Mlu7PYeCyf36U1p3W8ieHtG/Hd/mxOFW7l4Hkzac1j6dksGlVV6dc6ga93ZXEwx8z+cyX0a51AanIEAA9+/jPH88qTydK95/jxaC7/vKkLXVNiGHpVI77fn43V4Wbz8Xw6JEXSPSWW9zeeYPOJfJrFhnHgfAlaRaFZbBj7LZZLFUmIkBOyyUTmmYSOkpISRnVOomNydPnQ4DA916cmo3XZsNt1jO3ejAFtEtEqKoWFhfzt+k5kdExm37lifpfWjGFXNaKoqAhVVXnl5s6sOpzLwWwzd6Q3Z2i7RAoLC4mOjmZU16aUWJ1en9042sSa78/yl4wObOiYxIHzJfRrcxUZVzXi7IlSRqYmE2HUkWO2MaxDEv1aJ2A0Q9YZSSaiYQnZZCJCh9vtJi8vj+ZhJsb3SEJVVcrMhbhcLjQaDV0TTXRNjKSosACHw0F+bg7pyWH0SWmM0+mkIC8HVS3vH8nLzaF3kzD6N4/E6XSSl1t+rqCggL5NTV6fuzuzjMwfznPiqJkjB4tp1yGagTGxmHMdfLn5GKVmJ41TwmmdbKJzZDgOh4sTmwo5fcLsj8ckhF9JMhE1cvMn++vsXme++5CsFQsqHU9JSfF63STjblKuHV9nn/vVHR1RVRVLFU1QLper0vE9Pxd6/u6wu9m3q5CLnTtj4ZzUQoSQZCJ8L+Xa8XWaJIQQ/idDg4UQQtSaJBMhhBC1JslECCFErUkyEUIIUWtB1QF/+vRpli5dSklJCb/5zW+49tpr/R2SEEIIfJhM5syZQ2ZmJjExMcyYMcNzfMeOHbz//vu43W6GDx/OqFGjqr1Hs2bNuP/++3G73bKLohBCBBCfJZMhQ4YwcuRI3nzzTc8xt9vNvHnzePbZZ0lISOCZZ54hPT0dt9vNp59+6vX+SZMmERMTw7Zt2/jyyy8ZOXKkr0IXQghxGT5LJqmpqWRnZ3sdO3z4MI0bNyY5ORmAfv36sXXrVkaPHs3TTz9d5X0qlkl58cUXGTBgQL3HLYQQ4vL82meSn59PQkKC53VCQgKHDh2q9vo9e/awefNmnE4nPXr0qPa6FStWsGLFCgCmT59O06Z1vPPdt9vq9n5BYOsTDW/3wAcea3hlnj59ur9D8L2mH/o7gloJlJ9Sv47mqlgv6UKKUv3WQp07d2bChAncf//9l2zmysjIYPr06UH7P0Z1tbJQJmVuGKTMocuvySQhIYG8vDzP67y8POLi4vwYkRBCiF/Dr8mkbdu2ZGVlkZ2djdPpZMOGDbJsvBBCBCGf9Zm89tpr7N27l5KSEiZOnMi4ceMYNmwYEyZMYNq0abjdboYOHUrz5s19FVLAysjI8HcIPidlbhikzKFLUavquBBCCCGugCynIoQQotYkmQghhKg1SSZCCCFqLagWehQi1NjtdjIzM9m3bx8FBQUYDAaaN29OWlpayA5GkTKHZpmlAz5AnD17lrlz51JUVMSMGTM4ceIE27Zt45ZbbvF3aPWmIZb5Qv/5z3/Yvn07nTt3pk2bNkRHR+NwOMjKymL37t04HA7uvvtuWrZs6e9Q64yUOYTLrIqA8Je//EU9dOiQ+sQTT3iOPf74436MqP41xDJfaPv27Zc8X1hYqB4+fNhH0fiGlLmyUCmzNHMFCLvdTrt27byOaTSh3aXVEMt8obS0NK/XFosFRVEICwsDICYmhpiYGH+EVm8acpntdjsGg8HrXHFxcciUWZJJgIiKiuLcuXOetck2bdoU8kvLNMQyV+Xw4cO89dZbWK1WVFUlIiKCiRMn0rZtW3+HVm8aYpmfeeYZHnjgAdq3bw+U/7wvXLiQf/3rX36OrG5In0mAOH/+PO+++y4HDhwgIiKCpKQkHnnkERo1auTv0OpNQyxzVf70pz/x//7f/6NTp04A7N+/n7lz5/Lqq6/6ObL60xDLfPLkSd566y1SU1MpKCjwrAZy4crpwUxqJgHA7Xbz3Xff8dxzz3m+qVVU+0NVQyxzdcLCwjy/VAE6duwY8s+iIZa5RYsWjB49mtmzZxMWFsbf/va3kEkkIMkkIGg0Go4ePQqAyWTyczS+0RDLXJ22bdvy7rvv0r9/fxRFYcOGDaSmpnqeT5s2bfwcYd1riGV+6623OH/+PK+++ipnz57lpZdeYsSIESGza6w0cwWIBQsWkJWVRd++fTEajZ7jvXv39mNU9ashlrkqf/vb3y55/q9//auPIvGdhljmJUuWcMMNN3j6CC0WCx9++CGTJk3yc2R1Q5JJgJgzZ06Vxx988EEfR+I7DbHMomHLyckhKyuLrl27YrfbcblcIdO8J8lECD+zWCx8/vnn7Nu3D4DU1FTGjh1LeHi4nyOrPw2xzCtWrGDlypWYzWbeeOMNsrKyeO+99/jLX/7i79DqhPSZBIi8vDzmz5/PgQMHUBSFDh06cO+994ZUB93FGmKZqzJnzhxatGjBY489BsDatWuZM2cOf/rTn/wcWf1piGVevnw5L774IlOnTgWgSZMmFBUV+TmqutNwZogFuDlz5pCens4777zD22+/TXp6erXNQKGiIZa5KufPn2fcuHEkJyeTnJzMrbfeyvnz5/0dVr1qiGXW6/XodP/7/u5yuTz9J6FAkkmAKC4uZujQoWi1WrRaLUOGDKG4uNjfYdWrhljmqhgMBvbv3+95vX///kozpUNNQyxzamoqixYtwm63s3PnTmbOnEnPnj39HVadkWauABEdHc3atWsZMGAAAD/++CNRUVF+jqp+NcQyV+W+++7jzTffxGKxoKoqkZGRPPTQQ/4Oq141xDLffvvt/PDDD7Ro0YLvv/+eHj16MHz4cH+HVWekAz5A5ObmMm/ePA4ePIiiKLRv354JEyaQmJjo79DqTUMs86VYLBaAoO2E/u9//8uAAQOIjIys8XuCvczifySZBIj9+/fTsWPHyx4LJQ2xzFUJlZFN//73v1m/fj2tW7dm2LBhdOvWrdo+gVApc01MmTLlkn0jobKEjCSTAPHUU0/x0ksvXfZYKAnlMrvd7hqvgPzqq6/SokULBg8eDJSPbDpx4kRQjmxSVZWff/6Z1atXc+TIEfr27cuwYcNo3Lix13WhVObLycnJAcpHcwEMGjQIgHXr1mE0Ghk7dqzfYqtL0mfiZwcPHuTAgQMUFxezZMkSz3GLxYLb7fZjZPWnIZT54Ycfpk+fPgwdOpRmzZpd8trz5897/RK99dZbeeKJJ+o7xHqhKAqxsbHExsai1WopLS1l5syZdO3alTvvvNNzXSiV+XIqFi49cOAAL7zwguf4HXfcwXPPPSfJRNQNp9OJ1WrF5XJRVlbmOR4eHs7jjz/ux8jqT0Mo86uvvsr69et5++23UVWVoUOH0q9fvyqbcSpGNlU07wXryKalS5eyZs0aoqOjGTZsGHfeeSc6nQ63282jjz7qlUxCpcxXwmq1epX5wIEDWK1WP0dVd6SZK0Dk5OR4vsGYzWYiIiJCagx6VRpKmffu3cu//vUvLBYLvXv3ZuzYsV7NPsePH/eMbAKIiIjgoYceCrptXD/77DOGDRtW5RYCp0+f9qqhhUqZr8TRo0d56623vAYdTJo0KWQWtZRk4mdffPEFffv2JSUlBYfDwT//+U+OHz+OVqvlkUceoWvXrv4Osc41hDK73W4yMzNZtWoVOTk5DBo0iAEDBrB///5qN0S68JfMt99+yw033ODrsH8Vs9l8yfOXGt0VrGWujVAdwSbNXH62YcMGbrnlFgDWrFmDqqrMmzePs2fP8uabb4bEL9aLNYQyP/LII3Tu3Jnf/va3dOjQwXO8T58+7N27t8r3XPjLpWKF2WDw1FNPeWqUF383VRSF2bNnV/veYC3zr+FwONi8eTPZ2dlefYPSZyLqhE6n8/yPuGPHDvr3749Go6FZs2Yh0xl9sYZQ5ueff77a+TITJkzwcTT168033/R3CEHh5ZdfJjw8nDZt2qDX6/0dTp2TZOJner2ekydPEhsby549e7j77rs952w2mx8jqz+hXOZt27bx1ltveRLmY4895lUzCWWqqrJu3Tqys7MZO3Ysubm5FBYW0q5dO3+HFhDy8/P585//7O8w6o0kEz+75557mDlzJsXFxdxwww0kJSUBkJmZSatWrfwbXD0J5TL/+9//5u9//zspKSkcOnSIjz/+uNqNoO6+++4qBxyoqordbq/vUOvc3LlzURSFPXv2MHbsWEwmE/PmzePFF1/0XBNqZb4S7du35+TJk7Ro0cLfodQLSSZ+dtVVV/Haa6/hcrnQarWe42lpaaSlpfkxsvpTUeaLhUKZtVotKSkpQHk5LzX0c8GCBb4KyycOHz7MSy+9xJNPPgmUd7w7nU6va0KtzFdi//79rF69mqSkJPR6PaqqoihKyMyAl2QSIB555JEaT3ILFYWFhSxcuJCCggKmTp3K6dOnOXjwIMOGDfN3aL9aUVGR10TMi1/feOON/gjLJ7RaLW6321PzKC4uDsmh3r9WxT4moUqSSYC4kkluoWLOnDkMGTKExYsXA+WbBc2aNSuok8nw4cO9JmJe/DqUXXfddbzyyiueLwmbNm3id7/7nb/D8ruKodOhsj1vdSSZBIiwsDAyMjLIyMjwTHL78MMPq5zkFipKSkro168fX375JVD+zbam61kFqltvvdXfIfjNwIEDadOmDbt27QLgiSeeaDC17EupGDpd1ZS+yw2dDiaSTALExZPcbrrpJs8ktxdffLHKSW7Bzmg0UlJS4mkKOXjwYMjUxLKzs1m2bBk5OTm4XC7P8aeeeqrK63NycsjKyqJr167Y7XZcLldQfpO12Wyepq7LdaiHSpkvp6EMnZYZ8AHij3/8I507d2bYsGGVhpLOnz8/5OYmQPnyEu+//75nhEtxcTGPPfZY0I/ogvJv5UOHDqVFixZeta3U1NRK165YsYKVK1diNpt54403yMrK4r333uMvf/mLL0OutS+++IKNGzfSu3dvALZu3UqfPn08E1QvFCplFv8jNZMA4Ha7GTJkSLUzYUMxkQA0b96c559/nrNnz6KqKk2bNq2yKSAY6fV6rr/++hpdu3z5cl588UVPB22TJk0oKiqqz/Dqxfr163nppZc8CzaOGjWKp556qspkEiplFv8T3A3UIUKj0bBnzx5/h+Fzzz77LFqtlubNm9OiRQt0Oh3PPvusv8OqE9dffz2ff/45Bw8e5OjRo54/VdHr9eh0//te53K5gnIUVKNGjXA4HJ7XDoeD5OTkKq8NlTKHyooNdUFqJgGiffv2zJs3j379+mE0Gj3HQ2VF0QsVFhaSn5+P3W7n2LFjntpIWVlZ0M+Ar3Dy5EnWrl3L7t27vZq5/vrXv1a6NjU1lUWLFmG329m5cyfLly+nZ8+evgy3Tuh0Oh5//HG6du2Koijs3LmTjh07Mn/+fMC7hh0qZb6SfWvOnTtHQkICer2ePXv2cOLECQYPHkxERISPoq1f0mcSIKqbJV3VL59gt3r1atasWcORI0do27at57jJZGLIkCGeNvdgNnnyZF599VWvb9/Vcbvd/PDDD+zcuRNVVenWrRvDhw8Pum/qq1evvuT5IUOGeP4eKmUuKytj/fr1rF69+rJD+p944gmmT59OTk4O06ZNo2fPnmRlZfHMM8/4IfK6J8lE+M2mTZvo06ePv8OoF7NmzWLChAnExMT4OxThI5fbt6ZiS+qvv/4avV7Pddddx5NPPsnLL7/sx6jrjjRzBZDMzExOnTrl1e4cKstTX2jt2rUMGjSInJwcr9nhFUJhlnhRURGTJ0+mXbt2XrWTC4cGT5ky5ZLfxINtmY2srCw+/fRTTp8+7fUzfOE8ilAr85UM6ddqtfz444+sWbPG83Nw4bDxYCfJJEC8++672O129uzZw7Bhw9i0aVPIrrZa0S9S1bpVwdbMUZ1x48Zd9pqnn37aB5H4zpw5cxg3bhwffvghU6dOZdWqVZWuCbUyX8m+NQ8++CDfffcdo0ePJikpiezsbAYOHOjrkOuPKgLClClTvP5bVlamvvDCC/4MyS+WLFni7xD8oqCgQN26dau6detWtaCgwN/h/CpPPvmkqqqq+vjjj3uOPffcc9VeHwplzsnJ8XcIAUNqJgGiYmy+0WgkPz+fqKgosrOz/RyV74XKbnsXLrXudDpxOp2YTCY+/PDDSteuXLmSL774gi5duqCqKu+//z633HJL0K1RZjAYcLvdNGnShP/+97/Ex8dXO3ck2Mt8JfvWhFrTXnUkmQSItLQ0SktLuemmmzxr+QTL/1iisouXWt+yZQuHDx+u8tqvv/6al19+maioKKB8zbJnn3026P79x48fj91u59577+Wzzz5j9+7dPPTQQ1VeG+xlvpJ9ayqa9pYvXw7AoEGDAFi3bp3XNIBgJ8kkQFR0tPfp04eePXvicDhCZp0qAb169eKrr76q8lxCQoLXmlRhYWHVbvkbyCr6+EwmEw8++OAlrw32Ml/JvjWNGjUC4MCBA7zwwgue43fccQfPPfdcyAyykWQSQA4cOFBpYcDBgwf7MaL6UdEEpF4wKr3idajstrd582bP31VV5ciRI9VeGx8fz9SpU0lPT0dRFLZt20bbtm09I92CZXTb2bNn+frrr8nNzfX6Ga5qrlSwl/nX7FtjtVrZv38/HTt2BMr/f79UEgo2kkwCxBtvvMH58+dp1aqV14zpUEwmDWG3ve3bt3v+rtFoSEpK8uxAeLHk5GSvZUfS09MBgm4flFmzZnHNNdeQkZFx2a0Egr3Mv2bfmkmTJvHWW29hsVgACA8PZ9KkSfUapy/JpMUA8dhjjzFz5syQGRpbEzt37uT06dNAeRNJ+/bt/RyRqI2KSXni0i5MJqFEaiYBonnz5hQWFhIXF+fvUOpdbm4ur7zyCiaTiTZt2qCqKps3b8ZgMPDkk0+ydu1ahg8f7u8wf5Uvvvjikuerah8/cuQIixYtqtQ8FCyjfCp2EuzZsyfLly+nV69e6PV6z/nIyMhK7wn2Mle4kn1rHA4HmzdvJjs722uBSOkzEXWqpKSExx9//JIzpkPFvHnzuO6667zWagJYs2aNZ9XgYE0mVY3Osdls/PDDD5SUlFT5i+P111/nrrvuokWLFkFZM714J8FvvvnG63xVOwkGe5krvPLKKwwdOpSePXtetmnv5ZdfJjw8nDZt2ngl21AhySRANKTtXs+ePVspkUB5/9DChQuDuqnkpptu8vy9rKyMpUuXsmrVKvr16+d17kLR0dGePoNg9Nhjj5GQkOCpVa9evZrNmzfTqFGjalcCCPYyV7iSfWvy8/P585//XM8R+Y8kkwCRmppKYWGhZ9RPu3btQnaRwOr2gHC73RgMhqAvt9lsZsmSJaxbt47Bgwfz0ksvVdnUU2HcuHG8/fbbdOnSxesba7Csnvzee+/x3HPPAeWLHS5cuJB7772X48eP88477zBlypRK7wn2Mleo2LemW7duXi0KVW0d0b59e8+uoqFIkkmA2LBhAx9//LFnW9f58+dz1113heSquj179uTtt9/mnnvuwWQyAeXDJj/88EN69Ojh5+hq56OPPmLLli0MHz6cGTNmeMp3KatWreLs2bM4nU6vppJg+cXqdrs9yXLDhg0MHz6cPn360KdPH5544okq3xPsZa5wJfvW7N+/n9WrV5OUlIRer0dVVRRFCbp+oupIMgkQixcv5sUXX/R8Ky8uLuaFF14IyWRy5513snDhQh566CESExNRFIWcnBwGDx7M7bff7u/wamXJkiXodDoWLVrE4sWLPccrfnFUtZzKiRMnmDFjhi/DrFNutxuXy4VWq2X37t3cf//9XueqEuxlrrBlyxZmz55do31rKrYoDlWSTAKE2+32at6JjIwM2S1Bjx8/zo033shtt93GuXPn2L17N5mZmTidTqxW6yWbhALdZ599dsXvueqqqzh9+vRld+oLVP379+f5558nKioKg8FAp06dgPKdBasb/hrsZa7QsmVLSktLa9Q0G8wDDWpCkkmA6N69O9OmTaN///5AeXNBsDf5VKeijd1gMGA2m/nqq68u28Yeyg4cOMCaNWuCtvljzJgxdOnShcLCQs+WvVD+Benee++t8j3BXuYKNdm3psKLL77oGfXmcDjIzs6madOmzJw505ch1xuZtBhANm3axIEDB1BVldTUVHr16uXvkOrFE088wSuvvALA3LlziY6O9oz6ufBcQ5GTk1Pl8Yo1nUJRqJT54j1LKlT0fV7K0aNHWbFihVezYDCTmkkAqei0DHW/po09FFksFsLDw70WPAx1oVbmmiSN6rRp0+aSa7YFG0kmfvbcc8/xwgsveO1/AZfusA12v6aNPRS9/vrr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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Run Time: ~5s\n", - "\n", - "# Plot results\n", - "plot_results(\n", - " experiments=[dict_runs, sqlite_runs, numpy_runs, shapely_runs, numpy_index_runs],\n", - " title=\"Box Query\",\n", - " tick_label=[\n", - " \"DictionaryStore\",\n", - " \"SQLiteStore\",\n", - " \"NumPy\\n(Simple Loop)\",\n", - " \"Shapely\\n(Simple Loop)\",\n", - " \"NumPy\\n(With Bounds Index)\",\n", - " ],\n", - ")\n", - "plt.xticks(rotation=90)\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "LJiGGkespT56" - }, - "source": [ - "## 2.3) Size vs Approximate Lower Bound\n", - "\n", - "Here we calculate an estimated lower bound on file size by finding the\n", - "the Shannon entropy of each file. This tells us the theoretical minimum\n", - "number of bits per byte. The lowest lower bound is then used as an\n", - "estimate of the minimum file size possible to store the annotation data.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "0IO10faZpT56", - "outputId": "033c2530-072a-4aa5-cf34-c2298e90d86f" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - " " - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Approximate Lower Bound Size: 3.60 GB\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\r" - ] - } - ], - "source": [ - "# Run Time: ~5m\n", - "\n", - "\n", - "# Files to consider containing keys, geometry, and properties.\n", - "# Files which are missing keys e.g. cells.pickle are excluded\n", - "# for a fair comparison.\n", - "file_names = [\n", - " \"cells-dicionary-store.pickle\",\n", - " \"cells-dict.pickle\",\n", - " \"cells.db\",\n", - " \"cells.db.zstd\",\n", - " \"cells.geojson\",\n", - " \"cells.ndjson\",\n", - " \"cells.ndjson.zstd\",\n", - "]\n", - "\n", - "\n", - "def human_readible_bytes(byte_count: int) -> tuple[int, str]:\n", - " \"\"\"Convert bytes to human readble size and suffix.\"\"\"\n", - " byte_count_ref = 1024\n", - " for suffix in [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"]:\n", - " if byte_count < byte_count_ref:\n", - " return byte_count, suffix\n", - " byte_count /= byte_count_ref\n", - " return byte_count, \"PB\"\n", - "\n", - "\n", - "def shannon_entropy(\n", - " fp: Path,\n", - " sample_size: int = 1e9, # 1GiB\n", - " stride: int = 7,\n", - " skip: int = 1e5, # 100KiB\n", - ") -> float:\n", - " \"\"\"Calculate the Shannon entropy of a file from a sample.\n", - "\n", - " The first `skip` bytes are skipped to avoid sampling low entropy\n", - " (highly ordered) parts which commonly occur at the beginning e.g.\n", - " headers.\n", - "\n", - " Args:\n", - " fp: File path to calculate entropy of.\n", - " sample_size: Number of bytes to sample from the file.\n", - " stride: Number of bytes to skip between samples.\n", - " skip: Number of bytes to skip before sampling.\n", - " \"\"\"\n", - " npmmap = np.memmap(Path(fp), dtype=np.uint8, mode=\"r\")\n", - " values, counts = np.unique(\n", - " npmmap[int(skip) : int(skip + (sample_size * stride)) : int(stride)],\n", - " return_counts=True,\n", - " )\n", - " total = np.sum(counts)\n", - " frequencies = {v: 0 for v in range(256)}\n", - " for v, x in zip(values, counts):\n", - " frequencies[v] = x / total\n", - " frequency_array = np.array(list(frequencies.values()))\n", - " epsilon = 1e-16\n", - " return -np.sum(frequency_array * np.log2(frequency_array + epsilon))\n", - "\n", - "\n", - "# Find the min across all of the representations for the lowest lower\n", - "# bound.\n", - "bytes_lower_bounds = {\n", - " path: (\n", - " shannon_entropy(Path(path)) / 8 * len(np.memmap(path, dtype=np.uint8, mode=\"r\"))\n", - " )\n", - " for path in tqdm(\n", - " [Path.cwd() / name for name in file_names],\n", - " position=0,\n", - " leave=False,\n", - " )\n", - "}\n", - "\n", - "lowest_bytes_lower_bound = min(bytes_lower_bounds.values())\n", - "\n", - "size, suffix = human_readible_bytes(lowest_bytes_lower_bound)\n", - "logger.info(\"Approximate Lower Bound Size: %2f %s\", size, suffix)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "chwB3zeupT56" - }, - "source": [ - "### Plot Results\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "cu5jkrVppT56", - "outputId": "bb36aea5-d5d7-4560-a853-d2a8afba0eac" - }, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Get file sizes\n", - "file_sizes = {\n", - " path: path.stat().st_size for path in [Path.cwd() / name for name in file_names]\n", - "}\n", - "\n", - "# Sort by size\n", - "file_sizes = dict(sorted(file_sizes.items(), key=lambda x: x[1]))\n", - "\n", - "# Plot\n", - "plt.bar(\n", - " x=range(len(file_sizes)),\n", - " height=file_sizes.values(),\n", - " tick_label=[p.name for p in file_sizes],\n", - " color=[f\"C{i}\" for i in range(len(file_sizes))],\n", - ")\n", - "plt.xlabel(\"File Name\")\n", - "plt.ylabel(\"Bytes\")\n", - "plt.xticks(rotation=90)\n", - "plt.hlines(\n", - " y=lowest_bytes_lower_bound,\n", - " xmin=-0.5,\n", - " xmax=len(file_sizes) - 0.5,\n", - " linestyles=\"dashed\",\n", - " color=\"black\",\n", - " label=\"Approximate Bytes Lower Bound\",\n", - ")\n", - "plt.legend()\n", - "plt.tight_layout()\n", - "plt.title(\"Polygon Annotation File Sizes\")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gmuEWlImpT57" - }, - "source": [ - "The SQLite representation (4.9GB) appears to be quite compact compared\n", - "with GeoJSON and ndjson. Although not as compact as a dictionary pickle\n", - "or Zstandard compressed ndjson, it offers a good compromise between\n", - "compactness and read performance.\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Yhe5rMXPpT57" - }, - "source": [ - "# 3: Extra Bits\n", - "\n", - "## 3.1) Space Saving\n", - "\n", - "A lot of space can be saved by rounding the coordinates to the nearest\n", - "integer when storing them. Below we make a copy of the dataset with all\n", - "coordinates rounded.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "H2Jsc0repT57", - "outputId": "d2ca9eff-b67d-4bfc-ad5a-57c87bc6a7da" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "100%|██████████| 10008338/10008338 [51:00<00:00, 3270.16it/s] \n" - ] - } - ], - "source": [ - "# Run Time: ~50m\n", - "! rm integer-cells.db\n", - "int_cell_sqlite_store = SQLiteStore(\"integer-cells.db\")\n", - "\n", - "# We use batches of 1000 to speed up appending\n", - "batch = {}\n", - "batch_size = 1000\n", - "for key, annotation in tqdm(cell_sqlite_store.items(), total=len(cell_sqlite_store)):\n", - " geometry = Polygon(np.array(annotation.geometry.exterior.coords).round())\n", - " rounded_annotation = Annotation(geometry, annotation.properties)\n", - " batch[key] = rounded_annotation\n", - " if len(batch) >= batch_size:\n", - " int_cell_sqlite_store.append_many(batch.values(), batch.keys())\n", - " batch = {}\n", - "_ = int_cell_sqlite_store.append_many(batch.values(), batch.keys())" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "U6aooIROpT57" - }, - "source": [ - "Here the database size is reduced to 2.9GB, down from 4.9GB.\n", - "Additionally, when using integer coordinates, the database compresses\n", - "much better. Zstandard can compress to approximately 60% of the\n", - "original size (and 35% of the floating point coordinate\n", - "database size). This may be done for archival purposes.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "Q3TJ8XX4pT57", - "outputId": "b99d1af7-4c68-4394-cf9a-8bb2b64471a0" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "integer-cells.db : 60.58% ( 2.86 GiB => 1.73 GiB, integer-cells.db.zstd) \n" - ] } - ], - "source": [ - "# Run time: ~15s\n", - "! zstd -f -k integer-cells.db -o integer-cells.db.zstd" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "alFRiIAbpT57" - }, - "source": [ - "With higher (slower) compression settings the space can be further\n", - "reduced for long term storage.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "nVFqovfPpT57", - "outputId": "0948bbe6-4252-4c93-eab7-8e3be4e98235" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "integer-cells.db : 51.22% ( 2.86 GiB => 1.47 GiB, integer-cells.db.19.zstd) \n" - ] + ], + "metadata": { + "colab": { + "provenance": [] + }, + "interpreter": { + "hash": "a3ed8fb525a8bde66cc7655a5df08d8d0f8699a69b9eb5ccab28dc0a7837eec6" + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.12" } - ], - "source": [ - "# Run time: ~20m\n", - "! zstd -f -k -19 --long integer-cells.db -o integer-cells.db.19.zstd" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "C3voJ43OpT57" - }, - "source": [ - "## 3.2) Feature Comparison Summary\n", - "\n", - "Here we briefly summarise some of the positives and negatives of each format and construct a comparison matrix.\n", - "\n", - "**GeoJSON**\n", - "\n", - "*Positives*\n", - "\n", - "- Simple, based JSON which is well known.\n", - "- Well defined with a public specification.\n", - "- Popular format for geometry, many tools which work with it.\n", - "- Fast to write.\n", - "\n", - "*Negatives*\n", - "\n", - "- Requires loading the whole file into memory for parsing. Some\n", - " specialised parsers can, in some situations, reduce or avoid this but\n", - " it is not possible in general.\n", - "- Not a very compact representation.\n", - "\n", - "**ndjson (One GeoJSON Feature Per Line)**\n", - "\n", - "*Positives*\n", - "\n", - "- Simple.\n", - "- Better to parse than JSON/GeoJSON. Each line can be parsed\n", - " independently.\n", - "- Many tools to parse JSON lines.\n", - "- Fast to write.\n", - "\n", - "*Negatives*\n", - "\n", - "- Not a very compact representation.\n", - "- Requires loading the whole dataset from disk before querying OR\n", - " scanning through and reparsing each line for each query.\n", - "- Amending annotations can be tricky. The easiest way is to blank out a\n", - " line and append a modified copy each time. This could end up\n", - " fragmenting the file and wasting a lot of space. More complex methods\n", - " could be developed to reduce fragmenting the file.\n", - "\n", - "**pickle**\n", - "\n", - "*Positives*\n", - "\n", - "- Fast to write.\n", - "\n", - "*Negatives*\n", - "\n", - "- Vulnerable to arbitrary code execution when loading from disk.\n", - "- Requires loading the whole dataset into memory for querying.\n", - "\n", - "**SQLite (SQLiteStore Flavour)**\n", - "\n", - "*Positives*\n", - "\n", - "- Very fast to query (uses an R-TREE index to accelerate\n", - " spatial queries).\n", - "- Does not require loading data into memory before querying.\n", - "- Possible to index property lookups.\n", - "\n", - "*Negatives*\n", - "\n", - "- Not the most compact representation on disk.\n", - "\n", - "### Feature Matrix\n", - "\n", - "| Format | Size On-Disk | Size In-Memory | Partial Reads | Serialization | Query Performance |\n", - "| ----------: | :----------- | :------------- | :------------ | :------------ | :---------------- |\n", - "| SQLiteStore | Medium | Small | Yes | Slow | Fast |\n", - "| GeoJSON | Large | Large | No | Fast | Slow |\n", - "| ndjson | Large | Large | Yes | Fast | Medium |\n", - "| pickle | Small | Medium | No | Medium | Slow |\n", - "\n" - ] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "interpreter": { - "hash": "a3ed8fb525a8bde66cc7655a5df08d8d0f8699a69b9eb5ccab28dc0a7837eec6" - }, - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.12" - } - }, - "nbformat": 4, - "nbformat_minor": 0 + "nbformat": 4, + "nbformat_minor": 0 } diff --git a/benchmarks/annotation_store_alloc.py b/benchmarks/annotation_store_alloc.py index d5b6df9cb..82c642ada 100644 --- a/benchmarks/annotation_store_alloc.py +++ b/benchmarks/annotation_store_alloc.py @@ -102,7 +102,7 @@ import warnings from pathlib import Path from tempfile import NamedTemporaryFile -from typing import TYPE_CHECKING, Any, Generator +from typing import TYPE_CHECKING, Any sys.path.append("../") @@ -151,6 +151,7 @@ def __exit__(self: memray, *args: object) -> None: ) if TYPE_CHECKING: # pragma: no cover + from collections.abc import Generator from numbers import Number diff --git a/docker/3.8/Debian/Dockerfile b/docker/3.11/Debian/Dockerfile similarity index 91% rename from docker/3.8/Debian/Dockerfile rename to docker/3.11/Debian/Dockerfile index 9c4e5ecc8..3b399ddac 100644 --- a/docker/3.8/Debian/Dockerfile +++ b/docker/3.11/Debian/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.8-slim-bullseye +FROM python:3.11-slim-bullseye #get linux packages RUN apt-get -y update && apt-get -y install --no-install-recommends \ diff --git a/docker/3.11/Ubuntu/Dockerfile b/docker/3.11/Ubuntu/Dockerfile new file mode 100644 index 000000000..72d7adee8 --- /dev/null +++ b/docker/3.11/Ubuntu/Dockerfile @@ -0,0 +1,30 @@ +FROM ubuntu:22.04 AS builder-image + +# To avoid tzdata blocking the build with frontend questions +ENV DEBIAN_FRONTEND=noninteractive + +# Install python3.11 +RUN apt-get update && \ + apt install software-properties-common -y &&\ + add-apt-repository ppa:deadsnakes/ppa -y && apt update &&\ + apt-get install -y --no-install-recommends python3.11-venv &&\ + apt-get install libpython3.11-de -y &&\ + apt-get install python3.11-dev -y &&\ + apt-get install build-essential -y &&\ + apt-get clean + +# Add env to PATH +RUN python3.11 -m venv /venv +ENV PATH=/venv/bin:$PATH + +# install TIAToolbox and its requirements +RUN apt-get update && apt-get install --no-install-recommends -y \ + libopenjp2-7-dev libopenjp2-tools \ + openslide-tools \ + libgl1 \ + && apt-get clean && rm -rf /var/lib/apt/lists/* +RUN pip install --no-cache-dir tiatoolbox + +# activate virtual environment +ENV VIRTUAL_ENV=/opt/venv +ENV PATH="/opt/venv/bin:$PATH" diff --git a/docker/3.12/Debian/Dockerfile b/docker/3.12/Debian/Dockerfile new file mode 100644 index 000000000..412f8d015 --- /dev/null +++ b/docker/3.12/Debian/Dockerfile @@ -0,0 +1,14 @@ +FROM python:3.12-slim-bullseye + +#get linux packages +RUN apt-get -y update && apt-get -y install --no-install-recommends \ + libopenjp2-7-dev libopenjp2-tools \ + openslide-tools \ + libgl1 \ + build-essential \ + && pip3 --no-cache-dir install tiatoolbox \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# set the entry point to bash +ENTRYPOINT ["/bin/bash"] diff --git a/docker/3.12/Ubuntu/Dockerfile b/docker/3.12/Ubuntu/Dockerfile new file mode 100644 index 000000000..d99483d74 --- /dev/null +++ b/docker/3.12/Ubuntu/Dockerfile @@ -0,0 +1,30 @@ +FROM ubuntu:22.04 AS builder-image + +# To avoid tzdata blocking the build with frontend questions +ENV DEBIAN_FRONTEND=noninteractive + +# Install python3.12 +RUN apt-get update && \ + apt install software-properties-common -y &&\ + add-apt-repository ppa:deadsnakes/ppa -y && apt update &&\ + apt-get install -y --no-install-recommends python3.12-venv &&\ + apt-get install libpython3.12-de -y &&\ + apt-get install python3.12-dev -y &&\ + apt-get install build-essential -y &&\ + apt-get clean + +# Add env to PATH +RUN python3.12 -m venv /venv +ENV PATH=/venv/bin:$PATH + +# install TIAToolbox and its requirements +RUN apt-get update && apt-get install --no-install-recommends -y \ + libopenjp2-7-dev libopenjp2-tools \ + openslide-tools \ + libgl1 \ + && apt-get clean && rm -rf /var/lib/apt/lists/* +RUN pip install --no-cache-dir tiatoolbox + +# activate virtual environment +ENV VIRTUAL_ENV=/opt/venv +ENV PATH="/opt/venv/bin:$PATH" diff --git a/docs/installation.rst b/docs/installation.rst index e8fe41478..80895e939 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -60,7 +60,7 @@ MacPorts Installing Stable Release ========================= -Please note that TIAToolbox is tested for python version 3.8, 3.9 and 3.10. +Please note that TIAToolbox is tested for python version 3.9, 3.10, 3.11 and 3.12. Recommended ----------- diff --git a/examples/full-pipelines/slide-graph.ipynb b/examples/full-pipelines/slide-graph.ipynb index 54d1cdbde..8b10087e2 100644 --- a/examples/full-pipelines/slide-graph.ipynb +++ b/examples/full-pipelines/slide-graph.ipynb @@ -133,7 +133,7 @@ "import warnings\n", "from collections import OrderedDict\n", "from pathlib import Path\n", - "from typing import Callable, Iterator\n", + "from typing import TYPE_CHECKING, Callable\n", "\n", "# Third party imports\n", "import joblib\n", @@ -191,6 +191,9 @@ " WSIReader,\n", ")\n", "\n", + "if TYPE_CHECKING: # pragma: no cover\n", + " from collections.abc import Iterator\n", + "\n", "warnings.filterwarnings(\"ignore\")\n", "mpl.rcParams[\"figure.dpi\"] = 300 # for high resolution figure in notebook" ] diff --git a/pyproject.toml b/pyproject.toml index 05463efe8..0662f9e65 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -157,8 +157,8 @@ line-length = 88 # Allow unused variables when underscore-prefixed. lint.dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" -# Minimum Python version 3.8. -target-version = "py38" +# Minimum Python version 3.9. +target-version = "py39" [tool.ruff.lint.mccabe] # Unlike Flake8, default to a complexity level of 10. @@ -174,4 +174,4 @@ max-args = 10 [tool.mypy] ignore_missing_imports = true -python_version = 3.8 +python_version = 3.9 diff --git a/requirements/requirements.conda.yml b/requirements/requirements.conda.yml index 09be84a12..0d999ac35 100644 --- a/requirements/requirements.conda.yml +++ b/requirements/requirements.conda.yml @@ -9,6 +9,6 @@ dependencies: - openslide - pip>=20.0.2 - pixman>=0.39.0 - - python>=3.8, <=3.11 + - python>=3.9, <=3.12 - pip: - -r requirements.txt diff --git a/requirements/requirements.dev.conda.yml b/requirements/requirements.dev.conda.yml index 494d5a0d3..4a743d837 100644 --- a/requirements/requirements.dev.conda.yml +++ b/requirements/requirements.dev.conda.yml @@ -9,6 +9,6 @@ dependencies: - openslide - pip>=20.0.2 - pixman>=0.39.0 - - python>=3.8, <=3.11 + - python>=3.9, <=3.12 - pip: - -r requirements_dev.txt diff --git a/requirements/requirements.win64.conda.yml b/requirements/requirements.win64.conda.yml index f6386597f..1aeff0a7a 100644 --- a/requirements/requirements.win64.conda.yml +++ b/requirements/requirements.win64.conda.yml @@ -9,6 +9,6 @@ dependencies: - openjpeg>=2.4.0 - pip>=20.0.2 - pixman>=0.39.0 - - python>=3.8, <=3.11 + - python>=3.9, <=3.12 - pip: - -r requirements.txt diff --git a/requirements/requirements.win64.dev.conda.yml b/requirements/requirements.win64.dev.conda.yml index 078d75a38..64b4b07d1 100644 --- a/requirements/requirements.win64.dev.conda.yml +++ b/requirements/requirements.win64.dev.conda.yml @@ -9,6 +9,6 @@ dependencies: - openjpeg>=2.4.0 - pip>=20.0.2 - pixman>=0.39.0 - - python>=3.8, <=3.11 + - python>=3.9, <=3.12 - pip: - -r requirements_dev.txt diff --git a/setup.py b/setup.py index 92fe58e0b..efb7f20ec 100644 --- a/setup.py +++ b/setup.py @@ -34,16 +34,16 @@ setup( author="TIA Centre", author_email="tia@dcs.warwick.ac.uk", - python_requires=">=3.8, <3.12", + python_requires=">=3.9, <3.13", classifiers=[ "Development Status :: 2 - Pre-Alpha", "Intended Audience :: Developers", "Natural Language :: English", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", ], description="Computational pathology toolbox developed by TIA Centre.", dependency_links=dependency_links, diff --git a/tests/test_annotation_stores.py b/tests/test_annotation_stores.py index 562e9a8a1..cac3937ba 100644 --- a/tests/test_annotation_stores.py +++ b/tests/test_annotation_stores.py @@ -6,10 +6,11 @@ import pickle import sqlite3 import sys +from collections.abc import Generator from itertools import repeat, zip_longest from pathlib import Path from timeit import timeit -from typing import TYPE_CHECKING, Callable, ClassVar, Generator +from typing import TYPE_CHECKING, Callable, ClassVar import numpy as np import pandas as pd @@ -1801,13 +1802,13 @@ def test_load_cases_error( store._load_cases(["foo"], lambda: None, lambda: None) @staticmethod - def test_py38_init( + def test_py39_init( fill_store: Callable, # noqa: ARG004 store_cls: type[AnnotationStore], monkeypatch: object, ) -> None: - """Test that __init__ is compatible with Python 3.8.""" - py38_version = (3, 8, 0) + """Test that __init__ is compatible with Python 3.9.""" + py39_version = (3, 9, 0) class Connection(sqlite3.Connection): """Mock SQLite connection.""" @@ -1821,7 +1822,7 @@ def create_function( """Mock create_function without `deterministic` kwarg.""" return self.create_function(self, name, num_params) - monkeypatch.setattr(sys, "version_info", py38_version) + monkeypatch.setattr(sys, "version_info", py39_version) monkeypatch.setattr(sqlite3, "Connection", Connection) _ = store_cls() diff --git a/tests/test_app_bokeh.py b/tests/test_app_bokeh.py index 3d072a919..b29d78188 100644 --- a/tests/test_app_bokeh.py +++ b/tests/test_app_bokeh.py @@ -2,24 +2,18 @@ from __future__ import annotations +import importlib.resources as importlib_resources import io import json import multiprocessing import re -import sys import time from pathlib import Path -from typing import TYPE_CHECKING, Generator +from typing import TYPE_CHECKING import bokeh.models as bkmodels import matplotlib.pyplot as plt import numpy as np - -if sys.version_info >= (3, 9): # pragma: no cover - import importlib.resources as importlib_resources -else: # pragma: no cover - # To support Python 3.8 - import importlib_resources # type: ignore[import-not-found] import pytest import requests from bokeh.application import Application @@ -35,7 +29,9 @@ from tiatoolbox.visualization.tileserver import TileServer from tiatoolbox.visualization.ui_utils import get_level_by_extent -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover + from collections.abc import Generator + from bokeh.document import Document # constants diff --git a/tests/test_docs.py b/tests/test_docs.py index 020188797..ea446737a 100644 --- a/tests/test_docs.py +++ b/tests/test_docs.py @@ -9,10 +9,13 @@ import sys from doctest import DocTest from pathlib import Path -from typing import Generator +from typing import TYPE_CHECKING import pytest +if TYPE_CHECKING: # pragma: no cover + from collections.abc import Generator + @pytest.fixture() def source_files(root_path: Path) -> Generator: diff --git a/tests/test_dsl.py b/tests/test_dsl.py index ad811ac6e..1657db1b6 100644 --- a/tests/test_dsl.py +++ b/tests/test_dsl.py @@ -5,7 +5,7 @@ import json import sqlite3 from numbers import Number -from typing import Callable, ClassVar, Mapping +from typing import TYPE_CHECKING, Callable, ClassVar import pytest @@ -19,6 +19,9 @@ py_regexp, ) +if TYPE_CHECKING: # pragma: no cover + from collections.abc import Mapping + BINARY_OP_STRINGS = [ "+", "-", diff --git a/tests/test_wsireader.py b/tests/test_wsireader.py index 76a5d3861..8bdea210b 100644 --- a/tests/test_wsireader.py +++ b/tests/test_wsireader.py @@ -11,7 +11,7 @@ from pathlib import Path # When no longer supporting Python <3.9 this should be collections.abc.Iterable -from typing import TYPE_CHECKING, Callable, Iterable +from typing import TYPE_CHECKING, Callable import cv2 import glymur @@ -46,7 +46,9 @@ is_zarr, ) -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover + from collections.abc import Iterable + import requests from openslide import OpenSlide diff --git a/tiatoolbox/__init__.py b/tiatoolbox/__init__.py index 88d2eabc9..452ff5a85 100644 --- a/tiatoolbox/__init__.py +++ b/tiatoolbox/__init__.py @@ -2,16 +2,11 @@ from __future__ import annotations +import importlib.resources as importlib_resources import importlib.util import sys from pathlib import Path -from typing import TYPE_CHECKING, Dict, TypedDict - -if sys.version_info >= (3, 9): # pragma: no cover - import importlib.resources as importlib_resources -else: # pragma: no cover - # To support Python 3.8 - import importlib_resources # type: ignore[import-not-found] +from typing import TYPE_CHECKING, TypedDict import yaml @@ -92,9 +87,8 @@ def read_registry_files(path_to_registry: str | Path) -> dict: """ - path_to_registry = str(path_to_registry) # To pass tests with Python 3.8 pretrained_files_registry_path = importlib_resources.as_file( - importlib_resources.files("tiatoolbox") / path_to_registry, + importlib_resources.files("tiatoolbox") / str(path_to_registry), ) with pretrained_files_registry_path as registry_file_path: diff --git a/tiatoolbox/annotation/storage.py b/tiatoolbox/annotation/storage.py index 3fb786374..541e66a63 100644 --- a/tiatoolbox/annotation/storage.py +++ b/tiatoolbox/annotation/storage.py @@ -40,7 +40,7 @@ import zlib from abc import ABC, abstractmethod from collections import defaultdict -from collections.abc import MutableMapping +from collections.abc import Generator, Iterable, Iterator, MutableMapping from dataclasses import dataclass, field from functools import lru_cache from pathlib import Path @@ -50,9 +50,6 @@ Any, Callable, ClassVar, - Generator, - Iterable, - Iterator, ) import numpy as np diff --git a/tiatoolbox/cli/visualize.py b/tiatoolbox/cli/visualize.py index 7f5ed0ad5..86810954a 100644 --- a/tiatoolbox/cli/visualize.py +++ b/tiatoolbox/cli/visualize.py @@ -2,19 +2,13 @@ from __future__ import annotations +import importlib.resources as importlib_resources import os import subprocess -import sys from pathlib import Path from threading import Thread import click - -if sys.version_info >= (3, 9): # pragma: no cover - import importlib.resources as importlib_resources -else: # pragma: no cover - # To support Python 3.8 - import importlib_resources # type: ignore[import-not-found] from flask_cors import CORS from tiatoolbox.cli.common import tiatoolbox_cli diff --git a/tiatoolbox/data/__init__.py b/tiatoolbox/data/__init__.py index 1ac4e8e31..d7058493e 100644 --- a/tiatoolbox/data/__init__.py +++ b/tiatoolbox/data/__init__.py @@ -2,6 +2,7 @@ """Package to define datasets available to download via TIAToolbox.""" from __future__ import annotations +import importlib.resources as importlib_resources import sys import tempfile import zipfile @@ -9,11 +10,6 @@ from typing import TYPE_CHECKING from urllib.parse import urlparse -if sys.version_info >= (3, 9): # pragma: no cover - import importlib.resources as importlib_resources -else: # pragma: no cover - import importlib_resources # To support Python 3.8 - from tiatoolbox import logger, read_registry_files if TYPE_CHECKING: # pragma: no cover diff --git a/tiatoolbox/models/dataset/dataset_abc.py b/tiatoolbox/models/dataset/dataset_abc.py index 31fb2bfd5..b60ecd66e 100644 --- a/tiatoolbox/models/dataset/dataset_abc.py +++ b/tiatoolbox/models/dataset/dataset_abc.py @@ -4,9 +4,11 @@ from abc import ABC, abstractmethod from pathlib import Path -from typing import TYPE_CHECKING, Callable, Iterable, List, Union +from typing import TYPE_CHECKING, Callable, Union if TYPE_CHECKING: # pragma: no cover + from collections.abc import Iterable + try: from typing import TypeGuard except ImportError: @@ -18,7 +20,7 @@ from tiatoolbox.utils import imread -input_type = Union[List[Union[str, Path, np.ndarray]], np.ndarray] +input_type = Union[list[Union[str, Path, np.ndarray]], np.ndarray] class PatchDatasetABC(ABC, torch.utils.data.Dataset): diff --git a/tiatoolbox/tools/pyramid.py b/tiatoolbox/tools/pyramid.py index a6506fb46..cfbe55190 100644 --- a/tiatoolbox/tools/pyramid.py +++ b/tiatoolbox/tools/pyramid.py @@ -17,7 +17,7 @@ import zipfile from io import BytesIO from pathlib import Path -from typing import TYPE_CHECKING, Iterator +from typing import TYPE_CHECKING import defusedxml import numpy as np @@ -28,6 +28,8 @@ from tiatoolbox.utils.visualization import AnnotationRenderer, random_colors if TYPE_CHECKING: # pragma: no cover + from collections.abc import Iterator + from tiatoolbox.annotation import AnnotationStore from tiatoolbox.wsicore.wsireader import WSIMeta, WSIReader diff --git a/tiatoolbox/tools/stainextract.py b/tiatoolbox/tools/stainextract.py index 4126f7e55..cb2972ae2 100644 --- a/tiatoolbox/tools/stainextract.py +++ b/tiatoolbox/tools/stainextract.py @@ -2,22 +2,12 @@ from __future__ import annotations -from typing import TYPE_CHECKING - import numpy as np from sklearn.decomposition import DictionaryLearning from tiatoolbox.utils.misc import get_luminosity_tissue_mask from tiatoolbox.utils.transforms import rgb2od -if TYPE_CHECKING: # pragma: no cover - import sys - - if sys.version_info >= (3, 9): - from typing import Self - else: # pragma: no cover - from typing_extensions import Self # To support Python 3.8 - def vectors_in_correct_direction(e_vectors: np.ndarray) -> np.ndarray: """Points the eigen vectors in the right direction. @@ -92,14 +82,14 @@ class CustomExtractor: """ - def __init__(self: Self, stain_matrix: np.ndarray) -> None: + def __init__(self: CustomExtractor, stain_matrix: np.ndarray) -> None: """Initialize :class:`CustomExtractor`.""" self.stain_matrix = stain_matrix if self.stain_matrix.shape not in [(2, 3), (3, 3)]: msg = "Stain matrix must have shape (2, 3) or (3, 3)." raise ValueError(msg) - def get_stain_matrix(self: Self, _: np.ndarray) -> np.ndarray: + def get_stain_matrix(self: CustomExtractor, _: np.ndarray) -> np.ndarray: """Get the user defined stain matrix. Returns: @@ -131,11 +121,11 @@ class RuifrokExtractor: """ - def __init__(self: Self) -> None: + def __init__(self: RuifrokExtractor) -> None: """Initialize :class:`RuifrokExtractor`.""" self.__stain_matrix = np.array([[0.65, 0.70, 0.29], [0.07, 0.99, 0.11]]) - def get_stain_matrix(self: Self, _: np.ndarray) -> np.ndarray: + def get_stain_matrix(self: RuifrokExtractor, _: np.ndarray) -> np.ndarray: """Get the pre-defined stain matrix. Returns: @@ -175,7 +165,7 @@ class MacenkoExtractor: """ def __init__( - self: Self, + self: MacenkoExtractor, luminosity_threshold: float = 0.8, angular_percentile: float = 99, ) -> None: @@ -183,7 +173,7 @@ def __init__( self.__luminosity_threshold = luminosity_threshold self.__angular_percentile = angular_percentile - def get_stain_matrix(self: Self, img: np.ndarray) -> np.ndarray: + def get_stain_matrix(self: MacenkoExtractor, img: np.ndarray) -> np.ndarray: """Stain matrix estimation. Args: @@ -264,7 +254,7 @@ class VahadaneExtractor: """ def __init__( - self: Self, + self: VahadaneExtractor, luminosity_threshold: float = 0.8, regularizer: float = 0.1, ) -> None: @@ -272,7 +262,7 @@ def __init__( self.__luminosity_threshold = luminosity_threshold self.__regularizer = regularizer - def get_stain_matrix(self: Self, img: np.ndarray) -> np.ndarray: + def get_stain_matrix(self: VahadaneExtractor, img: np.ndarray) -> np.ndarray: """Stain matrix estimation. Args: diff --git a/tiatoolbox/typing.py b/tiatoolbox/typing.py index c70dbf3e1..ea0299e12 100644 --- a/tiatoolbox/typing.py +++ b/tiatoolbox/typing.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing import Callable, Dict, List, Literal, Sequence, SupportsFloat, Tuple, Union +from collections.abc import Sequence +from typing import Callable, Literal, SupportsFloat, Union import numpy as np from shapely.geometry import LineString, Point, Polygon # type: ignore[import-untyped] @@ -10,15 +11,15 @@ # Proper type annotations for shapely is not yet available. -JSON = Union[Dict[str, "JSON"], List["JSON"], str, int, float, bool, None] -NumPair = Tuple[SupportsFloat, SupportsFloat] -IntPair = Tuple[int, int] +JSON = Union[dict[str, "JSON"], list["JSON"], str, int, float, bool, None] +NumPair = tuple[SupportsFloat, SupportsFloat] +IntPair = tuple[int, int] # WSIReader Resolution = Union[SupportsFloat, NumPair, np.ndarray, Sequence[SupportsFloat]] Units = Literal["mpp", "power", "baseline", "level"] -Bounds = Tuple[SupportsFloat, SupportsFloat, SupportsFloat, SupportsFloat] -IntBounds = Tuple[int, int, int, int] +Bounds = tuple[SupportsFloat, SupportsFloat, SupportsFloat, SupportsFloat] +IntBounds = tuple[int, int, int, int] # Annotation Store Geometry = Union[Point, LineString, Polygon] diff --git a/tiatoolbox/wsicore/wsimeta.py b/tiatoolbox/wsicore/wsimeta.py index ac9200295..4a7ad0d9b 100644 --- a/tiatoolbox/wsicore/wsimeta.py +++ b/tiatoolbox/wsicore/wsimeta.py @@ -11,13 +11,15 @@ from numbers import Number from pathlib import Path -from typing import TYPE_CHECKING, Mapping, Sequence +from typing import TYPE_CHECKING import numpy as np from tiatoolbox import logger if TYPE_CHECKING: # pragma: no cover + from collections.abc import Mapping, Sequence + from tiatoolbox.typing import Resolution, Units diff --git a/tiatoolbox/wsicore/wsireader.py b/tiatoolbox/wsicore/wsireader.py index 7e3307189..dd38a53b1 100644 --- a/tiatoolbox/wsicore/wsireader.py +++ b/tiatoolbox/wsicore/wsireader.py @@ -11,7 +11,7 @@ from datetime import datetime from numbers import Number from pathlib import Path -from typing import TYPE_CHECKING, Iterable +from typing import TYPE_CHECKING import numpy as np import openslide @@ -31,6 +31,8 @@ from tiatoolbox.wsicore.wsimeta import WSIMeta if TYPE_CHECKING: # pragma: no cover + from collections.abc import Iterable + import glymur from tiatoolbox.typing import Bounds, IntBounds, IntPair, NumPair, Resolution, Units @@ -404,10 +406,9 @@ def info(self: WSIReader) -> WSIMeta: Returns: WSIMeta: - An object containing normalized slide metadata + An object containing normalized slide metadata. """ - # In Python>=3.8 this could be replaced with functools.cached_property if self._m_info is not None: return copy.deepcopy(self._m_info) self._m_info = self._info() From a3045987fde65a9dc2929415af79ddcf33033d97 Mon Sep 17 00:00:00 2001 From: Shan E Ahmed Raza <13048456+shaneahmed@users.noreply.github.com> Date: Tue, 20 Feb 2024 10:39:40 +0000 Subject: [PATCH 5/6] =?UTF-8?q?=F0=9F=93=8C=20Update=20`wsidicom`=20depend?= =?UTF-8?q?ency=20(#785)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove restriction on `wsidicom` version --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 11e61999d..f9a13c809 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -32,5 +32,5 @@ torch>=2.1.0 torchvision>=0.15.0 tqdm>=4.64.1 umap-learn>=0.5.3 -wsidicom>=0.7.0, <0.18.0 # newly released version is causing tests to fail for now +wsidicom>=0.18.0 zarr>=2.13.3 From 027ffdd1497c69075a6d1f8b631044f531490045 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 10:40:45 +0000 Subject: [PATCH 6/6] [pre-commit.ci] pre-commit autoupdate (#788) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.2.1 → v0.2.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.2.1...v0.2.2) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * :bug: Fix NPY002 Replace legacy `np.random.sample` call with `np.random.Generator` * :pushpin: Pin dependency * :bug: Fix deepsource errors --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Shan E Ahmed Raza <13048456+shaneahmed@users.noreply.github.com> --- .github/workflows/python-package.yml | 2 +- .pre-commit-config.yaml | 2 +- requirements/requirements_dev.txt | 2 +- tests/test_graph.py | 4 ++-- tiatoolbox/utils/misc.py | 2 +- tiatoolbox/utils/transforms.py | 2 +- tiatoolbox/utils/visualization.py | 14 +++++++------- tiatoolbox/wsicore/wsireader.py | 10 +++++----- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 9df1550c6..919e58b44 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -30,7 +30,7 @@ jobs: sudo apt update sudo apt-get install -y libopenslide-dev openslide-tools libopenjp2-7 libopenjp2-tools python -m pip install --upgrade pip - python -m pip install ruff==0.2.1 pytest pytest-cov pytest-runner + python -m pip install ruff==0.2.2 pytest pytest-cov pytest-runner pip install -r requirements/requirements.txt - name: Cache tiatoolbox static assets uses: actions/cache@v3 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ba7ff479f..60fb72afe 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -68,7 +68,7 @@ repos: language: python - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.2.1 + rev: v0.2.2 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] diff --git a/requirements/requirements_dev.txt b/requirements/requirements_dev.txt index 7c58e0703..697d05d2a 100644 --- a/requirements/requirements_dev.txt +++ b/requirements/requirements_dev.txt @@ -12,7 +12,7 @@ pytest>=7.2.0 pytest-cov>=4.0.0 pytest-runner>=6.0 pytest-xdist[psutil] -ruff==0.2.1 # This will be updated by pre-commit bot to latest version +ruff==0.2.2 # This will be updated by pre-commit bot to latest version toml>=0.10.2 twine>=4.0.1 wheel>=0.37.1 diff --git a/tests/test_graph.py b/tests/test_graph.py index a423064a6..99c7bdbe8 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -87,7 +87,7 @@ def test_affinity_to_edge_index_fuzz_output_shape() -> None: for _ in range(1000): # Generate some random square inputs input_shape = [rng.integers(2, 10)] * 2 - affinity_matrix = np.random.sample(input_shape) + affinity_matrix = rng.random(input_shape) threshold = rng.random() # Convert to torch randomly if rng.random() > 0.5: @@ -108,7 +108,7 @@ def test_affinity_to_edge_index_invalid_fuzz_input_shape() -> None: for _ in range(100): input_shape = [rng.integers(2, 10)] * 2 input_shape[1] -= 1 - affinity_matrix = np.random.sample(input_shape) + affinity_matrix = rng.random(input_shape) threshold = rng.random() # Convert to torch randomly if rng.random() > 0.5: diff --git a/tiatoolbox/utils/misc.py b/tiatoolbox/utils/misc.py index 5164c7917..4d3d4b66b 100644 --- a/tiatoolbox/utils/misc.py +++ b/tiatoolbox/utils/misc.py @@ -983,7 +983,7 @@ def select_cv2_interpolation(scale_factor: float | npt.NDArray[np.float64]) -> s interpolation type """ - if np.any(scale_factor > 1.0): # noqa: PLR2004 + if np.any(scale_factor > 1.0): return "cubic" return "area" diff --git a/tiatoolbox/utils/transforms.py b/tiatoolbox/utils/transforms.py index 36c43ec21..05396c798 100644 --- a/tiatoolbox/utils/transforms.py +++ b/tiatoolbox/utils/transforms.py @@ -141,7 +141,7 @@ def imresize( scale_factor_array = img.shape[:2][::-1] / np.array(output_size_array) # Return original if scale factor is 1 - if np.all(scale_factor_array == 1.0): # noqa: PLR2004 + if np.all(scale_factor_array == 1.0): return img # Get appropriate cv2 interpolation enum diff --git a/tiatoolbox/utils/visualization.py b/tiatoolbox/utils/visualization.py index e75b7376c..ba26fe47f 100644 --- a/tiatoolbox/utils/visualization.py +++ b/tiatoolbox/utils/visualization.py @@ -119,7 +119,7 @@ def overlay_prediction_mask( msg, ) if np.issubdtype(img.dtype, np.floating): - if not (img.max() <= 1.0 and img.min() >= 0): # noqa: PLR2004 + if not (img.max() <= 1.0 and img.min() >= 0): msg = "Not support float `img` outside [0, 1]." raise ValueError(msg) img = np.array(img * 255, dtype=np.uint8) @@ -157,7 +157,7 @@ def overlay_prediction_mask( cv2.addWeighted(rgb_prediction, alpha, overlay, 1 - alpha, 0, overlay) overlay = overlay.astype(np.uint8) - if min_val > 0.0: # noqa: PLR2004 + if min_val > 0.0: overlay[~prediction_sel] = img[~prediction_sel] if ax is None and not return_ax: @@ -310,7 +310,7 @@ def overlay_probability_map( overlay[overlay > 255.0] = 255.0 # noqa: PLR2004 overlay = overlay.astype(np.uint8) - if min_val > 0.0: # noqa: PLR2004 + if min_val > 0.0: overlay[~prediction_sel] = img[~prediction_sel] if ax is None and not return_ax: @@ -374,7 +374,7 @@ def _validate_overlay_probability_map( msg, ) - if prediction.max() > 1.0: # noqa: PLR2004 + if prediction.max() > 1.0: msg = "Not support float `prediction` outside [0, 1]." raise ValueError(msg) if prediction.min() < 0: @@ -382,15 +382,15 @@ def _validate_overlay_probability_map( raise ValueError(msg) # if `min_val` is defined, only display the overlay for areas with prob > min_val - if min_val < 0.0: # noqa: PLR2004 + if min_val < 0.0: msg = f"`min_val={min_val}` is not between [0, 1]." raise ValueError(msg) - if min_val > 1.0: # noqa: PLR2004 + if min_val > 1.0: msg = f"`min_val={min_val}` is not between [0, 1]." raise ValueError(msg) if np.issubdtype(img.dtype, np.floating): - if img.max() > 1.0: # noqa: PLR2004 + if img.max() > 1.0: msg = "Not support float `img` outside [0, 1]." raise ValueError(msg) if img.min() < 0: diff --git a/tiatoolbox/wsicore/wsireader.py b/tiatoolbox/wsicore/wsireader.py index dd38a53b1..f7e4cacf5 100644 --- a/tiatoolbox/wsicore/wsireader.py +++ b/tiatoolbox/wsicore/wsireader.py @@ -99,8 +99,8 @@ def is_zarr(path: Path) -> bool: _ = zarr.open(str(path), mode="r") except Exception: # skipcq: PYL-W0703 # noqa: BLE001 return False - else: - return True + + return True def is_ngff( # noqa: PLR0911 @@ -1578,7 +1578,7 @@ def save_tiles( # Rescale to the correct objective value if rescale != 1: - im = utils.transforms.imresize(img=im, scale_factor=(1 / rescale)) + im = utils.transforms.imresize(img=im, scale_factor=1 / rescale) img_save_name = ( "_".join( @@ -5520,7 +5520,7 @@ def read_rect( utils.transforms.background_composite(base_region, alpha=True), ) im_region = Image.fromarray(im_region) - if self.alpha < 1.0: # noqa: PLR2004 + if self.alpha < 1.0: im_region.putalpha( im_region.getchannel("A").point(lambda i: i * self.alpha), ) @@ -5713,7 +5713,7 @@ class docstrings for more information. utils.transforms.background_composite(base_region, alpha=True), ) im_region = Image.fromarray(im_region) - if self.alpha < 1.0: # noqa: PLR2004 + if self.alpha < 1.0: im_region.putalpha( im_region.getchannel("A").point(lambda i: i * self.alpha), )