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 056a1d1c4..a8d093d1b 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:
@@ -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/.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 18a6d5360..5e6d74074 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
@@ -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.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 bc0650353..60fb72afe 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.+
@@ -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.2
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
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_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..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 # noqa: E402\n",
- "from tiatoolbox.annotation.storage import ( # noqa: E402\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/74IwsXLuT5558HIDExkZdffhmtVsur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",
+ "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/vrrPP3005Um/UF5+3pVk/2CXaiV+Ur2rblwIUi3282xY8eIjo72Waz1TZq5hF8cPHjQ08ZeMXz27NmzWK3WKsfoCxEMKvatqWpU4ueff+75u1arpVGjRvTu3duzMV6wk2QSIN544w0efvjhyx4ToSMnJ4eIiAhPbWz37t1s3bqVRo0aMXLkyBoNNw02DaHMf/7zn5k2bVq158vKylAUpUZzkILJpReTET5TsXpuBZfLxdGjR/0UjfCFWbNmefazOH78OLNmzSIxMZHjx48zd+5cP0dXP0KtzJs3b/b82bRpE5988km11548eZInn3ySKVOm8Pjjj/PUU09x8uRJH0Zbv4L/a0CQW7x4MYsXL8ZutzN+/HigvL9Ep9ORkZHh5+hEfbLb7cTHxwOwdu1ahg4dyk033YTb7a5275NgF2plvpJ9a959913uvvtuunTpAsCePXt49913+cc//uGTWOubJBM/Gz16NKNHj+bTTz8N+tnf4spc2MK8Z88efv/73wNcdvXZYBZqZb7c9sQXstlsnkQC0LlzZ2w2W32E5ReSTAJEu3btPMMmAUpLS9mzZ0/IzjUR0KVLF2bOnElcXBxms9nzi6agoCAk+g6qEipl/jX71iQlJfHFF18waNAgANatWxd082ouRTrgA0RVk/WefPJJXn75ZT9FJOqbqqps2LCBgoIC+vXr52n+OXbsGEVFRXTv3t2/AdaDUCnzxXu2gPe+NR999FGl82azmf/85z+eicmdOnXi1ltvDerlgy4UPF8FQlxVOf3CndtEaKpYPudCrVu39vy9Yr5RKAmFMv+afWsiIyOZMGGCr0L0OUkmAaJNmzZ8+OGHjBgxAkVRWLZsmcy3CHF/+9vf6N27N1dffTWJiYme406n07NceZcuXarcSCxYhVKZa7pvTXFxMcuXLyciIoJhw4bx0UcfsX//fpKTk7n77rtp3LixH6Kve9LMFSCsViv/93//x65du1BVlW7dujFmzJiQG4su/sdut7Nq1Sp+/PFHsrOzCQ8Px+Fw4Ha76dq1KyNHjqRVq1b+DrNOhUqZL9y3ZuTIkZf8//Qf//gHbdq0wWq1smvXLoYMGUJ6ejr79u3jxx9/5Pnnn/dd4PVIkokQAcDpdFJSUoLBYCAiIsLf4fhEMJf5tttuQ6fTodVqL7sMUkV/qKqqPPjgg7z11luVzoUCaebysw8++IB77rmH6dOnV9lOXNVS1iL06HQ6zx7qDUUwl/lK9q2pGPasKEqltbiCdUh0VSSZ+FnFMMHf/va3fo5ECFEfzp8/z0svvYSqqp6/Q3ktJjs728/R1R1p5gogxcXFACG1kqgQDV11e55UqM0y9oFEkomfqarK559/zvLly1FVFVVV0Wg0XHfddVVOfBJCiEAkycTPlixZwk8//cQDDzxAUlISUF4tnjt3Lt26dePGG2/0c4RCCHF5odP7E6TWrl3Lo48+6kkkAMnJyTz88MOsXbvWj5EJIUTNSTLxM5fLVWUfSXR0tMyAF0IEDRnN5WeXWtwumBa+E0Jc2tmzZ/n666/Jzc31+qL417/+1Y9R1R35beVnx48f9+xjciFVVXE4HH6ISAhRH2bNmsU111xDRkZGSM0vqSDJxM+uZPKTECJ4aTQarr32Wn+HUW9kNJcQQtQjs9kMwNKlS4mJiaFXr17o9XrP+VBZgl6SiRBC1KOHHnoIRVGq3GZCURRmz57th6jqniQTIYTwAbvdjsFguOyxYBV6vUBCCBGAnnvuuRodC1bSAS+EEPWosLCQ/Px87HY7x44d8zR3lZWVYbPZ/Bxd3ZFkIoQQ9WjHjh2sWbOGvLw8FixY4DluMpn4/e9/78fI6pb0mQghhA9s2rSJPn36+DuMeiPJRAgh6tHatWsZNGgQ33zzTZUb4IXKYq7SzCWEEPWool/EarX6OZL6JTUTIYSoR1u2bKFDhw7ExMT4O5R6JclECCHq0YwZMzh48CBGo5EOHTp4/jRv3tzfodUpSSZCCOED2dnZHDx4kAMHDnDw4EFyc3Np164dzzzzjL9DqxPSZyKEED6QlJSEw+HAbrdjt9s9fw8VUjMRQoh6tGjRIg4ePEhJSQlNmjShffv2XHXVVbRs2TKklqKXmokQQtSjtWvXYjKZSEtLo0OHDlx11VWEh4f7O6w6JzUTIYSoZ2azmQMHDnDgwAEOHTqE1WqlZcuWdOjQgaFDh/o7vDohyUQIIXzE5XJx9OhR9u3bx/fff092dnbIbJAnyUQIIerRtm3bPLWSU6dO0bx5c9q3b+8ZIhwdHe3vEOuE9JkIIUQ9Wr16Ne3bt+fOO++kTZs26HSh+WtXaiZCCFGPVFWtck2uK70m0IXOuDQhhAhAf/vb31i2bBm5ublex51OJ7t372b27NmsWbPGT9HVHamZCCFEPbLb7axatYoff/yR7OxswsPDcTgcuN1uunbtysiRI2nVqpW/w6w1SSZCCOEjTqeTkpISDAYDERER/g6nTkkyEUIIUWvSZyKEEKLWJJkIIYSoNUkmQgghai00Z88IcQX279/Pxx9/zKlTp9BoNDRr1ozx48fTrl07Vq9ezcqVK3nhhRfqPY5FixaxePFiANxuN06nE4PBAECjRo2YOXNmvccgxK8lyUQ0aBaLhenTp/OHP/yBfv364XQ62bdvH3q9vk7u73K50Gq1Nbp2zJgxjBkzBsCnSUyIuiDJRDRoWVlZAAwYMAAAg8FAt27dADh9+jTvvfceTqeTu+66C61WywcffIDFYmH+/Pn89NNPGI1Ghg8fzujRo9FoNJ4k0LZtW9asWcOIESO45ZZbWLhwIRs3bsTpdHL11Vdzzz33eGodl/P1119z8OBB/vSnP3mOzZ8/H41Gwz333MPzzz9P+/bt2bVrF2fPnqVz5848+OCDREZGAnDw4EEWLFjA6dOnadSoEffccw+dO3euy8cohPSZiIatSZMmaDQaZs+ezU8//YTZbPaca9asGffddx/t27fno48+4oMPPgDKf5FbLBZmz57N888/z9q1a1m9erXnfYcOHSI5OZm5c+cyZswYPvnkE7KysnjllVd4/fXXyc/P54svvqhxjAMHDuTnn3+mtLQUKK/tbNiwgUGDBnmuWbNmDZMmTeKdd95Bo9Ewf/58APLz85k+fTpjxoxh/vz53HXXXcyYMYPi4uJaPDUhKpNkIhq08PBw/v73v6MoCu+88w5/+MMfeOmllygsLKzyerfbzYYNG7j99tsJCwsjKSmJG2+8kbVr13quiYuL47rrrkOr1aLX61m5ciXjx48nMjKSsLAwxowZw/r162scY1xcHJ06dWLjxo0A7Nixg6ioKNq0aeO5ZtCgQbRo0QKTycTvfvc7Nm7ciNvtZu3atfTo0YO0tDQ0Gg1du3albdu2ZGZm/roHJkQ1pJlLNHjNmjXjoYceAuDMmTO88cYbfPDBB0yePLnStcXFxTidThITEz3HGjVqRH5+vuf1heeKi4ux2Ww8/fTTnmOqquJ2u68oxsGDB/Pdd9+RkZHBunXrvGolAAkJCV6f73K5KC4uJjc3l02bNrF9+3bPeZfLJc1cos5JMhHiAikpKQwZMoTvv/++yvPR0dFotVpyc3Np1qwZALm5ucTHx1d5fVRUFAaDgZkzZ1Z7TU1cffXVzJ07l5MnT7J9+3buvPNOr/N5eXmev+fm5qLVaomOjiYhIYGBAwcyceLEX/3ZQtSENHOJBu3MmTN88803nl/Gubm5rF+/nquuugqA2NhY8vPzcTqdAGg0Gvr27cvChQspKysjJyeHJUuWMHDgwCrvr9FoGD58OB988AFFRUVAeT/Gjh07rihOg8FA7969ef3112nXrp1X7Qdg3bp1nD59GpvNxn/+8x/69OmDRqNh4MCBbN++nR07duB2u7Hb7ezZs8cr+QhRF6RmIhq0sLAwDh06xJIlS7BYLISHh9OzZ0/PN/8uXbp4OuI1Gg3z5s1jwoQJzJ8/nz/+8Y8YDAaGDx9+yX2877jjDr744gv+/Oc/U1JSQnx8PNdccw3du3e/oliHDBnCDz/8wKRJkyqdGzRoEG+++SZnz56lU6dOPPjgg0B5k9eTTz7Jxx9/zL/+9S80Gg3t2rXjvvvuu6LPFuJyZKFHIYJEbm4ukydP5t133yU8PNxz/Pnnn2fgwIEMHz7cj9GJhk6auYQIAm63myVLltCvXz+vRCJEoJBkIkSAs1qtjB8/np07dzJu3Dh/hyNElaSZSwghRK1JzUQIIUStSTIRQghRa5JMhBBC1JokEyGEELUmyUQIIUStSTIRQghRa/8fyqAhH2akvfwAAAAASUVORK5CYII=",
+ "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+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/74IwsXLuT5558HIDExkZdffhmtVsur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",
- "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/vrrPP3005Um/UF5+3pVk/2CXaiV+Ur2rblwIUi3282xY8eIjo72Waz1TZq5hF8cPHjQ08ZeMXz27NmzWK3WKsfoCxEMKvatqWpU4ueff+75u1arpVGjRvTu3duzMV6wk2QSIN544w0efvjhyx4ToSMnJ4eIiAhPbWz37t1s3bqVRo0aMXLkyBoNNw02DaHMf/7zn5k2bVq158vKylAUpUZzkILJpReTET5TsXpuBZfLxdGjR/0UjfCFWbNmefazOH78OLNmzSIxMZHjx48zd+5cP0dXP0KtzJs3b/b82bRpE5988km11548eZInn3ySKVOm8Pjjj/PUU09x8uRJH0Zbv4L/a0CQW7x4MYsXL8ZutzN+/HigvL9Ep9ORkZHh5+hEfbLb7cTHxwOwdu1ahg4dyk033YTb7a5275NgF2plvpJ9a959913uvvtuunTpAsCePXt49913+cc//uGTWOubJBM/Gz16NKNHj+bTTz8N+tnf4spc2MK8Z88efv/73wNcdvXZYBZqZb7c9sQXstlsnkQC0LlzZ2w2W32E5ReSTAJEu3btPMMmAUpLS9mzZ0/IzjUR0KVLF2bOnElcXBxms9nzi6agoCAk+g6qEipl/jX71iQlJfHFF18waNAgANatWxd082ouRTrgA0RVk/WefPJJXn75ZT9FJOqbqqps2LCBgoIC+vXr52n+OXbsGEVFRXTv3t2/AdaDUCnzxXu2gPe+NR999FGl82azmf/85z+eicmdOnXi1ltvDerlgy4UPF8FQlxVOf3CndtEaKpYPudCrVu39vy9Yr5RKAmFMv+afWsiIyOZMGGCr0L0OUkmAaJNmzZ8+OGHjBgxAkVRWLZsmcy3CHF/+9vf6N27N1dffTWJiYme406n07NceZcuXarcSCxYhVKZa7pvTXFxMcuXLyciIoJhw4bx0UcfsX//fpKTk7n77rtp3LixH6Kve9LMFSCsViv/93//x65du1BVlW7dujFmzJiQG4su/sdut7Nq1Sp+/PFHsrOzCQ8Px+Fw4Ha76dq1KyNHjqRVq1b+DrNOhUqZL9y3ZuTIkZf8//Qf//gHbdq0wWq1smvXLoYMGUJ6ejr79u3jxx9/5Pnnn/dd4PVIkokQAcDpdFJSUoLBYCAiIsLf4fhEMJf5tttuQ6fTodVqL7sMUkV/qKqqPPjgg7z11luVzoUCaebysw8++IB77rmH6dOnV9lOXNVS1iL06HQ6zx7qDUUwl/lK9q2pGPasKEqltbiCdUh0VSSZ+FnFMMHf/va3fo5ECFEfzp8/z0svvYSqqp6/Q3ktJjs728/R1R1p5gogxcXFACG1kqgQDV11e55UqM0y9oFEkomfqarK559/zvLly1FVFVVV0Wg0XHfddVVOfBJCiEAkycTPlixZwk8//cQDDzxAUlISUF4tnjt3Lt26dePGG2/0c4RCCHF5odP7E6TWrl3Lo48+6kkkAMnJyTz88MOsXbvWj5EJIUTNSTLxM5fLVWUfSXR0tMyAF0IEDRnN5WeXWtwumBa+E0Jc2tmzZ/n666/Jzc31+qL417/+1Y9R1R35beVnx48f9+xjciFVVXE4HH6ISAhRH2bNmsU111xDRkZGSM0vqSDJxM+uZPKTECJ4aTQarr32Wn+HUW9kNJcQQtQjs9kMwNKlS4mJiaFXr17o9XrP+VBZgl6SiRBC1KOHHnoIRVGq3GZCURRmz57th6jqniQTIYTwAbvdjsFguOyxYBV6vUBCCBGAnnvuuRodC1bSAS+EEPWosLCQ/Px87HY7x44d8zR3lZWVYbPZ/Bxd3ZFkIoQQ9WjHjh2sWbOGvLw8FixY4DluMpn4/e9/78fI6pb0mQghhA9s2rSJPn36+DuMeiPJRAgh6tHatWsZNGgQ33zzTZUb4IXKYq7SzCWEEPWool/EarX6OZL6JTUTIYSoR1u2bKFDhw7ExMT4O5R6JclECCHq0YwZMzh48CBGo5EOHTp4/jRv3tzfodUpSSZCCOED2dnZHDx4kAMHDnDw4EFyc3Np164dzzzzjL9DqxPSZyKEED6QlJSEw+HAbrdjt9s9fw8VUjMRQoh6tGjRIg4ePEhJSQlNmjShffv2XHXVVbRs2TKklqKXmokQQtSjtWvXYjKZSEtLo0OHDlx11VWEh4f7O6w6JzUTIYSoZ2azmQMHDnDgwAEOHTqE1WqlZcuWdOjQgaFDh/o7vDohyUQIIXzE5XJx9OhR9u3bx/fff092dnbIbJAnyUQIIerRtm3bPLWSU6dO0bx5c9q3b+8ZIhwdHe3vEOuE9JkIIUQ9Wr16Ne3bt+fOO++kTZs26HSh+WtXaiZCCFGPVFWtck2uK70m0IXOuDQhhAhAf/vb31i2bBm5ublex51OJ7t372b27NmsWbPGT9HVHamZCCFEPbLb7axatYoff/yR7OxswsPDcTgcuN1uunbtysiRI2nVqpW/w6w1SSZCCOEjTqeTkpISDAYDERER/g6nTkkyEUIIUWvSZyKEEKLWJJkIIYSoNUkmQgghai00Z88IcQX279/Pxx9/zKlTp9BoNDRr1ozx48fTrl07Vq9ezcqVK3nhhRfqPY5FixaxePFiANxuN06nE4PBAECjRo2YOXNmvccgxK8lyUQ0aBaLhenTp/OHP/yBfv364XQ62bdvH3q9vk7u73K50Gq1Nbp2zJgxjBkzBsCnSUyIuiDJRDRoWVlZAAwYMAAAg8FAt27dADh9+jTvvfceTqeTu+66C61WywcffIDFYmH+/Pn89NNPGI1Ghg8fzujRo9FoNJ4k0LZtW9asWcOIESO45ZZbWLhwIRs3bsTpdHL11Vdzzz33eGodl/P1119z8OBB/vSnP3mOzZ8/H41Gwz333MPzzz9P+/bt2bVrF2fPnqVz5848+OCDREZGAnDw4EEWLFjA6dOnadSoEffccw+dO3euy8cohPSZiIatSZMmaDQaZs+ezU8//YTZbPaca9asGffddx/t27fno48+4oMPPgDKf5FbLBZmz57N888/z9q1a1m9erXnfYcOHSI5OZm5c+cyZswYPvnkE7KysnjllVd4/fXXyc/P54svvqhxjAMHDuTnn3+mtLQUKK/tbNiwgUGDBnmuWbNmDZMmTeKdd95Bo9Ewf/58APLz85k+fTpjxoxh/vz53HXXXcyYMYPi4uJaPDUhKpNkIhq08PBw/v73v6MoCu+88w5/+MMfeOmllygsLKzyerfbzYYNG7j99tsJCwsjKSmJG2+8kbVr13quiYuL47rrrkOr1aLX61m5ciXjx48nMjKSsLAwxowZw/r162scY1xcHJ06dWLjxo0A7Nixg6ioKNq0aeO5ZtCgQbRo0QKTycTvfvc7Nm7ciNvtZu3atfTo0YO0tDQ0Gg1du3albdu2ZGZm/roHJkQ1pJlLNHjNmjXjoYceAuDMmTO88cYbfPDBB0yePLnStcXFxTidThITEz3HGjVqRH5+vuf1heeKi4ux2Ww8/fTTnmOqquJ2u68oxsGDB/Pdd9+RkZHBunXrvGolAAkJCV6f73K5KC4uJjc3l02bNrF9+3bPeZfLJc1cos5JMhHiAikpKQwZMoTvv/++yvPR0dFotVpyc3Np1qwZALm5ucTHx1d5fVRUFAaDgZkzZ1Z7TU1cffXVzJ07l5MnT7J9+3buvPNOr/N5eXmev+fm5qLVaomOjiYhIYGBAwcyceLEX/3ZQtSENHOJBu3MmTN88803nl/Gubm5rF+/nquuugqA2NhY8vPzcTqdAGg0Gvr27cvChQspKysjJyeHJUuWMHDgwCrvr9FoGD58OB988AFFRUVAeT/Gjh07rihOg8FA7969ef3112nXrp1X7Qdg3bp1nD59GpvNxn/+8x/69OmDRqNh4MCBbN++nR07duB2u7Hb7ezZs8cr+QhRF6RmIhq0sLAwDh06xJIlS7BYLISHh9OzZ0/PN/8uXbp4OuI1Gg3z5s1jwoQJzJ8/nz/+8Y8YDAaGDx9+yX2877jjDr744gv+/Oc/U1JSQnx8PNdccw3du3e/oliHDBnCDz/8wKRJkyqdGzRoEG+++SZnz56lU6dOPPjgg0B5k9eTTz7Jxx9/zL/+9S80Gg3t2rXjvvvuu6LPFuJyZKFHIYJEbm4ukydP5t133yU8PNxz/Pnnn2fgwIEMHz7cj9GJhk6auYQIAm63myVLltCvXz+vRCJEoJBkIkSAs1qtjB8/np07dzJu3Dh/hyNElaSZSwghRK1JzUQIIUStSTIRQghRa5JMhBBC1JokEyGEELUmyUQIIUStSTIRQghRa/8fyqAhH2akvfwAAAAASUVORK5CYII=",
- "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 41b85043f..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("../")
@@ -139,18 +139,19 @@ 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,
)
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 de6f2b60f..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"
]
@@ -397,7 +400,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..0662f9e65 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,29 +149,29 @@ 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"
+# Minimum Python version 3.9.
+target-version = "py39"
-[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]
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.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
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/requirements/requirements_dev.txt b/requirements/requirements_dev.txt
index 6911165c5..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.1.13 # 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/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 e09753556..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 = [
"+",
"-",
@@ -101,7 +104,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 +146,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 +162,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 +178,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 +194,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 +210,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 +225,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 +240,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 +255,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 +270,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 +285,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 +300,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 +315,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 +330,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 +345,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 +360,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 +375,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 +390,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 +405,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 +420,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 +435,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 +450,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 +465,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 +481,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 +495,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 +510,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 +525,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 +540,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 +555,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 +570,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 +585,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_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/tests/test_wsireader.py b/tests/test_wsireader.py
index 390751b5d..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
@@ -204,7 +206,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 +2648,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/__init__.py b/tiatoolbox/__init__.py
index aa8866c5c..5a42b9bed 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
@@ -94,9 +89,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 9863e08d3..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
@@ -2028,7 +2025,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/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/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..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
@@ -129,7 +131,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 +159,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 +225,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 +333,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 +345,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 +362,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 +370,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 +380,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 +458,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 +543,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 +593,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 +652,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/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/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/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/utils/misc.py b/tiatoolbox/utils/misc.py
index 9d0c2de97..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"
@@ -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/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 3e7c9da46..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:
@@ -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
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
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..f7e4cacf5 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
@@ -97,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
@@ -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()
@@ -1577,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(
@@ -5519,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),
)
@@ -5712,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),
)