[
  {
    "path": ".cargo/config.toml",
    "content": "[target.x86_64-unknown-linux-gnu]\nrustflags = \"-C target-cpu=x86-64-v3\"\n\n[target.x86_64-pc-windows-gnu]\nrustflags = \"-C target-cpu=x86-64-v3\"\n\n# Apple Silicone fix\n[target.aarch64-apple-darwin]\nrustflags = [\n    \"-C\", \"link-arg=-undefined\",\n    \"-C\", \"link-arg=dynamic_lookup\",\n]\n"
  },
  {
    "path": ".dockerignore",
    "content": "dist\n.idea\ntarget\n**/Dockerfile\n*/Dockerfile\ndata\n__pycache__\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Similari\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.head_ref }}\n  cancel-in-progress: true\n\non:\n  push:\n    branches:\n      - main\n    tags:\n      - '*'\n  pull_request:\n  workflow_dispatch:\n\npermissions:\n  contents: read\n  packages: read\n  pages: write\n  id-token: write\n\njobs:\n\n  linux:\n    strategy:\n      matrix:\n        include:\n          - docker_file: docker/Dockerfile.manylinux_2_28_ARM64\n            name: manylinux-arm\n            arch: linux/arm64\n            runner: ARM64\n          - docker_file: docker/Dockerfile.manylinux_2_28_X64\n            name: manylinux-x86\n            arch: linux/amd64\n            runner: X64\n    runs-on:\n      - ${{ matrix.runner }}\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v2\n\n      - name: Log in to GitHub Container Registry\n        uses: docker/login-action@v2\n        with:\n          registry: ghcr.io\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Build docker image\n        uses: docker/build-push-action@v5\n        with:\n          file: ${{ matrix.docker_file }}\n          platforms: ${{ matrix.arch }}\n          tags: similari\n          push: false\n          load: true\n          context: .\n\n      - name: Copy wheels\n        run: docker run --rm -v $(pwd)/distfiles:/tmp similari cp -R /opt/dist /tmp\n\n      - name: Upload wheels\n        uses: actions/upload-artifact@v4\n        with:\n          name: wheels-${{ github.job }}-${{ matrix.runner }}\n          path: distfiles/dist\n\n      - uses: actions/setup-python@v5\n        with:\n          python-version: '3.10'\n          \n      - name: Install pip\n        run: |\n          python -m ensurepip\n          python -m pip install --upgrade pip\n  \n      - name: Publish to PyPI\n        if: startsWith(github.ref, 'refs/tags/')\n        uses: PyO3/maturin-action@v1\n        env:\n          MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}\n        with:\n          command: upload\n          args: --skip-existing distfiles/dist/*\n\n  windows:\n    runs-on: windows-latest\n    strategy:\n      matrix:\n        target: [x64]\n        python: [\"3.10\", \"3.11\", \"3.12\", \"3.13\"]\n    steps:\n      - uses: actions/checkout@v3\n      - uses: actions/setup-python@v4\n        with:\n          python-version: ${{ matrix.python }}\n          architecture: ${{ matrix.target }}\n      - name: Install pip\n        run: |\n          python -m ensurepip\n          python -m pip install --upgrade pip\n      - name: Build wheels\n        uses: PyO3/maturin-action@v1\n        with:\n          working-directory: .\n          target: ${{ matrix.target }}\n          args: --release --out dist --find-interpreter\n          sccache: 'true'\n      - name: Upload wheels\n        uses: actions/upload-artifact@v4\n        with:\n          name: wheels-${{ github.job }}-${{ matrix.python }}\n          path: dist\n      - name: Publish to PyPI\n        if: startsWith(github.ref, 'refs/tags/')\n        uses: PyO3/maturin-action@v1\n        env:\n          MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}\n        with:\n          command: upload\n          args: --skip-existing dist/*\n\n  macos:\n    runs-on: macos-latest\n    strategy:\n      matrix:\n        target: [x86_64, aarch64]\n        python: [\"3.10\", \"3.11\", \"3.12\", \"3.13\"]\n    steps:\n      - uses: actions/checkout@v3\n      - uses: actions/setup-python@v4\n        with:\n          python-version: ${{ matrix.python }}\n      - name: Install pip\n        run: |\n          python -m ensurepip\n          python -m pip install --upgrade pip\n      - name: Build wheels\n        uses: PyO3/maturin-action@v1\n        with:\n          target: ${{ matrix.target }}\n          args: --release --out dist --find-interpreter\n          sccache: 'true'\n      - name: Upload wheels\n        uses: actions/upload-artifact@v4\n        with:\n          name: wheels-${{ github.job }}-${{ matrix.target }}-${{ matrix.python }}\n          path: dist\n      - name: Publish to PyPI\n        if: startsWith(github.ref, 'refs/tags/')\n        uses: PyO3/maturin-action@v1\n        env:\n          MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}\n        with:\n          command: upload\n          args: --skip-existing dist/*\n\n  sdist:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: actions/setup-python@v5\n        with:\n          python-version: '3.10'\n      - name: Install pip\n        run: |\n          python -m ensurepip\n          python -m pip install --upgrade pip\n      - name: Build sdist\n        uses: PyO3/maturin-action@v1\n        with:\n          command: sdist\n          args: --out dist\n\n      - name: Upload sdist\n        uses: actions/upload-artifact@v4\n        with:\n          name: wheels-${{ github.job }}\n          path: dist\n\n      - name: Publish to PyPI\n        if: startsWith(github.ref, 'refs/tags/')\n        uses: PyO3/maturin-action@v1\n        env:\n          MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}\n        with:\n          command: upload\n          args: --skip-existing dist/*\n"
  },
  {
    "path": ".gitignore",
    "content": "/target\n/Cargo.lock\n.idea\ndist\ndata\n__pycache__\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"similari-trackers-rs\"\nauthors = [\"Ivan Kudriavtsev <ivan.a.kudryavtsev@gmail.com>\"]\ndescription = \"Machine learning framework for building object trackers and similarity search engines\"\nhomepage = \"https://github.com/insight-platform/Similari\"\nrepository = \"https://github.com/insight-platform/Similari\"\nreadme = \"README.md\"\nkeywords = [\"machine-learning\", \"similarity\", \"tracking\", \"SORT\", \"DeepSORT\"]\ncategories = [\"algorithms\", \"data-structures\", \"computer-vision\", \"science\"]\nversion = \"0.26.12\"\nedition = \"2021\"\nlicense = \"Apache-2.0\"\nrust-version = \"1.66\"\n\n[lib]\ncrate-type = [\"cdylib\", \"lib\"]\nname = \"similari\"\n\n[features]\ndefault = [\"python\"]\npython = [\"dep:pyo3\", \"dep:pyo3-build-config\", \"dep:pyo3-log\"]\n\n[dependencies]\nitertools = \"0.12\"\nanyhow = \"1.0\"\nthiserror = \"1.0\"\nonce_cell = \"1.19\"\nnum_cpus = \"1.16\"\nultraviolet = \"0.9\"\ncrossbeam = \"0.8\"\nrand = \"0.8\"\nlog = \"0.4\"\nnalgebra = \"0.32\"\npathfinding = \"4.8\"\ngeo = \"0.27\"\nrayon = \"1.8\"\nenv_logger = \"0.10\"\n\n[dependencies.pyo3]\nversion = \"0.23.4\"\nfeatures = [\"extension-module\"]\noptional = true\n\n[dependencies.pyo3-log]\nversion = \"0.12.1\"\noptional = true\n\n[build-dependencies]\npyo3-build-config = { version = \"0.23.4\", optional = true }\n\n[dev-dependencies]\nwide = \"0.7\"\n\n[profile.dev]\nopt-level = 3\n\n[profile.release]\nopt-level = 3\ncodegen-units = 1\n\n[profile.bench]\nopt-level = 3\ncodegen-units = 1\n\n[package.metadata.maturin]\npython-source = \"python\"\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [2022] [Ivan A. Kudriavtsev <ivan.a.kudryavtsev@gmail.com>]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# Similari\n\nNewer versions (renamed):\n\n[![Rust](https://img.shields.io/crates/v/similari-trackers-rs.svg)](https://crates.io/crates/similari-trackers-rs)\n[![Rust](https://img.shields.io/crates/d/similari-trackers-rs.svg)](https://crates.io/crates/similari-trackers-rs)\n[![Rust](https://img.shields.io/github/license/insight-platform/Similari.svg)](https://img.shields.io/github/license/insight-platform/Similari.svg)\n\n[![PyPI version](https://badge.fury.io/py/similari-trackers-rs.svg)](https://pypi.org/project/similari-trackers-rs/)\n\nOlder versions:\n\n[![Rust](https://img.shields.io/crates/v/similari.svg)](https://crates.io/crates/similari)\n[![Rust](https://img.shields.io/crates/d/similari.svg)](https://crates.io/crates/similari)\n[![Rust](https://img.shields.io/github/license/insight-platform/Similari.svg)](https://img.shields.io/github/license/insight-platform/Similari.svg)\n\n:star: Star us on GitHub — it motivates us a lot!\n\nSimilari is a Rust framework with Python bindings that helps build sophisticated tracking systems. With Similari one \ncan develop highly efficient parallelized [SORT](https://github.com/abewley/sort), [DeepSORT](https://github.com/nwojke/deep_sort), and \nother sophisticated single observer (e.g. Cam) or multi-observer tracking engines.\n\n## Introduction\n\nThe primary purpose of Similari is to provide means to build sophisticated in-memory multiple object tracking engines.\n\nThe framework helps build various kinds of tracking and similarity search engines - the simplest one that holds \nvector features and allows comparing new vectors against the ones kept in the database. More sophisticated engines \noperate over tracks - a series of observations for the same feature collected during the lifecycle. Such systems are \noften used in video processing or other systems where the observer receives fuzzy or changing observation results.\n\n## Out-of-The-Box Stuff\n\nSimilari is a framework to build custom trackers, however it provides certain algorithms as an end-user functionality:\n\n**Bounding Box Kalman filter**, that predicts rectangular bounding boxes axis-aligned to scene, supports the oriented (rotated) \nbounding boxes as well.\n\n**2D Point Kalman filter**, that predicts 2D point motion.\n\n**2D Point Vector Kalman filter**, that predicts the vector of independent 2D points motion (used in the Keypoint Tracker).\n\n**Bounding box clipping**, that allows calculating the area of intersection for axis-aligned and oriented (rotated) \nbounding boxes.\n\n**Non-Maximum Suppression (NMS)** - filters rectangular bounding boxes co-axial to scene, and supports the oriented \n  bounding boxes.\n\n**SORT tracking** algorithm (axis-aligned and oriented boxes are supported) - IoU and Mahalanobis distances are \n  supported.\n\n**Batch SORT tracking** algorithm (axis-aligned and oriented boxes are supported) - IoU and Mahalanobis distances are \nsupported. Batch tracker allows passing multiple scenes to tracker in a single batch and get them back. If the platform\nsupports batching (like Nvidia DeepStream or Intel DL Streamer) the batch tracker is more beneficial to use.\n\n**VisualSORT tracking** - a DeepSORT-like algorithm (axis-aligned and oriented boxes are supported) - IoU and \nMahalanobis distances are supported for positional tracking, euclidean, cosine distances are used for visual tracking on \nfeature vectors.\n\n**Batch VisualSORT** - batched VisualSORT flavor;\n\n\n## Applicability Notes\n\nAlthough Similari allows building various tracking and similarity engines, there are competitive tools that sometimes \nmay fit better. The section will explain where it is applicable and what alternatives exist.\n\nSimilari fits best for the tracking tasks where objects are described by multiple observations for a certain feature \nclass, not a single feature vector. Also, their behavior is dynamic - you remove them from the index or modify them as \noften as add new ones. This is a very important point - it is less efficient than tools that work with growing or static \nobject spaces.\n\n**Fit**: track the person across the room: person ReID, age/gender, and face features are collected multiple times during \nthe tracking and used to merge tracks or provide aggregated results at the end of the track;\n\n**Not fit**: plagiarism database, when a single document is described by a number (or just one) constant ReID vectors, \ndocuments are added but not removed. The task is to find the top X most similar documents to a checked.\n\nIf your task looks like **Not fit**, can use Similari, but you're probably looking for `HNSW` or `NMS` implementations:\n* HNSW Rust - [Link](https://github.com/jean-pierreBoth/hnswlib-rs)\n* HNSW C/Python - [link](https://github.com/nmslib/hnswlib)\n* NMSLib - [link](https://github.com/nmslib/nmslib)\n\nSimilari objects support following features:\n\n**Track lifecycle** - the object is represented by its lifecycle (track) - it appears, evolves, and disappears. During \nits lifetime object evolves according to its behavioral properties (attributes, and feature observations).\n\n**Observations** - Similari assumes that an object is observed by an observer entity that collects its features \n(uniform vectors) and custom observation attributes (like GPS or screen box position)multiple times. Those \nfeatures are presented by vectors of float numbers and observation attributes. When the observation happened, the \ntrack is updated with gathered features. Future observations are used to find similar tracks in the index and merge them.\n\n**Track Attributes** - Arbitrary attributes describe additional track properties aside from feature observations. \nTrack attributes is crucial part when you are comparing objects in the wild, because there may be attributes \ndisposition when objects are incompatible, like `animal_type` that prohibits you from comparing `dogs` and `cats` \nbetween each other. Another popular use of attributes is a spatial or temporal characteristic of an object, e.g. objects \nthat are situated at distant locations at the same time cannot be compared. Attributes in Similari are dynamic and \nevolve upon every feature observation addition and when objects are merged. They are used in both distance calculations \nand compatibility guessing (which decreases compute space by skipping incompatible objects).\n\nIf you plan to use Similari to search in a large index, consider object attributes to split the lookup space. If the \nattributes of the two tracks are not compatible, their distance calculations are skipped.\n\n## Performance\n\nThe Similari is fast. It is usually faster than trackers built with Python and NumPy.\n\nTo run visual feature calculations performant the framework uses [ultraviolet](https://crates.io/crates/ultraviolet) - \nthe library for fast SIMD computations.\n\nParallel computations are implemented with index sharding and parallel computations based on a dedicated thread workers \npool.\n\nVector operations performance depends a lot on the optimization level defined for the build. On low or default \noptimization levels Rust may not use f32 vectorization, so when running benchmarks take care of proper optimization \nlevels configured.\n\n### Rust optimizations\n\nUse `RUSTFLAGS=\"-C target-cpu=native\"` to enable all cpu features like AVX, AVX2, etc. It is beneficial to ultraviolet.\n\nAlternatively you can add build instructions to `.cargo/config`:\n\n```\n[build]\nrustflags = \"-C target-cpu=native\"\n```\n\nTake a look at [benchmarks](benches) for numbers.\n\n### Performance Benchmarks\n\nSome benchmarks numbers are presented here: [Benchmarks](assets/benchmarks/benchmarks.md)\n\nYou can run your own benchmarks by:\n\n```\nrustup default nightly\ncargo bench\n```\n\n## Apple Silicone Build Notes\n\nYou may need to add following lines into your `~/.cargo/config` to build the code on Apple Silicone:\n\n```\n[build]\nrustflags = \"-C target-cpu=native\"\n\n# Apple Silicone fix\n[target.aarch64-apple-darwin]\nrustflags = [\n    \"-C\", \"link-arg=-undefined\",\n    \"-C\", \"link-arg=dynamic_lookup\",\n]\n```\n\n## Python API\n\nPython interface exposes ready-to-use functions and classes of Similari. As for now, the Python interface provides:\n* the Kalman filter for axis-aligned and oriented (rotated) boxes prediction;\n* the Kalman filter for 2D point motion prediction;\n* the 2D Point Vector Kalman filter, that predicts the vector of independent 2D points motion (used in the Keypoint Tracker);\n* NMS (Non-maximum suppression);\n* the Sutherland-Hodgman clipping, intersection area for oriented (rotated) boxes;\n* SORT with IoU and Mahalanobis metric;\n* BatchSORT with IoU and Mahalanobis metric;\n* VisualSORT - DeepSORT-like tracker with euclidean/cosine metric for visual features and IoU/Mahalanobis metric \n  for positional tracking (VisualSort).\n* BatchVisualSORT - batched VisualSORT flavor;\n\nPython API classes and functions can be explored in the python \n[documentation](assets/documentation/python/api.md) and tiny [examples](python) provided.\n\nThere is also [MOTChallenge](python/motchallenge) evaluation kit provided which you can use to simply evaluate trackers \nperformance and metrics.\n\n### Install Python API from PyPi\n\n*Please, keep in mind that the  PyPi package is built to conform broad range of platforms, so it may not be as fast as the one you build locally for your platform (see the following sections).* \n\nPlatforms:\n\n* Linux: X86_64, ARM64, ARMv7;\n* Windows: X86_64;\n* MacOS: X86_64, ARM64.\n\n```\npip3 install similari-trackers-rs\n```\n\n### Build Python API in Docker\n\nYou can build the wheel in the Docker and if you want to install it in the host system, \ncopy the resulting package to the host system as demonstrated by the following examples.\n\n#### Rust 1.67 Base Image\n\nIf you use other rust libraries you may find it beneficial to build with base Rust \ncontainer (and Python 3.8):  \n\n```\ndocker build -t similari-trackers-rs -f docker/rust_1.67/Dockerfile .\n\n# optional: copy and install to host system\ndocker run --rm -it -v $(pwd)/distfiles:/tmp similari-trackers-rs cp -R /opt/dist /tmp\npip3 install --force-reinstall distfiles/dist/*.whl\n```\n\n#### Python 3.8 Base Image\n\nPython 3.8 is still a very frequently used. Here is how to build Similari with it:\n\n```\ndocker build -t similari-trackers-rs -f docker/python_3.8/Dockerfile .\n\n# optional: copy and install to host system\ndocker run --rm -it -v $(pwd)/distfiles:/tmp similari-trackers-rs cp -R /opt/dist /tmp\npip3 install --force-reinstall distfiles/dist/*.whl\n```\n\n#### Python 3.10 Base Image\n\nIf you use the most recent Python environment, you can build with base Python container:\n\n```\ndocker build -t similari-trackers-rs -f docker/python_3.10/Dockerfile .\n\n# optional: copy and install to host system\ndocker run --rm -it -v $(pwd)/distfiles:/tmp similari-trackers-rs cp -R /opt/dist /tmp\npip3 install --force-reinstall distfiles/dist/*.whl\n```\n\n**NOTE**: If you are getting the `pip3` error like:\n\n```\nERROR: similari-trackers-rs-0.26.4-cp38-cp38-manylinux_2_28_x86_64.whl is not a supported wheel on this platform.\n```\n\nIt means that the Python version in the host system doesn't match to the one that is in the image used\nto build the wheel.\n\n### Build Python API in Host System\n\n#### Linux Instruction\n\n0. Install up-to-date Rust toolkit:\n\n```bash\ncurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\nsource $HOME/.cargo/env\nrustup update\n```\n\n1. Install build-essential tools `apt install build-essential -y`.\n\n2. Install Python3 (>= 3.8) and the development files (`python3-dev`).\n\n3. Install Maturin:\n```\npip3 install --upgrade maturin~=0.15\n```\n\n4. **Not in VENV**. Build the python module: \n\n```\nRUSTFLAGS=\" -C target-cpu=native -C opt-level=3\" maturin build --release --out dist\npip3 install --force-reinstall dist/*.whl\n```\n\n4. **In VENV**. Build the python module:\n\n```\nRUSTFLAGS=\" -C target-cpu=native -C opt-level=3\" maturin develop\n```\n\n5. Usage examples are located at [python](python).\n6. MOT Challenge Docker image for Similari trackers and conventional trackers is [here](python/motchallenge). \n   You can easily build all-in-one Docker image and try ours trackers.\n\n## Manuals and Articles\nCollected articles about how the Similari can be used to solve specific problems.\n\n#### Medium.com\n\n* IoU object tracker [example](https://medium.com/@kudryavtsev_ia/high-performance-object-tracking-engine-with-rust-59ccbc79cdb0);\n* Re-ID object tracker [example](https://medium.com/@kudryavtsev_ia/feature-based-object-tracker-with-similari-and-rust-25d72d01d2e2);\n* SORT object tracker [example](https://medium.com/p/9a1dd18c259c);\n* Python SORT object tracker [example](https://medium.com/@kudryavtsev_ia/high-performance-python-sort-tracker-225c2b507562);\n* Python Rotated SORT object tracker [example](https://medium.com/inside-in-sight/rotated-objects-tracking-with-angle-aware-detection-model-and-sort-tracker-42a96429898d);\n* [Why You Need a High-Performance Tracking Systems For Multiple Object Tracking](https://medium.com/inside-in-sight/why-you-need-a-high-performance-tracking-systems-for-multiple-object-tracking-d1ffd56ca8b7?source=friends_link&sk=7685979b41a112709eab8386c8e56e08).\n\n## Usage Examples\n\nTake a look at samples in the repo:\n* [simple.rs](examples/simple.rs) - an idea of simple usage.\n* [track_merging.rs](examples/track_merging.rs) - an idea of intra-cam track merging.\n* [incremental_track_build.rs](examples/incremental_track_build.rs) - very simple feature-based tracker.\n* [simple_sort_iou_tracker.rs](examples/simple_sort_iou_tracker.rs) - SORT tracker (with Kalman filter, IoU).\n* [simple_sort_iou_tracker_oriented.rs](examples/simple_sort_iou_tracker_oriented.rs) - Oriented (rotated) SORT tracker \n  (with Kalman filter, IoU).\n* [simple_sort_maha_tracker.rs](examples/simple_sort_maha_tracker.rs) - SORT tracker (with Kalman filter, Mahalanobis).\n* [simple_sort_maha_tracker_oriented.rs](examples/simple_sort_maha_tracker_oriented.rs) - Oriented SORT tracker (with Kalman filter, Mahalanobis).\n* [middleware_sort_tracker.rs](examples/middleware_sort_tracker.rs) - SORT tracker (with Kalman filter, middleware implementation).\n"
  },
  {
    "path": "assets/benchmarks/benchmarks.md",
    "content": "# Benchmarks\n\nAll benchmarks numbers received on Run on 4 cores of Intel(R) Core(TM) i5-7440HQ CPU @ 2.80GHz.\n\nVersion: v0.22.5\n\n**Non-Maximum Suppression (non-oriented boxes)**. Benchmark for filtering out of bounding boxes without orientation. \n\n| Objects |  Time (ns/iter) |     FPS |\n|---------|----------------:|--------:|\n| 10      |           1,586 |  632000 |\n| 100     |         148,906 |    6711 |\n| 500     |       4,082,791 |     250 |\n| 1000    |      13,773,713 |      72 |\n\nThe benchmark is located at [/benches/nms.rs](/benches/nms.rs).\n\n**Non-Maximum Suppression (oriented boxes)**. Benchmark for filtering out of bounding boxes with angular orientation. \n\n| Objects |   Time (ns/iter) |     FPS |\n|---------|-----------------:|--------:|\n| 10      |            2,169 |  461000 |\n| 100     |          139,204 |    7100 |\n| 300     |        1,752,410 |     570 |\n| 500     |        4,571,784 |     218 |\n| 1000    |       18,155,136 |      54 |\n\n\nThe benchmark is located at [/benches/nms_oriented.rs](/benches/nms_oriented.rs).\n\n**SORT tracking (IoU)**. Benchmark for N simultaneously observed objects. The benchmark uses the heuristics that \nseparate the observed objects based on object distances.\n\nThe benchmark is located at [/benches/simple_sort_iou_tracker.rs](/benches/simple_sort_iou_tracker.rs).\n\n| Objects |   Time (ns/iter) |     FPS |\n|---------|-----------------:|--------:|\n| 10      |          100,931 |    9900 |\n| 100     |        1,779,434 |     561 |\n| 500     |       18,705,819 |      53 |\n\n\n**Oriented SORT tracking (IoU)**. Benchmark for N simultaneously observed **oriented** objects. The benchmark uses \nthe heuristics that separate the observed objects based on object distances.\n\nThe benchmark is located at [/benches/simple_sort_iou_tracker_oriented.rs](/benches/simple_sort_iou_tracker_oriented.rs).\n\n| Objects |   Time (ns/iter) |  FPS |\n|---------|-----------------:|-----:|\n| 10      |          108,414 | 9170 |\n| 100     |        1,601,062 |  624 |\n| 500     |       18,945,655 |   52 |\n\n**SORT tracking (Mahalanobis)**. Benchmark for N simultaneously observed objects. The benchmark uses heuristics \nthat separate the observed objects based on object distances.\n\nThe benchmark is located at [/benches/simple_sort_maha_tracker.rs](/benches/simple_sort_maha_tracker.rs).\n\n| Objects | Time (ns/iter) |  FPS |\n|---------|---------------:|-----:|\n| 10      |        105,311 | 9500 |\n| 100     |      1,696,943 |  588 |\n| 500     |     18,233,557 |   54 |\n\n**Oriented SORT tracking (Mahalanobis)**. Benchmark for N simultaneously observed **oriented** objects. The benchmark \nuses the heuristics that separate the observed objects based on object distances.\n\nThe benchmark is located at [/benches/simple_sort_maha_tracker_oriented.rs](/benches/simple_sort_maha_tracker_oriented.rs).\n\n| Objects |  Time (ns/iter) |  FPS |\n|---------|----------------:|-----:|\n| 10      |         111,778 | 8900 |\n| 100     |       1,567,771 |  636 |\n| 500     |      17,762,559 |   56 |\n\n**Visual (256 @ f32, hist=3) tracking**. Benchmark for N simultaneously observed objects. The benchmark doesn't use \nheuristics that separate the observed objects based on object distances. The 3 last observations are used to select \nwinning track.\n\nThe benchmark located at [/benches/feature_tracker.rs](/benches/feature_tracker.rs).\n\n| Objects |  Time (ns/iter) |   FPS |\n|---------|----------------:|------:|\n| 10      |         101,465 |  9900 |\n| 100     |       4,020,673 |   250 |\n| 500     |      61,716,729 |    16 |\n\n**Visual SORT (aka DeepSORT) tracking**. Benchmark for N simultaneously observed objects with feature vectors. The benchmark uses heuristics \nthat separate the observed objects based on object distances. Every track holds 3 feature vectors for comparison with candidats.\n\nThe benchmark is located at [/benches/simple_visual_sort_tracker.rs](/benches/simple_visual_sort_tracker.rs).\n\n| Objects |  Vector Len |  Time (ns/iter) |   FPS |\n|---------|------------:|----------------:|------:|\n| 10      |         128 |         356,237 |  2800 |\n| 10      |         256 |         404,416 |  2460 |\n| 10      |         512 |         447,903 |  2230 |\n| 10      |        1024 |         573,197 |  1740 |\n| 10      |        2048 |         767,031 |  1300 |\n| 50      |         128 |       1,923,861 |   519 |\n| 50      |         256 |       2,105,886 |   474 |\n| 50      |         512 |       2,249,694 |   444 |\n| 50      |        1024 |       2,958,547 |   337 |\n| 50      |        2048 |       4,563,691 |   218 |\n| 100     |         128 |       3,807,716 |   262 |\n| 100     |         256 |       4,717,401 |   211 |\n| 100     |         512 |       5,775,469 |   173 |\n| 100     |        1024 |       7,497,783 |   133 |\n| 100     |        2048 |      10,527,237 |    94 |\n\n**BatchSORT tracking (IoU)**. Benchmark for N simultaneously observed objects. The benchmark uses the heuristics that \nseparate the observed objects based on object distances.\n\nThe benchmark is located at [/benches/batch_sort_iou_tracker.rs](/benches/batch_sort_iou_tracker.rs).\n\n| Objects |   Time (ns/iter) |  FPS |\n|---------|-----------------:|-----:|\n| 10      |          106,876 | 9300 |\n| 100     |        1,616,542 |  618 |\n| 500     |       20,454,230 |   48 |\n\n**BatchSORT tracking (Mahalanobis)**. Benchmark for N simultaneously observed objects. The benchmark uses heuristics \nthat separate the observed objects based on object distances.\n\nThe benchmark is located at [/benches/batch_sort_maha_tracker.rs](/benches/batch_sort_maha_tracker.rs).\n\n| Objects | Time (ns/iter) |  FPS |\n|---------|---------------:|-----:|\n| 10      |        114,592 | 8695 |\n| 100     |      1,533,445 |  649 |\n| 500     |     18,270,742 |   54 |\n\n"
  },
  {
    "path": "assets/documentation/python/api.md",
    "content": "# Python API\n\nPython API is generated with PyO3 & Maturin.\n\n## Functions\n[nms](https://docs.rs/similari/0.22.6/similari/utils/nms/nms_py/fn.nms_py.html) - non-maximum suppression implementation for oriented or axis-aligned bounding boxes.\n\n```python\nbbox1 = (BoundingBox(10.0, 11.0, 3.0, 3.8).as_xyaah(), 1.0)\nbbox2 = (BoundingBox(10.3, 11.1, 2.9, 3.9).as_xyaah(), 0.9)\n\nres = nms([bbox2, bbox1], nms_threshold = 0.7, score_threshold = 0.0)\nprint(res[0].as_ltwh())\n```\n\n[sutherland_hodgman_clip](https://docs.rs/similari/0.22.6/similari/utils/clipping/clipping_py/fn.sutherland_hodgman_clip_py.html) - calculates the resulting polygon for two oriented or axis-aligned bounding boxes.\n\n```python\nbbox1 = BoundingBox(0.0, 0.0, 5.0, 10.0).as_xyaah()\nbbox2 = BoundingBox(0.0, 0.0, 10.0, 5.0).as_xyaah()\n\nclip = sutherland_hodgman_clip(bbox1, bbox2)\nprint(clip)\n```\n\n[intersection_area](https://docs.rs/similari/0.22.6/similari/utils/clipping/clipping_py/fn.intersection_area_py.html) - calculates the area of intersection for two oriented or axis-aligned bounding boxes.\n\n```python\nbbox1 = BoundingBox(0.0, 0.0, 5.0, 10.0).as_xyaah()\nbbox2 = BoundingBox(0.0, 0.0, 5.0, 10.0).as_xyaah()\nbbox2.rotate(0.5)\n\nclip = sutherland_hodgman_clip(bbox1, bbox2)\nprint(clip)\n\narea = intersection_area(bbox1, bbox2)\nprint(\"Intersection area:\", area)\n```\n\n## Classes\n\n### Areas\n\n[Universal2DBox](https://docs.rs/similari/0.22.6/similari/utils/bbox/struct.Universal2DBox.html) - universal 2D bounding \nbox format that represents oriented and axis-aligned bounding boxes.\n\n```python\nubb = Universal2DBox(xc=3.0, yc=4.0, angle=0.0, aspect=1.5, height=5.0)\nprint(ubb)\n\nubb = Universal2DBox(3.0, 4.0, 0.0, 1.5, 5.0)\nprint(ubb)\n\nubb.rotate(0.5)\nubb.gen_vertices()\nprint(ubb)\n\npolygon = ubb.get_vertices()\nprint(polygon.get_points())\n\nprint(ubb.area())\nprint(ubb.get_radius())\n\nubb = Universal2DBox.new_with_confidence(xc=3.0, yc=4.0, angle=0.0, aspect=1.5, height=5.0, confidence=0.85)\nprint(ubb)\n```\n\n[BoundingBox](https://docs.rs/similari/0.22.6/similari/utils/bbox/struct.BoundingBox.html) - convenience class that must \nbe transformed to Universal2DBox by calling `as_xyaah()` before passing to any methods.\n\n```python\nbb = BoundingBox(left=1.0, top=2.0, width=10.0, height=15.0)\nprint(bb)\n\nbb = BoundingBox(1.0, 2.0, 10.0, 15.0)\nprint(bb.left, bb.top, bb.width, bb.height)\n\nuniversal_bb = bb.as_xyaah()\nprint(universal_bb)\n\nbb = BoundingBox.new_with_confidence(1.0, 2.0, 10.0, 15.0, 0.95)\nprint(bb)\n```\n\n[Polygon](https://docs.rs/similari/0.22.6/similari/utils/clipping/clipping_py/struct.PyPolygon.html) - return type \nfor [sutherland_hodgman_clip](https://docs.rs/similari/0.22.6/similari/utils/clipping/clipping_py/fn.sutherland_hodgman_clip_py.html). \nIt cannot be created manually, but returned from the function:\n\n```python\nbbox1 = BoundingBox(0.0, 0.0, 5.0, 10.0).as_xyaah()\nbbox2 = BoundingBox(0.0, 0.0, 5.0, 10.0).as_xyaah()\nbbox2.rotate(0.5)\n\nclip = sutherland_hodgman_clip(bbox1, bbox2)\nprint(clip)\n```\n\n### Kalman Filter\n\n[KalmanFilterState](https://docs.rs/similari/0.21.2/similari/utils/kalman/kalman_py/struct.PyKalmanFilterState.html) - predicted or updated \nstate of the oriented bounding box for Kalman filter.\n\n[KalmanFilter](https://docs.rs/similari/0.21.2/similari/utils/kalman/kalman_py/struct.PyKalmanFilter.html) - Kalman filter implementation.\n\n```python\nf = KalmanFilter()\nstate = f.initiate(BoundingBox(0.0, 0.0, 5.0, 10.0).as_xyaah())\nstate = f.predict(state)\nbox_ltwh = state.bbox()\nprint(box_ltwh)\n# if work with oriented box\n# import Universal2DBox and use it\n#\n#box_xyaah = state.universal_bbox()\n#print(box_xyaah)\n\nstate = f.update(state, BoundingBox(0.2, 0.2, 5.1, 9.9).as_xyaah())\nstate = f.predict(state)\nbox_ltwh = state.bbox()\nprint(box_ltwh)\n```\n\n### SORT Tracker\n\n[PositionalMetricType](https://docs.rs/similari/0.22.6/similari/trackers/sort/struct.PyPositionalMetricType.html) - enum type that \nallows setting the positional metric used by a tracker. Two positional metrics are supported:\n* IoU(threshold) - intersection over union with threshold that defines when the area is too low to merge the track candidate \n  with the track, and it is required to form a new track;\n* Mahalanobis - Mahalanobis distance is used to compute the distance between track candidates and\n  tracks kept in the store.\n\n```python\nmetric = PositionalMetricType.iou(threshold=0.3)\nmetric = PositionalMetricType.maha()\n```\n\n#### Produced Tracks\n\n[SortTrack](https://docs.rs/similari/0.22.6/similari/trackers/sort/struct.SortTrack.html) - the calling of the tracker's\n`predict` causes the track candidates to be merged with the current tracks or form new tracks. The resulting information \nis returned for each track in the form of the structure `SortTrack`. Fields are accessible by their names.\n\n```python\n...\ncustom_object_id = 13 # None is also a valid value\ntracks = sort.predict([(box, custom_object_id)])\nfor t in tracks:\n    print(t)\n```\n\nOutput:\n\n```\nSortTrack {\n    id: 2862991017811132132,\n    epoch: 1,\n    predicted_bbox: Universal2DBox {\n        xc: 13.5,\n        yc: 8.5,\n        angle: None,\n        aspect: 1.0,\n        height: 7.0,\n        confidence: 1.0,\n        _vertex_cache: None,\n    },\n    observed_bbox: Universal2DBox {\n        xc: 13.5,\n        yc: 8.5,\n        angle: None,\n        aspect: 1.0,\n        height: 7.0,\n        confidence: 1.0,\n        _vertex_cache: None,\n    },\n    scene_id: 0,\n    length: 1,\n    voting_type: Positional,\n    custom_object_id: None,\n}\n```\n\n[WastedSortTrack](https://docs.rs/similari/0.22.6/similari/trackers/sort/struct.PyWastedSortTrack.html) - the trackers \nreturn the structure for the track when it is wasted from the track store. Fields are accessible by their names. Despite\nthe `SortTrack` the `WastedSortTrack` includes historical data for predictions and observations.\n\n```python\nsort.skip_epochs(10)\nwasted = sort.wasted()\nprint(wasted[0])\n```\n\n```\nPyWastedSortTrack {\n    id: 3466318811797522494,\n    epoch: 1,\n    predicted_bbox: Universal2DBox {\n        xc: 13.5,\n        yc: 8.5,\n        angle: None,\n        aspect: 1.0,\n        height: 7.0,\n        confidence: 1.0,\n        _vertex_cache: None,\n    },\n    observed_bbox: Universal2DBox {\n        xc: 13.5,\n        yc: 8.5,\n        angle: None,\n        aspect: 1.0,\n        height: 7.0,\n        confidence: 1.0,\n        _vertex_cache: None,\n    },\n    scene_id: 0,\n    length: 1,\n    predicted_boxes: [\n        Universal2DBox {\n            xc: 13.5,\n            yc: 8.5,\n            angle: None,\n            aspect: 1.0,\n            height: 7.0,\n            confidence: 1.0,\n            _vertex_cache: None,\n        },\n    ],\n    observed_boxes: [\n        Universal2DBox {\n            xc: 13.5,\n            yc: 8.5,\n            angle: None,\n            aspect: 1.0,\n            height: 7.0,\n            confidence: 1.0,\n            _vertex_cache: None,\n        },\n    ],\n}\n```\n\n#### Tracker Usage\n\n[SORT](https://docs.rs/similari/0.22.6/similari/trackers/sort/simple_api/struct.Sort.html) - basic tracker that uses \nonly positional information for tracking. The SORT tracker is widely used in the environments with rare or no occlusions \nhappen. SORT is a high-performance low-resource tracker. Despite the original SORT, Similari SORT supports\nboth axis-aligned and oriented (rotated) bounding boxes.\n\nThe Similari SORT is able to achieve the following speeds:\n\n| Objects | Time (ms/prediction) |   FPS |  CPU Cores |\n|---------|---------------------:|------:|-----------:|\n| 10      |                0.149 |  6711 |          1 |\n| 100     |                1.660 |   602 |          1 |        \n| 200     |                4.895 |   204 |          2 |       \n| 300     |                8.991 |   110 |          4 |      \n| 500     |               17.432 |    57 |          4 |     \n| 1000    |               53.098 |    18 |          5 |    \n\nComparing to a standard Python SORT from the original [repository](https://github.com/abewley/sort), \nthe Similari SORT tracker works several times faster:\n\n| Objects |   Time (ms/prediction) |  FPS |  CPU Cores (NumPy) |  Similari Gain |\n|---------|-----------------------:|-----:|-------------------:|---------------:|\n| 10      |                  1.588 |  620 |                ALL |          x10.8 |\n| 100     |                 11.976 |   83 |                ALL |          x7.25 |\n| 200     |                 25.160 |   39 |                ALL |          x5.23 |\n| 300     |                 40.922 |   24 |                ALL |          x4.58 |\n| 500     |                 74.254 |   13 |                ALL |          x4.38 |\n| 1000    |                162.037 |    6 |                ALL |             x3 |\n\nThe examples of the tracker usage are located at:\n* [SORT IOU](/python/sort/sort_iou.py) - IoU SORT tracker;\n* [SORT_IOU_BENCH](/python/sort/sort_iou_bench.py) - IoU SORT tracker benchmark;\n* [SORT_MAHA](/python/sort/sort_maha.py) - Mahalanobis SORT tracker;\n* [SORT_IOU_ROTATED](/python/sort/sort_iou_rotated.py) - IoU SORT with rotated boxes.\n\nAlso, with Similari SORT you can use custom `scene_id` that allows combining several trackers in \none without the need to create a separate tracker for every object class. There are methods that support\n`scene_id` passing:\n\n* [SORT_IOU_SCENE_ID](/python/sort/sort_iou_scene_id.py) - IoU tracker with several scenes.\n\nTo increase the performance of the SORT in scenes with large number of objects one can use \n[SpatioTemporalConstraints](https://docs.rs/similari/0.22.6/similari/trackers/spatio_temporal_constraints/struct.SpatioTemporalConstraints.html).\n\nWhen certain tracks are not updated on the current prediction epoch and the `max_idle_epochs` is greater than `0` the\nidle tracks can be accessible as well:\n\n* [SORT_IDLE](/python/sort/sort_idle.py) - working with idle tracks;\n* The Medium [article](https://medium.com/@kudryavtsev_ia/high-performance-python-sort-tracker-225c2b507562)  where the tracker that \n  uses idle tracks is demonstrated.\n\n##### Methods\n\n```python\nSort(shards, bbox_history, max_idle_epochs, method, min_confidence, spatio_temporal_constraints)\n```\n\nParameters:\n* `shards` - the parallelism of the tracker, the more data holds, the more shards show be created,\n  1-2 shards is enough to manage up to 100 tracks, try it to get the lowest processing time;\n* `bbox_history` - the number of predictions the tracks hold in the history; the history is only \n  accessible via `wasted()` method, if you are receiving and analyze the tracking info on every \n  prediction step, you can set it to `1`;\n* `max_idle_epochs` - how long the track stays active in the tracker without updates;\n* `method` - `PositionalMetricType` instance that defines the method used for distance calculation (IoU or Mahalanobis);\n* `min_confidence` - minimal bounding box confidence allowed, when a bounding box has lowe confidence it is set to the parameter;\n* `spatio_temporal_constraints` - additional limitations that define how far the observation may be from the prediction for the \n  next and following epochs; the parameter helps decrease the processing time, but if you don't care, just set to `None`.\n\n```python\ndef skip_epochs(n: int)\n```\n\nDoes fast-forward on `n` epochs for the scene `0`.\n\n```python\ndef skip_epochs_for_scene(scene_id: int, n: int)\n```\n\nDoes fast-forward on `n` epochs for the scene `scene_id`.\n\n```python\ndef shard_stats() -> List[int]\n```\n\nShows how many elements is kept in every shard. Imbalance may \nlead to uneven cores load, but it can happen in the real world.  \n\n```python\ndef current_epoch() -> int\n```\n\nReturns the current epoch for scene `0`.\n\n```python\ndef current_epoch_with_scene(scene_id: int) -> int\n```\n\nReturns the current epoch for scene `scene_id`.\n\n```python\ndef predict(bboxes: List[(Universal2DBox, Optional[int] = None)]) -> List[SortTrack]\n```\n\nPredicts the tracks for specified bounding boxes. The second tuple parameter is the custom object id. \nIt is present in returned tracks and helps to find what track was chosen to the box without the need to\nlook for boxes in tracks by their coordinates.\n\n```python\ndef predict_with_scene(scene_id: int, bboxes: List[(Universal2DBox, Optional[int] = None)]) -> List[SortTrack]\n```\n\nThe same predict, but the scene id is set to `scene_id`. If there are some object classes, they can be tracked separately\nwith use of `scene_id`. The same apply to the case when the tracker tracks objects on multiple camers. `Scene_id` helps \nefficiently manage multiple classes, cameras, etc. within the one tracker.\n\n```python\ndef wasted() -> List[WastedSortTrack]\n```\n\nReturns the tracks that are expired.\n\n```python\ndef clear_wasted()\n```\n\nClears the expired tracks. Works faster than `wasted()` because doesn't require\ncapturing the information from the database.\n\n```python\ndef idle_tracks() -> List[SortTrack]\n```\n\nReturns the tracks that are active but wasn't updated during the last \nprediction. Scene is `0`.\n\n\n```python\ndef idle_tracks_with_scene(scene_id: int) -> List[SortTrack]\n```\n\nReturns the tracks that are active but wasn't updated during the last \nprediction. Scene is `scene_id`.\n\n### Batch SORT Tracker\n\nBatch SORT tracker is the same tracker as SORT tracker but allows passing to prediction\nthe batch with several scenes and receive the results back. In case, when the ML pipeline\nsupports the batching the Batch SORT tracker must be used, because it can efficeintly \nhandle results with less time required, when there are several independent scenes in the batch.\n\nAPI is almost the same, except the `predict(...)`. See examples at:\n\n* [BATCH_SORT_IOU_TRACKER](/python/sort/batch_sort_iou.py) - IoU flavor;\n* [BATCH_SORT_IOU_BENCHMARK](/python/sort/batch_sort_iou_bench.py) - IoU performance benchmark.\n\n### Visual SORT Tracker\n\nVisual SORT tracker is DeepSORT flavour with improvements. It uses custom user ReID model and \npositional tracking based on IoU or Mahalanobis distance.\n\n#### Tracker Configuration\n\n* [VisualSortOptions](https://docs.rs/similari/0.22.6/similari/trackers/visual/simple_api/options/struct.VisualSortOptions.html)\n* [VisualMetricType](https://docs.rs/similari/0.22.6/similari/trackers/visual/metric/struct.PyVisualMetricType.html)\n\n#### Tracker Usage\n\n* [VisualObservation](https://docs.rs/similari/0.22.6/similari/trackers/visual/simple_api/simple_visual_py/struct.PyVisualObservation.html)\n* [VisualObservationSet](https://docs.rs/similari/0.22.6/similari/trackers/visual/simple_api/simple_visual_py/struct.PyVisualObservationSet.html)\n* [VisualSort](https://docs.rs/similari/0.22.6/similari/trackers/visual/simple_api/struct.VisualSort.html)\n\n\n"
  },
  {
    "path": "benches/batch_sort_iou_tracker.rs",
    "content": "#![feature(test)]\n\nextern crate test;\n\nuse similari::examples::BoxGen2;\nuse similari::trackers::batch::PredictionBatchRequest;\nuse similari::trackers::sort::batch_api::BatchSort;\nuse similari::trackers::sort::metric::DEFAULT_MINIMAL_SORT_CONFIDENCE;\nuse similari::trackers::sort::PositionalMetricType::IoU;\nuse similari::trackers::sort::DEFAULT_SORT_IOU_THRESHOLD;\nuse similari::trackers::spatio_temporal_constraints::SpatioTemporalConstraints;\nuse similari::trackers::tracker_api::TrackerAPI;\nuse test::Bencher;\n\n#[bench]\nfn batch_sort_iou_00010(b: &mut Bencher) {\n    bench_batch_sort(10, b);\n}\n\n#[bench]\nfn batch_sort_iou_00100(b: &mut Bencher) {\n    bench_batch_sort(100, b);\n}\n\n#[bench]\nfn batch_sort_iou_00500(b: &mut Bencher) {\n    bench_batch_sort(500, b);\n}\n\nfn bench_batch_sort(objects: usize, b: &mut Bencher) {\n    let pos_drift = 1.0;\n    let box_drift = 0.001;\n    let mut iterators = Vec::default();\n\n    for i in 0..objects {\n        iterators.push(BoxGen2::new(\n            1000.0 * i as f32,\n            1000.0 * i as f32,\n            50.0,\n            50.0,\n            pos_drift,\n            box_drift,\n        ))\n    }\n\n    let mut iteration = 0;\n    let ncores = match objects {\n        10 => 1,\n        100 => 2,\n        _ => num_cpus::get(),\n    };\n\n    let mut tracker = BatchSort::new(\n        ncores,\n        ncores,\n        10,\n        1,\n        IoU(DEFAULT_SORT_IOU_THRESHOLD),\n        DEFAULT_MINIMAL_SORT_CONFIDENCE,\n        Some(SpatioTemporalConstraints::default().constraints(&[(1, 1.0)])),\n        1.0 / 20.0,\n        1.0 / 160.0,\n    );\n\n    let mut count = 0;\n    b.iter(|| {\n        count += 1;\n        let (mut batch, res) = PredictionBatchRequest::new();\n        for i in &mut iterators {\n            iteration += 1;\n            let b = i.next();\n            batch.add(0, (b.unwrap().into(), Some(1)));\n        }\n        tracker.predict(batch);\n        for _ in 0..res.batch_size() {\n            let (_scene, _tracks) = res.get();\n        }\n    });\n\n    eprintln!(\"Store stats: {:?}\", tracker.active_shard_stats());\n    assert_eq!(\n        tracker.active_shard_stats().into_iter().sum::<usize>(),\n        objects\n    );\n\n    let wasted = tracker.wasted();\n    assert!(wasted.is_empty());\n\n    tracker.skip_epochs(2);\n    let wasted = tracker.wasted();\n    assert_eq!(wasted.len(), objects);\n    for w in wasted {\n        assert_eq!(w.get_attributes().track_length, count);\n    }\n    eprintln!(\"Benchmark complete\");\n}\n"
  },
  {
    "path": "benches/batch_sort_maha_tracker.rs",
    "content": "#![feature(test)]\n\nextern crate test;\n\nuse similari::examples::BoxGen2;\nuse similari::prelude::PositionalMetricType::Mahalanobis;\nuse similari::trackers::batch::PredictionBatchRequest;\nuse similari::trackers::sort::batch_api::BatchSort;\nuse similari::trackers::sort::metric::DEFAULT_MINIMAL_SORT_CONFIDENCE;\nuse similari::trackers::spatio_temporal_constraints::SpatioTemporalConstraints;\nuse similari::trackers::tracker_api::TrackerAPI;\nuse test::Bencher;\n\n#[bench]\nfn batch_sort_maha_00010(b: &mut Bencher) {\n    bench_batch_sort(10, b);\n}\n\n#[bench]\nfn batch_sort_maha_00100(b: &mut Bencher) {\n    bench_batch_sort(100, b);\n}\n\n#[bench]\nfn batch_sort_maha_00500(b: &mut Bencher) {\n    bench_batch_sort(500, b);\n}\n\nfn bench_batch_sort(objects: usize, b: &mut Bencher) {\n    let pos_drift = 1.0;\n    let box_drift = 0.001;\n    let mut iterators = Vec::default();\n\n    for i in 0..objects {\n        iterators.push(BoxGen2::new(\n            1000.0 * i as f32,\n            1000.0 * i as f32,\n            50.0,\n            50.0,\n            pos_drift,\n            box_drift,\n        ))\n    }\n\n    let mut iteration = 0;\n    let ncores = match objects {\n        10 => 1,\n        100 => 2,\n        _ => num_cpus::get(),\n    };\n\n    let mut tracker = BatchSort::new(\n        ncores,\n        ncores,\n        10,\n        1,\n        Mahalanobis,\n        DEFAULT_MINIMAL_SORT_CONFIDENCE,\n        Some(SpatioTemporalConstraints::default().constraints(&[(1, 1.0)])),\n        1.0 / 20.0,\n        1.0 / 160.0,\n    );\n\n    let mut count = 0;\n    b.iter(|| {\n        count += 1;\n        let (mut batch, res) = PredictionBatchRequest::new();\n        for i in &mut iterators {\n            iteration += 1;\n            let b = i.next();\n            batch.add(0, (b.unwrap().into(), Some(1)));\n        }\n        tracker.predict(batch);\n        for _ in 0..res.batch_size() {\n            let (_scene, _tracks) = res.get();\n        }\n    });\n\n    eprintln!(\"Store stats: {:?}\", tracker.active_shard_stats());\n    assert_eq!(\n        tracker.active_shard_stats().into_iter().sum::<usize>(),\n        objects\n    );\n\n    let wasted = tracker.wasted();\n    assert!(wasted.is_empty());\n\n    tracker.skip_epochs(2);\n    let wasted = tracker.wasted();\n    assert_eq!(wasted.len(), objects);\n    for w in wasted {\n        assert_eq!(w.get_attributes().track_length, count);\n    }\n}\n"
  },
  {
    "path": "benches/bbox_own_areas.rs",
    "content": "#![feature(test)]\n\nextern crate test;\n\nuse similari::examples::BoxGen2;\nuse similari::utils::clipping::bbox_own_areas::{\n    exclusively_owned_areas, exclusively_owned_areas_normalized_shares,\n};\nuse test::Bencher;\n\n#[bench]\nfn bbox_own_areas_00010(b: &mut Bencher) {\n    bench_bbox_own_areas(10, b);\n}\n\n#[bench]\nfn bbox_own_areas_00025(b: &mut Bencher) {\n    bench_bbox_own_areas(25, b);\n}\n\n#[bench]\nfn bbox_own_areas_00050(b: &mut Bencher) {\n    bench_bbox_own_areas(50, b);\n}\n\n#[bench]\nfn bbox_own_areas_00100(b: &mut Bencher) {\n    bench_bbox_own_areas(100, b);\n}\n\nfn bench_bbox_own_areas(objects: usize, b: &mut Bencher) {\n    let pos_drift = 20.0;\n    let box_drift = 5.0;\n    let mut iterators = Vec::default();\n\n    for i in 0..objects {\n        iterators.push(BoxGen2::new(\n            i as f32, i as f32, 10.0, 10.0, pos_drift, box_drift,\n        ));\n    }\n\n    b.iter(|| {\n        let mut observations = Vec::new();\n        for i in &mut iterators {\n            let b = i.next();\n            observations.push(b.unwrap().into());\n        }\n        let input = observations.iter().collect::<Vec<_>>();\n        let polygons = exclusively_owned_areas(input.as_slice());\n        let areas =\n            exclusively_owned_areas_normalized_shares(input.as_slice(), polygons.as_slice());\n        assert_eq!(areas.len(), objects);\n    });\n}\n"
  },
  {
    "path": "benches/feature_tracker.rs",
    "content": "#![feature(test)]\n\nextern crate test;\n\nuse similari::distance::euclidean;\nuse similari::examples::FeatGen;\nuse similari::prelude::{NoopNotifier, ObservationBuilder, TrackStoreBuilder};\nuse similari::track::{\n    MetricOutput, MetricQuery, NoopLookup, Observation, ObservationMetric, ObservationMetricOk,\n    ObservationsDb, TrackAttributes, TrackAttributesUpdate, TrackStatus,\n};\nuse similari::voting::topn::TopNVoting;\nuse similari::voting::Voting;\nuse std::time::Instant;\nuse test::Bencher;\n\nconst FEAT0: u64 = 0;\n\n#[derive(Debug, Clone, Default)]\nstruct NoopAttributes;\n\n#[derive(Clone, Debug)]\nstruct NoopAttributesUpdate;\n\nimpl TrackAttributesUpdate<NoopAttributes> for NoopAttributesUpdate {\n    fn apply(&self, _attrs: &mut NoopAttributes) -> anyhow::Result<()> {\n        Ok(())\n    }\n}\n\nimpl TrackAttributes<NoopAttributes, ()> for NoopAttributes {\n    type Update = NoopAttributesUpdate;\n    type Lookup = NoopLookup<NoopAttributes, ()>;\n\n    fn compatible(&self, _other: &NoopAttributes) -> bool {\n        true\n    }\n\n    fn merge(&mut self, _other: &NoopAttributes) -> anyhow::Result<()> {\n        Ok(())\n    }\n\n    fn baked(&self, _observations: &ObservationsDb<()>) -> anyhow::Result<TrackStatus> {\n        Ok(TrackStatus::Ready)\n    }\n}\n\n#[derive(Clone, Default)]\npub struct TrackMetric;\n\nimpl ObservationMetric<NoopAttributes, ()> for TrackMetric {\n    fn metric(&self, mq: &MetricQuery<NoopAttributes, ()>) -> MetricOutput<()> {\n        let (e1, e2) = (mq.candidate_observation, mq.track_observation);\n        Some((\n            None,\n            match (e1.feature(), e2.feature()) {\n                (Some(x), Some(y)) => Some(euclidean(x, y)),\n                _ => None,\n            },\n        ))\n    }\n\n    fn optimize(\n        &mut self,\n        _feature_class: u64,\n        _merge_history: &[u64],\n        _attrs: &mut NoopAttributes,\n        observations: &mut Vec<Observation<()>>,\n        _prev_length: usize,\n        _is_merge: bool,\n    ) -> anyhow::Result<()> {\n        observations.reverse();\n        observations.truncate(3);\n        observations.reverse();\n        Ok(())\n    }\n\n    fn postprocess_distances(\n        &self,\n        unfiltered: Vec<ObservationMetricOk<()>>,\n    ) -> Vec<ObservationMetricOk<()>> {\n        unfiltered\n            .into_iter()\n            .filter(|x| {\n                if let Some(d) = x.feature_distance {\n                    d < 100.0\n                } else {\n                    false\n                }\n            })\n            .collect()\n    }\n}\n\nfn benchmark(objects: usize, flen: usize, b: &mut Bencher) {\n    let ncores = match objects {\n        10 => 1,\n        100 => 2,\n        _ => num_cpus::get(),\n    };\n\n    let mut store = TrackStoreBuilder::new(ncores)\n        .metric(TrackMetric::default())\n        .default_attributes(NoopAttributes::default())\n        .notifier(NoopNotifier)\n        .build();\n\n    let voting: TopNVoting<()> = TopNVoting::new(1, 100.0, 1);\n\n    let pos_drift = 0.1;\n    let mut iterators = Vec::default();\n\n    for i in 0..objects {\n        iterators.push(FeatGen::new(1000.0 * i as f32, flen, pos_drift));\n    }\n\n    let mut iteration = 0;\n    b.iter(|| {\n        let mut tracks = Vec::new();\n        let tm = Instant::now();\n        for i in &mut iterators {\n            iteration += 1;\n            let b = i.next().unwrap().feature().clone();\n            let t = store\n                .new_track(iteration)\n                .observation(\n                    ObservationBuilder::new(FEAT0)\n                        .observation(b.unwrap())\n                        .build(),\n                )\n                .build()\n                .unwrap();\n            tracks.push(t);\n        }\n\n        let search_tracks = tracks.clone();\n        let elapsed = tm.elapsed();\n        eprintln!(\"Construction time: {:?}\", elapsed);\n\n        let tm = Instant::now();\n        let (dists, errs) = store.foreign_track_distances(search_tracks, FEAT0, false);\n        let elapsed = tm.elapsed();\n        eprintln!(\"Lookup time: {:?}\", elapsed);\n\n        let tm = Instant::now();\n        let winners = voting.winners(dists);\n        let elapsed = tm.elapsed();\n        eprintln!(\"Voting time: {:?}\", elapsed);\n        assert!(errs.all().is_empty());\n\n        let tm = Instant::now();\n        for t in tracks {\n            let winners_opt = winners.get(&t.get_track_id());\n            if let Some(winners) = winners_opt {\n                let _res = store\n                    .merge_external_noblock(winners[0].winner_track, t, None, false)\n                    .unwrap();\n            } else {\n                store.add_track(t).unwrap();\n            }\n        }\n        let elapsed = tm.elapsed();\n        eprintln!(\"Merging time: {:?}\", elapsed);\n        eprintln!(\"Store stats: {:?}\", store.shard_stats());\n    });\n}\n\n#[bench]\nfn ft_0010_256x3(b: &mut Bencher) {\n    benchmark(10, 256, b);\n}\n\n#[bench]\nfn ft_0100_256x3(b: &mut Bencher) {\n    benchmark(100, 256, b);\n}\n\n#[bench]\nfn ft_0500_256x3(b: &mut Bencher) {\n    benchmark(500, 256, b);\n}\n"
  },
  {
    "path": "benches/kalman_2d_point.rs",
    "content": "#![feature(test)]\n\nextern crate test;\n\nuse nalgebra::Point2;\nuse similari::examples::FeatGen2;\nuse similari::utils::kalman::kalman_2d_point::Point2DKalmanFilter;\nuse test::Bencher;\n\n#[bench]\nfn kalman_2d_point_100k(b: &mut Bencher) {\n    const N: usize = 100_000;\n    let f = Point2DKalmanFilter::default();\n    let mut pt = FeatGen2::new(-10.0, 2.0, 0.2);\n\n    b.iter(|| {\n        let v = pt.next().unwrap().feature().as_ref().unwrap().clone();\n        let n = v[0].as_array_ref();\n\n        let mut state = f.initiate(&Point2::from([n[0], n[1]]));\n        for _i in 0..N {\n            let v = pt.next().unwrap().feature().as_ref().unwrap().clone();\n            let n = v[0].as_array_ref();\n            state = f.predict(&state);\n\n            let p = Point2::from([n[0], n[1]]);\n            state = f.update(&state, &p);\n        }\n    });\n}\n"
  },
  {
    "path": "benches/kalman_bbox.rs",
    "content": "#![feature(test)]\n\nextern crate test;\n\nuse similari::examples::FeatGen2;\nuse similari::utils::bbox::Universal2DBox;\nuse similari::utils::kalman::kalman_2d_box::Universal2DBoxKalmanFilter;\nuse test::Bencher;\n\n#[bench]\nfn kalman_2d_box_100k(b: &mut Bencher) {\n    const N: usize = 100_000;\n    let f = Universal2DBoxKalmanFilter::default();\n    let mut pt = FeatGen2::new(-10.0, 2.0, 0.2);\n\n    b.iter(|| {\n        let v = pt.next().unwrap().feature().as_ref().unwrap().clone();\n        let n = v[0].as_array_ref();\n\n        let bbox = Universal2DBox::new(n[0], n[1], Some(0.0), 2.0, 5.0);\n\n        let mut state = f.initiate(&bbox);\n        for _i in 0..N {\n            let v = pt.next().unwrap().feature().as_ref().unwrap().clone();\n            let n = v[0].as_array_ref();\n            state = f.predict(&state);\n\n            let bb = Universal2DBox::new(n[0], n[1], Some(0.0), 2.0, 5.0);\n            state = f.update(&state, &bb);\n        }\n    });\n}\n"
  },
  {
    "path": "benches/nms.rs",
    "content": "#![feature(test)]\n\nextern crate test;\n\nuse similari::examples::BoxGen2;\nuse similari::utils::nms::nms;\nuse test::Bencher;\n\n#[bench]\nfn nms_00010(b: &mut Bencher) {\n    bench_nms(10, b);\n}\n\n#[bench]\nfn nms_00100(b: &mut Bencher) {\n    bench_nms(100, b);\n}\n\n#[bench]\nfn nms_00300(b: &mut Bencher) {\n    bench_nms(300, b);\n}\n\n#[bench]\nfn nms_00500(b: &mut Bencher) {\n    bench_nms(500, b);\n}\n\n#[bench]\nfn nms_01000(b: &mut Bencher) {\n    bench_nms(1000, b);\n}\n\nfn bench_nms(objects: usize, b: &mut Bencher) {\n    let pos_drift = 10.0;\n    let box_drift = 1.0;\n    let mut iterators = Vec::default();\n\n    for i in 0..objects {\n        iterators.push(BoxGen2::new(\n            i as f32, i as f32, 50.0, 50.0, pos_drift, box_drift,\n        ));\n    }\n\n    b.iter(|| {\n        let mut observations = Vec::new();\n        for i in &mut iterators {\n            let b = i.next();\n            observations.push((b.unwrap().into(), None));\n        }\n        nms(&observations, 0.8, None);\n    });\n}\n"
  },
  {
    "path": "benches/nms_oriented.rs",
    "content": "#![feature(test)]\n\nextern crate test;\n\nuse similari::examples::BoxGen2;\nuse similari::utils::bbox::Universal2DBox;\nuse similari::utils::nms::nms;\nuse test::Bencher;\n\n#[bench]\nfn nms_oriented_00010(b: &mut Bencher) {\n    bench_nms(10, b);\n}\n\n#[bench]\nfn nms_oriented_00100(b: &mut Bencher) {\n    bench_nms(100, b);\n}\n\n#[bench]\nfn nms_oriented_00300(b: &mut Bencher) {\n    bench_nms(300, b);\n}\n\n#[bench]\nfn nms_oriented_00500(b: &mut Bencher) {\n    bench_nms(500, b);\n}\n\n#[bench]\nfn nms_oriented_01000(b: &mut Bencher) {\n    bench_nms(1000, b);\n}\n\nfn bench_nms(objects: usize, b: &mut Bencher) {\n    let pos_drift = 10.0;\n    let box_drift = 1.0;\n    let mut iterators = Vec::default();\n    for i in 0..objects {\n        iterators.push(BoxGen2::new(\n            i as f32, i as f32, 50.0, 50.0, pos_drift, box_drift,\n        ));\n    }\n\n    b.iter(|| {\n        let mut observations = Vec::new();\n        for (indx, i) in iterators.iter_mut().enumerate() {\n            let b = i.next();\n            let bb: Universal2DBox = b.unwrap().into();\n            observations.push((bb.rotate(indx as f32 / 10.0).gen_vertices().clone(), None));\n        }\n        nms(observations.as_slice(), 0.8, None);\n    });\n}\n"
  },
  {
    "path": "benches/simple_search.rs",
    "content": "#![feature(test)]\n\nextern crate test;\n\nuse rand::{distributions::Uniform, Rng};\nuse similari::examples::{UnboundAttributeUpdate, UnboundAttrs, UnboundMetric};\nuse similari::prelude::{ObservationBuilder, TrackStoreBuilder};\nuse similari::track::notify::NoopNotifier;\nuse similari::track::utils::FromVec;\nuse similari::track::Feature;\nuse test::Bencher;\n\n#[bench]\nfn simple_0512_0001k(b: &mut Bencher) {\n    bench_capacity_len(512, 1000, b);\n}\n\n#[bench]\nfn simple_0512_0010k(b: &mut Bencher) {\n    bench_capacity_len(512, 10000, b);\n}\n\n#[bench]\nfn simple_0128_001k(b: &mut Bencher) {\n    bench_capacity_len(128, 1000, b);\n}\n\n#[bench]\nfn simple_0128_010k(b: &mut Bencher) {\n    bench_capacity_len(128, 10000, b);\n}\n\nfn bench_capacity_len(vec_len: usize, count: usize, b: &mut Bencher) {\n    const DEFAULT_FEATURE: u64 = 0;\n    let mut db = TrackStoreBuilder::new(num_cpus::get())\n        .metric(UnboundMetric::default())\n        .default_attributes(UnboundAttrs::default())\n        .notifier(NoopNotifier)\n        .build();\n    let mut rng = rand::thread_rng();\n    let gen = Uniform::new(0.0, 1.0);\n\n    for i in 0..count {\n        let res = db.add(\n            i as u64,\n            DEFAULT_FEATURE,\n            Some(1.0),\n            Some(Feature::from_vec(\n                (0..vec_len).map(|_| rng.sample(&gen)).collect::<Vec<_>>(),\n            )),\n            Some(UnboundAttributeUpdate {}),\n        );\n        assert!(res.is_ok());\n    }\n\n    b.iter(|| {\n        let t = db\n            .new_track(count as u64 + 1)\n            .observation(\n                ObservationBuilder::new(DEFAULT_FEATURE)\n                    .observation_attributes(1.0)\n                    .observation(Feature::from_vec(\n                        (0..vec_len).map(|_| rng.sample(&gen)).collect::<Vec<_>>(),\n                    ))\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        let (dists, errs) = db.foreign_track_distances(vec![t], DEFAULT_FEATURE, true);\n        assert_eq!(dists.all().len(), count);\n        assert!(errs.all().is_empty());\n    });\n}\n"
  },
  {
    "path": "benches/simple_sort_iou_tracker.rs",
    "content": "#![feature(test)]\n\nextern crate test;\n\nuse similari::examples::BoxGen2;\nuse similari::trackers::sort::metric::DEFAULT_MINIMAL_SORT_CONFIDENCE;\nuse similari::trackers::sort::simple_api::Sort;\nuse similari::trackers::sort::PositionalMetricType::IoU;\nuse similari::trackers::sort::DEFAULT_SORT_IOU_THRESHOLD;\nuse similari::trackers::spatio_temporal_constraints::SpatioTemporalConstraints;\nuse similari::trackers::tracker_api::TrackerAPI;\nuse test::Bencher;\n\n#[bench]\nfn sort_iou_00010(b: &mut Bencher) {\n    bench_sort(10, b);\n}\n\n#[bench]\nfn sort_iou_00100(b: &mut Bencher) {\n    bench_sort(100, b);\n}\n\n#[bench]\nfn sort_iou_00500(b: &mut Bencher) {\n    bench_sort(500, b);\n}\n\nfn bench_sort(objects: usize, b: &mut Bencher) {\n    let pos_drift = 1.0;\n    let box_drift = 0.001;\n    let mut iterators = Vec::default();\n\n    for i in 0..objects {\n        iterators.push(BoxGen2::new(\n            1000.0 * i as f32,\n            1000.0 * i as f32,\n            50.0,\n            50.0,\n            pos_drift,\n            box_drift,\n        ))\n    }\n\n    let mut iteration = 0;\n    let ncores = match objects {\n        10 => 1,\n        100 => 2,\n        _ => num_cpus::get(),\n    };\n\n    let mut tracker = Sort::new(\n        ncores,\n        10,\n        1,\n        IoU(DEFAULT_SORT_IOU_THRESHOLD),\n        DEFAULT_MINIMAL_SORT_CONFIDENCE,\n        Some(SpatioTemporalConstraints::default().constraints(&[(1, 1.0)])),\n        1.0 / 20.0,\n        1.0 / 160.0,\n    );\n\n    let mut count = 0;\n    b.iter(|| {\n        count += 1;\n        let mut observations = Vec::new();\n        for i in &mut iterators {\n            iteration += 1;\n            let b = i.next();\n            observations.push((b.unwrap().into(), None));\n        }\n        let tracks = tracker.predict(&observations);\n        assert_eq!(tracks.len(), objects);\n    });\n    eprintln!(\"Store stats: {:?}\", tracker.active_shard_stats());\n    assert_eq!(\n        tracker.active_shard_stats().into_iter().sum::<usize>(),\n        objects\n    );\n\n    let wasted = tracker.wasted();\n    assert!(wasted.is_empty());\n\n    tracker.skip_epochs(2);\n    let wasted = tracker.wasted();\n    assert_eq!(wasted.len(), objects);\n    for w in wasted {\n        assert_eq!(w.get_attributes().track_length, count);\n    }\n}\n"
  },
  {
    "path": "benches/simple_sort_iou_tracker_oriented.rs",
    "content": "#![feature(test)]\n\nextern crate test;\n\nuse rand::Rng;\nuse similari::examples::BoxGen2;\nuse similari::trackers::sort::metric::DEFAULT_MINIMAL_SORT_CONFIDENCE;\nuse similari::trackers::sort::simple_api::Sort;\nuse similari::trackers::sort::PositionalMetricType::IoU;\nuse similari::trackers::sort::DEFAULT_SORT_IOU_THRESHOLD;\nuse similari::trackers::spatio_temporal_constraints::SpatioTemporalConstraints;\nuse similari::trackers::tracker_api::TrackerAPI;\nuse similari::utils::bbox::Universal2DBox;\nuse test::Bencher;\n\n#[bench]\nfn sort_iou_oriented_00010(b: &mut Bencher) {\n    bench_sort(10, b);\n}\n\n#[bench]\nfn sort_iou_oriented_00100(b: &mut Bencher) {\n    bench_sort(100, b);\n}\n\n#[bench]\nfn sort_iou_oriented_00500(b: &mut Bencher) {\n    bench_sort(500, b);\n}\n\nfn bench_sort(objects: usize, b: &mut Bencher) {\n    let pos_drift = 1.0;\n    let box_drift = 0.001;\n    let mut iterators = Vec::default();\n\n    for i in 0..objects {\n        iterators.push(BoxGen2::new(\n            1000.0 * i as f32,\n            1000.0 * i as f32,\n            50.0,\n            50.0,\n            pos_drift,\n            box_drift,\n        ))\n    }\n\n    let mut iteration = 0;\n    let ncores = match objects {\n        10 => 1,\n        100 => 2,\n        _ => num_cpus::get(),\n    };\n\n    let mut tracker = Sort::new(\n        ncores,\n        10,\n        1,\n        IoU(DEFAULT_SORT_IOU_THRESHOLD),\n        DEFAULT_MINIMAL_SORT_CONFIDENCE,\n        Some(SpatioTemporalConstraints::default().constraints(&[(1, 1.0)])),\n        1.0 / 20.0,\n        1.0 / 160.0,\n    );\n    let mut rng = rand::thread_rng();\n\n    let mut count = 0;\n    b.iter(|| {\n        count += 1;\n        let mut observations = Vec::new();\n        for i in &mut iterators {\n            iteration += 1;\n            let b = Universal2DBox::from(i.next().unwrap()).rotate(rng.gen_range(0.0..1.0));\n            observations.push((b, None));\n        }\n        let tracks = tracker.predict(&observations);\n        assert_eq!(tracks.len(), objects);\n    });\n    let wasted = tracker.wasted();\n    assert!(wasted.is_empty());\n\n    eprintln!(\"Store stats: {:?}\", tracker.active_shard_stats());\n    assert_eq!(\n        tracker.active_shard_stats().into_iter().sum::<usize>(),\n        objects\n    );\n\n    tracker.skip_epochs(2);\n    let wasted = tracker.wasted();\n    assert_eq!(wasted.len(), objects);\n    for w in wasted {\n        assert_eq!(w.get_attributes().track_length, count);\n    }\n}\n"
  },
  {
    "path": "benches/simple_sort_maha_tracker.rs",
    "content": "#![feature(test)]\n\nextern crate test;\n\nuse similari::examples::BoxGen2;\nuse similari::prelude::Sort;\nuse similari::trackers::sort::metric::DEFAULT_MINIMAL_SORT_CONFIDENCE;\nuse similari::trackers::sort::PositionalMetricType::Mahalanobis;\nuse similari::trackers::spatio_temporal_constraints::SpatioTemporalConstraints;\nuse similari::trackers::tracker_api::TrackerAPI;\nuse test::Bencher;\n\n#[bench]\nfn sort_maha_00010(b: &mut Bencher) {\n    bench_sort(10, b);\n}\n\n#[bench]\nfn sort_maha_00100(b: &mut Bencher) {\n    bench_sort(100, b);\n}\n\n#[bench]\nfn sort_maha_00500(b: &mut Bencher) {\n    bench_sort(500, b);\n}\n\nfn bench_sort(objects: usize, b: &mut Bencher) {\n    let pos_drift = 1.0;\n    let box_drift = 0.001;\n    let mut iterators = Vec::default();\n\n    for i in 0..objects {\n        iterators.push(BoxGen2::new(\n            1000.0 * i as f32,\n            1000.0 * i as f32,\n            50.0,\n            50.0,\n            pos_drift,\n            box_drift,\n        ))\n    }\n\n    let mut iteration = 0;\n    let ncores = match objects {\n        10 => 1,\n        100 => 2,\n        _ => num_cpus::get(),\n    };\n\n    let mut tracker = Sort::new(\n        ncores,\n        10,\n        1,\n        Mahalanobis,\n        DEFAULT_MINIMAL_SORT_CONFIDENCE,\n        Some(SpatioTemporalConstraints::default().constraints(&[(1, 1.0)])),\n        1.0 / 20.0,\n        1.0 / 160.0,\n    );\n\n    let mut count = 0;\n    b.iter(|| {\n        count += 1;\n        let mut observations = Vec::new();\n        for i in &mut iterators {\n            iteration += 1;\n            let b = i.next();\n            observations.push((b.unwrap().into(), None));\n        }\n        let tracks = tracker.predict(&observations);\n        assert_eq!(tracks.len(), objects);\n    });\n    eprintln!(\"Store stats: {:?}\", tracker.active_shard_stats());\n    assert_eq!(\n        tracker.active_shard_stats().into_iter().sum::<usize>(),\n        objects\n    );\n\n    let wasted = tracker.wasted();\n    assert!(wasted.is_empty());\n\n    tracker.skip_epochs(2);\n    let wasted = tracker.wasted();\n    assert_eq!(wasted.len(), objects);\n    for w in wasted {\n        assert_eq!(w.get_attributes().track_length, count);\n    }\n}\n"
  },
  {
    "path": "benches/simple_sort_maha_tracker_oriented.rs",
    "content": "#![feature(test)]\n\nextern crate test;\n\nuse similari::examples::BoxGen2;\nuse similari::prelude::Sort;\nuse similari::trackers::sort::metric::DEFAULT_MINIMAL_SORT_CONFIDENCE;\nuse similari::trackers::sort::PositionalMetricType::Mahalanobis;\nuse similari::trackers::spatio_temporal_constraints::SpatioTemporalConstraints;\nuse similari::trackers::tracker_api::TrackerAPI;\nuse similari::utils::bbox::Universal2DBox;\nuse test::Bencher;\n\n#[bench]\nfn sort_maha_oriented_00010(b: &mut Bencher) {\n    bench_sort(10, b);\n}\n\n#[bench]\nfn sort_maha_oriented_00100(b: &mut Bencher) {\n    bench_sort(100, b);\n}\n\n#[bench]\nfn sort_maha_oriented_00500(b: &mut Bencher) {\n    bench_sort(500, b);\n}\n\nfn bench_sort(objects: usize, b: &mut Bencher) {\n    let pos_drift = 1.0;\n    let box_drift = 0.001;\n    let mut iterators = Vec::default();\n\n    for i in 0..objects {\n        iterators.push(BoxGen2::new(\n            1000.0 * i as f32,\n            1000.0 * i as f32,\n            50.0,\n            50.0,\n            pos_drift,\n            box_drift,\n        ))\n    }\n\n    let mut iteration = 0;\n    let ncores = match objects {\n        10 => 1,\n        100 => 2,\n        _ => num_cpus::get(),\n    };\n\n    let mut tracker = Sort::new(\n        ncores,\n        10,\n        1,\n        Mahalanobis,\n        DEFAULT_MINIMAL_SORT_CONFIDENCE,\n        Some(SpatioTemporalConstraints::default().constraints(&[(1, 1.0)])),\n        1.0 / 20.0,\n        1.0 / 160.0,\n    );\n\n    let mut count = 0;\n    b.iter(|| {\n        count += 1;\n        let mut observations = Vec::new();\n        for i in &mut iterators {\n            iteration += 1;\n            let b = Universal2DBox::from(i.next().unwrap())\n                .rotate(tracker.current_epoch() as f32 / 10.0);\n            observations.push((b, None));\n        }\n        let tracks = tracker.predict(&observations);\n        assert_eq!(tracks.len(), objects);\n    });\n    let wasted = tracker.wasted();\n    assert!(wasted.is_empty());\n\n    eprintln!(\"Store stats: {:?}\", tracker.active_shard_stats());\n    assert_eq!(\n        tracker.active_shard_stats().into_iter().sum::<usize>(),\n        objects\n    );\n\n    tracker.skip_epochs(2);\n    let wasted = tracker.wasted();\n    assert_eq!(wasted.len(), objects);\n    for w in wasted {\n        assert_eq!(w.get_attributes().track_length, count);\n    }\n}\n"
  },
  {
    "path": "benches/simple_visual_sort_tracker.rs",
    "content": "#![feature(test)]\n\nextern crate test;\n\nuse rand::distributions::Uniform;\nuse rand::Rng;\nuse similari::examples::BoxGen2;\nuse similari::prelude::{VisualSort, VisualSortObservation, VisualSortOptions};\nuse similari::trackers::sort::PositionalMetricType;\nuse similari::trackers::spatio_temporal_constraints::SpatioTemporalConstraints;\nuse similari::trackers::tracker_api::TrackerAPI;\nuse similari::trackers::visual_sort::metric::VisualSortMetricType;\nuse test::Bencher;\n\n#[bench]\nfn visual_sort_iou_00010x3x0128(b: &mut Bencher) {\n    bench_visual_sort(10, 128, b);\n}\n\n#[bench]\nfn visual_sort_iou_00050x3x0128(b: &mut Bencher) {\n    bench_visual_sort(50, 128, b);\n}\n\n#[bench]\nfn visual_sort_iou_00100x3x0128(b: &mut Bencher) {\n    bench_visual_sort(100, 128, b);\n}\n\n#[bench]\nfn visual_sort_iou_00010x3x0256(b: &mut Bencher) {\n    bench_visual_sort(10, 256, b);\n}\n\n#[bench]\nfn visual_sort_iou_00050x3x0256(b: &mut Bencher) {\n    bench_visual_sort(50, 256, b);\n}\n\n#[bench]\nfn visual_sort_iou_00100x3x0256(b: &mut Bencher) {\n    bench_visual_sort(100, 256, b);\n}\n\n#[bench]\nfn visual_sort_iou_00010x3x0512(b: &mut Bencher) {\n    bench_visual_sort(10, 512, b);\n}\n\n#[bench]\nfn visual_sort_iou_00050x3x0512(b: &mut Bencher) {\n    bench_visual_sort(50, 512, b);\n}\n\n#[bench]\nfn visual_sort_iou_00100x3x0512(b: &mut Bencher) {\n    bench_visual_sort(100, 512, b);\n}\n\n#[bench]\nfn visual_sort_iou_00010x3x1024(b: &mut Bencher) {\n    bench_visual_sort(10, 1024, b);\n}\n\n#[bench]\nfn visual_sort_iou_00050x3x1024(b: &mut Bencher) {\n    bench_visual_sort(50, 1024, b);\n}\n\n#[bench]\nfn visual_sort_iou_00100x3x1024(b: &mut Bencher) {\n    bench_visual_sort(100, 1024, b);\n}\n\n#[bench]\nfn visual_sort_iou_00010x3x2048(b: &mut Bencher) {\n    bench_visual_sort(10, 2048, b);\n}\n\n#[bench]\nfn visual_sort_iou_00050x3x2048(b: &mut Bencher) {\n    bench_visual_sort(50, 2048, b);\n}\n\n#[bench]\nfn visual_sort_iou_00100x3x2048(b: &mut Bencher) {\n    bench_visual_sort(100, 2048, b);\n}\n\nfn bench_visual_sort(objects: usize, len: usize, b: &mut Bencher) {\n    let pos_drift = 1.0;\n    let box_drift = 0.001;\n    let mut iterators = Vec::default();\n\n    for i in 0..objects {\n        iterators.push(BoxGen2::new(\n            1000.0 * i as f32,\n            1000.0 * i as f32,\n            20.0,\n            50.0,\n            pos_drift,\n            box_drift,\n        ))\n    }\n\n    let mut iteration = 0;\n    //let ncores = num_cpus::get();\n\n    let opts = VisualSortOptions::default()\n        .positional_metric(PositionalMetricType::IoU(0.3))\n        .visual_metric(VisualSortMetricType::Euclidean(10.0))\n        .visual_max_observations(3)\n        .spatio_temporal_constraints(SpatioTemporalConstraints::default().constraints(&[(1, 1.0)]))\n        .visual_minimal_own_area_percentage_use(0.5)\n        .visual_minimal_own_area_percentage_collect(0.6)\n        .kalman_position_weight(1.0 / 20.0)\n        .kalman_velocity_weight(1.0 / 160.0)\n        .visual_min_votes(2);\n\n    let ncores = match objects {\n        10 => 1,\n        50 => 2,\n        _ => num_cpus::get(),\n    };\n\n    let mut tracker = VisualSort::new(ncores, &opts);\n\n    let mut count = 0;\n    b.iter(|| {\n        count += 1;\n        let mut observations = Vec::new();\n\n        let mut features = Vec::new();\n        let mut rng = rand::thread_rng();\n        let gen = Uniform::new(-0.01, 0.01);\n\n        for (index, _) in iterators.iter().enumerate() {\n            let f = (0..len)\n                .map(|_| rng.sample(&gen) + index as f32 * 10.0)\n                .collect::<Vec<f32>>();\n            features.push(f);\n        }\n\n        for (index, bi) in iterators.iter_mut().enumerate() {\n            iteration += 1;\n            let b = bi.next();\n            let f = &features[index];\n            observations.push(VisualSortObservation::new(\n                Some(f),\n                Some(1.0),\n                b.unwrap().into(),\n                Some(0),\n            ));\n        }\n        let tracks = tracker.predict(&observations);\n        assert_eq!(tracks.len(), objects);\n    });\n    eprintln!(\"Store stats: {:?}\", tracker.active_shard_stats());\n    assert_eq!(\n        tracker.active_shard_stats().into_iter().sum::<usize>(),\n        objects\n    );\n\n    let wasted = tracker.wasted();\n    assert!(wasted.is_empty());\n\n    tracker.skip_epochs(10);\n    let wasted = tracker.wasted();\n    assert_eq!(wasted.len(), objects);\n    for w in wasted {\n        assert_eq!(w.get_attributes().track_length, count);\n    }\n}\n"
  },
  {
    "path": "benches/track_search.rs",
    "content": "#![feature(test)]\n\nextern crate test;\n\nuse rand::{distributions::Uniform, Rng};\nuse similari::examples::{UnboundAttributeUpdate, UnboundAttrs, UnboundMetric};\nuse similari::track::Feature;\n\nuse similari::prelude::TrackStoreBuilder;\nuse similari::track::notify::NoopNotifier;\nuse similari::track::utils::FromVec;\nuse test::Bencher;\n\nfn bench_capacity_len(vec_len: usize, track_len: usize, count: usize, b: &mut Bencher) {\n    const DEFAULT_FEATURE: u64 = 0;\n    let mut db = TrackStoreBuilder::new(num_cpus::get())\n        .metric(UnboundMetric::default())\n        .default_attributes(UnboundAttrs::default())\n        .notifier(NoopNotifier)\n        .build();\n    let mut rng = rand::thread_rng();\n    let gen = Uniform::new(0.0, 1.0);\n\n    for i in 0..count {\n        for _j in 0..track_len {\n            let res = db.add(\n                i as u64,\n                DEFAULT_FEATURE,\n                Some(1.0),\n                Some(Feature::from_vec(\n                    (0..vec_len).map(|_| rng.sample(&gen)).collect::<Vec<_>>(),\n                )),\n                None,\n            );\n            assert!(res.is_ok());\n        }\n    }\n\n    let mut t = db.new_track(count as u64 + 1).build().unwrap();\n    for _j in 0..track_len {\n        let _ = t.add_observation(\n            DEFAULT_FEATURE,\n            Some(1.0),\n            Some(Feature::from_vec(\n                (0..vec_len).map(|_| rng.sample(&gen)).collect::<Vec<_>>(),\n            )),\n            Some(UnboundAttributeUpdate),\n        );\n    }\n\n    b.iter(move || {\n        let (dists, errs) = db.foreign_track_distances(vec![t.clone()], DEFAULT_FEATURE, true);\n        dists.all();\n        errs.all();\n    });\n}\n\n#[bench]\nfn track_0256_030_100(b: &mut Bencher) {\n    bench_capacity_len(256, 30, 100, b);\n}\n\n#[bench]\nfn track_0512_030_100(b: &mut Bencher) {\n    bench_capacity_len(512, 30, 100, b);\n}\n\n#[bench]\nfn track_1024_030_100(b: &mut Bencher) {\n    bench_capacity_len(1024, 30, 100, b);\n}\n\n#[bench]\nfn track_0256_030_01k(b: &mut Bencher) {\n    bench_capacity_len(256, 30, 1000, b);\n}\n"
  },
  {
    "path": "build.rs",
    "content": "fn main() {\n    #[cfg(feature = \"python\")]\n    pyo3_build_config::add_extension_module_link_args();\n}\n"
  },
  {
    "path": "docker/Dockerfile.manylinux_2_28_ARM64",
    "content": "FROM ghcr.io/insight-platform/manylinux_2_28_arm64:v0.0.7 AS builder\n\nWORKDIR /opt\nCOPY . .\nARG PYTHON_INTERPRETER\nRUN chmod +x /opt/docker/build-manylinux.sh\nRUN bash /opt/docker/build-manylinux.sh\nRUN rm -rf target\n\nFROM alpine:3.18 AS dist\nCOPY --from=builder /opt/dist /opt/dist\n"
  },
  {
    "path": "docker/Dockerfile.manylinux_2_28_X64",
    "content": "FROM ghcr.io/insight-platform/manylinux_2_28_x64:v0.0.7 AS builder\n\nWORKDIR /opt\nCOPY . .\nARG PYTHON_INTERPRETER\nRUN chmod +x /opt/docker/build-manylinux.sh\nRUN bash /opt/docker/build-manylinux.sh\nRUN rm -rf target\n\nFROM alpine:3.18 AS dist\nCOPY --from=builder /opt/dist /opt/dist\n"
  },
  {
    "path": "docker/build-manylinux.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\n\nARGS=-f\n\nif [[ -z $PYTHON_INTERPRETER ]]; then\n    ARGS=-f\nelse\n    ARGS=\"-i $PYTHON_INTERPRETER\"\nfi\n\necho \"Additional build args: $ARGS\"\n\nmaturin build $ARGS --release --out /opt/dist\n\n"
  },
  {
    "path": "examples/batch_sort_iou_tracker.rs",
    "content": "use similari::examples::BoxGen2;\nuse similari::trackers::batch::PredictionBatchRequest;\nuse similari::trackers::sort::batch_api::BatchSort;\nuse similari::trackers::sort::metric::DEFAULT_MINIMAL_SORT_CONFIDENCE;\nuse similari::trackers::sort::PositionalMetricType::IoU;\nuse similari::trackers::sort::DEFAULT_SORT_IOU_THRESHOLD;\nuse similari::trackers::tracker_api::TrackerAPI;\nuse similari::utils::bbox::BoundingBox;\n\nfn main() {\n    let mut tracker = BatchSort::new(\n        1,\n        1,\n        10,\n        1,\n        IoU(DEFAULT_SORT_IOU_THRESHOLD),\n        DEFAULT_MINIMAL_SORT_CONFIDENCE,\n        None,\n        1.0 / 20.0,\n        1.0 / 160.0,\n    );\n\n    let pos_drift = 1.0;\n    let box_drift = 0.01;\n    let mut b1 = BoxGen2::new_monotonous(100.0, 100.0, 10.0, 15.0, pos_drift, box_drift);\n    let mut b2 = BoxGen2::new_monotonous(10.0, 10.0, 12.0, 18.0, pos_drift, box_drift);\n\n    for _ in 0..10 {\n        let obj1b = b1.next().unwrap();\n        let obj2b = b2.next().unwrap();\n        let (mut batch, res) = PredictionBatchRequest::new();\n        batch.add(0, (obj1b.into(), None));\n        batch.add(0, (obj2b.into(), None));\n        tracker.predict(batch);\n        for _ in 0..res.batch_size() {\n            let predictions = res.get();\n            //eprintln!(\"Scene Tracks: {:?}\", &predictions);\n            drop(predictions);\n        }\n    }\n\n    tracker.skip_epochs(2);\n\n    let tracks = tracker.wasted();\n    for t in tracks {\n        eprintln!(\"Track id: {}\", t.get_track_id());\n        eprintln!(\n            \"Boxes: {:#?}\",\n            t.get_attributes()\n                .predicted_boxes\n                .clone()\n                .into_iter()\n                .map(|x| BoundingBox::try_from(x).unwrap())\n                .collect::<Vec<_>>()\n        );\n    }\n}\n"
  },
  {
    "path": "examples/incremental_track_build.rs",
    "content": "use similari::distance::euclidean;\nuse similari::examples::{BoxGen2, FeatGen2};\nuse similari::prelude::*;\nuse similari::track::{\n    MetricOutput, MetricQuery, NoopLookup, Observation, ObservationAttributes, ObservationMetric,\n    ObservationMetricOk, ObservationsDb, TrackAttributes, TrackAttributesUpdate, TrackStatus,\n};\nuse similari::utils::bbox::BoundingBox;\nuse similari::voting::topn::TopNVoting;\nuse similari::voting::Voting;\nuse std::thread;\nuse std::time::Duration;\n\nconst FEAT0: u64 = 0;\nconst MAX_DIST: f32 = 0.1;\n\n#[derive(Debug, Clone, Default)]\nstruct BBoxAttributes {\n    bboxes: Vec<BoundingBox>,\n}\n\n#[derive(Clone, Debug)]\nstruct BBoxAttributesUpdate {\n    bbox: BoundingBox,\n}\n\nimpl TrackAttributesUpdate<BBoxAttributes> for BBoxAttributesUpdate {\n    fn apply(&self, attrs: &mut BBoxAttributes) -> anyhow::Result<()> {\n        attrs.bboxes.push(self.bbox);\n        Ok(())\n    }\n}\n\nimpl TrackAttributes<BBoxAttributes, f32> for BBoxAttributes {\n    type Update = BBoxAttributesUpdate;\n    type Lookup = NoopLookup<BBoxAttributes, f32>;\n\n    fn compatible(&self, _other: &BBoxAttributes) -> bool {\n        true\n    }\n\n    fn merge(&mut self, other: &BBoxAttributes) -> anyhow::Result<()> {\n        self.bboxes.extend_from_slice(&other.bboxes);\n        Ok(())\n    }\n\n    fn baked(&self, _observations: &ObservationsDb<f32>) -> anyhow::Result<TrackStatus> {\n        Ok(TrackStatus::Ready)\n    }\n}\n\n#[derive(Clone, Default)]\npub struct TrackMetric;\n\nimpl ObservationMetric<BBoxAttributes, f32> for TrackMetric {\n    fn metric(&self, mq: &MetricQuery<BBoxAttributes, f32>) -> MetricOutput<f32> {\n        let (e1, e2) = (mq.candidate_observation, mq.track_observation);\n        Some((\n            f32::calculate_metric_object(&e1.attr().as_ref(), &e2.attr().as_ref()),\n            match (e1.feature().as_ref(), e2.feature().as_ref()) {\n                (Some(x), Some(y)) => Some(euclidean(x, y)),\n                _ => None,\n            },\n        ))\n    }\n\n    fn optimize(\n        &mut self,\n        _feature_class: u64,\n        _merge_history: &[u64],\n        _attrs: &mut BBoxAttributes,\n        observations: &mut Vec<Observation<f32>>,\n        _prev_length: usize,\n        _is_merge: bool,\n    ) -> anyhow::Result<()> {\n        observations.reverse();\n        observations.truncate(5);\n        observations.reverse();\n        Ok(())\n    }\n\n    fn postprocess_distances(\n        &self,\n        unfiltered: Vec<ObservationMetricOk<f32>>,\n    ) -> Vec<ObservationMetricOk<f32>> {\n        unfiltered\n            .into_iter()\n            .filter(|r| r.feature_distance.unwrap() < MAX_DIST)\n            .collect()\n    }\n}\n\nfn main() {\n    let mut store = TrackStoreBuilder::default()\n        .default_attributes(BBoxAttributes::default())\n        .metric(TrackMetric::default())\n        .notifier(NoopNotifier)\n        .build();\n\n    let voting: TopNVoting<f32> = TopNVoting::new(1, MAX_DIST, 1);\n    let feature_drift = 0.01;\n    let pos_drift = 5.0;\n    let box_drift = 2.0;\n\n    let mut p1 = FeatGen2::new(0.0, 0.0, feature_drift);\n    let mut b1 = BoxGen2::new(100.0, 100.0, 10.0, 15.0, pos_drift, box_drift);\n\n    let mut p2 = FeatGen2::new(1.0, 1.0, feature_drift);\n    let mut b2 = BoxGen2::new(10.0, 10.0, 12.0, 18.0, pos_drift, box_drift);\n\n    for _ in 0..10 {\n        let (obj1f, obj1b) = (p1.next().unwrap(), b1.next().unwrap());\n\n        let obj1t = store\n            .new_track_random_id()\n            .observation(\n                ObservationBuilder::new(FEAT0)\n                    .observation_attributes(obj1f.attr().unwrap())\n                    .observation(obj1f.feature().as_ref().unwrap().clone())\n                    .track_attributes_update(BBoxAttributesUpdate { bbox: obj1b })\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        let (obj2f, obj2b) = (p2.next().unwrap(), b2.next().unwrap());\n\n        let obj2t = store\n            .new_track_random_id()\n            .observation(\n                ObservationBuilder::new(FEAT0)\n                    .observation_attributes(obj2f.attr().unwrap())\n                    .observation(obj2f.feature().as_ref().unwrap().clone())\n                    .track_attributes_update(BBoxAttributesUpdate { bbox: obj2b })\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        thread::sleep(Duration::from_millis(2));\n\n        for t in [obj1t, obj2t] {\n            let search_track = t.clone();\n            let (dists, errs) = store.foreign_track_distances(vec![search_track], FEAT0, false);\n            assert!(errs.all().is_empty());\n            let winners = voting.winners(dists);\n            if winners.is_empty() {\n                store.add_track(t).unwrap();\n            } else {\n                store\n                    .merge_external(\n                        winners.get(&t.get_track_id()).unwrap()[0].winner_track,\n                        &t,\n                        None,\n                        false,\n                    )\n                    .unwrap();\n            }\n        }\n    }\n\n    let tracks = store.find_usable();\n    for (t, _) in tracks {\n        let t = store.fetch_tracks(&[t]);\n        eprintln!(\"Track id: {}\", t[0].get_track_id());\n        eprintln!(\"Boxes: {:#?}\", t[0].get_attributes());\n    }\n}\n"
  },
  {
    "path": "examples/middleware_sort_tracker.rs",
    "content": "use similari::examples::{current_time_ms, BoxGen2};\nuse similari::prelude::{NoopNotifier, ObservationBuilder, TrackStoreBuilder};\nuse similari::trackers::sort::metric::SortMetric;\nuse similari::trackers::sort::voting::SortVoting;\nuse similari::trackers::sort::{SortAttributes, SortAttributesOptions, DEFAULT_SORT_IOU_THRESHOLD};\nuse similari::trackers::spatio_temporal_constraints::SpatioTemporalConstraints;\nuse similari::voting::Voting;\nuse std::sync::Arc;\nuse std::thread;\nuse std::time::Duration;\n\nconst FEAT0: u64 = 0;\nconst BBOX_HISTORY: usize = 100;\n\nfn main() {\n    let mut store = TrackStoreBuilder::default()\n        .default_attributes(SortAttributes::new(Arc::new(SortAttributesOptions::new(\n            None,\n            0,\n            BBOX_HISTORY,\n            SpatioTemporalConstraints::default(),\n            1.0 / 20.0,\n            1.0 / 160.0,\n        ))))\n        .metric(SortMetric::default())\n        .notifier(NoopNotifier)\n        .build();\n\n    let pos_drift = 1.0;\n    let box_drift = 0.2;\n    let mut b1 = BoxGen2::new_monotonous(100.0, 100.0, 10.0, 15.0, pos_drift, box_drift);\n\n    let mut b2 = BoxGen2::new_monotonous(10.0, 10.0, 12.0, 18.0, pos_drift, box_drift);\n\n    for _ in 0..10 {\n        let obj1b = b1.next().unwrap();\n        let obj2b = b2.next().unwrap();\n\n        let track_id = u64::try_from(current_time_ms()).unwrap();\n        let obj1t = store\n            .new_track(track_id)\n            .observation(\n                ObservationBuilder::new(FEAT0)\n                    .observation_attributes(obj1b.into())\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        let obj2t = store\n            .new_track(track_id + 1)\n            .observation(\n                ObservationBuilder::new(FEAT0)\n                    .observation_attributes(obj2b.into())\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        thread::sleep(Duration::from_millis(2));\n\n        for t in [obj1t, obj2t] {\n            let search_track = t.clone();\n            let (dists, errs) = store.foreign_track_distances(vec![search_track], FEAT0, false);\n            assert!(errs.all().is_empty());\n            let voting = SortVoting::new(\n                DEFAULT_SORT_IOU_THRESHOLD,\n                1,\n                store.shard_stats().iter().sum(),\n            );\n            let dists = dists.all();\n            let mut winners = voting.winners(dists);\n            if winners.is_empty() {\n                store.add_track(t).unwrap();\n            } else {\n                let winner = winners.get_mut(&t.get_track_id()).unwrap().pop().unwrap();\n                if winner == t.get_track_id() {\n                    store.add_track(t).unwrap();\n                } else {\n                    store.merge_external(winner, &t, None, false).unwrap();\n                }\n            }\n        }\n    }\n\n    let tracks = store.find_usable();\n    for (t, _) in tracks {\n        let t = store.fetch_tracks(&[t]);\n        eprintln!(\"Track id: {}\", t[0].get_track_id());\n        eprintln!(\"Boxes: {:#?}\", t[0].get_attributes().predicted_boxes);\n    }\n}\n"
  },
  {
    "path": "examples/simple.rs",
    "content": "use similari::examples::{vec2, SimpleAttributeUpdate, SimpleAttrs, SimpleMetric};\nuse similari::store;\nuse similari::track::notify::NoopNotifier;\nuse similari::track::Track;\nuse similari::voting::topn::TopNVoting;\nuse similari::voting::Voting;\n\nfn main() {\n    const DEFAULT_FEATURE: u64 = 0;\n    let mut db = store::TrackStore::new(\n        SimpleMetric::default(),\n        SimpleAttrs::default(),\n        NoopNotifier,\n        num_cpus::get(),\n    );\n    let res = db.add(\n        0,\n        DEFAULT_FEATURE,\n        Some(1.0),\n        Some(vec2(1.0, 0.0)),\n        Some(SimpleAttributeUpdate {}),\n    );\n    assert!(res.is_ok());\n\n    let res = db.add(\n        0,\n        DEFAULT_FEATURE,\n        Some(1.0),\n        Some(vec2(1.0, 0.0)),\n        Some(SimpleAttributeUpdate {}),\n    );\n    // attribute implementation prevents secondary observations to be added to the same track\n    assert!(res.is_err());\n\n    let baked = db.find_usable();\n    assert_eq!(\n        baked\n            .into_iter()\n            .filter(|(_b, r)| r.is_ok())\n            .map(|(x, _)| x)\n            .collect::<Vec<_>>(),\n        vec![0u64]\n    );\n\n    let res = db.add(\n        1,\n        DEFAULT_FEATURE,\n        Some(0.9),\n        Some(vec2(0.9, 0.1)),\n        Some(SimpleAttributeUpdate {}),\n    );\n    assert!(res.is_ok());\n\n    let mut ext_track = Track::new(\n        2,\n        SimpleMetric::default(),\n        SimpleAttrs::default(),\n        NoopNotifier,\n    );\n\n    let res = ext_track.add_observation(\n        DEFAULT_FEATURE,\n        Some(0.8),\n        Some(vec2(0.66, 0.33)),\n        Some(SimpleAttributeUpdate {}),\n    );\n\n    assert!(res.is_ok());\n\n    let (dists, errs) = db.foreign_track_distances(vec![ext_track], 0, true);\n    let dists = dists.all();\n    let errs = errs.all();\n    assert_eq!(errs.len(), 0);\n\n    eprintln!(\"Distances: {:?}\", &dists);\n    eprintln!(\"Errs: {:?}\", &errs);\n\n    let top1_voting_engine: TopNVoting<f32> = TopNVoting::new(2, 1.0, 1);\n    let results = top1_voting_engine.winners(dists.clone());\n    eprintln!(\n        \"Voting results (the less distance, the better result): {:?}\",\n        &results\n    );\n\n    // max distance filter set to 0.4\n    let top1_voting_engine_filter: TopNVoting<f32> = TopNVoting::new(2, 0.4, 1);\n    let results = top1_voting_engine_filter.winners(dists);\n    eprintln!(\n        \"Voting results (the less distance, the better result): {:?}\",\n        &results\n    );\n}\n"
  },
  {
    "path": "examples/simple_sort_iou_tracker.rs",
    "content": "use similari::examples::BoxGen2;\nuse similari::trackers::sort::metric::DEFAULT_MINIMAL_SORT_CONFIDENCE;\nuse similari::trackers::sort::simple_api::Sort;\nuse similari::trackers::sort::PositionalMetricType::IoU;\nuse similari::trackers::sort::DEFAULT_SORT_IOU_THRESHOLD;\nuse similari::trackers::tracker_api::TrackerAPI;\nuse similari::utils::bbox::BoundingBox;\n\nfn main() {\n    let mut tracker = Sort::new(\n        1,\n        10,\n        1,\n        IoU(DEFAULT_SORT_IOU_THRESHOLD),\n        DEFAULT_MINIMAL_SORT_CONFIDENCE,\n        None,\n        1.0 / 20.0,\n        1.0 / 160.0,\n    );\n\n    let pos_drift = 1.0;\n    let box_drift = 0.01;\n    let mut b1 = BoxGen2::new_monotonous(100.0, 100.0, 10.0, 15.0, pos_drift, box_drift);\n    let mut b2 = BoxGen2::new_monotonous(10.0, 10.0, 12.0, 18.0, pos_drift, box_drift);\n\n    for _ in 0..100 {\n        let obj1b = b1.next().unwrap();\n        let obj2b = b2.next().unwrap();\n        let _tracks = tracker.predict(&[(obj1b.into(), None), (obj2b.into(), None)]);\n    }\n\n    tracker.skip_epochs(2);\n\n    let tracks = tracker.wasted();\n    for t in tracks {\n        eprintln!(\"Track id: {}\", t.get_track_id());\n        eprintln!(\n            \"Boxes: {:#?}\",\n            t.get_attributes()\n                .predicted_boxes\n                .clone()\n                .into_iter()\n                .map(|x| BoundingBox::try_from(x).unwrap())\n                .collect::<Vec<_>>()\n        );\n    }\n}\n"
  },
  {
    "path": "examples/simple_sort_iou_tracker_oriented.rs",
    "content": "use similari::examples::BoxGen2;\nuse similari::trackers::sort::metric::DEFAULT_MINIMAL_SORT_CONFIDENCE;\nuse similari::trackers::sort::simple_api::Sort;\nuse similari::trackers::sort::PositionalMetricType::IoU;\nuse similari::trackers::sort::DEFAULT_SORT_IOU_THRESHOLD;\nuse similari::trackers::tracker_api::TrackerAPI;\nuse similari::utils::bbox::Universal2DBox;\n\nfn main() {\n    let mut tracker = Sort::new(\n        1,\n        10,\n        1,\n        IoU(DEFAULT_SORT_IOU_THRESHOLD),\n        DEFAULT_MINIMAL_SORT_CONFIDENCE,\n        None,\n        1.0 / 20.0,\n        1.0 / 160.0,\n    );\n\n    let pos_drift = 1.0;\n    let box_drift = 0.1;\n    let mut b1 = BoxGen2::new_monotonous(100.0, 100.0, 10.0, 15.0, pos_drift, box_drift);\n    let mut b2 = BoxGen2::new_monotonous(10.0, 10.0, 12.0, 18.0, pos_drift, box_drift);\n\n    for i in 0..30 {\n        let obj1b = Universal2DBox::from(b1.next().unwrap()).rotate(0.35 + (i as f32 / 10.0));\n        let obj2b = Universal2DBox::from(b2.next().unwrap()).rotate(0.55 + (i as f32 / 10.0));\n        let _tracks = tracker.predict(&[(obj1b, None), (obj2b, None)]);\n    }\n\n    tracker.skip_epochs(2);\n\n    let tracks = tracker.wasted();\n    for t in tracks {\n        eprintln!(\"Track id: {}\", t.get_track_id());\n        eprintln!(\"Boxes: {:#?}\", t.get_attributes().predicted_boxes);\n    }\n}\n"
  },
  {
    "path": "examples/simple_sort_maha_tracker.rs",
    "content": "use similari::examples::BoxGen2;\nuse similari::prelude::Sort;\nuse similari::trackers::sort::metric::DEFAULT_MINIMAL_SORT_CONFIDENCE;\nuse similari::trackers::sort::PositionalMetricType::Mahalanobis;\nuse similari::trackers::tracker_api::TrackerAPI;\nuse similari::utils::bbox::BoundingBox;\n\nfn main() {\n    let mut tracker = Sort::new(\n        1,\n        10,\n        1,\n        Mahalanobis,\n        DEFAULT_MINIMAL_SORT_CONFIDENCE,\n        None,\n        1.0 / 20.0,\n        1.0 / 160.0,\n    );\n\n    let pos_drift = 1.0;\n    let box_drift = 0.2;\n    let mut b1 = BoxGen2::new_monotonous(100.0, 100.0, 10.0, 15.0, pos_drift, box_drift);\n    let mut b2 = BoxGen2::new_monotonous(10.0, 10.0, 12.0, 18.0, pos_drift, box_drift);\n\n    for _ in 0..10 {\n        let obj1b = b1.next().unwrap();\n        let obj2b = b2.next().unwrap();\n        let _tracks = tracker.predict(&[(obj1b.into(), None), (obj2b.into(), None)]);\n        //eprintln!(\"Tracked objects: {:#?}\", _tracks);\n    }\n\n    tracker.skip_epochs(2);\n\n    let tracks = tracker.wasted();\n    for t in tracks {\n        eprintln!(\"Track id: {}\", t.get_track_id());\n        eprintln!(\n            \"Boxes: {:#?}\",\n            t.get_attributes()\n                .predicted_boxes\n                .iter()\n                .map(|x| BoundingBox::try_from(x))\n                .collect::<Vec<_>>()\n        );\n    }\n}\n"
  },
  {
    "path": "examples/simple_sort_maha_tracker_oriented.rs",
    "content": "use similari::examples::BoxGen2;\nuse similari::prelude::Sort;\nuse similari::trackers::sort::metric::DEFAULT_MINIMAL_SORT_CONFIDENCE;\nuse similari::trackers::sort::PositionalMetricType::Mahalanobis;\nuse similari::trackers::tracker_api::TrackerAPI;\nuse similari::utils::bbox::Universal2DBox;\n\nfn main() {\n    let mut tracker = Sort::new(\n        1,\n        10,\n        1,\n        Mahalanobis,\n        DEFAULT_MINIMAL_SORT_CONFIDENCE,\n        None,\n        1.0 / 20.0,\n        1.0 / 160.0,\n    );\n\n    let pos_drift = 1.0;\n    let box_drift = 0.1;\n    let mut b1 = BoxGen2::new_monotonous(100.0, 100.0, 10.0, 15.0, pos_drift, box_drift);\n    let mut b2 = BoxGen2::new_monotonous(10.0, 10.0, 12.0, 18.0, pos_drift, box_drift);\n\n    for i in 0..30 {\n        let obj1b = Universal2DBox::from(b1.next().unwrap()).rotate(0.35 + (i as f32 / 10.0));\n        let obj2b = Universal2DBox::from(b2.next().unwrap()).rotate(0.55 + (i as f32 / 10.0));\n        let _tracks = tracker.predict(&[(obj1b, None), (obj2b, None)]);\n    }\n\n    tracker.skip_epochs(2);\n\n    let tracks = tracker.wasted();\n    for t in tracks {\n        eprintln!(\"Track id: {}\", t.get_track_id());\n        eprintln!(\"Boxes: {:#?}\", t.get_attributes().predicted_boxes);\n    }\n}\n"
  },
  {
    "path": "examples/track_merging.rs",
    "content": "use crate::Gender::{Female, Male};\nuse anyhow::Result;\nuse itertools::Itertools;\nuse once_cell::sync::OnceCell;\nuse similari::distance::euclidean;\nuse similari::examples::current_time_ms;\nuse similari::examples::FeatGen2;\nuse similari::store::TrackStore;\nuse similari::track::notify::NoopNotifier;\nuse similari::track::{\n    MetricOutput, MetricQuery, NoopLookup, Observation, ObservationAttributes, ObservationMetric,\n    ObservationsDb, TrackAttributes, TrackAttributesUpdate, TrackStatus,\n};\nuse similari::voting::topn::TopNVoting;\nuse similari::voting::Voting;\nuse std::cmp::{max, min};\n\nuse std::thread;\nuse std::time::Duration;\nuse thiserror::Error;\n\nconst FEATURE0: u64 = 0;\n\n#[derive(Debug, Error)]\nenum AppErrors {\n    #[error(\"Cam id passed ({0}) != id set ({1})\")]\n    WrongCamID(u64, u64),\n    #[error(\"Time passed {0} < time set {1}\")]\n    WrongTime(u128, u128),\n    #[error(\"Incompatible attributes\")]\n    IncompatibleAttributes,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]\nenum Gender {\n    Female,\n    Male,\n    #[default]\n    Unknown,\n}\n\n// person attributes\n#[derive(Debug, Clone, Default)]\nstruct CamTrackingAttributes {\n    start_time: u128, // when the track observation first appeared\n    end_time: u128,   // when the track observation last appeared\n    baked_period_ms: u128,\n    camera_id: OnceCell<u64>, // identifier of camera that detected the object\n    age: Vec<u8>,             // age detected during the observations\n    gender: Vec<Gender>,      // gender detected during the observations\n    screen_pos: Vec<(u16, u16)>, // person screen position\n}\n\nimpl CamTrackingAttributes {\n    // calculate age as average over observations\n    pub fn get_age(&self) -> Option<u8> {\n        if self.age.is_empty() {\n            return None;\n        }\n        u8::try_from(self.age.iter().map(|e| *e as u32).sum::<u32>() / self.age.len() as u32).ok()\n    }\n\n    // calculate gender as most frequent gender\n    pub fn get_gender(&self) -> Gender {\n        if self.gender.is_empty() {\n            return Gender::Unknown;\n        }\n\n        let groups = self.gender.clone();\n        let mut groups = groups.into_iter().counts().into_iter().collect::<Vec<_>>();\n        groups.sort_by(|(_, l), (_, r)| r.partial_cmp(l).unwrap());\n        groups[0].0\n    }\n}\n\n#[test]\nfn test_attributes_age_gender() {\n    use Gender::*;\n    let attrs = CamTrackingAttributes {\n        screen_pos: vec![(0, 0), (10, 15), (20, 25), (30, 35)],\n        start_time: 0,\n        end_time: 0,\n        baked_period_ms: 10,\n        camera_id: Default::default(),\n        age: vec![17, 24, 36],\n        gender: vec![Male, Female, Female, Unknown],\n    };\n    assert_eq!(attrs.get_age(), Some(25));\n    assert_eq!(attrs.get_gender(), Female);\n}\n\n// update\n#[derive(Clone, Debug)]\nenum CamTrackingAttributesUpdate {\n    DataUpdate {\n        time: u128,\n        gender: Option<Gender>,\n        age: Option<u8>,\n        camera_id: u64,\n        screen_pos: (u16, u16),\n    },\n    BakedPeriodUpdate(u128),\n}\n\nimpl TrackAttributesUpdate<CamTrackingAttributes> for CamTrackingAttributesUpdate {\n    fn apply(&self, attrs: &mut CamTrackingAttributes) -> Result<()> {\n        match self {\n            CamTrackingAttributesUpdate::DataUpdate {\n                time,\n                gender,\n                age,\n                camera_id,\n                screen_pos,\n            } => {\n                // initially, track start time is set to end time\n                if attrs.start_time == 0 {\n                    attrs.start_time = *time;\n                }\n\n                // if future track observation is submited with older timestamp\n                // then it's incorrect situation, timestamp should increase.\n                if attrs.end_time > *time {\n                    return Err(AppErrors::WrongTime(*time, attrs.end_time).into());\n                }\n\n                attrs.end_time = *time;\n\n                // update may be without the gender, if observer cannot determine the\n                // gender within the observation\n                if let Some(gender) = gender {\n                    attrs.gender.push(*gender);\n                }\n\n                // same for age\n                if let Some(age) = age {\n                    attrs.age.push(*age);\n                }\n\n                // track with <id> always goes from the same camera. If camera id changed\n                // it's a wrong case.\n                if let Err(_r) = attrs.camera_id.set(*camera_id) {\n                    if *camera_id != *attrs.camera_id.get().unwrap() {\n                        return Err(AppErrors::WrongCamID(\n                            *camera_id,\n                            *attrs.camera_id.get().unwrap(),\n                        )\n                        .into());\n                    }\n                }\n\n                attrs.screen_pos.push(*screen_pos);\n            }\n            CamTrackingAttributesUpdate::BakedPeriodUpdate(p) => {\n                attrs.baked_period_ms = *p;\n            }\n        }\n\n        Ok(())\n    }\n}\n\n#[test]\nfn cam_tracking_attributes_update_test() {\n    use Gender::*;\n    let mut attrs = CamTrackingAttributes {\n        start_time: 0,\n        end_time: 0,\n        baked_period_ms: 10,\n        camera_id: Default::default(),\n        age: Vec::default(),\n        gender: Vec::default(),\n        screen_pos: Vec::default(),\n    };\n\n    let update = CamTrackingAttributesUpdate::DataUpdate {\n        time: 10,\n        gender: Some(Female),\n        age: Some(30),\n        camera_id: 10,\n        screen_pos: (10, 10),\n    };\n    assert!(update.apply(&mut attrs).is_ok());\n\n    // incorrect cam id\n    let update = CamTrackingAttributesUpdate::DataUpdate {\n        time: 20,\n        gender: Some(Female),\n        age: Some(10),\n        camera_id: 20,\n        screen_pos: (10, 15),\n    };\n    assert!(update.apply(&mut attrs).is_err());\n\n    // incorrect time\n    let update = CamTrackingAttributesUpdate::DataUpdate {\n        time: 5,\n        gender: Some(Female),\n        age: Some(10),\n        camera_id: 20,\n        screen_pos: (20, 25),\n    };\n    assert!(update.apply(&mut attrs).is_err());\n}\n\n#[test]\nfn feat_gen() {\n    use similari::examples::FeatGen2;\n    use std::ops::Sub;\n    use ultraviolet::f32x8;\n\n    let drift = 0.01;\n    let mut gen = FeatGen2::new(0.0, 0.0, drift);\n    let v1 = gen.next().unwrap().feature().unwrap()[0];\n    let v2 = gen.next().unwrap().feature().unwrap()[0];\n    assert!(v1.sub(v2).abs().reduce_add() <= 2.0 * f32x8::splat(drift).reduce_add());\n}\n\nimpl TrackAttributes<CamTrackingAttributes, f32> for CamTrackingAttributes {\n    type Update = CamTrackingAttributesUpdate;\n    type Lookup = NoopLookup<CamTrackingAttributes, f32>;\n\n    fn compatible(&self, other: &CamTrackingAttributes) -> bool {\n        (self.start_time >= other.end_time || self.end_time <= other.start_time)\n            && self.camera_id.get().unwrap() == other.camera_id.get().unwrap()\n    }\n\n    fn merge(&mut self, other: &CamTrackingAttributes) -> Result<()> {\n        if self.compatible(other) {\n            self.start_time = min(self.start_time, other.start_time);\n            self.end_time = max(self.end_time, other.end_time);\n            self.screen_pos.extend_from_slice(&other.screen_pos);\n            self.age.extend_from_slice(&other.age);\n            self.gender.extend_from_slice(&other.gender);\n            Ok(())\n        } else {\n            Err(AppErrors::IncompatibleAttributes.into())\n        }\n    }\n\n    fn baked(&self, _observations: &ObservationsDb<f32>) -> Result<TrackStatus> {\n        let now = current_time_ms();\n        if now > self.end_time + self.baked_period_ms {\n            Ok(TrackStatus::Ready)\n        } else {\n            Ok(TrackStatus::Pending)\n        }\n    }\n}\n\n#[derive(Clone)]\npub struct CamTrackingAttributesMetric {\n    merge_extension: f32,\n    initial_capacity: u64,\n    max_capacity: u64,\n}\n\nimpl Default for CamTrackingAttributesMetric {\n    fn default() -> Self {\n        Self {\n            merge_extension: 1.5,\n            initial_capacity: 4,\n            max_capacity: 12,\n        }\n    }\n}\n\nimpl ObservationMetric<CamTrackingAttributes, f32> for CamTrackingAttributesMetric {\n    fn metric(&self, mq: &MetricQuery<CamTrackingAttributes, f32>) -> MetricOutput<f32> {\n        let (e1, e2) = (mq.candidate_observation, mq.track_observation);\n        Some((\n            f32::calculate_metric_object(&e1.attr().as_ref(), &e2.attr().as_ref()),\n            match (e1.feature().as_ref(), e2.feature().as_ref()) {\n                (Some(x), Some(y)) => Some(euclidean(x, y)),\n                _ => None,\n            },\n        ))\n    }\n\n    fn optimize(\n        &mut self,\n        _feature_class: u64,\n        merge_history: &[u64],\n        _attrs: &mut CamTrackingAttributes,\n        features: &mut Vec<Observation<f32>>,\n        _prev_length: usize,\n        _is_merge: bool,\n    ) -> Result<()> {\n        let merges = merge_history.len();\n        let mut current_capacity =\n            (self.initial_capacity as f32 * self.merge_extension.powf(merges as f32)) as u64;\n        if current_capacity > self.max_capacity {\n            current_capacity = self.max_capacity\n        }\n        features.sort_by(|l, r| r.attr().partial_cmp(l.attr()).unwrap());\n        features.truncate(current_capacity as usize);\n        Ok(())\n    }\n}\n\nstruct TrackObservation {\n    pub track_id: u64,\n    pub age: Option<u8>,\n    pub gender: Option<Gender>,\n    pub camera_id: u64,\n    pub screen_pos: (u16, u16),\n    pub class: u64,\n    pub feature: Observation<f32>,\n}\n\nimpl TrackObservation {\n    pub fn new(\n        track_id: u64,\n        age: Option<u8>,\n        gender: Option<Gender>,\n        camera_id: u64,\n        screen_pos: (u16, u16),\n        feature: Observation<f32>,\n    ) -> Self {\n        Self {\n            track_id,\n            age,\n            gender,\n            camera_id,\n            screen_pos,\n            feature,\n            class: FEATURE0,\n        }\n    }\n}\n\nfn main() {\n    let drift = 0.01;\n    let mut p1 = FeatGen2::new(1.0, 0.0, drift);\n    let mut p2 = FeatGen2::new(1.0, 1.0, drift);\n\n    let m = Some(Male);\n    let f = Some(Female);\n    let observations = vec![\n        // track 1 (person 1)\n        TrackObservation::new(1, Some(13), f, 1, (30, 30), p1.next().unwrap()),\n        TrackObservation::new(1, Some(17), m, 1, (35, 30), p1.next().unwrap()),\n        TrackObservation::new(1, Some(23), m, 1, (35, 35), p1.next().unwrap()),\n        TrackObservation::new(1, None, None, 1, (40, 35), p1.next().unwrap()),\n        TrackObservation::new(1, Some(18), m, 1, (40, 40), p1.next().unwrap()),\n        // track 2 (person 2)\n        TrackObservation::new(2, Some(46), f, 1, (100, 100), p2.next().unwrap()),\n        TrackObservation::new(2, Some(30), f, 1, (135, 130), p2.next().unwrap()),\n        TrackObservation::new(2, Some(40), f, 1, (135, 135), p2.next().unwrap()),\n        TrackObservation::new(2, None, None, 1, (140, 135), p2.next().unwrap()),\n        TrackObservation::new(2, Some(54), m, 1, (140, 140), p2.next().unwrap()),\n        // track 3 (person 1)\n        TrackObservation::new(3, Some(18), f, 1, (50, 40), p1.next().unwrap()),\n        TrackObservation::new(3, Some(17), m, 1, (55, 50), p1.next().unwrap()),\n        TrackObservation::new(3, Some(20), m, 1, (65, 55), p1.next().unwrap()),\n        TrackObservation::new(3, Some(17), None, 1, (70, 50), p1.next().unwrap()),\n        TrackObservation::new(3, None, m, 1, (75, 55), p1.next().unwrap()),\n        // track 4 (person 2)\n        TrackObservation::new(4, Some(48), f, 1, (150, 140), p2.next().unwrap()),\n        TrackObservation::new(4, Some(47), f, 1, (155, 150), p2.next().unwrap()),\n        TrackObservation::new(4, Some(30), m, 1, (165, 155), p2.next().unwrap()),\n        TrackObservation::new(4, Some(57), None, 1, (170, 150), p2.next().unwrap()),\n        TrackObservation::new(4, None, f, 1, (175, 155), p2.next().unwrap()),\n        // track 5 (person 1)\n        TrackObservation::new(5, None, None, 1, (80, 55), p1.next().unwrap()),\n        TrackObservation::new(5, None, None, 1, (85, 60), p1.next().unwrap()),\n        TrackObservation::new(5, None, None, 1, (90, 65), p1.next().unwrap()),\n        TrackObservation::new(5, None, None, 1, (90, 50), p1.next().unwrap()),\n        TrackObservation::new(5, None, m, 1, (90, 50), p1.next().unwrap()),\n    ];\n\n    // collect tracks here until they are initially ready\n    let mut temp_store = TrackStore::new(\n        CamTrackingAttributesMetric::default(),\n        CamTrackingAttributes {\n            baked_period_ms: 20,\n            ..Default::default()\n        },\n        NoopNotifier,\n        1,\n    );\n\n    // merge tracks here until they are initially complete\n    let merge_store_baked_period_ms = 60;\n    let mut merge_store: TrackStore<CamTrackingAttributes, CamTrackingAttributesMetric, f32> =\n        TrackStore::new(\n            CamTrackingAttributesMetric::default(),\n            CamTrackingAttributes {\n                baked_period_ms: merge_store_baked_period_ms,\n                ..Default::default()\n            },\n            NoopNotifier,\n            1,\n        );\n    let voting_machine: TopNVoting<f32> = TopNVoting::new(1, 0.1, 3);\n\n    let mut idx = 0;\n    loop {\n        if let Some(TrackObservation {\n            track_id,\n            age,\n            gender,\n            camera_id,\n            screen_pos,\n            class,\n            feature,\n        }) = observations.get(idx)\n        {\n            let update = CamTrackingAttributesUpdate::DataUpdate {\n                time: current_time_ms(),\n                gender: *gender,\n                age: *age,\n                camera_id: *camera_id,\n                screen_pos: *screen_pos,\n            };\n            temp_store\n                .add(\n                    *track_id,\n                    *class,\n                    *feature.attr(),\n                    feature.feature().clone(),\n                    Some(update),\n                )\n                .unwrap();\n        }\n        idx += 1;\n\n        thread::sleep(Duration::from_millis(1));\n        let baked = temp_store.find_usable();\n        for (id, s) in baked {\n            let mut track = temp_store.fetch_tracks(&[id]).pop().unwrap();\n            if let Ok(TrackStatus::Ready) = s {\n                let search_track = track.clone();\n                track\n                    .add_observation(\n                        0,\n                        None,\n                        None,\n                        Some(CamTrackingAttributesUpdate::BakedPeriodUpdate(0)),\n                    )\n                    .unwrap();\n\n                let (dists, _errs) =\n                    merge_store.foreign_track_distances(vec![search_track], FEATURE0, false);\n                let dists = dists.all();\n\n                let mut winners = voting_machine.winners(dists);\n                if winners.is_empty() {\n                    let _track_id = merge_store.add_track(track).unwrap();\n                } else {\n                    let winner = winners\n                        .get_mut(&track.get_track_id())\n                        .unwrap()\n                        .pop()\n                        .unwrap();\n\n                    merge_store\n                        .merge_external(winner.winner_track, &track, Some(&[FEATURE0]), true)\n                        .unwrap();\n                }\n            }\n        }\n\n        if idx > 100 {\n            break;\n        }\n    }\n\n    let baked = merge_store.find_usable();\n    for (id, s) in baked {\n        if let Ok(TrackStatus::Ready) = s {\n            let track = merge_store.fetch_tracks(&[id]).pop().unwrap();\n            eprintln!(\n                \"Composite Track is ready: {}, age: {:?}, gender: {:?}\\nCoordinates: {:?}\",\n                track.get_track_id(),\n                track.get_attributes().get_age(),\n                track.get_attributes().get_gender(),\n                track.get_attributes().screen_pos\n            );\n            eprintln!(\"Merge history: {:?}\", track.get_merge_history());\n        }\n    }\n}\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"maturin>=1.8,<1.9\"]\nbuild-backend = \"maturin\"\n\n[project]\nname = \"similari-trackers-rs\"\nrequires-python = \">=3.8\"\nclassifiers = [\n    \"Programming Language :: Rust\",\n    \"Programming Language :: Python :: Implementation :: CPython\",\n    \"Programming Language :: Python :: Implementation :: PyPy\",\n]\n\n[tool.black]\nskip-string-normalization = true\n\n[tool.pylint.messages_control]\nmax-line-length = 88\n\n[tool.maturin]\npython-source = \"python\"\n"
  },
  {
    "path": "python/bb.py",
    "content": "from similari import nms, BoundingBox, Universal2DBox\n\nif __name__ == '__main__':\n    bb = BoundingBox(left=1.0, top=2.0, width=10.0, height=15.0)\n    print(bb)\n\n    bb = BoundingBox(1.0, 2.0, 10.0, 15.0)\n    print(bb.left, bb.top, bb.width, bb.height)\n\n    bb = BoundingBox.new_with_confidence(1.0, 2.0, 10.0, 15.0, 0.95)\n\n    universal_bb = bb.as_xyaah()\n    print(universal_bb)\n\n    ubb = Universal2DBox(xc=3.0, yc=4.0, angle=0.0, aspect=1.5, height=5.0)\n    print(ubb)\n\n    ubb = Universal2DBox.new_with_confidence(xc=3.0, yc=4.0, angle=0.0, aspect=1.5, height=5.0, confidence=0.85)\n    print(ubb)\n\n    ubb = Universal2DBox(3.0, 4.0, 0.0, 1.5, 5.0)\n    print(ubb)\n    ubb.rotate(0.5)\n\n    polygon = ubb.get_vertices()\n    points = polygon.get_points()\n    print(\"Points\", points)\n\n    print(ubb)\n    print(ubb.area())\n    print(ubb.get_radius())\n"
  },
  {
    "path": "python/bugfixes/bug_vs_1/bug_visual_sort.py",
    "content": "import pathlib\nimport collections\nimport json\nfrom similari import (\n    VisualSort,\n    SpatioTemporalConstraints,\n    PositionalMetricType,\n    VisualSortOptions,\n    VisualSortMetricType,\n    Universal2DBox, VisualSortObservation, VisualSortObservationSet\n)\n\n\ndef main():\n    data_dir_path = pathlib.Path('.') / 'in'\n    out_dir_path = pathlib.Path('.') / 'out'\n    log_file_path = out_dir_path / 'log.txt'\n\n    constraints = SpatioTemporalConstraints()\n    constraints.add_constraints([(1, 1.0)])\n\n    opts = VisualSortOptions()\n    opts.spatio_temporal_constraints(constraints)\n    opts.max_idle_epochs(3)\n    opts.kept_history_length(10)\n    # opts.visual_metric(VisualSortMetricType.cosine(0.2))\n    opts.visual_metric(VisualSortMetricType.euclidean(1.0))\n    opts.positional_metric(PositionalMetricType.maha())\n    opts.visual_minimal_track_length(1)\n    opts.visual_minimal_area(5.0)\n    opts.visual_minimal_quality_use(0.45)\n    opts.visual_minimal_quality_collect(0.5)\n    opts.visual_max_observations(5)\n    opts.visual_min_votes(1)\n\n    tracker = VisualSort(shards=4, opts=opts)\n\n    with open(log_file_path, 'w', encoding='utf8') as log_file:\n\n        for filepath in sorted(data_dir_path.glob('*.json')):\n\n            with open(filepath, 'r', encoding='utf8') as objfile:\n                objs = json.load(objfile)\n\n            observation_set = VisualSortObservationSet()\n\n            for obj in objs:\n                bbox = Universal2DBox.new_with_confidence(\n                    xc=obj['bbox']['xc'],\n                    yc=obj['bbox']['yc'],\n                    angle=obj['bbox']['angle'],\n                    aspect=obj['bbox']['aspect'],\n                    height=obj['bbox']['height'],\n                    confidence=obj['bbox']['confidence']\n                )\n                observation = VisualSortObservation(\n                    feature=obj['feature'],\n                    feature_quality=obj['feature_quality'],\n                    bounding_box=bbox,\n                    custom_object_id=None,\n                )\n                observation_set.add(observation)\n\n            tracks = tracker.predict(observation_set)\n\n            log_file.write(f'===={filepath.name}====\\n')\n\n            for track in tracks:\n                log_file.write(str(track) + '\\n')\n\n            count = collections.Counter((track.id for track in tracks))\n            trk_id, id_count = count.most_common(1)[0]\n            assert id_count == 1, f'{filepath.name} trk id {trk_id} count {id_count}'\n\n\nif __name__ == '__main__':\n    main()"
  },
  {
    "path": "python/bugfixes/bug_vs_1/in/fixed-1/bug_vs_1.json",
    "content": "[{\"bbox\": {\"xc\": 305.5, \"yc\": 933.5, \"angle\": null, \"aspect\": 0.467576801776886, \"height\": 293.0, \"confidence\": 0.92041015625}, \"feature\": [0.0, 0.04798045472202683, 0.0, 0.010914579055066775, 0.0, 0.02977623176387587, 0.0, 0.0, 0.0, 0.0, 0.016284260035054694, 0.001173857471064838, 0.024717401498412485, 0.040369157426764106, 0.04385612708360236, 0.0, 0.00763728408010481, 0.010529785203135526, 0.022386300025399663, 0.0, 0.07306464790025802, 0.032678640495372016, 0.045982797731782876, 0.10017310160950363, 0.033819562226336475, 0.05667408136239487, 0.06454582489371787, 0.0, 0.08559382983784407, 0.015917139647805265, 0.04438150921819147, 0.0, 0.13213185679705566, 0.0, 0.11380987411551315, 0.0, 0.0, 0.07089769856593077, 0.0, 0.04676114027745912, 0.035688762185164216, 0.0, 0.0, 0.0, 0.0, 0.0, 0.012307968708633292, 0.05840991314882036, 0.0, 0.05707684073613281, 0.04391609614657561, 0.0036140039119275363, 0.0, 0.0017013437054403237, 0.09755632474613003, 0.0, 0.02079875537650801, 0.0681918752031675, 0.07608407418645195, 0.04258694989790395, 0.08175267620640501, 0.05978065291336169, 0.08315479779534411, 0.03736043532052716, 0.018018025660486985, 0.0, 0.023677018428370816, 0.03757923535590306, 0.0, 0.0, 0.05291462126273113, 0.035643801422443104, 0.1332258592645793, 0.055302516965971556, 0.0, 0.04317316237765416, 0.08369497749092662, 0.0, 0.0, 0.05152964285436086, 0.0, 0.02019598153979461, 0.033465479040076816, 0.024514141192723767, 0.03393330874123572, 0.028203438863095284, 0.0, 0.0023781261060830186, 0.010773334502869502, 0.0, 0.06717414660343925, 0.0, 0.019005050466475992, 0.0, 0.05265358404048878, 0.05566288567968723, 0.01563373428576407, 0.0904854545360144, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.034273293012957555, 0.0, 0.09738356436685418, 0.0, 0.029846648454665038, 0.0, 0.07898508333439283, 0.0, 0.02090804658860042, 0.027124174399818016, 0.01718084792067523, 0.03770993721855356, 0.03183878270194381, 0.0, 0.030255938454779474, 0.05970738895193555, 0.0, 0.08853050889596904, 0.0, 0.004265377199541311, 0.051174954938054414, 0.05758486895089437, 0.050563921038656465, 0.08013234785891624, 0.014568835597064218, 0.06197623071785851, 0.0, 0.02126713941354314, 0.0, 0.0, 0.02587417677730811, 0.036736351621596795, 0.0, 0.0, 0.06412660495135786, 0.0, 0.03523902857252665, 0.019936962375019008, 0.10404500484756425, 0.016100485093544045, 0.0023240647574518094, 0.03967828541732531, 0.06631066539723629, 0.18366813335677828, 0.029498974490643697, 0.0, 0.0, 0.0, 0.051570004003695796, 0.0048893048483412286, 0.008482605059751165, 0.10117455289213914, 0.0, 0.01254778425845717, 0.021502819205414452, 0.01537549966659841, 0.030642212062810065, 0.024482248554688, 0.06710612821704018, 0.0, 0.04890353390685614, 0.0, 0.028633729488470937, 0.0, 0.0, 0.0, 0.09452611410743503, 0.07160461424676241, 0.012649880557379263, 0.05839732376875539, 0.0, 0.009919679014604808, 0.08434665657952996, 0.14412301224452886, 0.0, 0.08173372799827229, 0.01512453898782509, 0.0, 0.0, 0.0, 0.0, 0.041593896116599985, 0.0, 0.031920918328033634, 0.07112939263703916, 0.01776808546366762, 0.017306656967492624, 0.02312175484128033, 0.0, 0.03148485069819585, 0.1704454176812899, 0.11066558939981021, 0.0, 0.03368551373263158, 0.004243474919489686, 0.004622862851305701, 0.07495218446523934, 0.058133198758243826, 0.0, 0.01330028833849788, 0.004341696307557994, 0.03244069525646936, 0.005945561509450083, 0.018834998773868045, 0.0, 0.0, 0.019548191964901297, 0.01989655083151351, 0.0, 0.0, 0.028337345336864946, 0.02432021984309853, 0.013598565707466261, 0.0, 0.08048496045165301, 0.0, 0.0874426636798309, 0.005366505001920218, 0.0, 0.0724773450739083, 0.02828072519559021, 0.02372884196086534, 0.01797419074934919, 0.08768101894403044, 0.039205794255389066, 0.0, 0.0, 0.013598629845501527, 0.014610157671606075, 0.09186486209789305, 0.0, 0.058247030026975385, 0.0, 0.0, 0.09091549089989312, 0.0, 0.03150206947002052, 0.06254181365669438, 0.01225169789058634, 0.0038969863637986985, 0.0, 0.0, 0.07857200689955361, 0.01602740438364724, 0.027450364412669532, 0.0, 0.09409516148590967, 0.015817321394457153, 0.0030017662790463, 0.01590380466308033, 0.014643952689580702, 0.005670816500152819, 0.0, 0.006660354142737766, 0.0007245688160469242, 0.14461072702726419, 0.0, 0.043314957829761476, 0.0, 0.06667924377946595, 0.0007543604323493484, 0.12349680650803797, 0.04236547209955572, 0.03472676266614934, 0.04596606228586681, 0.025300778160746308, 0.10181186506084301, 0.07550152673728834, 0.024127136869219974, 0.03447111074822517, 0.106297422695171, 0.0, 0.0, 0.030828794188685636, 0.029723702712993426, 0.0, 0.04974285798020517, 0.0, 0.06767012144885912, 0.0, 0.0585409792238854, 0.00974117713585063, 0.03188282949766239, 0.02527041109169221, 0.031831777912235175, 0.02482376526732385, 0.02887667062219195, 0.01964763340729222, 0.016230579935503645, 0.04101439980539973, 0.04034218738293496, 0.029465799091902615, 0.0, 0.02656306279829779, 0.012862081247055169, 0.03662773614951879, 0.0, 0.0, 0.0, 0.0, 0.1689107044483041, 0.006577409346613747, 0.10716671297485374, 0.014843271942066767, 0.0, 0.00704429702037969, 0.0, 0.019127846170957967, 0.0, 0.02667560214374758, 0.0, 0.01873506942428028, 0.0008894500953878777, 0.0, 0.0, 0.07156702019552036, 0.028845815645941013, 0.0, 0.04190256499260344, 0.0, 0.03521943440275304, 0.0, 0.0, 0.01634890773863614, 0.0, 0.005429504014398726, 0.041520178607638764, 0.0, 0.04583373635653762, 0.0, 0.05030235696826729, 0.057548301108216564, 0.002820136812081385, 0.0, 0.01824311465928508, 0.04750592030553857, 0.0, 0.03953371828583687, 0.1457659171804311, 0.0, 0.1574744529567951, 0.04542473268693776, 0.04453916509016231, 0.04181505780606013, 0.007604693368274499, 0.0, 0.07679844362323908, 0.042031390817722426, 0.02403922652931214, 0.0, 0.050766643042977774, 0.0, 0.018818086948355003, 0.0, 0.0, 0.0, 0.0, 0.006879248094522451, 0.06746084362107603, 0.0, 0.0, 0.0, 0.0, 0.07956918842155575, 0.07032394719017501, 0.00333943327791365, 0.004845725916683467, 0.044120930124772445, 0.0119437814911752, 0.012848116335198164, 0.0357715941670655, 0.16278488070009622, 0.0, 0.03431682899503813, 0.03760851436900176, 0.021957654082499743, 0.0, 0.045737465165604105, 0.007419497082089485, 0.12490138366747008, 0.0, 0.010618052883525552, 0.0, 0.00033674182918343436, 0.04693589809840467, 0.03241089397651207, 0.0, 0.07803741637561568, 0.023464771925812575, 0.007252182709200904, 0.0, 0.051041066789437685, 0.0, 0.014054501239049591, 0.0, 0.0, 0.0625707628170403, 0.0, 0.0, 0.02199343165295731, 0.04421938659019322, 0.0, 0.015833099351132467, 0.026396647503224985, 0.027994110641141625, 0.04817055069597731, 0.0, 0.011087873154421805, 0.0, 0.09860018959522898, 0.1292100852508417, 0.0, 0.02441519911075027, 0.007966643199091676, 0.004957345568520429, 0.03505209826874535, 0.09908119737199082, 0.08407592993267428, 0.0, 0.02512772686966764, 0.0, 0.0, 0.07648374577192067, 0.049191362502686534, 0.0, 0.0, 0.0, 0.0, 0.05454482228636257, 0.0, 0.024788574101760143, 0.014086627522785257, 0.04567804127593062, 0.007425916612226326, 0.018995382802981776, 0.016798568050661978, 0.007535047479230574, 0.067286488953495, 0.03415200798827049, 0.012372275106241363, 0.0, 0.0012604419575249178, 0.006389362353825593, 0.03071875164532297, 0.0, 0.0, 0.0, 0.030321237846612104, 0.0034120109050963404, 0.029150214761311074, 0.045832723891838074, 0.02442711504144495, 0.043250187576719785, 0.0, 0.013013175568953927, 0.0, 0.0, 0.0, 0.0, 0.001317269402092209, 0.028104645672989363, 0.014031205388383516, 0.016918469526302597, 0.0, 0.059215825887084295, 0.0, 0.01990825602294946, 0.0, 0.023160982121777536, 0.013936473510296372, 0.0, 0.0, 0.003884981670644496, 0.027189757831521107, 0.0, 0.008177628129402684, 0.0, 0.0, 0.0, 0.016660475424769183, 0.0, 0.07348575991465835, 0.03219229551781843, 0.0, 0.0, 0.02837258918724334, 0.02280512368632749, 0.11993205115828735, 0.05257386962523023, 0.03774251704982432, 0.014440543492024402, 0.04558809226275899, 0.013454894217827425, 0.027534130977370393, 0.009872885736590392, 0.060455861498331, 0.0575815108666194, 0.08000787425761882, 0.018070400092891556, 0.12929152223047596, 0.019591276690090895, 0.007744143200806395, 0.12134473719789232, 0.0, 0.018444960492231783, 0.0, 0.046054467404903834, 0.0], \"feature_quality\": 0.92041015625}, {\"bbox\": {\"xc\": 323.0, \"yc\": 391.5, \"angle\": null, \"aspect\": 0.42487046122550964, \"height\": 193.0, \"confidence\": 0.8486328125}, \"feature\": [0.006122838423213256, 0.0, 0.0, 0.0766355041319142, 0.037614528851285516, 0.0, 0.0, 0.039276633052557394, 0.0, 0.025147595494320156, 0.09173169794424595, 0.0, 0.001453311857910955, 0.0, 0.10663431158003096, 0.009304692591093445, 0.10919504293094608, 0.0, 0.0, 0.0, 0.0466677063944322, 0.002153724353981306, 0.01592853446955818, 0.030088273415634262, 0.0, 0.0, 0.0, 0.0, 0.002997262982469633, 0.12451182134281802, 0.0, 0.029531433720088946, 0.0, 0.045573733483929735, 0.020243500363612315, 0.01942410344640321, 0.0, 0.0, 0.022742901656490292, 0.014439456076556095, 0.0, 0.00538509614457412, 0.004376631180414201, 0.0, 0.0, 0.1418337566970053, 0.003694078318656237, 0.061087093856550194, 0.09412000122589657, 0.0, 0.0, 0.034211491617683436, 0.0, 0.030053508566041665, 0.03153635214854327, 0.06191777946027778, 0.0, 0.0, 0.028456213234730687, 0.0, 0.018055020795125783, 0.054840828873331283, 0.0, 0.00609382990204634, 0.017521921954866175, 0.02337442236887714, 0.0, 0.007864673154286236, 0.0, 0.016214399149370017, 0.0, 0.03150120916721983, 0.0252247256412953, 0.09484052833031266, 0.01210262015935526, 0.08531960183611294, 0.014123806251176301, 0.011264079596095815, 0.0, 0.09660270363979939, 0.061230399965128524, 0.030126645060197983, 0.1108926217059823, 0.0493157599968215, 0.008777339171613436, 0.019197553091632565, 0.005210541811500034, 0.019535733750759367, 0.009381416246457934, 0.028007266156817423, 0.020924901014355814, 0.012563922691573826, 0.0585187591491395, 0.00296869785711595, 0.006380700994003344, 0.04708787764762829, 0.2033081214374626, 0.02364412191719543, 0.04965489180268665, 0.014681467656059797, 0.0, 0.03862288110310259, 0.04785603480660252, 0.07305416033498582, 0.03756743981597305, 0.013473109896977269, 0.0022408415417091467, 0.005786183743773326, 0.0, 0.07414027683160361, 0.0, 0.001965695805702106, 0.08738878520657688, 0.009574855350782016, 0.007276231099335698, 0.008894373123838622, 0.02670775828966233, 0.11022116446540715, 0.0, 0.0, 0.028223129927133318, 0.11746665709090734, 0.02184942000709214, 0.0, 0.013549834279518166, 0.04738478086527513, 0.0, 0.029277090683101324, 0.0328136142351023, 0.0, 0.11792582536037585, 0.0, 0.09496016920995146, 0.01589457533142284, 0.04368494803613422, 0.0, 0.025818534985294597, 0.020489251084192934, 0.04023727380605892, 0.047485215561692704, 0.0003331081492134138, 0.058882556778678125, 0.0, 0.00711767319282909, 0.0, 0.02346366436606176, 0.02686160554741432, 0.02658108706853548, 0.04859850227752439, 0.0, 0.0, 0.0, 0.06527205806154043, 0.0, 0.040024679420862005, 0.03766290644318847, 0.007704686346142883, 0.13639269705626636, 0.021551981587610878, 0.026941941087984766, 0.006301976876347411, 0.0, 0.0, 0.008979273877882598, 0.016729205139973682, 0.10538615109321875, 0.06440805614361994, 0.05571570935027757, 0.005156384257938219, 0.0, 0.01804480251227615, 0.0, 0.010500134242862055, 0.0, 0.0, 0.007670182552879847, 0.005105751328413706, 0.07162196604347784, 0.0, 0.05289296179694433, 0.0, 0.0, 0.008146443103758883, 0.012653154508288767, 0.025532425974210522, 0.0, 0.01950017627888481, 0.013634169290318687, 0.004383148498948205, 0.0, 0.028498531992823286, 0.012072547051930739, 0.0, 0.10095570376856511, 0.06517979973662594, 0.039803129131055354, 0.0, 0.0, 0.01955464470035996, 0.033317006464845735, 0.0, 0.06439793966546709, 0.0, 0.05999264682703137, 0.012047244221726084, 0.030211730517048948, 0.0, 0.030607564630306852, 0.0, 0.029405038825996176, 0.0, 0.0, 0.03481317191909551, 0.007878055381676057, 0.045651454098146124, 0.0, 0.028484962881097257, 0.0, 0.05252387614058515, 0.08002558889938208, 0.0, 0.05576299908627795, 0.0, 0.07722637277452978, 0.007894638639602748, 0.0, 0.019648387919494425, 0.03137901735275066, 0.0426434859882234, 0.1369906571231881, 0.0006864712972673514, 0.05210319004649398, 0.062000543770042486, 0.009257220333815297, 0.0, 0.030480826508950697, 0.0, 0.0, 0.042048891293706196, 0.15804175087659594, 0.0, 0.022383061320950227, 0.0856750514805168, 0.008828160803415168, 0.0, 0.060870459279244504, 0.07412556459856819, 0.008965863290612962, 0.0, 0.0, 0.031347204839364255, 0.04457227195573088, 0.018920596322787708, 0.0, 0.0019293540740742503, 0.037965935394874965, 0.018210259868517837, 0.0, 0.0, 0.027211121432349553, 0.0, 0.056602463163572335, 0.0, 0.0, 0.0, 0.02422280890066662, 0.022199148227537772, 0.029019242655839345, 0.17625386649930053, 0.0, 0.049900933393829464, 0.0468666007761501, 0.15612973068829292, 0.030382314466949178, 0.033407478844508405, 0.00039320391619932817, 0.0, 0.004249620004317794, 0.0797563009329093, 0.033634526587940396, 0.0, 0.019215091132179635, 0.05150022700590266, 0.03810090515314101, 0.0, 0.022705484067370545, 0.0, 0.0, 0.0, 0.03531701208950472, 0.019322042783543013, 0.009097302607430463, 0.0, 0.0, 0.00730188079268576, 0.019659289748165196, 0.06454205439420829, 0.0, 0.0031942027140096367, 0.0, 0.005243554893131861, 0.0029552100073532337, 0.03516702468411295, 0.020831086531933617, 0.018110467996042694, 0.0, 0.037913631050382486, 0.012332045042456722, 0.05223130107690366, 0.0, 0.05727062193197227, 0.05293023976819423, 0.0, 0.0, 0.0, 0.047517650538082185, 0.050112501005941855, 0.0, 0.011987177269105236, 0.0400181086548622, 0.0, 0.09876922975260485, 0.0, 0.04814474710921817, 0.0, 0.0, 0.0, 0.0, 0.03796412617997816, 0.01700946328969936, 0.01109961192694197, 0.02181775292898683, 0.14260634381464163, 0.026725783538401063, 0.11543592680876452, 0.041532348805846275, 0.009055375070244008, 0.06544085606618884, 0.0, 0.02517684834675931, 0.0, 0.03956634594990696, 0.0, 0.0, 0.11882121802936757, 0.04358581643983525, 0.13318591891708878, 0.045952519673537855, 0.08992479837711734, 0.015065725120942657, 0.0, 0.030726658673288727, 0.06644903088696298, 0.012261008633759394, 0.027072547787818008, 0.013693736672748978, 0.03350027237125733, 0.05905743397807624, 0.04984223571438011, 0.0, 0.003781601997994734, 0.023602988456643745, 0.0, 0.0, 0.0, 0.13446381801017898, 0.0, 0.028711484148811688, 0.007131178313031436, 0.016136157876847315, 0.016967282695123802, 0.006540976491179667, 0.0, 0.0061125975978950555, 0.0, 0.07988789659265397, 0.03140416020414611, 0.011771471296069327, 0.005856732580690788, 0.0, 0.0, 0.016149197604150164, 0.06125116812326868, 0.07181024655838154, 0.011767314028559269, 0.010851489884103123, 0.017958046004045422, 0.029658587786349028, 0.013482611183891111, 0.02204083319795059, 0.12465510418175138, 0.058131848944729496, 0.012989573946161528, 0.06757701529230198, 0.02249190217661501, 0.028816104472619133, 0.0, 0.01931606684784288, 0.011517928153127719, 0.0, 0.014607729059839751, 0.0, 0.09808923255232391, 0.029435652952666366, 0.042507134594786965, 0.025168634162083148, 0.0, 0.041378652800786626, 0.0, 0.0, 0.14086407568643097, 0.0, 0.0, 0.0, 0.0, 0.0, 0.04064179331430373, 0.07175818072775006, 0.0, 0.05758653644956366, 0.0, 0.001547650634521495, 0.005676133957334454, 0.003053225751458021, 0.006184783672489427, 0.0004504804657097696, 0.018689223334095995, 0.022595388105229926, 0.06940890645813369, 0.10455583198639952, 0.01954127338061622, 0.02152413363998709, 0.0, 0.068505590475028, 0.0, 0.020550774571153934, 0.04045142434876353, 0.0, 0.0, 0.0460969049119019, 0.0, 0.05863714928536089, 6.427390673123632e-05, 0.017681367016587454, 0.0, 0.0, 0.01155145753000743, 0.03570075762219813, 0.0, 0.0, 0.036403544531026434, 0.024167136275064012, 0.0162123485119066, 0.0, 0.0811006174101693, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0028311967977824697, 0.0, 0.019789228900058218, 0.0513420341419516, 0.0, 0.0, 0.010800786054879077, 0.0, 0.08412937113046591, 0.04235233909900225, 0.1300398163771834, 0.03862185723872369, 0.0, 0.012118559138986707, 0.02626082112733378, 0.0, 0.08237795803178011, 0.019777082145381285, 0.0, 0.018975649394092856, 0.0, 0.01821098268186487, 0.05686578246610906, 0.006293175860312036, 0.011901793669928032, 0.057512312099504755, 0.0, 0.09164707788029448, 0.0, 0.021504302085057173, 0.022878547688813426, 0.0, 0.007758600659197069, 0.07935199085146925, 0.03458069071356187, 0.055608628261514026, 0.1607466958522434, 0.0, 0.07377720637847045, 0.0, 0.02363828850807077, 0.1427361069898445, 0.06435634517507433, 0.05122164281626237, 0.03827805986904105, 0.016169526547741418, 0.055760084563244834, 0.05780365968941382, 0.015562142274604852, 0.0, 0.07432198367180187, 0.0374062568026427], \"feature_quality\": 0.8486328125}, {\"bbox\": {\"xc\": 332.0, \"yc\": 330.5, \"angle\": null, \"aspect\": 0.47058823704719543, \"height\": 187.0, \"confidence\": 0.6943359375}, \"feature\": [0.02620238825780028, 0.0, 0.011925813060074284, 0.08029330925475944, 0.014345740988649547, 0.0, 0.03616348150450254, 0.04287531038462128, 0.0, 0.03319846873014149, 0.018516378900507145, 0.006509543280545765, 0.026449856816343667, 0.03967817218427939, 0.0803840667635659, 0.055657843530713014, 0.01988912899497622, 0.00633838734744727, 0.0, 0.0, 0.01450586189115482, 0.0, 0.12937189608923877, 0.09965828965896847, 0.0, 0.04500444173149082, 0.0745727474674009, 0.0, 0.008108444329281261, 0.050308505064384064, 0.009031549308628865, 0.0, 0.04168193420940139, 0.04747916587356377, 0.09792524584575306, 0.040704624619986925, 0.03716849162930361, 0.0, 0.0, 0.005048225573880291, 0.0, 0.02659999395911804, 0.021308169949341677, 0.07404483960432375, 0.03788201115289556, 0.003611888954651547, 0.0159418713813989, 0.0, 0.03461916761998724, 0.01750875174382876, 0.05635315950108093, 0.0, 0.0, 0.02159459517209209, 0.0, 0.0, 0.0, 0.06242017678844223, 0.06289863094232374, 0.02101421654281155, 0.10138850919837153, 0.04994264111618612, 0.0, 0.011865715308703562, 0.08143637383556425, 0.0, 0.04784282245417457, 0.023448451394274178, 0.007386440537749995, 0.0, 0.008142748894004704, 0.06186874455594845, 0.053282012348291684, 0.04074661279994136, 0.0, 0.10116709769847411, 0.0039712314088181724, 0.013637469987551895, 0.0, 0.06740287053463194, 0.06718549784267779, 0.0, 0.09070357770754443, 0.04902457380271287, 0.015082361963523248, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.06746555881019062, 0.0, 0.022794742857049813, 0.0784982760050686, 0.013272366328080746, 0.039556318746057646, 0.005487141099283725, 0.0, 0.0, 0.0, 0.039889961392937254, 0.07166300322536623, 0.0, 0.07029446905840118, 0.014203807975733694, 0.0032033220225575467, 0.02280556522368063, 0.07904838767267401, 0.0, 0.05753803875696431, 0.044495441080300846, 0.0, 0.0, 0.08702088852194098, 0.0180513039003852, 0.036440260338113976, 0.02186591101190436, 0.03125571053740851, 0.0072162430986010455, 0.05439955725360765, 0.05666474534061877, 0.02346498737285816, 0.03883987855705564, 0.055028141320893724, 0.028996585481892188, 0.03493873950339383, 0.005408936788734137, 0.025185189415471126, 0.09693303893491537, 0.0, 0.01517822822700735, 0.02914390076064414, 0.02259708706640346, 0.01724551592969298, 0.0, 0.0, 0.0766418027510092, 0.03629416200328288, 0.01564509972553127, 0.0652106508870733, 0.06557410986490883, 0.06349444668597459, 0.0, 0.01558965045895922, 0.0349392792963409, 0.030253594570328137, 0.012225601772949658, 0.0, 0.0, 0.006771907353169921, 0.13893317144745623, 0.0, 0.06804635674292112, 0.002938057165290362, 0.0, 0.05789892889872137, 0.0, 0.03291868729826196, 0.0008130833085187575, 0.0, 0.04236155762766512, 0.05327017064051528, 0.06258203273970595, 0.03834744039213902, 0.05316237591681685, 0.0, 0.0, 0.013907367665983128, 0.0, 0.0, 0.0, 0.04238466751321166, 0.0, 0.036237035522911, 0.005745767587707392, 0.1220524965101307, 0.005927879028331014, 0.014132539643085496, 0.03371738325715403, 0.0, 0.0014856123051923931, 0.0, 1.7087573383163114e-05, 0.0, 0.015658099337374942, 0.032836612262617575, 0.010981750137598601, 0.0, 0.026385638324171633, 0.056312636473411413, 0.05487354847651606, 0.08851256777453682, 0.0, 0.029952004539470903, 0.0, 0.0, 0.06930526956541147, 0.11076847196133126, 0.0, 0.07216310211250938, 0.0, 0.0, 0.028859894966995783, 0.03598153031491008, 0.043072050455089564, 0.08976211133373156, 0.0, 0.022566755040399882, 0.024309168116910918, 0.04340668352563545, 0.0, 0.03575395698213023, 0.0482330011828902, 0.028011619989831076, 0.15754280319425507, 0.0, 0.00628414779542625, 0.10621486541226537, 0.0, 0.06099494990329492, 0.095883238044457, 0.08636769086018438, 0.014513485261637248, 0.0, 0.07693373434377775, 0.06843423171245192, 0.0, 0.035773109992591534, 0.0, 0.0890065072385875, 0.0, 0.025045990309244797, 0.0, 0.03743286004471226, 0.0, 0.0, 0.07604544721441128, 0.026408697604129387, 0.01860927750767732, 0.03282493924013713, 0.032852789182500154, 0.0, 0.0, 0.06389254880402045, 0.043661562188251374, 0.0, 0.03750141133920053, 0.0, 0.05578236701619081, 0.024635868158966793, 0.00749882229660357, 0.0, 0.004344859097762681, 0.0, 0.056223609193783525, 0.08456778019565035, 0.012994466942146144, 0.09017742417154526, 0.055863499823967766, 0.11545813525053875, 0.025160949338441648, 0.009320927728953555, 0.007906067760137967, 0.15922140503309432, 0.008454655838052359, 0.030588458980708486, 0.13912104831051678, 0.016554754462315992, 0.07436697550422892, 0.010888946717131146, 0.010111943687299552, 0.0, 0.04469895748008684, 0.03549186581821703, 0.033891174898001905, 0.03577534627765797, 0.015570729993227346, 0.05513339130641309, 0.005958382751867982, 0.0, 0.05352500592691224, 0.0719052112124656, 0.07378286170053784, 0.034976291260064504, 0.0, 0.011441142853036627, 0.017075091970289454, 0.05917751042672068, 0.015403100185261904, 0.001610603591142733, 0.029811118580284974, 0.0, 0.013592835858240835, 0.0, 0.047576362341096017, 0.006766425081051216, 0.00035464648897549477, 0.0, 0.06497129127168572, 0.008581544532358508, 0.0, 0.0035233047751492956, 0.15406953439379623, 0.0, 0.06088998427298873, 0.0, 0.0828918676823867, 0.03820624116026059, 0.0, 0.041354024452794486, 0.11455369288939919, 0.0, 0.04163536742855661, 0.013280640341847592, 0.08968832356569871, 0.0, 0.0012980167851076093, 0.04350507524897907, 0.048795754608532846, 0.07794047710754731, 0.006503275416905874, 0.02146597745350235, 0.0500272970368587, 0.06429215463099032, 0.04242418806826519, 0.023996488232739272, 0.0, 0.0, 0.0, 0.05995552556832804, 0.04097387526981865, 0.0, 0.09682692142501677, 0.02046071430075735, 0.07199104311063001, 0.08187911008292117, 0.0, 0.0023684198734538276, 0.03363727701989249, 0.0, 0.06441775770236226, 0.0008948709767125285, 0.05356390475616065, 0.0, 0.05256255510228193, 0.04477070656582853, 0.029137734108181827, 0.03975518425125503, 0.09948765725273494, 0.03517800754676361, 0.04621437798319411, 0.09946081219277786, 0.00942118161747508, 0.03811179908263238, 0.04812793434140644, 0.0004954388805363209, 0.0049624349433686375, 0.0, 0.018456463088277066, 0.0559592600566944, 0.05554877161834517, 0.00040166864481342595, 0.05643896248176604, 0.0, 0.07517634683256533, 0.1005017739737279, 0.03174270739663545, 0.03286329827643847, 0.01534731836767782, 0.0416419943508979, 0.023217318801749563, 0.01299904433814152, 0.0, 0.021536152946411723, 0.0, 0.029390631923465187, 0.06918574398427399, 0.007325416824593949, 0.0, 0.004526682869711942, 0.03181860855044769, 0.0037175624607532778, 0.03507288768990112, 0.0, 0.0, 0.05096299437354973, 0.0, 0.0, 0.04947272243828018, 0.09350823582977581, 0.016134088276145507, 0.06570406019733666, 0.018348643061784242, 0.0, 0.0, 0.017502387488591176, 0.051099383664703346, 0.011919499410425489, 0.06906048792265373, 0.029193525564925677, 0.07203458801488716, 0.0, 0.02939549969914861, 0.0031083187650965253, 0.037847534288191244, 0.04498808889694245, 0.0, 0.0, 0.030973791621850173, 0.007556573515017989, 0.0, 0.0, 0.03062113332253597, 0.0, 0.02090850869720314, 0.04541953286862982, 0.0, 0.06973343140319745, 0.0, 0.002299393699735057, 0.0, 0.012446203583426477, 0.0057886684756024605, 0.013838201875059577, 0.011145952019371137, 0.0, 0.04618015896601362, 0.12849970635668678, 0.015996401313107964, 0.056402068597749606, 0.0, 0.05335430604656032, 0.0, 0.019794461601988403, 0.005606908864310413, 0.011177513038245285, 0.0, 0.025488102421006676, 0.03178286172644379, 0.039960748972491966, 0.028475299721576433, 0.025303811325380256, 0.008204634709512604, 0.03874779566378093, 0.004696803496806482, 0.04032420554053757, 0.002941390507228032, 0.07803061771012854, 0.035799285130944665, 0.06516261413436372, 0.0067663329065859226, 0.0006650362066837052, 0.04166888278707395, 0.058864276290862314, 0.013864234835806115, 0.008893782822523228, 0.0, 0.05444731447068392, 0.0, 0.004418592946745583, 0.02233682734284641, 0.0, 0.01925968342778578, 0.046740560436672594, 0.0, 0.008852680240372592, 0.06969517357807369, 0.09906027618689027, 0.06466640464818743, 0.0218233565313135, 0.0010660025859937936, 0.049041905011977804, 0.0, 0.05759821603140313, 0.01062617840224417, 0.043018109717021386, 0.0, 0.014750719442295739, 0.0, 0.012654638364484681, 0.05887603606578068, 0.0, 0.014227335959835224, 0.09028956615629957, 0.0, 0.09112075090319358, 0.0, 0.028484468972306838, 0.020352343637262713, 0.0, 0.012015894622801926, 0.1090521906469544, 0.02196328821368228, 0.01085401801681265, 0.05960093461748012, 0.04414677821271096, 0.04015888190154689, 0.0, 0.06598464649905691, 0.09929991533791116, 0.055836804170987096, 0.0, 0.04906916937538485, 0.0, 0.0808957133641125, 0.06626327605134402, 0.04517822132338898, 0.026661366489368232, 0.08979742921512573, 0.0], \"feature_quality\": 0.6943359375}]\n"
  },
  {
    "path": "python/bugfixes/bug_vs_1/in/fixed-1/bug_vs_2.json",
    "content": "[{\"bbox\": {\"xc\": 293.0, \"yc\": 936.5, \"angle\": null, \"aspect\": 0.4320557415485382, \"height\": 287.0, \"confidence\": 0.9208984375}, \"feature\": [0.0, 0.04753167844180798, 0.0, 0.006542685751840367, 0.0016779792149485143, 0.011839572191505584, 0.0, 0.0, 0.0, 0.0, 0.006708212091246266, 0.0, 0.04389686354756224, 0.03970039272597332, 0.004767956936242561, 0.0, 0.0011268489905920435, 0.008954652189128838, 0.008568849585413625, 0.0, 0.044796127548754255, 0.022998480319707453, 0.042199042961223246, 0.10684276377335718, 0.02716946400710874, 0.019249891379068063, 0.08522185538664392, 0.04911326598230316, 0.04527778929549651, 0.0, 0.0, 0.0, 0.11484148457462652, 0.0025471469106063925, 0.09798997528782201, 0.0, 0.0, 0.07307906044016045, 0.0, 0.0510372354953907, 0.036777248994903367, 0.0, 0.0, 0.026654113038220798, 0.0031241567677631056, 0.0, 0.00015581804528503698, 0.02513689875648859, 0.0, 0.06745624443088763, 0.021825509691625798, 0.014388911981111249, 0.02058183701886171, 0.0, 0.10164523845915902, 0.0, 0.06572968748424443, 0.030589878785778516, 0.05263353519705747, 0.03134512862541044, 0.08208861595113383, 0.04820384524425177, 0.08225596736550972, 0.055507301382457526, 0.0434418138459092, 0.03402714166181525, 0.010380763795106945, 0.041400212120939105, 0.0, 0.0, 0.016709026770459587, 0.031002818702896496, 0.09067680865840379, 0.07218759179261292, 0.0, 0.05594868481117391, 0.09866344364006929, 0.007827166078287005, 0.0, 0.062162748412250696, 0.00858363031036951, 0.0662378357694877, 0.005175788270381841, 0.01798757284327094, 0.04580312733493015, 0.04283190760799379, 0.0, 0.03551958466175176, 0.025975796412955523, 0.005720358451309114, 0.05961205013003764, 0.0, 0.025765886644201593, 0.0, 0.03327250308575175, 0.060519574324089664, 0.005431849600649877, 0.09562483617205723, 0.0, 0.0, 0.010046739631609859, 0.0, 0.029396648140487897, 0.0, 0.0337570886711597, 0.0032545615991904538, 0.03235784360974131, 0.0, 0.029735158002414868, 0.01584444907153058, 0.09520689923337228, 0.0067985140814516174, 0.01254984418864262, 0.05386172407555219, 0.015748653843895677, 0.018478552292088533, 0.0328393659047189, 0.0, 0.04468438949812464, 0.043540322572464776, 0.0, 0.12787408869265807, 0.0, 0.006721372851536019, 0.0, 0.03642322512415801, 0.06374314596435411, 0.05772339910054623, 0.020095505962199438, 0.01968378624802223, 0.0, 0.025670424938703834, 0.0, 0.0, 0.04272004869916814, 0.043067911126111615, 0.003413245795868229, 0.005364415375652629, 0.05212157058185468, 0.0, 0.02586864400118713, 0.031935202498195175, 0.12768335586574092, 0.007300887320490922, 0.0050308380388514725, 0.0464493096252257, 0.04849026057529405, 0.18396757556786242, 0.0, 0.0035886296318885062, 0.0, 0.0, 0.05174802578825305, 0.0, 0.011497957203618858, 0.07979587089479603, 0.0, 0.03391664938028326, 0.021607469884992216, 0.005064441265742291, 0.07304439737985823, 0.028302040107360652, 0.06641291746001789, 0.0, 0.04107068295265621, 0.0, 0.0183835403185318, 0.0, 0.02418543331139231, 0.0, 0.07393247734952454, 0.07679052732104745, 0.012761562181944967, 0.043210012474299786, 0.0, 0.011496456935050726, 0.06314345224400394, 0.12253173807246873, 0.0, 0.08702529209114399, 0.019462885356017045, 0.0, 0.0, 0.0, 0.0, 0.05325936217810486, 0.0, 0.028175668918226083, 0.07838011009604978, 0.05825631866758522, 0.010936176931785526, 0.008015106964665864, 0.0, 0.029340472321290837, 0.16639544463878705, 0.14628252115118162, 0.0, 0.030169273669996412, 0.0026955244597777813, 0.0, 0.11780561550352601, 0.056554240154031946, 0.0, 0.023457311488378842, 0.0, 0.05094195275299092, 0.0, 0.015789328437352777, 0.0, 0.0, 0.012461406203691551, 0.004181152045241056, 0.017044758055975717, 0.011767152565921753, 0.0187546900280754, 0.050894697198339924, 0.014680349899877359, 0.0, 0.08162573835709123, 0.013428851658919784, 0.08067726182815084, 0.03771620794090575, 0.0, 0.0643752575715955, 0.05058384062134496, 0.038843796316374996, 0.0, 0.06427691153876537, 0.033601860263524894, 0.0027837143367975587, 0.0, 0.02576397383082968, 0.01779787195941227, 0.11463413839414625, 0.011406168890021842, 0.06880251176691182, 0.0, 0.0, 0.07216798487450177, 0.0, 0.02187205404227703, 0.06486536079849181, 0.01556252989780053, 0.00805056954841881, 0.0, 0.0, 0.06899587558378888, 0.02770084495634431, 0.025816126466616306, 0.0, 0.07482999820365831, 0.0, 0.035202099477543304, 0.026593170292868458, 0.010072432457150358, 0.006250655163074454, 0.019221902247802833, 0.011177607447744688, 0.03125982598096814, 0.13980041266486037, 0.0, 0.03978614626445469, 0.005979746868262322, 0.06415150256680695, 0.04385206234396925, 0.1210261145565758, 0.03953425919034213, 0.06729899862105741, 0.055579067908943534, 0.013325047251601262, 0.08844381403483048, 0.09683541694106759, 0.017853995484197582, 0.0, 0.08641876988271764, 0.0, 0.003514205387187694, 0.043058730551604855, 0.04483185509086005, 0.0013767873715557371, 0.05049243928639487, 0.008726786843576943, 0.04858600815690936, 0.0, 0.03236215034507321, 0.05202797985419944, 0.021802400210025618, 0.0, 0.040068954443184444, 0.008089236036464813, 0.028026801835255837, 0.029653185930951852, 0.0487605320403809, 0.05342706222189239, 0.03177870972789412, 0.052833769337540096, 0.0, 0.020686839549258262, 0.024258173676034857, 0.08447231144838266, 0.0, 0.0, 0.0, 0.0, 0.13337829632769574, 0.003615535397257036, 0.09911579727427224, 0.029171989023425132, 0.0, 0.01695433055918217, 0.0, 0.02819823686214017, 0.009732813372645324, 0.032197629801519925, 0.0, 0.037750385244227753, 0.02031960727220356, 0.0, 0.00779863657143372, 0.0709829051848555, 0.04710787058383086, 0.0, 0.052790511400142674, 0.0, 0.029704429806072116, 0.00020612812015775457, 0.013694517729478753, 0.040117111785913574, 0.0, 0.0, 0.05112345852147975, 0.0, 0.028821277132090442, 0.0, 0.0813170014469191, 0.05510876218423673, 0.00486153023242722, 0.0, 0.04170871196311132, 0.04375720725364776, 0.0, 0.03633847563838219, 0.1356867436527342, 0.007837446578587518, 0.16741044895625443, 0.04498551234194636, 0.07054439911088325, 0.02709492006630993, 0.0, 0.00236058557986188, 0.07879044226516863, 0.0340647262365871, 0.023020223174013966, 0.0, 0.039051890887992276, 0.0, 0.010021833546441646, 0.0, 0.0, 0.03347513347239044, 0.0, 0.013454833846871952, 0.09406699261869143, 0.0, 0.017830499023950473, 0.004155908661117073, 0.0, 0.07165159498996711, 0.1064840287571624, 0.0057542266365527865, 0.0, 0.050925004715192616, 0.04709654245214834, 0.009846103405205798, 0.028284848028974196, 0.11229365435037329, 0.0, 0.011222829330827732, 0.032675400844028174, 0.011569928259359699, 0.022979500934537274, 0.0599063956180969, 0.04078811184192638, 0.1309041803062416, 0.0, 0.004141233687082724, 0.01440923823790843, 0.0071292785599497855, 0.039328981544370985, 0.0189319599492127, 0.0, 0.08576610773375758, 0.0, 0.015883121370066776, 0.0, 0.07016968292245848, 0.0, 0.02091899652286756, 0.0, 0.004820688587377037, 0.08713368329942177, 0.0, 0.0, 0.05282070270719149, 0.03882057294916701, 0.0, 0.02081717581690132, 0.009341848729441627, 0.03963868067171927, 0.08192168289205193, 0.0, 0.017575502175486004, 0.0, 0.08623317817752428, 0.12230099188587856, 0.0, 0.03363303935390671, 0.025099662811128385, 0.014602084921158212, 0.04578494747321069, 0.07835151318751024, 0.08994998606099773, 0.0, 0.027297157663811498, 0.0, 0.0, 0.06414198265967284, 0.03804088651197456, 0.02103945263298448, 0.0, 0.0, 0.023771524204509946, 0.06096524357037326, 0.0, 0.0, 0.015557744378076626, 0.045957611999820104, 0.012773264509195989, 0.021644350228352534, 0.030253593179497867, 0.02062859054715993, 0.07957066559165502, 0.03319295979918989, 0.0, 0.0, 0.005858223371096734, 0.007226069124554946, 0.0, 0.025790186114193536, 0.0, 0.0, 0.007995687144338971, 0.011828890186332656, 0.030992527162664613, 0.0, 0.03155200066989087, 0.059539386463865654, 0.007038235732244679, 0.03507511005225208, 0.0, 0.0, 0.0, 0.0, 0.0037664329560256286, 0.013134073870390076, 0.026838949379688923, 0.0157975142559383, 0.0, 0.06268450718972475, 0.0, 0.009300627949906276, 0.0, 0.028403586558189725, 0.020816201978744783, 0.0, 0.0, 0.008366608501617941, 0.059686735846797186, 0.0, 0.013810897199675458, 0.0, 0.0, 0.0, 0.012374366222681198, 0.001938268257881815, 0.07373014213578771, 0.034029073068755786, 0.0, 0.013741908087502254, 0.059264791994074355, 0.013129187248136795, 0.08700322152519051, 0.0, 0.02003645976084647, 0.031786477191185665, 0.04000540162563689, 0.01271099697207938, 0.02401430507839023, 0.004790700938009529, 0.08209760594156222, 0.0037517640830059826, 0.05447154199401665, 0.00485001471243594, 0.11132751393124267, 0.0, 0.017348445650169138, 0.10768741381511632, 0.0, 0.04964533489357772, 0.0, 0.05393462945811625, 0.0], \"feature_quality\": 0.9208984375}, {\"bbox\": {\"xc\": 315.0, \"yc\": 391.0, \"angle\": null, \"aspect\": 0.4444444477558136, \"height\": 198.0, \"confidence\": 0.890625}, \"feature\": [0.023307214626985415, 0.0, 0.0, 0.08409643227383341, 0.03243969678776673, 0.026487125361806665, 0.020346978537585526, 0.05291739726568768, 0.0, 0.026720866897089524, 0.02726928385309465, 0.005625723298529271, 0.0, 0.024297508226820492, 0.12724321604962516, 0.013190750590899837, 0.051586614090751784, 0.0, 0.0, 0.02811360617307387, 0.06300243051151476, 0.0, 0.0556653250514604, 0.043554145672820295, 0.0, 0.032688890063862996, 0.0, 0.0, 0.025831499636724577, 0.10766655819086371, 0.04286413303333236, 0.028385218055541603, 0.0, 0.0849838713955653, 0.010591878041525877, 0.03316241055194405, 0.0007115094544445379, 0.0, 0.0, 0.02930222108192184, 0.0238827586825985, 0.0077372987560255955, 0.012888323111577235, 0.03550841198067447, 0.0, 0.07601850612340554, 0.020556859357284497, 0.06339801851227717, 0.12215279157330032, 0.0, 0.0, 0.02189702889218193, 0.0, 0.024044764938572508, 0.025489127196964143, 0.0972122224806114, 0.0, 0.02680813647563391, 0.04888517468616466, 0.0, 0.06769680892099308, 0.02738905748138527, 0.0, 0.01525293480656137, 0.0015218395092495207, 0.027488593643553678, 0.0, 0.019515155829034733, 0.0, 0.04925530806116104, 0.0, 0.020677000503712354, 0.034928300580094995, 0.06108031065376421, 0.0, 0.04805752384110649, 0.03468404749345217, 0.0006132390909872507, 0.0063607695599095715, 0.09221497716795073, 0.07782558747785102, 0.0029232399293784043, 0.10794996261182192, 0.05813386981038525, 0.000630242880202229, 0.048926230190538926, 0.00929947055121356, 0.0074611195285914705, 0.022648302544067453, 0.01412482723161216, 0.03280863173405473, 0.003575661189253023, 0.015330322674611423, 0.024973368754849887, 0.05559958724204327, 0.003327498228949102, 0.1379472723446054, 0.02110701802967914, 0.03375380049825685, 0.013609662677502278, 0.005399875746569154, 0.020248162764229603, 0.03820743854879538, 0.09492212641953955, 0.0038353234059107337, 0.07814950411550237, 0.017483009563155072, 0.0, 0.0, 0.12864907412520393, 0.0, 0.0, 0.07005264279148204, 0.0, 0.007357073279717185, 0.0, 0.018247330108912514, 0.08886417582566399, 0.015746710071401538, 0.0033138617747320307, 0.05523803461659891, 0.1187237940893791, 0.03483696965981664, 0.015027779347180345, 0.06885255758830741, 0.015301154251436855, 0.0, 0.0, 0.008087642744077951, 0.0, 0.17550378034568065, 0.0, 0.08594906457189705, 0.014583339397501909, 0.05710690695524859, 0.0, 0.032718802844424216, 0.023531403353875015, 0.0, 0.0, 0.003870989310065914, 0.0, 0.0, 0.019754133166185767, 0.0002325162667648613, 0.0228486958031584, 0.05653711003108592, 0.04828367000155311, 0.06480491522606988, 0.0, 0.0, 0.0009018210984746485, 0.12254178555299172, 0.03491157051532598, 0.04870936253147009, 0.06636361290959099, 0.01440186932771632, 0.08718356864757643, 0.028788553232109985, 0.03993322382601404, 0.02702452210004449, 0.005813255420198094, 0.0, 0.0, 0.031105728455641447, 0.07113630996674954, 0.06732442782637477, 0.033799761570810764, 0.02045872668829235, 0.0, 0.0, 0.0, 0.006609320345059399, 0.004192521740381308, 0.0, 0.0, 0.013824849873204472, 0.09366904936205515, 0.04848328561357106, 0.04848070766026059, 0.0017622617852363143, 0.01832584316836099, 0.0, 0.0, 0.010315698148298027, 0.0, 0.019371096708762473, 0.0, 0.027205511466759272, 0.0, 0.0, 0.051591690102125505, 0.0, 0.12776220492290224, 0.06671188161859666, 0.03819307338334424, 0.0, 0.0, 0.06686657479627463, 0.03335084881887516, 0.0, 0.0, 0.0, 0.027743478124427304, 0.031786065780690376, 0.00576364313484598, 0.0, 0.025789338914763694, 0.0, 0.04183055218855052, 0.00165027145206134, 0.0, 0.05524354738865747, 0.0, 0.0489309333574256, 0.0, 0.0, 0.0, 0.04340955391756653, 0.06400797078769536, 0.004400891208320262, 0.04552012535812272, 0.006693876814166757, 0.09672742877314683, 0.0, 0.0, 0.02345392893264047, 0.02915754410507104, 0.04053308533170765, 0.09927227349317201, 0.06179870208506663, 0.03283272015109321, 0.02826326861335154, 0.0, 0.04367718169037262, 0.0071976529665783735, 0.0, 0.0, 0.0514989210673971, 0.12703408225048773, 0.0, 0.08281977013936805, 0.06955682886625018, 0.0, 0.012718865292211997, 0.05730200049627357, 0.05860394148696107, 0.021016189117870853, 0.011953091984542506, 0.0, 0.03944619837797697, 0.02170383952121582, 0.029476517890094037, 0.0, 0.0, 0.019529462404637907, 0.013133230007659855, 0.00026081990711242963, 0.005152158202268515, 0.024647987370999404, 0.0, 0.04297163741482406, 0.015268628896291363, 0.009717068360999141, 0.007340780641426729, 0.013162788585958374, 0.023389221771912553, 0.021424605632162773, 0.1123274019019066, 0.0, 0.04362924720521224, 0.004481427614904725, 0.104679104453909, 0.01747173767535901, 0.10620149241074105, 0.0, 0.0, 0.0, 0.02950214828540397, 0.0511827809004757, 0.0, 0.01076917092146903, 0.04576805096296301, 0.03211381739020854, 0.04583244653222675, 0.0, 0.04455730507063358, 0.0, 0.0, 0.041044300397429685, 0.05268459316797226, 0.0142625546535418, 0.0, 0.0, 0.004981934054323639, 0.0, 0.05214039468102064, 0.041826493509991465, 0.0096077670022386, 0.0, 0.02695678690944673, 0.016513458101087182, 0.04944420705735115, 0.0, 0.02121406035032438, 0.0, 0.02598909567505174, 0.00783021826013628, 0.0184017463164001, 0.006850560715088095, 0.10343110873414815, 0.010259063070714792, 0.0, 0.010160628131370643, 0.0, 0.0779481095026462, 0.04050233365105056, 0.0, 0.0, 0.0, 0.0, 0.027068270074250254, 0.0, 0.07840162155764709, 0.0, 0.0, 0.0, 0.0, 0.039986489987339266, 0.041560056176369035, 0.009538339363987113, 0.0, 0.1549433350233832, 0.0, 0.12181647519327957, 0.0, 0.024685873697233726, 0.07452168464568565, 0.0, 0.045325159649493296, 0.0, 0.020080543867138003, 0.03283173211320252, 0.0, 0.13895190714131747, 0.004707965594959616, 0.14573063537121259, 0.041054244692534346, 0.061144178914396254, 0.006792287784936739, 0.0, 0.050089643475237075, 0.02524169960583156, 0.10777596141606446, 0.037296313149436024, 0.0, 0.049000516791409175, 0.10157112607333976, 0.05221328045188954, 0.0, 0.0, 0.08461075526467252, 0.0, 0.0, 0.0, 0.15409077816658995, 0.0, 0.0, 0.017077216276147052, 0.009614926282183729, 0.03171721738298127, 0.04536173569367312, 0.0, 0.02477753485120061, 0.0, 0.08334032495949074, 0.0, 0.04056592227831732, 0.0, 0.025394856131587894, 0.0, 0.0012573632710496114, 0.05792616879934808, 0.08571839101410765, 0.004484944004473235, 0.0, 0.0, 0.006008349627016808, 0.053378291641532204, 0.0325693374790897, 0.10801296267743411, 0.04557301601178599, 0.04496171616986459, 0.07657223345017625, 0.0121211170158075, 0.049212952927431906, 0.010588990494132403, 0.010939007577464861, 0.030164077745492148, 0.0, 0.03733679340803027, 0.0, 0.07419810356807265, 0.024107746361960353, 0.006093824891467258, 0.02839335938123388, 0.0, 0.05590791898013584, 0.0, 0.0, 0.17038338982529644, 5.107538267147133e-05, 0.0, 0.0, 0.0, 0.0, 0.0708353232650519, 0.043429730130665874, 0.0, 0.027313745558175628, 0.0, 0.0, 0.031088846589902703, 0.0, 0.016223699344799634, 0.025190705132702447, 0.0, 0.023965993551158003, 0.04721720693618817, 0.10392413697842676, 0.009816027945800072, 0.04809219837840235, 0.0, 0.10977208557814506, 0.0, 0.014099476469667516, 0.06607580359733574, 0.0, 0.0, 0.02944430146323782, 0.0, 0.03849775923498841, 0.03645820135334114, 0.0369276498469886, 0.0, 0.0, 0.0, 0.03001879356476243, 0.0, 0.04453838054974077, 0.03311589287583447, 0.0, 0.0, 0.0, 0.0897593061965288, 0.009486353523998675, 0.0, 0.0023473090476100046, 0.0, 0.0, 0.023901211821532677, 0.0, 0.0, 0.02535153692854224, 0.016552071474071256, 0.0, 0.01761212294584635, 0.031378892969080266, 0.12032277495655129, 0.0162291801587593, 0.10663481227359314, 0.03744779453817553, 0.010838667802268111, 0.007950492565309514, 0.0, 0.003910026460757146, 0.05148540279156655, 0.017291098516144566, 0.024319229081366193, 0.01362484277447512, 0.009360252192127796, 0.0046705007161544085, 0.042712997857330834, 0.009480287477353081, 0.0, 0.04471102618947031, 0.0, 0.05047806220914846, 0.0, 0.0, 0.03750164926115532, 0.0, 0.032037999463766474, 0.06403070897505574, 0.0002202358055028483, 0.0061725909525319495, 0.10220697506155864, 0.0, 0.026520335152903387, 0.0, 0.032999384299980464, 0.1272856883630502, 0.05041134435136537, 0.023840177179002227, 0.028373212463058524, 0.0038459761055407975, 0.045742836022938646, 0.09845931272090369, 0.0, 0.0, 0.10160680196440085, 0.0], \"feature_quality\": 0.890625}, {\"bbox\": {\"xc\": 335.0, \"yc\": 327.5, \"angle\": null, \"aspect\": 0.5027322173118591, \"height\": 183.0, \"confidence\": 0.79541015625}, \"feature\": [0.03164942942101613, 0.0, 0.007107999888776774, 0.07363514221037072, 0.03159357210051089, 0.0, 0.015270417364568299, 0.06980842211999445, 0.0379726509906046, 0.026754924179743525, 0.007430819719777799, 0.0, 0.0, 0.014997856259681567, 0.07307223057265774, 0.05309880967536088, 0.0, 0.017120371865332613, 0.0, 0.0, 0.006234411164680942, 0.0, 0.12309932795260528, 0.03476719121267206, 0.0187646077156958, 0.03661468216967664, 0.11278229406165943, 0.0, 0.01866303114822902, 0.06592861185045738, 0.05054792251279059, 0.0, 0.0, 0.12001299662235056, 0.10514186917888871, 0.03175999456277465, 0.05149713079024695, 0.0, 0.0, 0.006358989304880876, 0.0, 0.01886787956280533, 0.0025006797120022653, 0.08056465742673936, 0.066632958246937, 0.0, 0.0, 0.0, 0.009204688841879883, 0.013330805398459787, 0.0268073005569495, 0.0, 0.0, 0.028768802835929648, 0.0, 0.0, 0.028898487672907164, 0.07097222058643356, 0.0, 0.008164977197412056, 0.14294901081752243, 0.036490925596986426, 0.0, 0.029743012722116012, 0.08269013716453405, 0.0, 0.06490800020212863, 0.061188990045691526, 0.0057050053443774935, 0.0, 0.0, 0.0954120996235178, 0.033986036944804815, 0.012328462908605134, 0.0022436742173302967, 0.0642689017934303, 0.05254688317162827, 0.03691679421198609, 0.0, 0.03416176896048611, 0.03420576279980032, 0.0, 0.0929803061455431, 0.060926032821651765, 0.009860361708794277, 0.0, 0.0, 0.0, 0.0, 0.0, 0.02898140921168724, 0.0, 0.044272055213797235, 0.0, 0.04528949183395023, 0.04457420665426653, 0.04612854749155704, 0.005220074832076944, 0.0, 0.0, 0.0, 0.0, 0.011669298090082107, 0.02302121387460653, 0.0, 0.06619162933050632, 0.0, 0.0, 0.037951162770717264, 0.05518819606365974, 0.0, 0.05395776835630596, 0.09591291049115948, 0.0, 0.0, 0.05374562548831125, 0.007503865646284913, 0.018463720492649778, 0.0015933679012330522, 0.04968032868222297, 0.011753050466527155, 0.0, 0.04435596402411226, 0.027072009840332964, 0.033293447422730764, 0.04674818784436948, 0.0420040004881392, 0.08112876491448058, 0.012860391369908082, 0.00045085012632276294, 0.07110886731000961, 0.0, 0.0075940741064822416, 0.0458304053932367, 0.04812520915189081, 0.020235515996897654, 0.0, 0.0, 0.04958439184546109, 0.03576721384617348, 0.01550862559160883, 0.0364403731228274, 0.07467674612528052, 0.0613936240879275, 0.0, 0.018394152612518408, 0.0324561345543124, 0.02944377906301408, 0.024077650037928642, 0.0, 0.0, 0.0, 0.16886372057025195, 0.03858922292202674, 0.08082647442168242, 0.0, 0.0, 0.03402050569959174, 0.0, 0.02530646242072877, 0.02709616091231807, 0.0, 0.042423442568006474, 0.026208001242311256, 0.049460171765007985, 0.049370334690405665, 0.016692619091276818, 0.0, 0.0, 0.041389713649988086, 0.0, 0.0, 0.0, 0.080416937502708, 0.0, 0.017905880788632166, 0.005034031506825571, 0.0941965319750421, 0.0, 0.035094441598501565, 0.0578525638318171, 0.0, 0.05070319297827934, 0.0, 0.0, 0.0, 0.031049328237948096, 0.03062574008114182, 0.022160000228395017, 0.0, 0.018553736017740803, 0.039159087176182994, 0.04552817515644772, 0.0969273621213195, 0.0, 0.038990240568319345, 0.0, 0.0, 0.07401992317972947, 0.0971321305808067, 0.0, 0.06129256085530811, 0.0, 0.0, 0.04308227250217082, 0.03534544263979771, 0.0274140484413373, 0.029519293747743258, 0.0, 0.012344732031082387, 0.033376225274104346, 0.04217301395879749, 0.0, 0.015215240241705095, 0.04293694891327357, 0.06306657192666258, 0.12337701545334952, 0.0, 0.0, 0.10571422710480928, 0.0, 0.051438163332652755, 0.07577979262923833, 0.08457932991496538, 0.07025720424125233, 0.0, 0.04230147050020336, 0.0631495954371505, 0.02416853927515271, 0.0, 0.0, 0.07935646882184878, 0.0, 0.04543930681306983, 0.0, 0.05381882263422601, 0.0, 0.010079948511460914, 0.08163212507481812, 0.0, 0.023403533617744363, 0.04508324014962179, 0.02964168297253297, 0.04739114727273265, 0.0, 0.04987651297794155, 0.0, 0.0, 0.029318307511601187, 0.0, 0.0, 0.04843366429793534, 0.0, 0.0, 0.0, 0.0, 0.06128228025312733, 0.11209754477339456, 0.00569627691381767, 0.11240864191368753, 0.016026118103636734, 0.09190827758107756, 0.0, 0.012666418006218455, 0.041273674481549893, 0.15434446968006166, 0.02091572754416387, 0.02015819131435441, 0.11099638938110934, 0.015599060591225266, 0.09516169418969696, 0.0, 0.0035432535169059045, 0.0, 0.04335726238773507, 0.035415786895431914, 0.0022217794142272056, 0.0016950197887113006, 0.008831707621394768, 0.05554758607776766, 0.01635961193903229, 0.01199403278752855, 0.09611339613891764, 0.05468069677899963, 0.081427755232007, 0.01493295937903067, 0.0, 0.0, 0.04291342589430717, 0.06470210078369998, 0.029572754733114385, 0.0045675526591807585, 0.04795019325570317, 0.0, 0.009681475522786547, 0.0, 0.06281689420002946, 0.0, 0.020554794049205537, 0.00844537332164565, 0.07399737352706513, 0.010308058063117593, 0.0, 0.005715601711221742, 0.14582599423159265, 0.0, 0.09684774075781116, 0.0, 0.06441840158727113, 0.08599825695932911, 0.0, 0.07847661057587524, 0.08960737869534151, 0.0, 0.056153965473483974, 0.0035716671221554417, 0.051404192848710936, 0.0, 0.014905708598881271, 0.031719201244563164, 0.028693858265748818, 0.05254928877691764, 0.017075548347122844, 0.01316392058720255, 0.015394328053589678, 0.09515551099614007, 0.03540120030613393, 0.036060190150476464, 0.005998960229444872, 0.007255563958322864, 0.006283446230301221, 0.020753998097999316, 0.022349306068912392, 0.0, 0.05254687853655064, 0.01712483312755038, 0.06910977223392069, 0.058044752690619615, 0.0, 0.0, 0.02774274033630937, 0.0, 0.10342246859215733, 0.0030524857037165258, 0.04321618452994584, 0.0, 0.028861671251301232, 0.036897248089625245, 0.0, 0.008467435132389555, 0.07610936518895987, 0.06864533282043257, 0.030010943384451528, 0.08191281610586586, 0.01745748106127867, 0.026408415046366165, 0.06144227386271988, 0.0, 0.009753819814417595, 0.0, 0.011143343085073334, 0.0470097730854311, 0.07994216650118519, 0.003660396413204464, 0.056315215189113935, 0.0, 0.08079913673382748, 0.11008404851111282, 0.02047541834481153, 0.02046476230134281, 0.003493497407021989, 0.04963811239517943, 0.03718823826316427, 0.0, 0.0, 0.015781844877518856, 0.04526203363407694, 0.04241410288658435, 0.10260718624770887, 0.0, 0.0, 0.01009810758684133, 0.0, 0.0, 0.022623704982078965, 0.0, 0.0, 0.04206900281680616, 0.0, 0.0, 0.02490426282964081, 0.08106193636522786, 0.022140071712129786, 0.05741123955046404, 0.009457916458591988, 0.0, 0.0, 0.021754620974053666, 0.05402819372579866, 0.0, 0.10929764270029209, 0.005278933944731563, 0.0, 0.011055200131344362, 0.01628906837506005, 0.0, 0.07865427310138895, 0.038581102266021, 0.0, 0.0, 0.006485999122681844, 0.009326120343754532, 0.0, 0.0, 0.10192428172034029, 0.0, 0.028249465563005463, 0.05406118157328317, 0.0, 0.10392114732408973, 0.0064079913455730485, 0.02100737461658025, 0.0, 0.0, 0.0, 0.0005190397588788842, 0.0004573933350498482, 0.003144912627943961, 0.06483978112958726, 0.08784526123318266, 0.017229158296051063, 0.05931299336126953, 0.0, 0.0, 0.007681505575795186, 0.02352601090900902, 0.010820651298091805, 0.03837023172683541, 0.0, 0.03187109042092168, 0.04151285375735367, 0.07181162387486253, 0.05892093068495586, 0.011506970718903433, 0.02493169321904833, 0.052100469575038735, 0.0, 0.08121012906717737, 0.029140835024270256, 0.10589717361881988, 0.025300077601295035, 0.10690092748981252, 0.006491424481046409, 0.0, 0.053607198894925726, 0.0625834346100197, 0.03553195584604371, 0.024352704814579695, 0.0176956892883192, 0.022735980467482528, 0.018827560181281396, 0.03193483896109003, 0.02504528043142086, 0.0, 0.01887247292473552, 0.04701085769359625, 0.0, 0.021505791466626633, 0.004288958131683027, 0.05191179410507888, 0.06497699796771166, 0.008928587696454118, 0.012577409451742296, 0.027339667033088374, 0.0, 0.04444864240130106, 0.0015103084688935175, 0.03505701566418744, 0.04946824143515981, 0.0074103060249619285, 0.014582758406321934, 0.004708873279160792, 0.06580236160600285, 0.0, 0.0, 0.07707390166813484, 0.00021000493843840225, 0.04737699638073178, 0.0, 0.011399417218834257, 0.012140640292928986, 0.0, 0.028126302280251734, 0.07513713911599866, 0.016486442726959843, 0.026567546531519746, 0.08787482375829946, 0.028340493852558393, 0.041252065749644205, 0.0, 0.044280259301200296, 0.06750227632638256, 0.039785740401447875, 0.0, 0.04710499148516051, 0.0, 0.13303381034653186, 0.05935844956757564, 0.047036888289559854, 0.0376373031241575, 0.07865537624986461, 0.0], \"feature_quality\": 0.79541015625}]\n"
  },
  {
    "path": "python/bugfixes/bug_vs_1/in/in-1.json",
    "content": "[{\"bbox\": {\"xc\": 316.0, \"yc\": 932.0, \"angle\": null, \"aspect\": 0.5, \"height\": 296.0, \"confidence\": 0.9267578125}, \"feature\": [0.0, 0.04258357828024642, 0.0, 0.0014462762972122971, 0.0, 0.024944440668679246, 0.0, 0.0, 0.0, 0.0, 0.0197188836599551, 0.0, 0.03178173198631047, 0.03466449768163708, 0.05894796066843041, 0.0, 0.010570168667088407, 0.010946645655506262, 0.03100957310317401, 0.0, 0.0664711566225772, 0.01816009632028984, 0.02718766608837893, 0.10607468312203099, 0.03811870451915187, 0.03395282129989212, 0.04953707899277783, 0.023830781380729086, 0.09917198248423059, 0.0, 0.0, 0.0, 0.1448656111755325, 0.0, 0.09213059201047719, 0.0, 0.0, 0.06854326801671841, 0.0, 0.05198110100054844, 0.06363063721556238, 0.0, 0.0, 0.0, 0.0, 0.0, 0.013905147391293267, 0.02990821396635606, 0.0, 0.056842783043827914, 0.029073139584900855, 0.006850207602707511, 0.0, 0.0, 0.09438037525800579, 0.0, 0.042260223977515124, 0.03138657273537624, 0.07290300477170669, 0.037673975208599844, 0.08530933369204156, 0.050893087302431445, 0.10352836260456924, 0.04043851302268662, 0.05489051026500338, 0.0011089131839616055, 0.022928385073186893, 0.0469887580111759, 0.0, 0.0, 0.045647005261296024, 0.0, 0.12134046827546444, 0.06721484268334003, 0.0, 0.06050428572509429, 0.08081337163687659, 0.0005084766108150381, 0.0, 0.06540399460923671, 0.0, 0.021025020038149947, 0.035250448947166384, 0.015826525614691594, 0.04376133352058002, 0.038913573626384264, 8.076353231785493e-06, 0.03608482496839247, 0.023932879393451224, 0.0, 0.06560250012520115, 0.0, 0.01403728277857975, 0.0, 0.07827641365203489, 0.0415220256764882, 0.020970752942797603, 0.10813580998560066, 0.0, 0.0, 0.002214588567151275, 0.0, 0.005795206068453409, 0.0, 0.026887783447670805, 0.0, 0.05249997814523335, 0.0, 0.04129303110458543, 0.0, 0.08375052157185203, 0.004444638773455853, 0.040595450335593995, 0.020734342235816584, 0.018105571282207706, 0.0206044810248246, 0.0323058288105143, 0.0, 0.033999769129488315, 0.05723638337450876, 0.0, 0.12014002759155916, 0.0, 0.011683971006246787, 0.016363037481549204, 0.06315253079160837, 0.047285858474842214, 0.07638909288006819, 0.018380684662531465, 0.07032879490057888, 0.0, 0.018429732078364885, 0.0, 0.0, 0.026030562035852824, 0.01094964522532907, 0.010531757728099443, 0.0, 0.06772971889974458, 0.0, 0.026092123616437052, 0.02236679756855408, 0.1135614922554658, 0.013953521227421828, 0.000890634499219668, 0.04095666927939308, 0.060400117412209055, 0.17859069720470921, 0.03378391499376763, 0.0032180154944928363, 0.0, 0.0, 0.05130533610400468, 0.0, 0.005473836460437772, 0.09301394110596392, 0.0, 0.028592577636270274, 0.006051747572232489, 0.006566878915398152, 0.048658924796285766, 0.031057397262219007, 0.06639713043829068, 0.0, 0.05569476443260515, 0.0, 0.020693409315290533, 0.0, 0.009103662310182576, 0.0, 0.08216281986592636, 0.09142915899635093, 0.0110111031782664, 0.03696570051700317, 0.0, 0.0, 0.08125800633088144, 0.12431912333579884, 0.0, 0.06427006935799254, 0.025449762866594464, 0.0, 0.0, 0.0, 0.0, 0.02468020368573027, 0.0, 0.037952976878457334, 0.07060538610125844, 0.02077023119326846, 0.012382914704072476, 0.0008400878616685772, 0.0, 0.0343782082848725, 0.14409173793067231, 0.11353034989479699, 0.0, 0.05133434733958721, 0.0023805925951703544, 0.01401416367583354, 0.07461377557220246, 0.049147997728611864, 0.0, 0.005224198022246032, 0.0, 0.030949288845976586, 0.0, 0.011047210654886739, 0.0, 0.0, 0.021344765395439152, 0.017070411063172825, 0.0, 0.0, 0.022101265240018837, 0.01577350205110207, 0.013167710528193575, 0.0, 0.06221572785251998, 0.0, 0.09161353510793785, 0.028692312488088074, 0.0, 0.07114291171683573, 0.03180532980372987, 0.035801105987826036, 0.02464304641599132, 0.06645903218788973, 0.040701601090419946, 0.015260441571293233, 0.0, 0.027524730779350262, 0.0237730810563794, 0.11288029817133437, 0.0, 0.052336115802183045, 0.0, 0.0, 0.07173528789647293, 0.0, 0.029864710629631867, 0.0781947911106755, 0.03254259996148454, 0.0, 0.0, 0.0, 0.07966382564424948, 0.00910820446764051, 0.03659893073577005, 0.0, 0.07544265256981797, 0.0, 0.018432771071749014, 0.03468828698492575, 0.007064982096041728, 0.001218623252781915, 0.010446679429993392, 0.00695313632618606, 0.011171696744889445, 0.15538826881929155, 0.0, 0.03209989589559696, 0.013040523361046634, 0.08209062293488513, 0.024818030708884064, 0.11147282747111956, 0.05417107307361547, 0.05562656842984429, 0.049266592812165715, 0.031085309143634405, 0.11466146818847511, 0.09474818131570792, 0.007561891869164739, 0.04223425849364153, 0.07826097763819644, 0.001776938461026637, 0.0, 0.021181892020584275, 0.03603628893246876, 0.0, 0.05667255435881797, 0.0, 0.053730416780141656, 0.0, 0.04882684663976469, 0.039510680633379994, 0.031178700181241893, 0.004781854400347673, 0.044337760053694136, 0.015489108364443303, 0.02853312014747313, 0.025649994008157297, 0.0218338631082895, 0.04696076502986292, 0.03950083600692436, 0.030620241780990515, 0.0, 0.02204001454237531, 0.018499832802331282, 0.08131371294941574, 0.0, 0.0, 0.0, 0.0, 0.14457479595392825, 0.0024123524977675676, 0.10938783921546337, 0.013843308719392322, 0.0, 0.02513219368989019, 0.0, 0.02405653646227872, 0.004032656362518405, 0.02030251682007872, 0.0, 0.028298421549440917, 0.0, 0.0, 0.0, 0.07971117897333356, 0.03526245398478242, 0.0, 0.04239689082157861, 0.0009508524403550118, 0.05707654448748815, 0.0, 0.0, 0.0156515863772975, 0.0, 0.014240037028067138, 0.03935111658490117, 0.0, 0.03774708226071385, 0.00881512971277882, 0.04390183909312873, 0.057149088345868994, 0.0064124579520905365, 0.0, 0.029015067552687283, 0.04241158792523915, 0.0, 0.03712453016085396, 0.15106681572150854, 0.010115509124633309, 0.16731543251145406, 0.05609066259371728, 0.09700351540067054, 0.04244058564417208, 0.027965251906328117, 0.0, 0.08626918702424072, 0.034386550310448065, 0.028695439339694578, 0.0, 0.054225572204785875, 0.0, 0.028422817528446317, 0.0, 0.0, 0.028127656703997004, 0.0, 0.0, 0.09604671683128004, 0.0, 0.0, 0.0, 0.0, 0.09043899232465819, 0.0568464055059196, 0.011713786482480268, 0.0, 0.013973775926841186, 0.022922198953221864, 0.013049615560674766, 0.028825009942720248, 0.10754795287247833, 0.0, 0.03459610118190722, 0.052606255055455534, 0.016685630346353262, 0.011131297732022427, 0.03588141741417462, 0.0026561934196701617, 0.11170234919235655, 0.0, 0.01665326022334617, 0.0, 0.00235503514515643, 0.07106906575454383, 0.02430234574627871, 0.0, 0.0837396271522778, 0.0, 0.0, 0.0, 0.056223008615459966, 0.0, 0.015533370886319797, 0.0, 0.0, 0.04689598873944981, 0.0, 0.0, 0.04999573045241357, 0.034117197275627065, 0.0, 0.02008228779543885, 0.042912659136856476, 0.05334311520817247, 0.053100608492223735, 0.0, 0.01258394107518653, 0.0, 0.08697504448833467, 0.1351951332332922, 0.013517044957835527, 0.020653192777193057, 0.027426681003182077, 0.028323530978840113, 0.04528356508141335, 0.08318052445839569, 0.10051885353875742, 0.0, 0.04913570658857938, 0.0, 0.01102759912271065, 0.06764294651488918, 0.05834338796530791, 0.0, 0.0, 0.0, 0.0045447751824053955, 0.04410557104693688, 0.0, 0.028118222082579112, 0.009187075807613406, 0.03919071900969726, 0.0029559183661330016, 0.03356595676625986, 0.01658881847001056, 0.027523906263724918, 0.06661103141814477, 0.02537005067837796, 0.0, 0.0, 0.003745317454232082, 0.021943264617343257, 0.0, 0.00047375617476064555, 0.0, 0.0, 0.039852935717796134, 0.0, 0.023370971994777976, 0.014344498201693614, 0.025941210223699558, 0.04636201800271623, 0.0, 0.017130173802973343, 0.0, 0.0, 0.0, 0.0, 0.00035525215259352085, 0.028617367171629035, 0.040227883072034724, 0.020686394174150295, 0.0, 0.05317415708822453, 0.0, 0.003075765147758804, 0.0, 0.048869446613740895, 0.017751120800693732, 0.0, 0.0, 0.0, 0.03588927959868953, 0.0, 0.004563933906820025, 0.0, 0.0, 0.0, 0.02071745994047139, 0.0, 0.08804108461975871, 0.03484170546338834, 0.0, 0.0, 0.04666583624123167, 0.018170797001219874, 0.08917856474984659, 0.011761621905399927, 0.01992403035642095, 0.027733461640733943, 0.03758859729142775, 0.022319045498306926, 0.027459292173106963, 0.014287083979760373, 0.08230150970187936, 0.02640687682447553, 0.04921642801996571, 0.010669490134706068, 0.11379328477383491, 0.005413677795442844, 0.01636956264414159, 0.12320075574823947, 0.0, 0.03533035937957696, 0.0, 0.0730790005605442, 0.0], \"feature_quality\": 0.9267578125}, {\"bbox\": {\"xc\": 331.5, \"yc\": 387.0, \"angle\": null, \"aspect\": 0.45876288414001465, \"height\": 194.0, \"confidence\": 0.86328125}, \"feature\": [0.009234536144390015, 0.0, 0.014517390951002759, 0.06924937635613279, 0.0162199631554995, 0.0, 0.0, 0.0, 0.0, 0.0, 0.08129763967236317, 0.0021192182550187136, 0.056082567978569295, 0.013102758266628644, 0.13436325585278586, 0.0018991826605241407, 0.08665264601578322, 0.0, 0.0, 0.0, 0.040739089298463135, 0.0, 0.053729497887817744, 0.05949632463124769, 0.0, 0.0051716339249422965, 0.0, 0.0, 0.011101729085942768, 0.1823419891603576, 0.0, 0.02547818559971576, 0.01573327934805632, 0.0, 0.011132701606895723, 0.036647524085701016, 0.026945078167677758, 0.0, 0.06002550295833251, 0.01601433433261647, 0.028586019578549608, 0.030833360243470415, 0.03126244457624106, 0.0, 0.00907539174244052, 0.13155160942531668, 0.029999955394350748, 0.07133340258386033, 0.07671393272490046, 0.0, 0.0, 0.0311081605038765, 0.007143426839299023, 0.006148442492763528, 0.07382910194880497, 0.058563456914068626, 0.0, 0.061119891662264204, 0.03750302361312318, 0.006669909903346032, 0.026122439280587415, 0.05772278096455543, 0.0, 0.025874027986709004, 0.0, 0.0, 0.0, 0.0, 0.0, 0.008237312614496443, 0.037637345687987836, 0.014795174597809005, 0.0444455938644177, 0.058947818062610464, 0.001580673455503364, 0.05390592397295391, 0.03243087490862847, 0.0, 0.0, 0.06870781743568351, 0.0987938005979452, 0.02172205215774789, 0.11444890838511614, 0.06273103076544716, 0.0029945192043285603, 0.0, 0.0, 0.0697461516094205, 0.020057757760603093, 0.05928032167055184, 0.0515767051952473, 0.0, 0.07064391818806331, 0.009008035450323118, 0.0, 0.08894094151525331, 0.18823422594999628, 0.016240576001949638, 0.01911618050110817, 0.010534622043215671, 0.0, 0.011181402201375961, 0.05234504355719266, 0.08177771637682671, 0.038211688987679814, 0.05715470422263481, 0.0, 0.005270085347436106, 0.0, 0.0746774776386264, 0.0, 0.009701802958151214, 0.06743969019258389, 0.03528635831387704, 0.010071226794573594, 0.0012211047212915738, 0.030432226914707997, 0.08641581588867989, 0.030213980826114976, 0.016352021064925164, 0.02010534073567384, 0.13587162656415003, 0.005013854990941927, 0.018765919893019498, 0.0005251550204774953, 0.08237284739753524, 0.0, 0.0, 0.03694781658741445, 0.009285040708081812, 0.14926289499422332, 0.0, 0.057887973002356645, 0.01991124705994314, 0.057833512065955245, 0.005770213787663892, 0.029235774576608697, 0.0, 0.021913750183403184, 0.03476864544708725, 0.0, 0.016361313084180325, 0.0, 0.0, 0.0, 0.03378772272499272, 0.059030127445003695, 0.06349803324348659, 0.1022944685952912, 5.898799865809602e-05, 0.0001847839882275225, 0.0, 0.06371689205048528, 0.009262810600603818, 0.05333744751773859, 0.02803676354748484, 0.0, 0.09275086137743778, 0.0128579272993997, 0.012336222559048473, 0.0, 0.0, 0.0, 0.030200140753286364, 0.02395733699635618, 0.09146021870519916, 0.10143419091618187, 0.05411498454484726, 0.015621885564088993, 0.0, 0.0, 0.0, 0.0041298311864967855, 0.004042035607649308, 0.026630301318417298, 0.01915268458071493, 0.0699226092508694, 0.07866035239154578, 0.0, 0.032858749582744144, 0.0, 0.0, 0.00649066215791249, 0.015436216109013141, 0.024117395873951914, 0.0, 0.013311613722489197, 0.019897504285745472, 0.02518307096298419, 0.015478128151715485, 0.0, 0.025269235474916992, 0.0, 0.051545001619715904, 0.0490014260689613, 0.03557191664723426, 0.0, 0.0, 0.0, 0.04163900696575763, 0.02502476083105272, 0.03983617904084393, 0.005660335364131201, 0.05047084577163512, 0.0316357899419444, 0.03526434515604912, 0.0, 0.03652156443371209, 0.0, 0.03731963673133542, 0.0, 0.0, 0.0, 0.008795211130712502, 0.0286092792107307, 0.018401285384478386, 0.023074746374447917, 0.0, 0.08668667949907183, 0.05279903370979227, 0.0, 0.0831922543922626, 0.006433893666142077, 0.08815402174449034, 0.02818247219186269, 0.0, 0.02613965324918633, 0.006910683912118517, 0.06386399706170182, 0.12904721629621252, 0.0, 0.05405995559481657, 0.05312436877535652, 0.0, 0.0, 0.029021770182386292, 0.0, 0.0, 0.03201572531762465, 0.1523664373145247, 0.0011584757127426337, 0.020208123590802617, 0.07809438992257865, 0.018356405733532248, 0.006523399202686079, 0.05208227414101818, 0.07682085077184436, 0.02382438236171019, 0.0, 0.003542228318645955, 0.03918661075096855, 0.011041824685593955, 0.012559566306995146, 0.0, 0.019668494864815856, 0.008678206897309521, 0.007637978029961849, 0.0, 0.0, 0.024208322759415517, 0.0, 0.05189973664998163, 0.00779298000844122, 0.0, 0.017119349624862893, 0.007329559777718223, 0.010926020906921406, 0.019266535812536775, 0.16415193121458063, 0.0, 0.0604541902075012, 0.08166612273621203, 0.16830133388920718, 0.04072819974087558, 0.07839003049785587, 0.017239274132040558, 0.0, 0.008349609697915913, 0.0913632566749243, 0.04663252522798588, 0.0, 0.01907518227662888, 0.053177825169135745, 0.011403181282149342, 0.023102586931361562, 0.01875617425176872, 0.0, 0.0, 0.0, 0.0, 0.021505332605783602, 0.002018887424003206, 0.0, 0.0, 0.010726335847027332, 0.0005855333818516203, 0.035564327354020625, 0.013452890019773631, 0.02330561761782911, 0.0, 0.018605364003498128, 0.012897634344749432, 0.04324266985251426, 0.011862108682846179, 0.0, 0.0, 0.016290437605769384, 0.011907963949794485, 0.05029815385017303, 0.0, 0.04003686667109064, 0.006766553740800783, 0.0, 0.0, 0.0, 0.08411625478556212, 0.0029207612539759024, 0.005502695146422245, 0.022613874315978565, 0.020548050821315753, 0.0, 0.07864083481211695, 0.0, 0.06611410942275817, 0.0, 0.0, 0.0, 0.0, 0.022865683172861834, 0.029378990272280192, 0.009854343544236971, 0.016900227848591375, 0.1120308848839776, 0.05130379568391281, 0.09316363898560075, 0.008162942274449702, 0.023827606364994987, 0.05402376050410537, 0.0, 0.009800225782736604, 0.0, 0.03973317660637235, 0.020052788956193127, 0.0, 0.13183363871103262, 0.0, 0.12120601955675307, 0.06092445815808848, 0.07131243867343097, 0.0, 0.0, 0.0029290250633743284, 0.045794531565055474, 0.0, 0.009610815589754861, 0.0, 0.03522367958769995, 0.034660551927493836, 0.002152702954306095, 0.0, 0.0, 0.05399956207162031, 0.0, 0.0005916816445391436, 0.01294109001708324, 0.1565285992582686, 0.0, 0.003675551438985485, 0.018567846177344638, 0.0287258613784508, 0.024240436567012517, 0.02121417302691527, 0.008745095761544482, 0.028557908163284955, 0.0079390831067283, 0.02620278165281994, 0.0022739285019610475, 0.006904428530541268, 0.0, 0.03632419284599567, 0.0, 0.0, 0.10046717926861962, 0.031864128791228324, 0.018143258619139242, 0.016451330095959966, 0.0, 0.010299046936966873, 0.025441701242804458, 0.05778374250138099, 0.14214301245134323, 0.02025651256669456, 0.0, 0.10792073826239788, 0.019227484875522734, 0.06432292051785955, 0.0, 0.00450180056548008, 0.01682216936449331, 0.0, 0.0, 0.0, 0.07674748760411253, 0.024869069873078558, 0.04300752272875698, 0.025795400174135275, 0.0, 0.016836648452654878, 0.0, 0.0, 0.13227039911687694, 0.0, 0.0, 0.0, 0.0034516196260024655, 0.0, 0.06553297888613466, 8.626106069042748e-05, 0.0, 0.05502748792153901, 0.0, 0.008500214830153702, 0.0, 0.05230037559651115, 0.006865957440773798, 0.0, 0.003590040076984056, 0.0, 0.017897594039315453, 0.1009977986673195, 0.013829392331597192, 0.026020141603766995, 0.03529402649787252, 0.10900875785054183, 0.0, 0.013133847808908004, 0.050682883155696815, 0.0, 0.0, 0.024498969548201844, 0.0, 0.05045490457431666, 0.02553614665713842, 0.015606964687549083, 0.0, 0.0, 0.0008994332285872168, 0.06611855886285431, 0.0, 0.0, 0.04652317208557565, 0.025466430873220623, 0.014223041554855102, 0.0, 0.06308288891186822, 0.021642727476601186, 0.012924893739570143, 0.0, 0.0, 0.0, 0.0, 0.0, 0.049049496852033136, 0.05620224529463051, 0.0, 0.0, 0.01611916966291962, 0.0, 0.11450769779574818, 0.04498103086019569, 0.08614802901877996, 0.03216763740714836, 0.0, 0.015016430369729472, 0.012847334897090437, 0.045068236730448866, 0.08003336229941767, 0.02490882556774376, 0.008706476094137869, 0.022075465767417694, 0.0, 0.0, 0.013347536640003021, 0.004951337662156038, 0.0, 0.0, 0.0, 0.08600207055359294, 0.0, 0.0, 0.023410187349166712, 0.0, 0.0, 0.09341111410886792, 0.0209056574760407, 0.04239175913890284, 0.10698423105048296, 0.0, 0.04103982095886218, 0.0, 0.016354925560543482, 0.14079959710543472, 0.04453205290193606, 0.0, 0.04699254594063533, 0.05721407216566706, 0.031198230109510703, 0.03150848651697135, 0.0, 0.0, 0.05182151906947351, 0.03127070707079313], \"feature_quality\": 0.86328125}, {\"bbox\": {\"xc\": 686.5, \"yc\": 1038.5, \"angle\": null, \"aspect\": 1.7228915691375732, \"height\": 83.0, \"confidence\": 0.5986328125}, \"feature\": [0.006430684021418562, 0.033046188678213885, 0.05034406748365619, 0.0, 0.08189372846530255, 0.004664766851065584, 0.0, 0.00497593795740513, 0.10034924313054516, 0.0, 0.030048575345329284, 0.003260800930602242, 0.038869911201783514, 0.01271640317328567, 0.04987390117844973, 0.0, 0.0, 0.0, 0.0017954545216625284, 0.029573318408006005, 0.13677300610901458, 0.043836328578581725, 0.0, 0.045915362633327864, 0.008553721160254296, 0.06593103167839942, 0.0, 0.03258749702259974, 0.0, 0.0, 0.0, 0.0003863036597332952, 0.0, 0.049894412943689175, 0.0, 0.0125662543986348, 0.0, 0.04405435521176748, 0.014914207934970745, 0.02017636181222173, 0.0, 0.0, 0.004822285745891287, 0.04688068825761074, 0.008010850119663591, 0.05193147830173278, 0.033962564641230125, 0.0290722870364919, 0.0, 0.009917780494205351, 0.005984922592108475, 0.03282151479046842, 0.0, 0.002848151410113906, 0.0, 0.09252659146241621, 0.008607522052982422, 0.050196041187542624, 0.0, 0.0, 0.005572260117039223, 0.0, 0.09251384830026058, 0.05837892969632277, 0.04997322239578436, 0.03970733262139831, 0.052766022768017715, 0.01651764201509094, 0.0, 0.01702747628342921, 0.0, 0.02436370312911867, 0.0809968569193137, 0.05737663066173865, 0.019714374511222515, 0.07064814902731376, 0.006969957315797227, 0.0, 0.0927083784327638, 0.061014397201512374, 0.0521508646477315, 0.012065142190535776, 0.06456587586871963, 0.023803445486388745, 0.02545133657911384, 0.03842860563012536, 0.03569926404978476, 0.05666213474225423, 0.0, 0.03313174073058432, 0.04475391501763878, 0.024848065582276313, 0.11552732758784667, 0.04209530752183346, 0.08081409161901532, 0.044237667713563765, 0.05077585092015772, 0.051205097331532626, 0.0, 0.0, 6.176079448900484e-05, 0.0017959214047587476, 0.06784298273032106, 0.022887850943693364, 0.06967831728160499, 0.009250569831431459, 0.0, 0.01418918564121708, 0.0, 0.0, 0.0, 0.10888058706590809, 0.023905488202082242, 0.016981037738127366, 0.021738161942010278, 0.03478627493240324, 0.007364732623720656, 0.0, 0.0, 0.0, 0.043098626341027305, 0.05415192248902697, 0.012304698300837743, 0.03008197536403061, 0.0, 0.030007052286210277, 0.0, 0.06042678984755222, 0.03673582535997599, 0.07485857437103685, 0.0113297665957139, 0.04064017437494225, 0.034201658532085004, 0.0072483854597795135, 0.0, 0.0, 0.0271401434202046, 0.11940379419921542, 0.04982715275957238, 0.02769752410960466, 0.0027853395699921064, 0.04756301950722999, 0.026134801613654883, 0.0047590269727278795, 0.021246679651181107, 0.028173950068309816, 0.036672975951569535, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.02811792202403058, 0.06946884689002276, 0.01185951568220299, 0.020777572512510355, 0.04071795160603004, 0.0, 0.011094689661309512, 0.11102278143900267, 0.0, 0.0, 0.02326412451915231, 0.04629042780547894, 0.037456547769231956, 0.03151596041666777, 0.0, 0.056640951411540096, 0.0, 0.05788038767707752, 0.011091249960984647, 0.0, 0.023349647553261497, 0.01929888275568117, 0.10999154705248416, 0.0, 0.0, 0.0, 0.12792853855287642, 0.0019507988605356422, 0.0, 0.026683876862137964, 0.0, 0.015444824460197983, 0.025141307548627867, 0.0, 0.020601442030373996, 0.0, 0.02498220042218184, 0.09589991458739827, 0.0753155042036464, 0.0, 0.024715083181889086, 0.16699875037568646, 0.05301287282555872, 0.12189493730933407, 0.0, 0.02295463647196539, 0.0024222708579526406, 0.0, 0.06326791391570233, 0.06376688291793459, 0.02027808325430851, 0.014955579684584653, 0.05315899635282732, 0.037990052647820266, 0.012080100067844832, 0.0, 0.027277499287109125, 0.013201475537477518, 0.0, 0.0, 0.0, 0.0, 0.023134464709678466, 0.041370920520727925, 0.07436216312126069, 0.0, 0.0, 0.011666478953787782, 0.05046171995117045, 0.0, 0.0, 0.07073022096106688, 0.006444393595310535, 0.01321178531115385, 0.0, 0.050872867258455745, 0.0065829905110668325, 0.0, 0.029874567341730232, 0.11099848071793686, 0.07069571410297207, 0.10309770359958509, 5.8256871829249255e-05, 0.030530925174795786, 0.0, 0.0, 0.03980563405412322, 0.0, 0.031437644275013873, 0.0, 0.0839632113666308, 0.02857634215162872, 0.0060993851236179316, 0.02026867097399805, 2.563575692023532e-06, 0.01658490323557421, 0.06580413896740928, 0.0, 0.0, 0.059546682565635375, 0.05620631174903817, 0.0, 0.0, 0.09783814374761676, 0.06886733978892277, 0.0, 0.07656878632526704, 0.0, 0.0, 0.0, 0.0, 0.17594925894937208, 0.03101519020950494, 0.0016291688732934198, 0.0568356183444178, 0.007184697112712925, 0.023139200904461418, 0.06924581253403805, 0.018155942572472175, 0.08232767583500032, 0.0, 0.0, 0.011195893956526115, 0.0, 0.03343376486643058, 0.0, 0.030429867007251917, 0.009595182411131263, 0.0, 0.0, 0.04656939693276029, 0.0, 0.017840592836512306, 0.004420069844872841, 0.001237449939645544, 0.012438736758661254, 0.052845852004722624, 0.0, 0.0237303194680331, 0.027933734828930832, 0.0, 0.0, 0.04099189642864886, 0.061285759398879744, 0.16213087901410084, 0.022451723058935783, 0.08100144180459155, 0.0, 0.0, 0.0, 0.0011915501753640056, 0.03206446980905644, 0.0, 0.09856664305088833, 0.06998400393656787, 0.06904111357369887, 0.05195816266568716, 0.0, 0.051121151647195794, 0.0, 0.03995380544154304, 0.011778775479368056, 0.024966200996567125, 0.04739425344525287, 0.038660150627588756, 0.03404384479099765, 0.08997267770774307, 0.025104854394296217, 0.022348961104909817, 0.0, 0.0053568513455650125, 0.0, 0.06131323554681972, 0.09511864350330657, 0.008032504478939839, 0.0, 0.005699163191694104, 0.02004965979266267, 0.06694558471016378, 0.11210762499045174, 0.023038830884255818, 0.0350264093351973, 0.05246997846672237, 0.05041476840446432, 0.0, 0.07205243388955926, 0.0, 0.014849794649556089, 0.030751913743362214, 0.01195789691514634, 0.0, 0.0, 0.0, 0.0, 0.01534973265444087, 0.04889053034113386, 0.02180134298768819, 0.01683004121570134, 0.04803235671925782, 0.025731373163862682, 0.0, 0.03233165544953643, 0.02281096498777261, 0.009955686634332562, 0.0, 0.0, 0.0027210832940981525, 2.5614341884696584e-05, 0.03333347982827579, 0.02114556344711473, 0.0012072784616001466, 0.06991052140814541, 0.06556989527164941, 0.027422654848091664, 0.03280482929024839, 0.025908249829859814, 0.008166004544380474, 0.0, 0.1412039536563911, 0.024479728501269855, 0.0, 0.0, 0.0355322805381417, 0.0809691486252837, 0.005398679096427168, 0.0, 0.056291119690280784, 0.05481442597527916, 0.026034029483257642, 0.06613534511041055, 0.12594532279678516, 0.015649077782953654, 0.0008844388403648762, 0.02814675166658468, 0.0182983745623629, 0.08536569709667866, 0.007197513856888155, 0.06370311321609984, 0.02750791464511664, 0.0, 0.012883132775142777, 0.01363853822479401, 0.01758820443653801, 0.030472627487940038, 0.009554806609698194, 0.0, 0.0, 0.05050773047711882, 0.006593598758287472, 0.04996076941624126, 0.07599805516295799, 0.08750237800013531, 0.011637279328402717, 0.0002926209885153383, 0.004714131577104773, 0.0, 0.14003411976320013, 0.09591105759971913, 0.0022948586682677535, 0.1111376522989026, 0.01659179921808751, 0.08303540635414706, 0.03000470387978179, 0.0, 0.053477716352558345, 0.0, 0.06390396518409017, 0.0, 0.03571175019305501, 0.0, 0.0, 0.0, 0.10236421315537814, 0.0, 0.014841393862923566, 0.1299237595781943, 0.0, 0.02337546136937831, 0.10765410123582095, 0.0, 0.0, 0.04262323260332955, 0.05677494530560526, 0.0, 0.021173926724755848, 0.0008626292852092536, 0.06396550047980848, 0.0, 0.061979718680329365, 0.0, 0.010975054624731842, 0.0, 0.07101905215170809, 0.0, 0.0, 0.0836764943635951, 0.0, 0.016858528857319255, 0.047994790507333844, 0.016290390683926396, 0.0, 0.07058483947219316, 0.018813836300651154, 0.0357789523406493, 0.05252280828406502, 0.1092152836911914, 0.016482809737923522, 0.015406359718541667, 0.03892053563127099, 0.0337561867672042, 0.0, 0.0, 0.04355541937326237, 0.0, 0.0012877573705380027, 0.0, 0.009042997026861826, 0.0, 0.005572857582311077, 0.003329979947244343, 0.02274099781442757, 0.0, 0.012028413362722227, 0.0, 0.04098971591358905, 0.024961771566260265, 0.014510414684179946, 0.04592161399589477, 0.006594210214506716, 0.12970939753685812, 0.02591339849849914, 0.02735412407867861, 0.04155288574610893, 0.05329600400059682, 0.0, 0.11452364396765423, 0.05532580650239558, 0.0, 0.0, 0.026272565808949887, 0.0, 0.1250496783087007, 0.06635947801485695, 0.015412030715883533, 0.01162656226270238, 0.00011490237191801355, 0.046227939052605194, 0.007581866945372902, 0.052192725562320796, 0.0, 0.054666946880663495, 0.026064455130180617, 0.018975851471421244, 0.0, 0.018699647369890646, 0.0, 0.05454028631576337, 0.024859929905662586, 0.06821543211349355], \"feature_quality\": 0.5986328125}]\n"
  },
  {
    "path": "python/bugfixes/bug_vs_1/in/in-2.json",
    "content": "[{\"bbox\": {\"xc\": 305.5, \"yc\": 933.5, \"angle\": null, \"aspect\": 0.467576801776886, \"height\": 293.0, \"confidence\": 0.92041015625}, \"feature\": [0.0, 0.04798045472202683, 0.0, 0.010914579055066775, 0.0, 0.02977623176387587, 0.0, 0.0, 0.0, 0.0, 0.016284260035054694, 0.001173857471064838, 0.024717401498412485, 0.040369157426764106, 0.04385612708360236, 0.0, 0.00763728408010481, 0.010529785203135526, 0.022386300025399663, 0.0, 0.07306464790025802, 0.032678640495372016, 0.045982797731782876, 0.10017310160950363, 0.033819562226336475, 0.05667408136239487, 0.06454582489371787, 0.0, 0.08559382983784407, 0.015917139647805265, 0.04438150921819147, 0.0, 0.13213185679705566, 0.0, 0.11380987411551315, 0.0, 0.0, 0.07089769856593077, 0.0, 0.04676114027745912, 0.035688762185164216, 0.0, 0.0, 0.0, 0.0, 0.0, 0.012307968708633292, 0.05840991314882036, 0.0, 0.05707684073613281, 0.04391609614657561, 0.0036140039119275363, 0.0, 0.0017013437054403237, 0.09755632474613003, 0.0, 0.02079875537650801, 0.0681918752031675, 0.07608407418645195, 0.04258694989790395, 0.08175267620640501, 0.05978065291336169, 0.08315479779534411, 0.03736043532052716, 0.018018025660486985, 0.0, 0.023677018428370816, 0.03757923535590306, 0.0, 0.0, 0.05291462126273113, 0.035643801422443104, 0.1332258592645793, 0.055302516965971556, 0.0, 0.04317316237765416, 0.08369497749092662, 0.0, 0.0, 0.05152964285436086, 0.0, 0.02019598153979461, 0.033465479040076816, 0.024514141192723767, 0.03393330874123572, 0.028203438863095284, 0.0, 0.0023781261060830186, 0.010773334502869502, 0.0, 0.06717414660343925, 0.0, 0.019005050466475992, 0.0, 0.05265358404048878, 0.05566288567968723, 0.01563373428576407, 0.0904854545360144, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.034273293012957555, 0.0, 0.09738356436685418, 0.0, 0.029846648454665038, 0.0, 0.07898508333439283, 0.0, 0.02090804658860042, 0.027124174399818016, 0.01718084792067523, 0.03770993721855356, 0.03183878270194381, 0.0, 0.030255938454779474, 0.05970738895193555, 0.0, 0.08853050889596904, 0.0, 0.004265377199541311, 0.051174954938054414, 0.05758486895089437, 0.050563921038656465, 0.08013234785891624, 0.014568835597064218, 0.06197623071785851, 0.0, 0.02126713941354314, 0.0, 0.0, 0.02587417677730811, 0.036736351621596795, 0.0, 0.0, 0.06412660495135786, 0.0, 0.03523902857252665, 0.019936962375019008, 0.10404500484756425, 0.016100485093544045, 0.0023240647574518094, 0.03967828541732531, 0.06631066539723629, 0.18366813335677828, 0.029498974490643697, 0.0, 0.0, 0.0, 0.051570004003695796, 0.0048893048483412286, 0.008482605059751165, 0.10117455289213914, 0.0, 0.01254778425845717, 0.021502819205414452, 0.01537549966659841, 0.030642212062810065, 0.024482248554688, 0.06710612821704018, 0.0, 0.04890353390685614, 0.0, 0.028633729488470937, 0.0, 0.0, 0.0, 0.09452611410743503, 0.07160461424676241, 0.012649880557379263, 0.05839732376875539, 0.0, 0.009919679014604808, 0.08434665657952996, 0.14412301224452886, 0.0, 0.08173372799827229, 0.01512453898782509, 0.0, 0.0, 0.0, 0.0, 0.041593896116599985, 0.0, 0.031920918328033634, 0.07112939263703916, 0.01776808546366762, 0.017306656967492624, 0.02312175484128033, 0.0, 0.03148485069819585, 0.1704454176812899, 0.11066558939981021, 0.0, 0.03368551373263158, 0.004243474919489686, 0.004622862851305701, 0.07495218446523934, 0.058133198758243826, 0.0, 0.01330028833849788, 0.004341696307557994, 0.03244069525646936, 0.005945561509450083, 0.018834998773868045, 0.0, 0.0, 0.019548191964901297, 0.01989655083151351, 0.0, 0.0, 0.028337345336864946, 0.02432021984309853, 0.013598565707466261, 0.0, 0.08048496045165301, 0.0, 0.0874426636798309, 0.005366505001920218, 0.0, 0.0724773450739083, 0.02828072519559021, 0.02372884196086534, 0.01797419074934919, 0.08768101894403044, 0.039205794255389066, 0.0, 0.0, 0.013598629845501527, 0.014610157671606075, 0.09186486209789305, 0.0, 0.058247030026975385, 0.0, 0.0, 0.09091549089989312, 0.0, 0.03150206947002052, 0.06254181365669438, 0.01225169789058634, 0.0038969863637986985, 0.0, 0.0, 0.07857200689955361, 0.01602740438364724, 0.027450364412669532, 0.0, 0.09409516148590967, 0.015817321394457153, 0.0030017662790463, 0.01590380466308033, 0.014643952689580702, 0.005670816500152819, 0.0, 0.006660354142737766, 0.0007245688160469242, 0.14461072702726419, 0.0, 0.043314957829761476, 0.0, 0.06667924377946595, 0.0007543604323493484, 0.12349680650803797, 0.04236547209955572, 0.03472676266614934, 0.04596606228586681, 0.025300778160746308, 0.10181186506084301, 0.07550152673728834, 0.024127136869219974, 0.03447111074822517, 0.106297422695171, 0.0, 0.0, 0.030828794188685636, 0.029723702712993426, 0.0, 0.04974285798020517, 0.0, 0.06767012144885912, 0.0, 0.0585409792238854, 0.00974117713585063, 0.03188282949766239, 0.02527041109169221, 0.031831777912235175, 0.02482376526732385, 0.02887667062219195, 0.01964763340729222, 0.016230579935503645, 0.04101439980539973, 0.04034218738293496, 0.029465799091902615, 0.0, 0.02656306279829779, 0.012862081247055169, 0.03662773614951879, 0.0, 0.0, 0.0, 0.0, 0.1689107044483041, 0.006577409346613747, 0.10716671297485374, 0.014843271942066767, 0.0, 0.00704429702037969, 0.0, 0.019127846170957967, 0.0, 0.02667560214374758, 0.0, 0.01873506942428028, 0.0008894500953878777, 0.0, 0.0, 0.07156702019552036, 0.028845815645941013, 0.0, 0.04190256499260344, 0.0, 0.03521943440275304, 0.0, 0.0, 0.01634890773863614, 0.0, 0.005429504014398726, 0.041520178607638764, 0.0, 0.04583373635653762, 0.0, 0.05030235696826729, 0.057548301108216564, 0.002820136812081385, 0.0, 0.01824311465928508, 0.04750592030553857, 0.0, 0.03953371828583687, 0.1457659171804311, 0.0, 0.1574744529567951, 0.04542473268693776, 0.04453916509016231, 0.04181505780606013, 0.007604693368274499, 0.0, 0.07679844362323908, 0.042031390817722426, 0.02403922652931214, 0.0, 0.050766643042977774, 0.0, 0.018818086948355003, 0.0, 0.0, 0.0, 0.0, 0.006879248094522451, 0.06746084362107603, 0.0, 0.0, 0.0, 0.0, 0.07956918842155575, 0.07032394719017501, 0.00333943327791365, 0.004845725916683467, 0.044120930124772445, 0.0119437814911752, 0.012848116335198164, 0.0357715941670655, 0.16278488070009622, 0.0, 0.03431682899503813, 0.03760851436900176, 0.021957654082499743, 0.0, 0.045737465165604105, 0.007419497082089485, 0.12490138366747008, 0.0, 0.010618052883525552, 0.0, 0.00033674182918343436, 0.04693589809840467, 0.03241089397651207, 0.0, 0.07803741637561568, 0.023464771925812575, 0.007252182709200904, 0.0, 0.051041066789437685, 0.0, 0.014054501239049591, 0.0, 0.0, 0.0625707628170403, 0.0, 0.0, 0.02199343165295731, 0.04421938659019322, 0.0, 0.015833099351132467, 0.026396647503224985, 0.027994110641141625, 0.04817055069597731, 0.0, 0.011087873154421805, 0.0, 0.09860018959522898, 0.1292100852508417, 0.0, 0.02441519911075027, 0.007966643199091676, 0.004957345568520429, 0.03505209826874535, 0.09908119737199082, 0.08407592993267428, 0.0, 0.02512772686966764, 0.0, 0.0, 0.07648374577192067, 0.049191362502686534, 0.0, 0.0, 0.0, 0.0, 0.05454482228636257, 0.0, 0.024788574101760143, 0.014086627522785257, 0.04567804127593062, 0.007425916612226326, 0.018995382802981776, 0.016798568050661978, 0.007535047479230574, 0.067286488953495, 0.03415200798827049, 0.012372275106241363, 0.0, 0.0012604419575249178, 0.006389362353825593, 0.03071875164532297, 0.0, 0.0, 0.0, 0.030321237846612104, 0.0034120109050963404, 0.029150214761311074, 0.045832723891838074, 0.02442711504144495, 0.043250187576719785, 0.0, 0.013013175568953927, 0.0, 0.0, 0.0, 0.0, 0.001317269402092209, 0.028104645672989363, 0.014031205388383516, 0.016918469526302597, 0.0, 0.059215825887084295, 0.0, 0.01990825602294946, 0.0, 0.023160982121777536, 0.013936473510296372, 0.0, 0.0, 0.003884981670644496, 0.027189757831521107, 0.0, 0.008177628129402684, 0.0, 0.0, 0.0, 0.016660475424769183, 0.0, 0.07348575991465835, 0.03219229551781843, 0.0, 0.0, 0.02837258918724334, 0.02280512368632749, 0.11993205115828735, 0.05257386962523023, 0.03774251704982432, 0.014440543492024402, 0.04558809226275899, 0.013454894217827425, 0.027534130977370393, 0.009872885736590392, 0.060455861498331, 0.0575815108666194, 0.08000787425761882, 0.018070400092891556, 0.12929152223047596, 0.019591276690090895, 0.007744143200806395, 0.12134473719789232, 0.0, 0.018444960492231783, 0.0, 0.046054467404903834, 0.0], \"feature_quality\": 0.92041015625}, {\"bbox\": {\"xc\": 323.0, \"yc\": 391.5, \"angle\": null, \"aspect\": 0.42487046122550964, \"height\": 193.0, \"confidence\": 0.8486328125}, \"feature\": [0.006122838423213256, 0.0, 0.0, 0.0766355041319142, 0.037614528851285516, 0.0, 0.0, 0.039276633052557394, 0.0, 0.025147595494320156, 0.09173169794424595, 0.0, 0.001453311857910955, 0.0, 0.10663431158003096, 0.009304692591093445, 0.10919504293094608, 0.0, 0.0, 0.0, 0.0466677063944322, 0.002153724353981306, 0.01592853446955818, 0.030088273415634262, 0.0, 0.0, 0.0, 0.0, 0.002997262982469633, 0.12451182134281802, 0.0, 0.029531433720088946, 0.0, 0.045573733483929735, 0.020243500363612315, 0.01942410344640321, 0.0, 0.0, 0.022742901656490292, 0.014439456076556095, 0.0, 0.00538509614457412, 0.004376631180414201, 0.0, 0.0, 0.1418337566970053, 0.003694078318656237, 0.061087093856550194, 0.09412000122589657, 0.0, 0.0, 0.034211491617683436, 0.0, 0.030053508566041665, 0.03153635214854327, 0.06191777946027778, 0.0, 0.0, 0.028456213234730687, 0.0, 0.018055020795125783, 0.054840828873331283, 0.0, 0.00609382990204634, 0.017521921954866175, 0.02337442236887714, 0.0, 0.007864673154286236, 0.0, 0.016214399149370017, 0.0, 0.03150120916721983, 0.0252247256412953, 0.09484052833031266, 0.01210262015935526, 0.08531960183611294, 0.014123806251176301, 0.011264079596095815, 0.0, 0.09660270363979939, 0.061230399965128524, 0.030126645060197983, 0.1108926217059823, 0.0493157599968215, 0.008777339171613436, 0.019197553091632565, 0.005210541811500034, 0.019535733750759367, 0.009381416246457934, 0.028007266156817423, 0.020924901014355814, 0.012563922691573826, 0.0585187591491395, 0.00296869785711595, 0.006380700994003344, 0.04708787764762829, 0.2033081214374626, 0.02364412191719543, 0.04965489180268665, 0.014681467656059797, 0.0, 0.03862288110310259, 0.04785603480660252, 0.07305416033498582, 0.03756743981597305, 0.013473109896977269, 0.0022408415417091467, 0.005786183743773326, 0.0, 0.07414027683160361, 0.0, 0.001965695805702106, 0.08738878520657688, 0.009574855350782016, 0.007276231099335698, 0.008894373123838622, 0.02670775828966233, 0.11022116446540715, 0.0, 0.0, 0.028223129927133318, 0.11746665709090734, 0.02184942000709214, 0.0, 0.013549834279518166, 0.04738478086527513, 0.0, 0.029277090683101324, 0.0328136142351023, 0.0, 0.11792582536037585, 0.0, 0.09496016920995146, 0.01589457533142284, 0.04368494803613422, 0.0, 0.025818534985294597, 0.020489251084192934, 0.04023727380605892, 0.047485215561692704, 0.0003331081492134138, 0.058882556778678125, 0.0, 0.00711767319282909, 0.0, 0.02346366436606176, 0.02686160554741432, 0.02658108706853548, 0.04859850227752439, 0.0, 0.0, 0.0, 0.06527205806154043, 0.0, 0.040024679420862005, 0.03766290644318847, 0.007704686346142883, 0.13639269705626636, 0.021551981587610878, 0.026941941087984766, 0.006301976876347411, 0.0, 0.0, 0.008979273877882598, 0.016729205139973682, 0.10538615109321875, 0.06440805614361994, 0.05571570935027757, 0.005156384257938219, 0.0, 0.01804480251227615, 0.0, 0.010500134242862055, 0.0, 0.0, 0.007670182552879847, 0.005105751328413706, 0.07162196604347784, 0.0, 0.05289296179694433, 0.0, 0.0, 0.008146443103758883, 0.012653154508288767, 0.025532425974210522, 0.0, 0.01950017627888481, 0.013634169290318687, 0.004383148498948205, 0.0, 0.028498531992823286, 0.012072547051930739, 0.0, 0.10095570376856511, 0.06517979973662594, 0.039803129131055354, 0.0, 0.0, 0.01955464470035996, 0.033317006464845735, 0.0, 0.06439793966546709, 0.0, 0.05999264682703137, 0.012047244221726084, 0.030211730517048948, 0.0, 0.030607564630306852, 0.0, 0.029405038825996176, 0.0, 0.0, 0.03481317191909551, 0.007878055381676057, 0.045651454098146124, 0.0, 0.028484962881097257, 0.0, 0.05252387614058515, 0.08002558889938208, 0.0, 0.05576299908627795, 0.0, 0.07722637277452978, 0.007894638639602748, 0.0, 0.019648387919494425, 0.03137901735275066, 0.0426434859882234, 0.1369906571231881, 0.0006864712972673514, 0.05210319004649398, 0.062000543770042486, 0.009257220333815297, 0.0, 0.030480826508950697, 0.0, 0.0, 0.042048891293706196, 0.15804175087659594, 0.0, 0.022383061320950227, 0.0856750514805168, 0.008828160803415168, 0.0, 0.060870459279244504, 0.07412556459856819, 0.008965863290612962, 0.0, 0.0, 0.031347204839364255, 0.04457227195573088, 0.018920596322787708, 0.0, 0.0019293540740742503, 0.037965935394874965, 0.018210259868517837, 0.0, 0.0, 0.027211121432349553, 0.0, 0.056602463163572335, 0.0, 0.0, 0.0, 0.02422280890066662, 0.022199148227537772, 0.029019242655839345, 0.17625386649930053, 0.0, 0.049900933393829464, 0.0468666007761501, 0.15612973068829292, 0.030382314466949178, 0.033407478844508405, 0.00039320391619932817, 0.0, 0.004249620004317794, 0.0797563009329093, 0.033634526587940396, 0.0, 0.019215091132179635, 0.05150022700590266, 0.03810090515314101, 0.0, 0.022705484067370545, 0.0, 0.0, 0.0, 0.03531701208950472, 0.019322042783543013, 0.009097302607430463, 0.0, 0.0, 0.00730188079268576, 0.019659289748165196, 0.06454205439420829, 0.0, 0.0031942027140096367, 0.0, 0.005243554893131861, 0.0029552100073532337, 0.03516702468411295, 0.020831086531933617, 0.018110467996042694, 0.0, 0.037913631050382486, 0.012332045042456722, 0.05223130107690366, 0.0, 0.05727062193197227, 0.05293023976819423, 0.0, 0.0, 0.0, 0.047517650538082185, 0.050112501005941855, 0.0, 0.011987177269105236, 0.0400181086548622, 0.0, 0.09876922975260485, 0.0, 0.04814474710921817, 0.0, 0.0, 0.0, 0.0, 0.03796412617997816, 0.01700946328969936, 0.01109961192694197, 0.02181775292898683, 0.14260634381464163, 0.026725783538401063, 0.11543592680876452, 0.041532348805846275, 0.009055375070244008, 0.06544085606618884, 0.0, 0.02517684834675931, 0.0, 0.03956634594990696, 0.0, 0.0, 0.11882121802936757, 0.04358581643983525, 0.13318591891708878, 0.045952519673537855, 0.08992479837711734, 0.015065725120942657, 0.0, 0.030726658673288727, 0.06644903088696298, 0.012261008633759394, 0.027072547787818008, 0.013693736672748978, 0.03350027237125733, 0.05905743397807624, 0.04984223571438011, 0.0, 0.003781601997994734, 0.023602988456643745, 0.0, 0.0, 0.0, 0.13446381801017898, 0.0, 0.028711484148811688, 0.007131178313031436, 0.016136157876847315, 0.016967282695123802, 0.006540976491179667, 0.0, 0.0061125975978950555, 0.0, 0.07988789659265397, 0.03140416020414611, 0.011771471296069327, 0.005856732580690788, 0.0, 0.0, 0.016149197604150164, 0.06125116812326868, 0.07181024655838154, 0.011767314028559269, 0.010851489884103123, 0.017958046004045422, 0.029658587786349028, 0.013482611183891111, 0.02204083319795059, 0.12465510418175138, 0.058131848944729496, 0.012989573946161528, 0.06757701529230198, 0.02249190217661501, 0.028816104472619133, 0.0, 0.01931606684784288, 0.011517928153127719, 0.0, 0.014607729059839751, 0.0, 0.09808923255232391, 0.029435652952666366, 0.042507134594786965, 0.025168634162083148, 0.0, 0.041378652800786626, 0.0, 0.0, 0.14086407568643097, 0.0, 0.0, 0.0, 0.0, 0.0, 0.04064179331430373, 0.07175818072775006, 0.0, 0.05758653644956366, 0.0, 0.001547650634521495, 0.005676133957334454, 0.003053225751458021, 0.006184783672489427, 0.0004504804657097696, 0.018689223334095995, 0.022595388105229926, 0.06940890645813369, 0.10455583198639952, 0.01954127338061622, 0.02152413363998709, 0.0, 0.068505590475028, 0.0, 0.020550774571153934, 0.04045142434876353, 0.0, 0.0, 0.0460969049119019, 0.0, 0.05863714928536089, 6.427390673123632e-05, 0.017681367016587454, 0.0, 0.0, 0.01155145753000743, 0.03570075762219813, 0.0, 0.0, 0.036403544531026434, 0.024167136275064012, 0.0162123485119066, 0.0, 0.0811006174101693, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0028311967977824697, 0.0, 0.019789228900058218, 0.0513420341419516, 0.0, 0.0, 0.010800786054879077, 0.0, 0.08412937113046591, 0.04235233909900225, 0.1300398163771834, 0.03862185723872369, 0.0, 0.012118559138986707, 0.02626082112733378, 0.0, 0.08237795803178011, 0.019777082145381285, 0.0, 0.018975649394092856, 0.0, 0.01821098268186487, 0.05686578246610906, 0.006293175860312036, 0.011901793669928032, 0.057512312099504755, 0.0, 0.09164707788029448, 0.0, 0.021504302085057173, 0.022878547688813426, 0.0, 0.007758600659197069, 0.07935199085146925, 0.03458069071356187, 0.055608628261514026, 0.1607466958522434, 0.0, 0.07377720637847045, 0.0, 0.02363828850807077, 0.1427361069898445, 0.06435634517507433, 0.05122164281626237, 0.03827805986904105, 0.016169526547741418, 0.055760084563244834, 0.05780365968941382, 0.015562142274604852, 0.0, 0.07432198367180187, 0.0374062568026427], \"feature_quality\": 0.8486328125}, {\"bbox\": {\"xc\": 332.0, \"yc\": 330.5, \"angle\": null, \"aspect\": 0.47058823704719543, \"height\": 187.0, \"confidence\": 0.6943359375}, \"feature\": [0.02620238825780028, 0.0, 0.011925813060074284, 0.08029330925475944, 0.014345740988649547, 0.0, 0.03616348150450254, 0.04287531038462128, 0.0, 0.03319846873014149, 0.018516378900507145, 0.006509543280545765, 0.026449856816343667, 0.03967817218427939, 0.0803840667635659, 0.055657843530713014, 0.01988912899497622, 0.00633838734744727, 0.0, 0.0, 0.01450586189115482, 0.0, 0.12937189608923877, 0.09965828965896847, 0.0, 0.04500444173149082, 0.0745727474674009, 0.0, 0.008108444329281261, 0.050308505064384064, 0.009031549308628865, 0.0, 0.04168193420940139, 0.04747916587356377, 0.09792524584575306, 0.040704624619986925, 0.03716849162930361, 0.0, 0.0, 0.005048225573880291, 0.0, 0.02659999395911804, 0.021308169949341677, 0.07404483960432375, 0.03788201115289556, 0.003611888954651547, 0.0159418713813989, 0.0, 0.03461916761998724, 0.01750875174382876, 0.05635315950108093, 0.0, 0.0, 0.02159459517209209, 0.0, 0.0, 0.0, 0.06242017678844223, 0.06289863094232374, 0.02101421654281155, 0.10138850919837153, 0.04994264111618612, 0.0, 0.011865715308703562, 0.08143637383556425, 0.0, 0.04784282245417457, 0.023448451394274178, 0.007386440537749995, 0.0, 0.008142748894004704, 0.06186874455594845, 0.053282012348291684, 0.04074661279994136, 0.0, 0.10116709769847411, 0.0039712314088181724, 0.013637469987551895, 0.0, 0.06740287053463194, 0.06718549784267779, 0.0, 0.09070357770754443, 0.04902457380271287, 0.015082361963523248, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.06746555881019062, 0.0, 0.022794742857049813, 0.0784982760050686, 0.013272366328080746, 0.039556318746057646, 0.005487141099283725, 0.0, 0.0, 0.0, 0.039889961392937254, 0.07166300322536623, 0.0, 0.07029446905840118, 0.014203807975733694, 0.0032033220225575467, 0.02280556522368063, 0.07904838767267401, 0.0, 0.05753803875696431, 0.044495441080300846, 0.0, 0.0, 0.08702088852194098, 0.0180513039003852, 0.036440260338113976, 0.02186591101190436, 0.03125571053740851, 0.0072162430986010455, 0.05439955725360765, 0.05666474534061877, 0.02346498737285816, 0.03883987855705564, 0.055028141320893724, 0.028996585481892188, 0.03493873950339383, 0.005408936788734137, 0.025185189415471126, 0.09693303893491537, 0.0, 0.01517822822700735, 0.02914390076064414, 0.02259708706640346, 0.01724551592969298, 0.0, 0.0, 0.0766418027510092, 0.03629416200328288, 0.01564509972553127, 0.0652106508870733, 0.06557410986490883, 0.06349444668597459, 0.0, 0.01558965045895922, 0.0349392792963409, 0.030253594570328137, 0.012225601772949658, 0.0, 0.0, 0.006771907353169921, 0.13893317144745623, 0.0, 0.06804635674292112, 0.002938057165290362, 0.0, 0.05789892889872137, 0.0, 0.03291868729826196, 0.0008130833085187575, 0.0, 0.04236155762766512, 0.05327017064051528, 0.06258203273970595, 0.03834744039213902, 0.05316237591681685, 0.0, 0.0, 0.013907367665983128, 0.0, 0.0, 0.0, 0.04238466751321166, 0.0, 0.036237035522911, 0.005745767587707392, 0.1220524965101307, 0.005927879028331014, 0.014132539643085496, 0.03371738325715403, 0.0, 0.0014856123051923931, 0.0, 1.7087573383163114e-05, 0.0, 0.015658099337374942, 0.032836612262617575, 0.010981750137598601, 0.0, 0.026385638324171633, 0.056312636473411413, 0.05487354847651606, 0.08851256777453682, 0.0, 0.029952004539470903, 0.0, 0.0, 0.06930526956541147, 0.11076847196133126, 0.0, 0.07216310211250938, 0.0, 0.0, 0.028859894966995783, 0.03598153031491008, 0.043072050455089564, 0.08976211133373156, 0.0, 0.022566755040399882, 0.024309168116910918, 0.04340668352563545, 0.0, 0.03575395698213023, 0.0482330011828902, 0.028011619989831076, 0.15754280319425507, 0.0, 0.00628414779542625, 0.10621486541226537, 0.0, 0.06099494990329492, 0.095883238044457, 0.08636769086018438, 0.014513485261637248, 0.0, 0.07693373434377775, 0.06843423171245192, 0.0, 0.035773109992591534, 0.0, 0.0890065072385875, 0.0, 0.025045990309244797, 0.0, 0.03743286004471226, 0.0, 0.0, 0.07604544721441128, 0.026408697604129387, 0.01860927750767732, 0.03282493924013713, 0.032852789182500154, 0.0, 0.0, 0.06389254880402045, 0.043661562188251374, 0.0, 0.03750141133920053, 0.0, 0.05578236701619081, 0.024635868158966793, 0.00749882229660357, 0.0, 0.004344859097762681, 0.0, 0.056223609193783525, 0.08456778019565035, 0.012994466942146144, 0.09017742417154526, 0.055863499823967766, 0.11545813525053875, 0.025160949338441648, 0.009320927728953555, 0.007906067760137967, 0.15922140503309432, 0.008454655838052359, 0.030588458980708486, 0.13912104831051678, 0.016554754462315992, 0.07436697550422892, 0.010888946717131146, 0.010111943687299552, 0.0, 0.04469895748008684, 0.03549186581821703, 0.033891174898001905, 0.03577534627765797, 0.015570729993227346, 0.05513339130641309, 0.005958382751867982, 0.0, 0.05352500592691224, 0.0719052112124656, 0.07378286170053784, 0.034976291260064504, 0.0, 0.011441142853036627, 0.017075091970289454, 0.05917751042672068, 0.015403100185261904, 0.001610603591142733, 0.029811118580284974, 0.0, 0.013592835858240835, 0.0, 0.047576362341096017, 0.006766425081051216, 0.00035464648897549477, 0.0, 0.06497129127168572, 0.008581544532358508, 0.0, 0.0035233047751492956, 0.15406953439379623, 0.0, 0.06088998427298873, 0.0, 0.0828918676823867, 0.03820624116026059, 0.0, 0.041354024452794486, 0.11455369288939919, 0.0, 0.04163536742855661, 0.013280640341847592, 0.08968832356569871, 0.0, 0.0012980167851076093, 0.04350507524897907, 0.048795754608532846, 0.07794047710754731, 0.006503275416905874, 0.02146597745350235, 0.0500272970368587, 0.06429215463099032, 0.04242418806826519, 0.023996488232739272, 0.0, 0.0, 0.0, 0.05995552556832804, 0.04097387526981865, 0.0, 0.09682692142501677, 0.02046071430075735, 0.07199104311063001, 0.08187911008292117, 0.0, 0.0023684198734538276, 0.03363727701989249, 0.0, 0.06441775770236226, 0.0008948709767125285, 0.05356390475616065, 0.0, 0.05256255510228193, 0.04477070656582853, 0.029137734108181827, 0.03975518425125503, 0.09948765725273494, 0.03517800754676361, 0.04621437798319411, 0.09946081219277786, 0.00942118161747508, 0.03811179908263238, 0.04812793434140644, 0.0004954388805363209, 0.0049624349433686375, 0.0, 0.018456463088277066, 0.0559592600566944, 0.05554877161834517, 0.00040166864481342595, 0.05643896248176604, 0.0, 0.07517634683256533, 0.1005017739737279, 0.03174270739663545, 0.03286329827643847, 0.01534731836767782, 0.0416419943508979, 0.023217318801749563, 0.01299904433814152, 0.0, 0.021536152946411723, 0.0, 0.029390631923465187, 0.06918574398427399, 0.007325416824593949, 0.0, 0.004526682869711942, 0.03181860855044769, 0.0037175624607532778, 0.03507288768990112, 0.0, 0.0, 0.05096299437354973, 0.0, 0.0, 0.04947272243828018, 0.09350823582977581, 0.016134088276145507, 0.06570406019733666, 0.018348643061784242, 0.0, 0.0, 0.017502387488591176, 0.051099383664703346, 0.011919499410425489, 0.06906048792265373, 0.029193525564925677, 0.07203458801488716, 0.0, 0.02939549969914861, 0.0031083187650965253, 0.037847534288191244, 0.04498808889694245, 0.0, 0.0, 0.030973791621850173, 0.007556573515017989, 0.0, 0.0, 0.03062113332253597, 0.0, 0.02090850869720314, 0.04541953286862982, 0.0, 0.06973343140319745, 0.0, 0.002299393699735057, 0.0, 0.012446203583426477, 0.0057886684756024605, 0.013838201875059577, 0.011145952019371137, 0.0, 0.04618015896601362, 0.12849970635668678, 0.015996401313107964, 0.056402068597749606, 0.0, 0.05335430604656032, 0.0, 0.019794461601988403, 0.005606908864310413, 0.011177513038245285, 0.0, 0.025488102421006676, 0.03178286172644379, 0.039960748972491966, 0.028475299721576433, 0.025303811325380256, 0.008204634709512604, 0.03874779566378093, 0.004696803496806482, 0.04032420554053757, 0.002941390507228032, 0.07803061771012854, 0.035799285130944665, 0.06516261413436372, 0.0067663329065859226, 0.0006650362066837052, 0.04166888278707395, 0.058864276290862314, 0.013864234835806115, 0.008893782822523228, 0.0, 0.05444731447068392, 0.0, 0.004418592946745583, 0.02233682734284641, 0.0, 0.01925968342778578, 0.046740560436672594, 0.0, 0.008852680240372592, 0.06969517357807369, 0.09906027618689027, 0.06466640464818743, 0.0218233565313135, 0.0010660025859937936, 0.049041905011977804, 0.0, 0.05759821603140313, 0.01062617840224417, 0.043018109717021386, 0.0, 0.014750719442295739, 0.0, 0.012654638364484681, 0.05887603606578068, 0.0, 0.014227335959835224, 0.09028956615629957, 0.0, 0.09112075090319358, 0.0, 0.028484468972306838, 0.020352343637262713, 0.0, 0.012015894622801926, 0.1090521906469544, 0.02196328821368228, 0.01085401801681265, 0.05960093461748012, 0.04414677821271096, 0.04015888190154689, 0.0, 0.06598464649905691, 0.09929991533791116, 0.055836804170987096, 0.0, 0.04906916937538485, 0.0, 0.0808957133641125, 0.06626327605134402, 0.04517822132338898, 0.026661366489368232, 0.08979742921512573, 0.0], \"feature_quality\": 0.6943359375}]\n"
  },
  {
    "path": "python/bugfixes/github-84.py",
    "content": "from similari import (\n    Sort,\n    Universal2DBox,\n    SpatioTemporalConstraints,\n    PositionalMetricType,\n)\n\nBOXES_1 = [\n    [654.375, 418.359375, -1.08950759090885, 0.06182759814798534, 340.5220642089844],\n    [550.3125, 379.3359375, -1.129803881362839, 0.07216824447658342, 293.8331604003906],\n    [\n        125.15625,\n        355.60546875,\n        -0.6551687205652192,\n        0.1363352988349452,\n        182.83360290527344,\n    ],\n    [839.375, 329.4140625, 0.9311933167479997, 0.07460492907503219, 287.0589904785156],\n    [555.0, 551.6015625, -0.5173672116741088, 0.06403988519157659, 366.5615234375],\n    [380.9375, 265.078125, -1.2747445594776385, 0.05473158703331464, 354.41259765625],\n    [\n        242.1875,\n        308.14453125,\n        -0.2078592736722506,\n        0.06851543739648944,\n        304.702392578125,\n    ],\n    [927.5, 485.859375, -0.02181425001386707, 0.059192059468350856, 310.4801940917969],\n    [725.625, 556.875, -0.23166345637797628, 0.08713287963547522, 184.94488525390625],\n    [\n        864.375,\n        502.3828125,\n        0.24739611380713303,\n        0.05943324441452824,\n        316.27691650390625,\n    ],\n    [668.75, 381.4453125, -0.8396859698494138, 0.07229550543674212, 305.55511474609375],\n    [288.4375, 433.125, -0.17867848497603436, 0.06008579970810128, 336.22802734375],\n    [662.5, 232.55859375, -0.5881607246406537, 0.0673030215962557, 297.13165283203125],\n    [\n        229.21875,\n        409.21875,\n        -0.1168992563119464,\n        0.05855327449025996,\n        353.38519287109375,\n    ],\n    [\n        316.09375,\n        103.53515625,\n        -1.2623163856185657,\n        0.06354494174959537,\n        285.65826416015625,\n    ],\n    [\n        514.375,\n        114.873046875,\n        0.844491710615489,\n        0.07671649949878948,\n        260.29510498046875,\n    ],\n    [\n        126.09375,\n        605.0390625,\n        -0.29419124969021504,\n        0.11571208548527089,\n        215.46337890625,\n    ],\n    [\n        413.75,\n        213.92578125,\n        -0.9657712011303433,\n        0.07631818226761462,\n        293.21502685546875,\n    ],\n    [451.25, 447.890625, -0.6795026892355458, 0.07388564032534921, 314.9710693359375],\n    [818.75, 131.30859375, -1.3748421322093567, 0.06306835024561531, 266.639404296875],\n    [856.25, 227.109375, -1.566501180588817, 0.059453860794436535, 274.05706787109375],\n    [917.5, 101.42578125, -1.2209488386160925, 0.0685205241197251, 276.1524353027344],\n    [727.5, 172.001953125, -1.3254025439428534, 0.08630334951440506, 187.4688720703125],\n]\n\nBOXES_2 = [\n    [534.375, 368.4375, -1.101980439295209, 0.07122194536958791, 294.6826171875],\n    [655.625, 418.359375, -1.09799175635157, 0.060449629503938826, 340.4578857421875],\n    [864.375, 501.328125, 0.2527380654139389, 0.056794797327308334, 326.08819580078125],\n    [\n        242.1875,\n        301.9921875,\n        -0.22382585651365716,\n        0.06272859667119125,\n        319.5481262207031,\n    ],\n    [376.5625, 261.2109375, -1.283783461375422, 0.05985874299287419, 338.7218322753906],\n    [\n        125.15625,\n        355.078125,\n        -0.6595017502378054,\n        0.13481610398035634,\n        183.08370971679688,\n    ],\n    [725.0, 556.875, -0.22640149947241367, 0.08722859430117187, 184.79248046875],\n    [\n        288.125,\n        432.0703125,\n        -0.18401136519037714,\n        0.06062317187469461,\n        334.13873291015625,\n    ],\n    [928.75, 483.046875, 0.010908012380531129, 0.05898330096331063, 310.26654052734375],\n    [668.75, 380.7421875, -0.8257274770489542, 0.07123307222138725, 299.7037353515625],\n    [840.0, 328.359375, 0.9279294796740531, 0.07103401647407939, 292.3738098144531],\n    [\n        316.09375,\n        102.3046875,\n        -1.2634194669420764,\n        0.059841705904805406,\n        292.3897399902344,\n    ],\n    [\n        554.6875,\n        550.8984375,\n        -0.4945779763045692,\n        0.06358155933944147,\n        365.6000671386719,\n    ],\n    [662.5, 231.6796875, -0.5988330098140101, 0.06578038672280338, 300.5981750488281],\n    [229.21875, 409.21875, -0.1168992563119464, 0.05883235030416289, 350.3829345703125],\n    [513.75, 113.90625, 0.8408897186621297, 0.07426706369369915, 264.0218505859375],\n    [\n        125.546875,\n        605.0390625,\n        -0.28365944626436046,\n        0.11471792490605068,\n        216.64146423339844,\n    ],\n    [\n        413.75,\n        212.87109375,\n        -0.9610671265388818,\n        0.07466581785562328,\n        294.78729248046875,\n    ],\n    [451.5625, 447.890625, -0.6795026892355458, 0.07267504740494687, 317.4398193359375],\n    [819.375, 129.7265625, -1.369520470561106, 0.0636213571415013, 268.4916076660156],\n    [855.625, 225.0, -1.5572955648067945, 0.06000232800760282, 269.3219909667969],\n    [918.75, 99.84375, -1.21361023191627, 0.07017798850556049, 272.5237121582031],\n    [727.5, 170.595703125, -1.350716023262111, 0.08038329382212916, 191.7749786376953],\n]\n\n\ndef create_detections(boxes):\n    detections = []\n    for box in boxes:\n        xc, yc, angle, aspect, height = box\n        similari_box = Universal2DBox(\n            xc=xc,\n            yc=yc,\n            angle=angle,\n            aspect=aspect,\n            height=height,\n        )\n        detections.append((similari_box, None))\n\n    return detections\n\n\ndef main():\n    metric = PositionalMetricType.iou(threshold=0.3)\n    # metric = PositionalMetricType.maha()\n\n    tracker = Sort(\n        shards=4,\n        bbox_history=10,\n        max_idle_epochs=5,\n        method=metric,\n        spatio_temporal_constraints=None,\n    )\n\n    tracks = tracker.predict(create_detections(BOXES_1))\n\n    print('predict 1 done')\n\n    tracks = tracker.predict(create_detections(BOXES_2))\n\n    print('predict 2 done')\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "python/clipping_intersection.py",
    "content": "from similari import sutherland_hodgman_clip, intersection_area, BoundingBox\n\nif __name__ == '__main__':\n    bbox1 = BoundingBox(0.0, 0.0, 5.0, 10.0).as_xyaah()\n    bbox2 = BoundingBox(0.0, 0.0, 10.0, 5.0).as_xyaah()\n\n    clip = sutherland_hodgman_clip(bbox1, bbox2)\n    print(clip)\n\n    area = intersection_area(bbox1, bbox2)\n    print(\"Intersection area:\", area)\n\n\n    bbox1 = BoundingBox(0.0, 0.0, 5.0, 10.0).as_xyaah()\n    bbox2 = BoundingBox(0.0, 0.0, 5.0, 10.0).as_xyaah()\n    bbox2.rotate(0.5)\n\n    clip = sutherland_hodgman_clip(bbox1, bbox2)\n    print(clip)\n\n    area = intersection_area(bbox1, bbox2)\n    print(\"Intersection area:\", area)\n\n"
  },
  {
    "path": "python/kalman_2d_point.py",
    "content": "from similari import Point2DKalmanFilter\n\nif __name__ == '__main__':\n    f = Point2DKalmanFilter()\n    state = f.initiate(1.0, 2.0)\n\n    for i in range(1, 21):\n        state = f.predict(state)\n        print(\"Predicted\", state.x(), state.y())\n\n        pt = (1.0 + i * 0.1, 2.0 + i * 0.1)\n        print(\"Observation:\", pt)\n        state = f.update(state, pt[0], pt[1])\n\n\n"
  },
  {
    "path": "python/kalman_2d_vec.py",
    "content": "from similari import Vec2DKalmanFilter\n\nif __name__ == '__main__':\n    f = Vec2DKalmanFilter()\n    state = f.initiate([(1.0, 2.0), (3.0, 4.0)])\n\n    for i in range(1, 21):\n        state = f.predict(state)\n        print(\"Predicted [0]:\", state[0].x(), state[0].y())\n        print(\"Predicted [1]:\", state[1].x(), state[1].y())\n\n        pt1 = (1.0 + i * 0.1, 2.0 + i * 0.1)\n        pt2 = (3.0 + i * 0.05, 4.0 + i * 0.05)\n        print(\"Observation:\",  [pt1, pt2])\n\n        state = f.update(state, [pt1, pt2])\n\n\n"
  },
  {
    "path": "python/kalman_bbox.py",
    "content": "from similari import Universal2DBoxKalmanFilter, BoundingBox\n\nif __name__ == '__main__':\n    f = Universal2DBoxKalmanFilter()\n    state = f.initiate(BoundingBox(0.0, 0.0, 5.0, 10.0).as_xyaah())\n    state = f.predict(state)\n    box_ltwh = state.bbox()\n    box_xyaah = state.universal_bbox()\n    print(box_ltwh)\n    print(box_xyaah)\n    # if work with oriented box\n    # import Universal2DBox and use it\n    #\n    #box_xyaah = state.universal_bbox()\n    #print(box_xyaah)\n\n    state = f.update(state, BoundingBox(0.2, 0.2, 5.1, 9.9).as_xyaah())\n    state = f.predict(state)\n    box_ltwh = state.bbox()\n    print(box_ltwh)\n\n    for i in range(1, 21):\n        state = f.predict(state)\n        box_xyaah = state.universal_bbox()\n        print(\"Prediction:\", box_xyaah)\n\n        obs = BoundingBox(0.2 + i * 0.2, 0.2 + i * 0.2, 5.0, 10.0).as_xyaah()\n        state = f.update(state, obs)\n        print(\"Observation:\", obs)\n\n"
  },
  {
    "path": "python/motchallenge/Dockerfile",
    "content": "FROM python:3.8-buster as base\n\nWORKDIR /opt\nCOPY docker/common/install-basic-deps.sh .\nRUN bash /opt/install-basic-deps.sh\n\nFROM base as chef\nENV PATH=\"/root/.cargo/bin:$PATH\"\nRUN rustc -V\n\nFROM chef AS planner\nWORKDIR /opt\nCOPY . .\nRUN cargo chef prepare --recipe-path recipe.json\n\nFROM chef AS builder\nWORKDIR /opt\nCOPY --from=planner /opt/recipe.json recipe.json\nRUN cargo chef cook --release --recipe-path recipe.json\nCOPY . .\n\nENV RUSTFLAGS=\" -C target-cpu=native -C opt-level=3\"\n\nRUN maturin build --release --out dist\nRUN python3 -m pip install --upgrade pip\nRUN python3 -m pip install dist/*.whl\n\nRUN python3 -m pip install -r /opt/python/motchallenge/requirements.txt\n\n# add original Sort (https://github.com/abewley/sort)\nRUN git clone https://github.com/abewley/sort.git /tmp/sort \\\n    && python3 -m pip install -r /tmp/sort/requirements.txt \\\n    && cp /tmp/sort/sort.py /opt/python/motchallenge/original_sort.py \\\n    && rm -r /tmp/sort\n\n# install package with MOTChallenge Official Evaluation Kit\n# https://github.com/JonathonLuiten/TrackEval/tree/master/docs/MOTChallenge-Official\nRUN git clone https://github.com/JonathonLuiten/TrackEval.git /tmp/TrackEval \\\n    && cd /tmp/TrackEval \\\n    && python3 setup.py install \\\n    && rm -rf /tmp/TrackEval\n\nWORKDIR /opt/python\nVOLUME /data\n\nCMD [\"/opt/python/motchallenge/config.yml\"]\nENTRYPOINT [\"python3\", \"-m\", \"motchallenge\"]\n"
  },
  {
    "path": "python/motchallenge/README.md",
    "content": "# Trackers Evaluation on MOT Challenge Data\n\nAn easy way to run Similari trackers and measure their performance on real data from \n[Multiple Object Tracking Benchmark (MOTChallenge)](https://motchallenge.net/).\n\n## Download The Data\n\nFor the [MOT20](https://motchallenge.net/data/MOT20/) use one of the links:\n\n* [Get files (no img) only](https://motchallenge.net/data/MOT20Labels.zip) (13.9 MB);\n* [Get all data](https://motchallenge.net/data/MOT20.zip) (5.0 GB).\n\n```bash\ncurl -o MOT20Labels.zip https://motchallenge.net/data/MOT20Labels.zip\n```\n\nExtract the label files to the `data/` folder:\n\n```bash\nunzip MOT20Labels.zip\nmkdir data\nmv MOT20Labels data/MOT20\n\n```\n\nThe `data` folder structure for `MOT20` should be:\n```\nMOT20/\n  test/\n    MOT20-04/\n    ...\n  train/\n    MOT20-01/\n      det/\n      gt/\n      ...\n    ...\n```\n\n## Build The Docker Image\n\n```bash\ndocker build -t similari_py_mot -f python/motchallenge/Dockerfile .\n```\n\n## Run The Processing and Evaluation\n\nExecute code to process default `data/MOT20/train` data using default Similari SORT that uses \nIoU metric with `0.3` threshold: \n\n```bash\ndocker run --rm -v $(pwd)/data:/data similari_py_mot\n```\n\nPredefined config file for the canonical (python+numpy) SORT:\n\n```shell\ndocker run --rm \\\n    -v $(pwd)/data:/data \\\n    -v $(pwd)/python/motchallenge/confs/original-sort-config.toml.yml:/opt/custom_config.yml \\\n    similari_py_mot /opt/custom_config.yml\n```\n\nPredefined config file for the Similari SORT that uses Mahalanobis metric:\n\n```shell\ndocker run --rm \\\n    -v $(pwd)/data:/data \\\n    -v $(pwd)/python/motchallenge/confs/similari-maha-sort-config.toml.yml:/opt/custom_config.yml \\\n    similari_py_mot /opt/custom_config.yml\n```\n\n\n## Interpreting The Results\n\nThe whole process is logged on the stdout. \n\nThe FPS processing performance is displayed for every processed file like:\n\n```\nProcessing MOT20-01...\nRead file \"/data/MOT20/train/MOT20-01/det/det.txt\".\n429 frames collected, with an average of 29.4 detections per frame.\n\n100%|██████████| 429/429 [00:00<00:00, 1166.01it/s]MOT20-01 processing ended after 0:00:00.368002 (1165.76 FPS).\nResulting file /data/MOT20/output/sort_iou/data/MOT20-01.txt was successfully written.\n```\n\nThe resulting files will be saved to the specified folder (`output_path` in the config file).\n\nThe `data/MOT20/output/<NAME>/data` folder output structure:\n* `pedestrian_detailed.csv` - detailed evaluation info;\n* `pedestrian_summary.txt` - evaluation summary;\n* `processing_stats.csv` - processing statistics: frames, detections, FPS, etc.\n"
  },
  {
    "path": "python/motchallenge/__init__.py",
    "content": ""
  },
  {
    "path": "python/motchallenge/__main__.py",
    "content": "\"\"\"Entrypoint.\n>>> python -m motchallenge [config_file_path]\n\"\"\"\nfrom datetime import timedelta\nfrom pathlib import Path\nfrom time import time\nimport sys\n\nfrom tqdm import tqdm\n\nfrom .config import load_config, OriginalSortParams\nfrom .trackers import OriginalSort, SimilariTracker\nfrom .evaluator import evaluate\nfrom .utils import read_detections, write_csv\n\n\ndef main(config_file_path: str):\n    \"\"\"Entrypoint.\n    :param config_file_path: Configuration file path\n    TODO: Replace `print` with `logger.info`\n    \"\"\"\n    config = load_config(config_file_path)\n\n    tracker_name = config.name\n\n    data_path = Path(config.data_path)\n    output_path = Path(config.output_path)\n    tracker_path = output_path / tracker_name\n    res_path = tracker_path / 'data'\n    res_path.mkdir(parents=True, exist_ok=True)\n\n    processing_stat_rows = [('sample', 'num_frames', 'avg_dets', 'exec_seconds', 'fps')]\n    for folder in data_path.iterdir():\n        if not folder.is_dir():\n            continue\n        sample = folder.stem\n        print(f'Processing {sample}...')\n        det_file_path = folder / 'det' / 'det.txt'\n        res_file_path = res_path / f'{sample}.txt'\n\n        print(f'Read file \"{det_file_path}\".')\n        frame_detections = read_detections(det_file_path)\n        num_frames = len(frame_detections)\n        avg_dets = sum(map(len, frame_detections.values())) / num_frames\n        print(\n            f'{num_frames} frames collected, '\n            f'with an average of {avg_dets:.1f} detections per frame.'\n        )\n\n        # init tracker\n        if isinstance(config.tracker, OriginalSortParams):\n            tracker = OriginalSort(config.tracker)\n        else:\n            tracker = SimilariTracker(config.tracker)\n\n        # track and collect result rows\n        result_rows = []\n        frame_iter = tqdm(range(1, len(frame_detections) + 1))\n        start_time = time()\n        for frame_num in frame_iter:\n            detections = frame_detections[frame_num]\n            result_rows.extend(\n                [\n                    (frame_num,) + row + (-1.0, -1.0, -1.0)\n                    for row in tracker.process_frame(frame_num, detections)\n                ]\n            )\n\n        exec_seconds = time() - start_time\n        fps = num_frames / exec_seconds\n        print(\n            f'{sample} processing ended after {timedelta(seconds=exec_seconds)} '\n            f'({fps:.2f} FPS).'\n        )\n\n        write_csv(res_file_path, result_rows)\n        print(f\"Resulting file {res_file_path} was successfully written.\\n\")\n\n        processing_stat_rows.append((sample, num_frames, avg_dets, exec_seconds, fps))\n\n    write_csv(tracker_path / 'processing_stats.csv', processing_stat_rows)\n\n    evaluate(tracker_name, data_path, output_path, config.evaluator)\n\n\nif __name__ == '__main__':\n    main(sys.argv[1] if len(sys.argv) > 1 else 'motchallenge/config.toml.yml')\n"
  },
  {
    "path": "python/motchallenge/config.py",
    "content": "from dataclasses import dataclass, asdict\nfrom enum import Enum\nfrom typing import Any, Dict, List, Optional, Union\nfrom omegaconf import OmegaConf, MISSING\n\n\nclass TrackerType(Enum):\n    \"\"\"Supported tracker types.\"\"\"\n\n    OriginalSort = 0\n    Sort = 1\n    # VisualSort = 2\n\n\n@dataclass\nclass Tracker:\n    \"\"\"Base tracker configuration (without parameters specification).\"\"\"\n\n    type: TrackerType\n    params: Dict[str, Any]\n\n\n@dataclass\nclass OriginalSortParams:\n    \"\"\"Original Sort tracker parameters.\n    https://github.com/abewley/sort\n    \"\"\"\n\n    max_age: int = 1\n    \"\"\"Maximum number of frames to keep alive a track without associated detections.\"\"\"\n\n    min_hits: int = 3\n    \"\"\"Minimum number of associated detections before track is initialised.\"\"\"\n\n    iou_threshold: float = 0.3\n    \"\"\"Minimum IOU for match.\"\"\"\n\n\nclass PositionalMetricType(Enum):\n    \"\"\"Positional metric type.\"\"\"\n\n    IoU = 0\n    Maha = 1\n\n\n@dataclass\nclass PositionalMetric:\n    \"\"\"Positional metric configuration.\"\"\"\n\n    type: PositionalMetricType\n    threshold: float = 0.3\n\n\n@dataclass\nclass SortParams:\n    \"\"\"Sort tracker parameters.\"\"\"\n\n    shards: int = 4\n    \"\"\"Amount of cpu threads to process the data, \n    keep 1 for up to 100 simultaneously tracked objects, \n    try it before setting high - higher numbers may lead to unexpected latencies.\n    \"\"\"\n\n    bbox_history: int = 10\n    \"\"\"How many last bboxes are kept within stored track \n    (valuable for offline trackers), for online - keep 1    \n    \"\"\"\n\n    max_idle_epochs: int = 10\n    \"\"\"How long track survives without being updated.\"\"\"\n\n    positional_metric: PositionalMetric = PositionalMetric(PositionalMetricType.IoU)\n    \"\"\"Setting the positional metric used by a tracker. \n    Two positional metrics are supported: IoU and Mahalanobis.\n    \"\"\"\n\n    spatio_temporal_constraints: Optional[List[List]] = None\n    \"\"\"Defining the constraints for objects compared across different epochs.\n    https://docs.rs/similari/latest/similari/trackers/spatio_temporal_constraints/struct.SpatioTemporalConstraints.html\n    \"\"\"\n\n    use_confidence: bool = False\n    \"\"\"Whether to use bounding box confidences.\"\"\"\n\n\n@dataclass\nclass VisualSortParams:\n    \"\"\"Visual Sort tracker parameters.\n    TODO\n    \"\"\"\n\n\n@dataclass\nclass Evaluator:\n    \"\"\"Evaluator configuration.\"\"\"\n\n    num_cores: int = 1\n    \"\"\"Number of cores to use.\"\"\"\n\n\n@dataclass\nclass ConfigSchema:\n    \"\"\"Configuration schema.\"\"\"\n\n    name: str\n    data_path: str\n    output_path: str\n    tracker: Tracker\n    evaluator: Evaluator = Evaluator()\n\n\n@dataclass\nclass Config(ConfigSchema):\n    \"\"\"Configuration object.\"\"\"\n\n    tracker: Union[OriginalSortParams, SortParams, VisualSortParams] = MISSING\n\n\nclass ConfigException(Exception):\n    \"\"\"Configuration exception.\"\"\"\n\n\ndef load_config(config_file_path: str) -> Config:\n    \"\"\"Loads, pareses and validate specified configuration file.\"\"\"\n    config = OmegaConf.unsafe_merge(ConfigSchema, OmegaConf.load(config_file_path))\n\n    tracker_params_schema = SortParams\n    if config.tracker.type == TrackerType.OriginalSort:\n        tracker_params_schema = OriginalSortParams\n    # elif config.toml.tracker.type == TrackerType.VisualSort:\n    #     tracker_params_schema = VisualSortParams\n    tracker_params = OmegaConf.to_object(\n        OmegaConf.unsafe_merge(\n            tracker_params_schema, config.tracker.params\n        )\n    )\n\n    print(f'Configuration:\\n{OmegaConf.to_yaml(config)}')\n\n    return Config(\n        name=config.name,\n        data_path=config.data_path,\n        output_path=config.output_path,\n        tracker=tracker_params,\n        evaluator=config.evaluator\n    )\n"
  },
  {
    "path": "python/motchallenge/config.yml",
    "content": "# Default tracker and evaluator configuration.\n# Use as a template to configure\n\n# unique run name (used to build resulting folder)\nname: sort_iou\n\n# data location configuration, mot challenge format\n# detection and ground truth files location\n# ground truth file: {data_path}/{sample}/gt/gt.txt\n# detection file: {data_path}/{sample}/det/det.txt\ndata_path: /data/MOT20/train\n# tracker and evaluation output path\n# tracker predicted detections file: {output_path}/{name}/{sample}.txt\n# tracker evaluation output folder: {output_path}/{name}/data\noutput_path: /data/MOT20/output\n\n# tracker configuration\ntracker:\n  # supported tracker types:\n  #  type: Sort\n  #  type: VisualSort [TODO]\n  #  type: OriginalSort (https://github.com/abewley/sort)\n  type: Sort\n  # tracker initialization parameters\n  params:\n    # amount of cpu threads to process the data, default 4\n    shards: 4\n    # how many last bboxes are kept within stored track, default 10\n    bbox_history: 10\n    # how long track survives without being updated, default 10\n    max_idle_epochs: 100\n    # tracker positional metric configuration, default IoU\n    positional_metric:\n      # supported types: IoU, Maha (Mahalanobis)\n      type: IoU\n      # IoU threshold, required for IoU positional metric type, default 0.3\n      threshold: 0.3\n    # constraints for objects compared across different epochs (epoch_delta, max_allowed_distance)\n    spatio_temporal_constraints:\n      - [1, 1.0]\n    # whether to use bounding box confidences\n    use_confidence: false\n\n#  type: OriginalSort\n#  params:\n#    # maximum number of frames to keep alive a track without associated detections\n#    max_age: 100\n#    # minimum number of associated detections before track is initialised\n#    min_hits: 1\n#    # minimum IOU for match\n#    iou_threshold: 0.3\n\n#  type: VisualSort\n#  params:\n#    ...\n\n# evaluator configuration\nevaluator:\n  # number of cores to use\n  num_cores: 4\n"
  },
  {
    "path": "python/motchallenge/confs/original-sort-config.yml",
    "content": "# Default tracker and evaluator configuration.\n# Use as a template to configure\n\n# unique run name (used to build resulting folder)\nname: original_sort\n\n# data location configuration, mot challenge format\n# detection and ground truth files location\n# ground truth file: {data_path}/{sample}/gt/gt.txt\n# detection file: {data_path}/{sample}/det/det.txt\ndata_path: /data/MOT20/train\n# tracker and evaluation output path\n# tracker predicted detections file: {output_path}/{name}/{sample}.txt\n# tracker evaluation output folder: {output_path}/{name}/data\noutput_path: /data/MOT20/output\n\n# tracker configuration\ntracker:\n  # supported tracker types:\n  #  type: Sort\n  #  type: VisualSort [TODO]\n  #  type: OriginalSort (https://github.com/abewley/sort)\n#  type: Sort\n#  # tracker initialization parameters\n#  params:\n#    # amount of cpu threads to process the data, default 4\n#    shards: 4\n#    # how many last bboxes are kept within stored track, default 10\n#    bbox_history: 10\n#    # how long track survives without being updated, default 10\n#    max_idle_epochs: 100\n#    # tracker positional metric configuration, default IoU\n#    positional_metric:\n#      # supported types: IoU, Maha (Mahalanobis)\n#      type: IoU\n#      # IoU threshold, required for IoU positional metric type, default 0.3\n#      threshold: 0.3\n#    # constraints for objects compared across different epochs (epoch_delta, max_allowed_distance)\n#    spatio_temporal_constraints:\n#      - [1, 1.0]\n#    # whether to use bounding box confidences\n#    use_confidence: false\n\n  type: OriginalSort\n  params:\n    # maximum number of frames to keep alive a track without associated detections\n    max_age: 100\n    # minimum number of associated detections before track is initialised\n    min_hits: 1\n    # minimum IOU for match\n    iou_threshold: 0.3\n\n#  type: VisualSort\n#  params:\n#    ...\n\n# evaluator configuration\nevaluator:\n  # number of cores to use\n  num_cores: 4\n"
  },
  {
    "path": "python/motchallenge/confs/similari-maha-sort-config.yml",
    "content": "# Default tracker and evaluator configuration.\n# Use as a template to configure\n\n# unique run name (used to build resulting folder)\nname: maha_sort\n\n# data location configuration, mot challenge format\n# detection and ground truth files location\n# ground truth file: {data_path}/{sample}/gt/gt.txt\n# detection file: {data_path}/{sample}/det/det.txt\ndata_path: /data/MOT20/train\n# tracker and evaluation output path\n# tracker predicted detections file: {output_path}/{name}/{sample}.txt\n# tracker evaluation output folder: {output_path}/{name}/data\noutput_path: /data/MOT20/output\n\n# tracker configuration\ntracker:\n  # supported tracker types:\n  #  type: Sort\n  #  type: VisualSort [TODO]\n  #  type: OriginalSort (https://github.com/abewley/sort)\n  type: Sort\n  # tracker initialization parameters\n  params:\n    # amount of cpu threads to process the data, default 4\n    shards: 4\n    # how many last bboxes are kept within stored track, default 10\n    bbox_history: 10\n    # how long track survives without being updated, default 10\n    max_idle_epochs: 100\n    # tracker positional metric configuration, default IoU\n    positional_metric:\n      # supported types: IoU, Maha (Mahalanobis)\n      type: Maha\n    # constraints for objects compared across different epochs (epoch_delta, max_allowed_distance)\n    spatio_temporal_constraints:\n      - [1, 1.0]\n    # whether to use bounding box confidences\n    use_confidence: false\n\n# evaluator configuration\nevaluator:\n  # number of cores to use\n  num_cores: 4\n"
  },
  {
    "path": "python/motchallenge/evaluator.py",
    "content": "from pathlib import Path\nimport trackeval\nfrom .config import Evaluator\n\n\ndef evaluate(\n    tracker_name: str, data_path: Path, output_path: Path, eval_conf: Evaluator\n):\n    eval_config = trackeval.Evaluator.get_default_eval_config()\n    eval_config['DISPLAY_LESS_PROGRESS'] = False\n    eval_config['PLOT_CURVES'] = True\n    if eval_conf.num_cores > 1:\n        eval_config['USE_PARALLEL'] = True\n        eval_config['NUM_PARALLEL_CORES'] = eval_conf.num_cores\n    eval_config['LOG_ON_ERROR'] = './eval_error_log.txt'\n    evaluator = trackeval.Evaluator(eval_config)\n\n    dataset_config = trackeval.datasets.MotChallenge2DBox.get_default_dataset_config()\n    dataset_config['GT_FOLDER'] = str(data_path)\n    dataset_config['SKIP_SPLIT_FOL'] = True\n    dataset_config['SEQ_INFO'] = {\n        sub.name: None for sub in data_path.iterdir() if sub.is_dir()\n    }\n    dataset_config['TRACKERS_FOLDER'] = str(output_path)\n    dataset_config['TRACKERS_TO_EVAL'] = [tracker_name]\n    dataset_list = [trackeval.datasets.MotChallenge2DBox(dataset_config)]\n\n    metrics_list = [\n        # trackeval.metrics.HOTA(),\n        # Similarity score threshold required for a TP match. Default 0.5.\n        trackeval.metrics.CLEAR(config={'THRESHOLD': 0.5}),\n        # Similarity score threshold required for a IDTP match. Default 0.5.\n        trackeval.metrics.Identity(config={'THRESHOLD': 0.5}),\n    ]\n\n    evaluator.evaluate(dataset_list, metrics_list)\n"
  },
  {
    "path": "python/motchallenge/requirements.txt",
    "content": "numpy==1.20\nscipy\nomegaconf~=2.2\ntqdm\n"
  },
  {
    "path": "python/motchallenge/trackers.py",
    "content": "\"\"\"Unified tracker interface for supported trackers.\"\"\"\nfrom abc import abstractmethod\nfrom dataclasses import asdict\nfrom typing import Dict, List, Tuple, Union\nimport numpy as np\n\nfrom similari import (\n    Sort as SortImpl,\n    VisualSort as VisualSortImpl,\n    BoundingBox,\n    SpatioTemporalConstraints,\n    PositionalMetricType,\n)\nfrom .config import (\n    OriginalSortParams,\n    SortParams,\n    VisualSortParams,\n    PositionalMetricType as PositionalMetricConfigType,\n)\nfrom .original_sort import Sort as OriginalSortImpl\n\n\nclass Tracker:\n    @abstractmethod\n    def process_frame(\n        self, frame_num: int, detections: List[Tuple[float, float, float, float, float]]\n    ) -> List[Tuple[int, float, float, float, float, float]]:\n        \"\"\"(left, top, width, height, confidence) =>\n        (track_id, left, top, width, height, confidence)\n        \"\"\"\n        pass\n\n\nclass OriginalSort(Tracker):\n    def __init__(self, params: OriginalSortParams):\n        self._tracker = OriginalSortImpl(**asdict(params))\n\n    def process_frame(\n        self, frame_num: int, detections: List[Tuple[float, float, float, float, float]]\n    ) -> List[Tuple[int, float, float, float, float, float]]:\n        # tuple(top, left, width, height) to np.array([x1, y1, x2, y2])\n        np_detections = np.array(detections)\n        np_detections[:, 2:4] += np_detections[:, 0:2]\n        tracks = self._tracker.update(np_detections)\n        return [\n            (\n                int(track[4]),\n                track[0],\n                track[1],\n                track[2] - track[0],\n                track[3] - track[1],\n                1.0,\n            )\n            for track in tracks\n        ]\n\n\nclass SimilariTracker(Tracker):\n    def __init__(self, params: Union[SortParams, VisualSortParams]):\n        constraints = None\n        if params.spatio_temporal_constraints:\n            constraints = SpatioTemporalConstraints()\n            constraints.add_constraints(\n                list(map(tuple, params.spatio_temporal_constraints))\n            )\n\n        positional_metric = None\n        if params.positional_metric:\n            if params.positional_metric.type == PositionalMetricConfigType.IoU:\n                positional_metric = PositionalMetricType.iou(\n                    threshold=params.positional_metric.threshold\n                )\n            else:\n                positional_metric = PositionalMetricType.maha()\n\n        if isinstance(params, SortParams):\n            self._tracker = SortImpl(\n                shards=params.shards,\n                bbox_history=params.bbox_history,\n                max_idle_epochs=params.max_idle_epochs,\n                method=positional_metric,\n                spatio_temporal_constraints=constraints,\n            )\n        else:\n            raise NotImplementedError\n\n        self._use_confidence = params.use_confidence\n        self._track_id_map: Dict[int, int] = {}  # to have 1-based track id\n\n    def process_frame(\n        self, frame_num: int, detections: List[Tuple[float, float, float, float, float]]\n    ) -> List[Tuple[int, float, float, float, float, float]]:\n        if self._use_confidence:\n            dets = [\n                (BoundingBox.new_with_confidence(*detection).as_xyaah(), 0)\n                for detection in detections\n            ]\n        else:\n            dets = [\n                (BoundingBox(*detection[:-1]).as_xyaah(), 0) for detection in detections\n            ]\n\n        tracks = self._tracker.predict(dets)\n\n        rows = []\n        for track in tracks:\n            track_id = track.id\n            _bbox = track.predicted_bbox.as_ltwh()\n            if track_id not in self._track_id_map:\n                self._track_id_map[track_id] = len(self._track_id_map) + 1\n            rows.append(\n                (\n                    self._track_id_map[track_id],\n                    _bbox.left,\n                    _bbox.top,\n                    _bbox.width,\n                    _bbox.height,\n                    1.0,\n                )\n            )\n\n        # TODO\n        # if frame_num % ...:\n        #     self._tracker.wasted()\n\n        return rows\n"
  },
  {
    "path": "python/motchallenge/utils.py",
    "content": "from pathlib import Path\nfrom typing import Dict, List, Tuple, Union\nimport csv\n\n\ndef read_detections(\n    file_path: Union[str, Path]\n) -> Dict[int, List[Tuple[float, float, float, float, float]]]:\n    \"\"\"Reads detections in `motchallenge` (csv) format from specified file.\n    :param file_path: File with detections\n    :return: Grouped by frame detection bboxes\n    \"\"\"\n    frame_detections = {}\n    with open(file_path, 'r') as file_obj:\n        # row format: frame, id, left, top, width, height, conf, x, y, z\n        # all frame numbers, target IDs and bounding boxes are 1-based\n        for row in csv.reader(file_obj):\n            frame_num = int(row[0])\n            if frame_num not in frame_detections:\n                frame_detections[frame_num] = []\n            frame_detections[frame_num].append(tuple(map(float, row[2:7])))\n    return frame_detections\n\n\ndef write_csv(file_path: Union[str, Path], rows: List[Tuple]):\n    \"\"\"Writes csv file.\"\"\"\n    with open(file_path, mode=\"w\", newline=\"\") as res_file:\n        csv_writer = csv.writer(res_file, lineterminator=\"\\n\")\n        csv_writer.writerows(rows)\n"
  },
  {
    "path": "python/nms.py",
    "content": "from similari import nms, BoundingBox\n\nif __name__ == '__main__':\n\n    print(\"With score\")\n    bbox1 = (BoundingBox(10.0, 11.0, 3.0, 3.8).as_xyaah(), 1.0)\n    bbox2 = (BoundingBox(10.3, 11.1, 2.9, 3.9).as_xyaah(), 0.9)\n    res = nms([bbox2, bbox1], nms_threshold = 0.7, score_threshold = 0.0)\n    print(res[0].as_ltwh())\n\n    print(\"No score\")\n    bbox1 = (BoundingBox(10.0, 11.0, 3.0, 4.0).as_xyaah(), None)\n    bbox2 = (BoundingBox(10.3, 11.1, 2.9, 3.9).as_xyaah(), None)\n    res = nms([bbox2, bbox1], nms_threshold = 0.7, score_threshold = 0.0)\n    print(res[0].as_ltwh())\n"
  },
  {
    "path": "python/sort/batch_sort_iou.py",
    "content": "from similari import BatchSort, BoundingBox, SpatioTemporalConstraints, PositionalMetricType, SortPredictionBatchRequest\n\nif __name__ == '__main__':\n    constraints = SpatioTemporalConstraints()\n    constraints.add_constraints([(1, 1.0)])\n    sort = BatchSort(distance_shards=4, voting_shards=4, bbox_history=10, max_idle_epochs=5,\n                     method=PositionalMetricType.iou(threshold=0.3),\n                     spatio_temporal_constraints=constraints,\n                     kalman_position_weight=0.1,\n                     kalman_velocity_weight=0.1)\n\n    box1 = BoundingBox(10., 5., 7., 7.).as_xyaah()\n    box2 = BoundingBox(5., 5., 3., 7.).as_xyaah()\n    batch = SortPredictionBatchRequest()\n    batch.add(0, box1, 11111)\n    batch.add(1, box2, 22222)\n    prediction_result = sort.predict(batch)\n    for _ in range(prediction_result.batch_size()):\n        scene_id, tracks = prediction_result.get()\n        print(\"Scene\", scene_id)\n        print(tracks[0])\n\n    sort.skip_epochs_for_scene(0, 10)\n    sort.skip_epochs_for_scene(1, 10)\n\n    # you have to call wasted from time to time to purge wasted tracks\n    # out of the waste bin. Without doing that the memory utilization will grow.\n    wasted = sort.wasted()\n    for w in wasted:\n        print(w)\n"
  },
  {
    "path": "python/sort/batch_sort_iou_bench.py",
    "content": "import timeit\n\nfrom similari import BatchSort, BoundingBox, SpatioTemporalConstraints, PositionalMetricType, SortPredictionBatchRequest\n\nif __name__ == '__main__':\n    # all train\n\n    def bench(n):\n        dets = []\n        for i in range(n):\n            dets.append(BoundingBox(1000 * i, 1000 * i, 50, 60).as_xyaah())\n\n        shards = 4\n        if n <= 100:\n            shards = 1\n        elif n <= 200:\n            shards = 2\n\n        constraints = SpatioTemporalConstraints()\n        constraints.add_constraints([(1, 1.0)])\n        mot_tracker = BatchSort(distance_shards=shards,\n                                voting_shards=shards,\n                                bbox_history=10,\n                                max_idle_epochs=5,\n                                method=PositionalMetricType.iou(threshold=0.3),\n                                spatio_temporal_constraints=constraints,\n                                kalman_position_weight=0.1,\n                                kalman_velocity_weight=0.1)\n\n        def run_it(mot_tracker, detections):\n            batch = SortPredictionBatchRequest()\n            for d in detections:\n                batch.add(0, d, 11111)\n            prediction_result = mot_tracker.predict(batch)\n            for _ in range(prediction_result.batch_size()):\n                _scene_id, _tracks = prediction_result.get()\n\n        count = 100\n        duration = timeit.timeit(lambda: run_it(mot_tracker, dets), number=count)\n        print(\"Run for {} took {} ms\".format(n, duration / float(count) * 1000.0))\n\n\n    bench(10)\n    bench(100)\n    bench(200)\n    bench(300)\n    bench(500)\n    bench(1000)\n"
  },
  {
    "path": "python/sort/sort_idle.py",
    "content": "from similari import Sort, BoundingBox, SpatioTemporalConstraints, PositionalMetricType\n\nif __name__ == '__main__':\n    sort = Sort(shards=4, bbox_history=10, max_idle_epochs=5,\n                method=PositionalMetricType.iou(threshold=0.3))\n\n    box = BoundingBox(10., 5., 7., 7.).as_xyaah()\n    tracks = sort.predict([(box, 11111)])\n    for t in tracks:\n        print(t)\n\n    tracks = sort.predict([])\n    print(\"Tracks:\",  tracks)\n\n    idle_tracks = sort.idle_tracks()\n    print(\"Idle Tracks:\", idle_tracks)\n\n    sort.skip_epochs(10)\n\n    idle_tracks = sort.idle_tracks()\n    print(\"Idle Tracks:\", idle_tracks)\n\n    # or just clear wasted\n    sort.clear_wasted()"
  },
  {
    "path": "python/sort/sort_iou.py",
    "content": "from similari import Sort, BoundingBox, SpatioTemporalConstraints, PositionalMetricType\n\nif __name__ == '__main__':\n    constraints = SpatioTemporalConstraints()\n    constraints.add_constraints([(1, 1.0)])\n    sort = Sort(shards=4, bbox_history=10, max_idle_epochs=5,\n                method=PositionalMetricType.iou(threshold=0.3),\n                spatio_temporal_constraints=constraints,\n                kalman_position_weight=0.1,\n                kalman_velocity_weight=0.1)\n\n    box = BoundingBox(10., 5., 7., 7.).as_xyaah()\n    tracks = sort.predict([(box, 11111)])\n    for t in tracks:\n        print(t)\n    sort.skip_epochs(10)\n\n    # you have to call wasted from time to time to purge wasted tracks\n    # out of the waste bin. Without doing that the memory utilization will grow.\n    wasted = sort.wasted()\n    print(wasted[0])\n\n    # or just clear wasted\n    sort.clear_wasted()\n"
  },
  {
    "path": "python/sort/sort_iou_bench.py",
    "content": "from similari import Sort, BoundingBox, SpatioTemporalConstraints, PositionalMetricType\nimport timeit\n\nif __name__ == '__main__':\n    # all train\n\n    def bench(n):\n        dets = []\n        for i in range(n):\n            dets.append((BoundingBox(1000 * i, 1000 * i, 50, 60).as_xyaah(), None))\n\n        shards = 4\n        if n <= 100:\n            shards = 1\n        elif n <= 200:\n            shards = 2\n\n\n        constraints = SpatioTemporalConstraints()\n        constraints.add_constraints([(1, 1.0)])\n        mot_tracker = Sort(shards=shards, bbox_history=1, max_idle_epochs=10,\n                    method=PositionalMetricType.iou(threshold=0.3),\n                    spatio_temporal_constraints=constraints)\n\n        count = 100\n        duration = timeit.timeit(lambda: mot_tracker.predict(dets), number=count)\n        print(\"Run for {} took {} ms\".format(n, duration / float(count) * 1000.0))\n\n    bench(10)\n    bench(100)\n    bench(200)\n    bench(300)\n    bench(500)\n    bench(1000)"
  },
  {
    "path": "python/sort/sort_iou_rotated.py",
    "content": "from similari import Sort, Universal2DBox, SpatioTemporalConstraints, PositionalMetricType\n\nif __name__ == '__main__':\n    constraints = SpatioTemporalConstraints()\n    constraints.add_constraints([(1, 1.0)])\n    sort = Sort(shards=4, bbox_history=10, max_idle_epochs=5,\n                method=PositionalMetricType.iou(threshold=0.3),\n                spatio_temporal_constraints=constraints)\n\n    box = Universal2DBox(10., 5., 0.32, 1.0, 7.)\n    tracks = sort.predict([(box, 11111)])\n    for t in tracks:\n        print(t)\n\n    box = Universal2DBox(11., 5.2, 0.35, 1.05, 7.02)\n    tracks = sort.predict([(box, 11111)])\n    for t in tracks:\n        print(t)\n\n    box = Universal2DBox(11.6, 5.26, 0.37, 1.06, 7.025)\n    tracks = sort.predict([(box, 11111)])\n    for t in tracks:\n        print(t)\n\n    # you have to call wasted from time to time to purge wasted tracks\n    # out of the waste bin. Without doing that the memory utilization will grow.\n    sort.skip_epochs(10)\n    wasted = sort.wasted()\n    print(wasted[0])\n\n\n    # or just clear wasted\n    sort.clear_wasted()"
  },
  {
    "path": "python/sort/sort_iou_scene_id.py",
    "content": "from similari import Sort, BoundingBox, SpatioTemporalConstraints, PositionalMetricType\n\nif __name__ == '__main__':\n    constraints = SpatioTemporalConstraints()\n    constraints.add_constraints([(1, 1.0)])\n    sort = Sort(shards=4, bbox_history=10, max_idle_epochs=5,\n                method=PositionalMetricType.iou(threshold=0.3),\n                spatio_temporal_constraints=constraints)\n\n    box = BoundingBox(10., 5., 7., 7.).as_xyaah()\n    tracks = sort.predict_with_scene(1, [(box, 11111)])\n    for t in tracks:\n        print(t)\n\n    box = BoundingBox(10., 5., 7., 7.).as_xyaah()\n    tracks = sort.predict_with_scene(2, [(box, 22222)])\n    for t in tracks:\n        print(t)\n\n    sort.skip_epochs_for_scene(1, 10)\n    sort.skip_epochs_for_scene(2, 10)\n\n    # you have to call wasted from time to time to purge wasted tracks\n    # out of the waste bin. Without doing that the memory utilization will grow.\n    wasted = sort.wasted()\n    print(wasted[0])\n\n\n    # or just clear wasted\n    sort.clear_wasted()"
  },
  {
    "path": "python/sort/sort_maha.py",
    "content": "from similari import Sort, BoundingBox, SpatioTemporalConstraints, PositionalMetricType\n\nif __name__ == '__main__':\n    constraints = SpatioTemporalConstraints()\n    constraints.add_constraints([(1, 1.0)])\n    sort = Sort(shards = 4, bbox_history = 10,\n                max_idle_epochs = 5,\n                method=PositionalMetricType.maha(),\n                spatio_temporal_constraints=constraints)\n\n    box = BoundingBox(10., 5., 7., 7.).as_xyaah()\n    tracks = sort.predict([(box, 1111)])\n    for t in tracks:\n        print(t)\n\n    # you have to call wasted from time to time to purge wasted tracks\n    # out of the waste bin. Without doing that the memory utilization will grow.\n    sort.skip_epochs(10)\n    wasted = sort.wasted()\n    print(wasted[0])\n\n    # or just clear wasted\n    sort.clear_wasted()"
  },
  {
    "path": "python/visual_sort/batch_visual_sort.py",
    "content": "import numpy as np\nfrom similari import (\n    BatchVisualSort,\n    SpatioTemporalConstraints,\n    PositionalMetricType,\n    VisualSortOptions,\n    VisualSortMetricType,\n    BoundingBox, VisualSortObservation, VisualSortObservationSet,\n    VisualSortPredictionBatchRequest\n)\n\ndef get_opts():\n    # init the parameters of the algorithm\n    constraints = SpatioTemporalConstraints()\n    constraints.add_constraints([(1, 1.0)])\n\n    # choose the values according to your case\n    opts = VisualSortOptions()\n    opts.spatio_temporal_constraints(constraints)\n    opts.max_idle_epochs(15)\n    opts.kept_history_length(25)\n    opts.max_idle_epochs(15)\n    opts.kept_history_length(25)\n\n    # two alternative visual metrics are available\n    opts.visual_metric(VisualSortMetricType.euclidean(0.7))\n    # opts.visual_metric(VisualSortMetricType.cosine(0.2))\n\n    # two alternative positional metrics are used as a fallback\n    # when the visual metric match fails\n    opts.positional_metric(PositionalMetricType.maha())\n    # opts.positional_metric(PositionalMetricType.iou(threshold=0.3))\n\n    # options specific to the VisualSort algorithm\n    # choose the values according to your case\n    opts.visual_minimal_track_length(7)\n    opts.visual_minimal_area(5.0)\n    opts.visual_minimal_quality_use(0.45)\n    opts.visual_minimal_quality_collect(0.5)\n    opts.visual_max_observations(25)\n    opts.visual_min_votes(5)\n    return opts\n\n\ndef build_observation(obj):\n    # let's say each obj is a list where\n    # obj[:4] are left, top, width, height of the bounding box\n    # and obj[4:132] is a 128 length feature vector\n    # then the mandatory values for a VisualSortObservationSet structure are\n    bbox = BoundingBox(*obj[:4]).as_xyaah()\n    feature = obj[4:132]\n\n    # optionally, it's possible to set a feature_quality value\n    # it can be the quality score from the reid model\n    # or the bounding box confidence from the detector.\n    # The algorithm will use feature quality values as weights\n    # for potential matches based on visual metrics.\n    # let's say obj[132] is the feature_quality value\n    feature_quality = obj[132]\n\n    # custom_object_id is optional\n    # if set, it will allow to establish which input object was assigned to which output track\n    return VisualSortObservation(\n        feature=feature,\n        feature_quality=feature_quality,\n        bounding_box=bbox,\n        custom_object_id=None,\n    )\n\n\ndef generate_objs(n_objs):\n    # in this example\n    # each object has 4 values for the bounding box\n    # 128 values for the visual features\n    # and 1 value for the feature_quality\n    return np.random.rand(n_objs, 4 + 128 + 1)\n\n\ndef build_prediction_request(objs, n_batches):\n    # for each set of detected objects\n    # a VisualSortPredictionBatchRequest object needs to be initialized\n    # and filled with VisualSortObservation structures\n    batch_request = VisualSortPredictionBatchRequest()\n    # split objs into n batches\n    for batch_i, batch_objs in enumerate(np.split(objs, n_batches)):\n        for obj in batch_objs:\n            # to add a VisualSortObservation to a batch request\n            # scene id is passed as a first argument\n            # the algorithm will only match observations with tracks\n            # with the same scene id\n            batch_request.add(batch_i, build_observation(obj))\n    return batch_request\n\n\ndef main(n_frames=10, n_objs=6, n_batches=2):\n    assert n_objs % n_batches == 0, 'For simplicity, batches of equal size are expected.'\n\n    tracker = BatchVisualSort(distance_shards=1, voting_shards=1, opts=get_opts())\n\n    objs = generate_objs(n_objs)\n\n    for frame_i in range(n_frames):\n        print(f'======== {frame_i} ========')\n        # same objects are reused for simplicity\n        # in a real case the prediction request is built\n        # from the objects detected on a frame\n        batch_request = build_prediction_request(objs, n_batches)\n\n        # tracker is called for each frame\n        # even if there were no objects to add to the VisualSortPredictionBatchRequest\n        result = tracker.predict(batch_request)\n\n        # the result is parsed like so\n        for _ in range(result.batch_size()):\n            scene_id, tracks = result.get()\n            print(\"Scene\", scene_id)\n            for track in tracks:\n                print(track)\n\n            # example conversion from Similari track\n            # to a left, top, width, height bbox + track id\n            track = tracks[0]\n            bbox = track.predicted_bbox.as_ltwh()\n            print((\n                track.id,\n                bbox.left,\n                bbox.top,\n                bbox.width,\n                bbox.height,\n                bbox.confidence,\n            ))\n\n    # you have to call wasted from time to time to purge wasted tracks\n    # out of the waste bin. Without doing that the memory utilization will grow.\n    print('++++ Wasted ++++')\n    wasted = tracker.wasted()\n    for w in wasted:\n        print(w)\n\n    # or just\n    # tracker.clear_wasted()\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "python/visual_sort/visual_sort.py",
    "content": "from similari import (\n    VisualSort,\n    SpatioTemporalConstraints,\n    PositionalMetricType,\n    VisualSortOptions,\n    VisualSortMetricType,\n    BoundingBox, VisualSortObservation, VisualSortObservationSet\n)\n\ndef get_opts():\n    # init the parameters of the algorithm\n    constraints = SpatioTemporalConstraints()\n    constraints.add_constraints([(1, 1.0)])\n\n    # choose the values according to your case\n    opts = VisualSortOptions()\n    opts.spatio_temporal_constraints(constraints)\n    opts.max_idle_epochs(15)\n    opts.kept_history_length(25)\n    opts.max_idle_epochs(15)\n    opts.kept_history_length(25)\n\n    # two alternative visual metrics are available\n    opts.visual_metric(VisualSortMetricType.euclidean(0.7))\n    # opts.visual_metric(VisualSortMetricType.cosine(0.2))\n\n    # two alternative positional metrics are available to be used as a fallback\n    # when the visual metric match fails\n    opts.positional_metric(PositionalMetricType.maha())\n    # opts.positional_metric(PositionalMetricType.iou(threshold=0.3))\n\n    # options specific to the VisualSort algorithm\n    # choose the values according to your case\n    opts.visual_minimal_track_length(7)\n    opts.visual_minimal_area(5.0)\n    opts.visual_minimal_quality_use(0.45)\n    opts.visual_minimal_quality_collect(0.5)\n    opts.visual_max_observations(25)\n    opts.visual_min_votes(5)\n    return opts\n\ntracker = VisualSort(shards=1, opts=get_opts())\n\n\nassert False\n\n# let's say frame_objs is a list of objs detected in a frame\nfor frame_objs in frames:\n    # for each set of detected objects\n    # a VisualSortObservationSet object needs to be initialized\n    # and filled with VisualSortObservation structures\n    observation_set = VisualSortObservationSet()\n    for obj in frame_objs:\n        # let's say each obj is a list where\n        # obj[:4] are left, top, width, height of the bounding box\n        # and obj[4:132] is a 128 length feature vector\n        # then the mandatory values for a VisualSortObservation structure are\n        bbox = BoundingBox(*obj[:4]).as_xyaah()\n        feature = obj[4:132]\n\n        # optionally, it's possible to set a feature_quality value\n        # it can be the quality score from the reid model\n        # or the bounding box confidence from the detector.\n        # The algorithm will use feature quality values as weights\n        # for potential matches based on visual metrics.\n        # let's say obj[132] is the feature_quality value\n        feature_quality = obj[132]\n\n        # custom_object_id is optional\n        # if set, it will allow establishing which input object was assigned to which output track\n        observation = VisualSortObservation(\n            feature=feature,\n            feature_quality=feature_quality,\n            bounding_box=bbox,\n            custom_object_id=None,\n        )\n        observation_set.add(observation)\n\n    # tracker is called for each frame\n    # even if there were no objects to add to the VisualSortObservationSet\n    tracks = tracker.predict(observation_set)\n\n    # results are parsed like so\n    results = []\n    for track in tracks:\n        bbox = track.predicted_bbox.as_ltwh()\n        results.append(\n            (\n                track.id,\n                bbox.left,\n                bbox.top,\n                bbox.width,\n                bbox.height,\n                bbox.confidence,\n            )\n        )"
  },
  {
    "path": "python/visual_sort.py",
    "content": "from similari import VisualSortOptions, VisualSortObservation, VisualSortObservationSet, VisualSort, BoundingBox, \\\n    VisualSortMetricType, PositionalMetricType, SpatioTemporalConstraints\n\nimport numpy as np\n\nif __name__ == '__main__':\n    constraints = SpatioTemporalConstraints()\n    constraints.add_constraints([(1, 1.0)])\n\n    opts = VisualSortOptions()\n    opts.spatio_temporal_constraints(constraints)\n    opts.max_idle_epochs(3)\n    opts.kept_history_length(10)\n    opts.visual_metric(VisualSortMetricType.euclidean(1.0))\n    opts.positional_metric(PositionalMetricType.maha())\n    opts.visual_minimal_track_length(3)\n    opts.visual_minimal_area(5.0)\n    opts.visual_minimal_quality_use(0.45)\n    opts.visual_minimal_quality_collect(0.5)\n    opts.visual_max_observations(5)\n    opts.visual_min_votes(2)\n    print(opts)\n\n    tracker = VisualSort(shards=4, opts=opts)\n    observation_set = VisualSortObservationSet()\n    observation_set.add(VisualSortObservation(feature=np.array([0.1, 0.1]),\n                                          feature_quality=0.96,\n                                          bounding_box=BoundingBox(0, 0, 5, 10).as_xyaah(),\n                                          custom_object_id=10))\n    tracks = tracker.predict(observation_set)\n    print(tracks[0])\n\n    # you have to call wasted from time to time to purge wasted tracks\n    # out of the waste bin. Without doing that the memory utilization will grow.\n    tracker.skip_epochs(10)\n    wasted = tracker.wasted()\n    print(wasted[0])\n\n    # or just clear wasted\n    tracker.clear_wasted()"
  },
  {
    "path": "src/distance.rs",
    "content": "use crate::track::Feature;\nuse std::ops::{Mul, MulAssign, SubAssign};\n\n/// Euclidian distance between two feature vectors\n///\n/// When the features distances lengths don't match, the longer feature vector is truncated to\n/// shorter one when the distance is calculated\n///\npub fn euclidean(f1: &Feature, f2: &Feature) -> f32 {\n    let mut acc = 0.0;\n    for i in 0..f1.len().min(f2.len()) {\n        let mut block1 = f1[i];\n        let block2 = &f2[i];\n        block1.sub_assign(block2);\n        block1.mul_assign(block1);\n        acc += block1.reduce_add();\n    }\n    acc.sqrt()\n}\n\n/// Cosine distance between two vectors\n///\n/// When the features distances lengths don't match, the longer feature vector is truncated to\n/// shorter one when the distance is calculated\n///  \npub fn cosine(f1: &Feature, f2: &Feature) -> f32 {\n    let mut divided = 0.0;\n    let len = f1.len().min(f2.len());\n    for i in 0..len {\n        let mut block1 = f1[i];\n        let block2 = &f2[i];\n        block1.mul_assign(block2);\n        divided += block1.reduce_add();\n    }\n\n    let f1_divisor = f1\n        .iter()\n        .take(len)\n        .fold(0.0_f32, |acc, a| acc + a.mul(a).reduce_add());\n\n    let f2_divisor = f2\n        .iter()\n        .take(len)\n        .fold(0.0_f32, |acc, a| acc + a.mul(a).reduce_add());\n\n    divided / (f1_divisor * f2_divisor).sqrt()\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::distance::{cosine, euclidean};\n    use crate::track::utils::FromVec;\n    use crate::track::Feature;\n    use crate::EPS;\n\n    #[test]\n    fn euclidean_distances() {\n        let v1 = Feature::from_vec(vec![1f32, 0.0, 0.0]);\n        let v2 = Feature::from_vec(vec![0f32, 1.0f32, 0.0]);\n        let d = euclidean(&v1, &v1);\n        assert!(d.abs() < EPS);\n\n        let d = euclidean(&v1, &v2);\n        assert!((d - 2.0f32.sqrt()).abs() < EPS);\n    }\n\n    #[test]\n    fn cosine_distances() {\n        let v1 = dbg!(Feature::from_vec(vec![1f32, 0.0, 0.0]));\n        let v2 = dbg!(Feature::from_vec(vec![0f32, 1.0f32, 0.0]));\n        let v3 = dbg!(Feature::from_vec(vec![-1.0f32, 0.0, 0.0]));\n        let d = cosine(&v1, &v1);\n        assert!((d - 1.0).abs() < EPS);\n        let d = cosine(&v1, &v3);\n        assert!((d + 1.0).abs() < EPS);\n        let d = cosine(&v1, &v2);\n        assert!(d.abs() < EPS);\n    }\n}\n"
  },
  {
    "path": "src/examples/iou.rs",
    "content": "use crate::track::{\n    MetricOutput, MetricQuery, NoopLookup, Observation, ObservationAttributes, ObservationMetric,\n    ObservationsDb, TrackAttributes, TrackAttributesUpdate, TrackStatus,\n};\nuse crate::utils::bbox::BoundingBox;\nuse anyhow::Result;\n\n#[derive(Debug, Clone, Default)]\npub struct BBoxAttributes {\n    pub bboxes: Vec<BoundingBox>,\n}\n\n#[derive(Clone, Debug)]\npub struct BBoxAttributesUpdate;\n\nimpl TrackAttributesUpdate<BBoxAttributes> for BBoxAttributesUpdate {\n    fn apply(&self, _attrs: &mut BBoxAttributes) -> Result<()> {\n        Ok(())\n    }\n}\n\nimpl TrackAttributes<BBoxAttributes, BoundingBox> for BBoxAttributes {\n    type Update = BBoxAttributesUpdate;\n    type Lookup = NoopLookup<BBoxAttributes, BoundingBox>;\n\n    fn compatible(&self, _other: &BBoxAttributes) -> bool {\n        true\n    }\n\n    fn merge(&mut self, other: &BBoxAttributes) -> Result<()> {\n        self.bboxes.extend_from_slice(&other.bboxes);\n        Ok(())\n    }\n\n    fn baked(&self, _observations: &ObservationsDb<BoundingBox>) -> Result<TrackStatus> {\n        Ok(TrackStatus::Ready)\n    }\n}\n\n#[derive(Clone)]\npub struct IOUMetric {\n    history: usize,\n}\n\nimpl Default for IOUMetric {\n    fn default() -> Self {\n        Self { history: 3 }\n    }\n}\n\nimpl ObservationMetric<BBoxAttributes, BoundingBox> for IOUMetric {\n    fn metric(&self, mq: &MetricQuery<'_, BBoxAttributes, BoundingBox>) -> MetricOutput<f32> {\n        let (e1, e2) = (mq.candidate_observation, mq.track_observation);\n        let box_m_opt =\n            BoundingBox::calculate_metric_object(&e1.attr().as_ref(), &e2.attr().as_ref());\n        if let Some(box_m) = &box_m_opt {\n            if *box_m < 0.01 {\n                None\n            } else {\n                Some((box_m_opt, None))\n            }\n        } else {\n            None\n        }\n    }\n\n    fn optimize(\n        &mut self,\n        _feature_class: u64,\n        _merge_history: &[u64],\n        attrs: &mut BBoxAttributes,\n        features: &mut Vec<Observation<BoundingBox>>,\n        prev_length: usize,\n        is_merge: bool,\n    ) -> Result<()> {\n        if !is_merge {\n            if let Some(bb) = &features[prev_length].attr() {\n                attrs.bboxes.push(*bb);\n            }\n        }\n        // Kalman filter should be used here to generate better prediction for next\n        // comparison\n        features.reverse();\n        features.truncate(self.history);\n        features.reverse();\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/examples.rs",
    "content": "pub mod iou;\n\nuse crate::distance::euclidean;\nuse crate::track::utils::FromVec;\nuse crate::track::{\n    Feature, MetricOutput, MetricQuery, NoopLookup, Observation, ObservationAttributes,\n    ObservationMetric, ObservationsDb, TrackAttributes, TrackAttributesUpdate, TrackStatus,\n};\nuse crate::utils::bbox::BoundingBox;\nuse anyhow::Result;\nuse rand::distributions::Uniform;\nuse rand::prelude::ThreadRng;\nuse rand::Rng;\nuse std::time::{Duration, SystemTime, UNIX_EPOCH};\nuse thiserror::Error;\n\n#[derive(Debug, Error)]\npub enum AppError {\n    #[error(\"Only single feature vector can be set\")]\n    SetError,\n    #[error(\"Incompatible attributes\")]\n    Incompatible,\n}\n\n#[derive(Debug, Clone, Default)]\npub struct SimpleAttrs {\n    set: bool,\n}\n\n#[derive(Default, Clone)]\npub struct SimpleAttributeUpdate;\n\nimpl TrackAttributesUpdate<SimpleAttrs> for SimpleAttributeUpdate {\n    fn apply(&self, attrs: &mut SimpleAttrs) -> Result<()> {\n        if attrs.set {\n            return Err(AppError::SetError.into());\n        }\n        attrs.set = true;\n        Ok(())\n    }\n}\n\nimpl TrackAttributes<SimpleAttrs, f32> for SimpleAttrs {\n    type Update = SimpleAttributeUpdate;\n    type Lookup = NoopLookup<SimpleAttrs, f32>;\n\n    fn compatible(&self, other: &SimpleAttrs) -> bool {\n        self.set && other.set\n    }\n\n    fn merge(&mut self, other: &SimpleAttrs) -> Result<()> {\n        if self.compatible(other) {\n            Ok(())\n        } else {\n            Err(AppError::Incompatible.into())\n        }\n    }\n\n    fn baked(&self, _observations: &ObservationsDb<f32>) -> Result<TrackStatus> {\n        if self.set {\n            Ok(TrackStatus::Ready)\n        } else {\n            Ok(TrackStatus::Pending)\n        }\n    }\n}\n\n#[derive(Default, Clone)]\npub struct SimpleMetric;\n\nimpl ObservationMetric<SimpleAttrs, f32> for SimpleMetric {\n    fn metric(&self, mq: &MetricQuery<'_, SimpleAttrs, f32>) -> MetricOutput<f32> {\n        let (e1, e2) = (mq.candidate_observation, mq.track_observation);\n        Some((\n            f32::calculate_metric_object(&e1.attr().as_ref(), &e2.attr().as_ref()),\n            match (e1.feature().as_ref(), e2.feature().as_ref()) {\n                (Some(x), Some(y)) => Some(euclidean(x, y)),\n                _ => None,\n            },\n        ))\n    }\n\n    fn optimize(\n        &mut self,\n        _feature_class: u64,\n        _merge_history: &[u64],\n        _attrs: &mut SimpleAttrs,\n        _features: &mut Vec<Observation<f32>>,\n        _prev_length: usize,\n        _is_merge: bool,\n    ) -> Result<()> {\n        Ok(())\n    }\n}\n\n#[derive(Debug, Clone, Default)]\npub struct UnboundAttrs;\n\n#[derive(Default, Clone)]\npub struct UnboundAttributeUpdate;\n\nimpl TrackAttributesUpdate<UnboundAttrs> for UnboundAttributeUpdate {\n    fn apply(&self, _attrs: &mut UnboundAttrs) -> Result<()> {\n        Ok(())\n    }\n}\n\nimpl TrackAttributes<UnboundAttrs, f32> for UnboundAttrs {\n    type Update = UnboundAttributeUpdate;\n    type Lookup = NoopLookup<UnboundAttrs, f32>;\n\n    fn compatible(&self, _other: &UnboundAttrs) -> bool {\n        true\n    }\n\n    fn merge(&mut self, _other: &UnboundAttrs) -> Result<()> {\n        Ok(())\n    }\n\n    fn baked(&self, _observations: &ObservationsDb<f32>) -> Result<TrackStatus> {\n        Ok(TrackStatus::Ready)\n    }\n}\n\n#[derive(Default, Clone)]\npub struct UnboundMetric;\n\nimpl ObservationMetric<UnboundAttrs, f32> for UnboundMetric {\n    fn metric(&self, mq: &MetricQuery<'_, UnboundAttrs, f32>) -> MetricOutput<f32> {\n        let (e1, e2) = (mq.candidate_observation, mq.track_observation);\n        Some((\n            f32::calculate_metric_object(&e1.attr().as_ref(), &e2.attr().as_ref()),\n            match (e1.feature().as_ref(), e2.feature().as_ref()) {\n                (Some(x), Some(y)) => Some(euclidean(x, y)),\n                _ => None,\n            },\n        ))\n    }\n\n    fn optimize(\n        &mut self,\n        _feature_class: u64,\n        _merge_history: &[u64],\n        _attrs: &mut UnboundAttrs,\n        _features: &mut Vec<Observation<f32>>,\n        _prev_length: usize,\n        _is_merge: bool,\n    ) -> Result<()> {\n        Ok(())\n    }\n}\n\npub fn vec2(x: f32, y: f32) -> Feature {\n    Feature::from_vec(vec![x, y])\n}\n\npub struct FeatGen2 {\n    x: f32,\n    y: f32,\n    gen: ThreadRng,\n    dist: Uniform<f32>,\n}\n\nimpl FeatGen2 {\n    pub fn new(x: f32, y: f32, drift: f32) -> Self {\n        Self {\n            x,\n            y,\n            gen: rand::thread_rng(),\n            dist: Uniform::new(-drift, drift),\n        }\n    }\n}\n\nimpl Iterator for FeatGen2 {\n    type Item = Observation<f32>;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        self.x += self.gen.sample(self.dist);\n        self.y += self.gen.sample(self.dist);\n        Some(Observation::new(\n            Some(self.gen.sample(self.dist) + 0.7),\n            Some(vec2(self.x, self.y)),\n        ))\n    }\n}\n\npub struct BoxGen2 {\n    x: f32,\n    y: f32,\n    width: f32,\n    height: f32,\n    gen: ThreadRng,\n    dist_pos: Uniform<f32>,\n    dist_box: Uniform<f32>,\n}\n\nimpl BoxGen2 {\n    pub fn new(x: f32, y: f32, width: f32, height: f32, pos_drift: f32, box_drift: f32) -> Self {\n        Self {\n            x,\n            y,\n            width,\n            height,\n            gen: rand::thread_rng(),\n            dist_pos: Uniform::new(-pos_drift, pos_drift),\n            dist_box: Uniform::new(-box_drift, box_drift),\n        }\n    }\n    pub fn new_monotonous(\n        x: f32,\n        y: f32,\n        width: f32,\n        height: f32,\n        pos_drift: f32,\n        box_drift: f32,\n    ) -> Self {\n        Self {\n            x,\n            y,\n            width,\n            height,\n            gen: rand::thread_rng(),\n            dist_pos: Uniform::new(0.0, pos_drift),\n            dist_box: Uniform::new(-box_drift, box_drift),\n        }\n    }\n}\n\nimpl Iterator for BoxGen2 {\n    type Item = BoundingBox;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        self.x += self.gen.sample(self.dist_pos);\n        self.y += self.gen.sample(self.dist_pos);\n\n        self.width += self.gen.sample(self.dist_box);\n        self.height += self.gen.sample(self.dist_box);\n\n        if self.width < 1.0 {\n            self.width = 1.0;\n        }\n        if self.height < 1.0 {\n            self.height = 1.0;\n        }\n\n        Some(BoundingBox::new(self.x, self.y, self.width, self.height))\n    }\n}\n\n#[inline]\npub fn current_time_span() -> Duration {\n    SystemTime::now().duration_since(UNIX_EPOCH).unwrap()\n}\n\n#[inline]\npub fn current_time_ms() -> u128 {\n    current_time_span().as_millis()\n}\n\n#[inline]\npub fn current_time_sec() -> u64 {\n    current_time_span().as_secs()\n}\n\npub struct FeatGen {\n    x: f32,\n    len: usize,\n    gen: ThreadRng,\n    dist: Uniform<f32>,\n}\n\nimpl FeatGen {\n    pub fn new(x: f32, len: usize, drift: f32) -> Self {\n        Self {\n            x,\n            len,\n            gen: rand::thread_rng(),\n            dist: Uniform::new(-drift, drift),\n        }\n    }\n}\n\nimpl Iterator for FeatGen {\n    type Item = Observation<()>;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        let v = (0..self.len)\n            .map(|_| self.x + self.gen.sample(self.dist))\n            .collect::<Vec<_>>();\n        Some(Observation::<()>::new(None, Some(Feature::from_vec(v))))\n    }\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "//!\n//! # Similari\n//!\n//! The purpose of the crate is to provide tools to config.toml in-memory vector (feature) similarity engines.\n//! Similarity calculation is an important resource demanding task broadly used in machine learning and AI systems.\n//! Read more about it at GitHub [README](https://github.com/insight-platform/Similari/blob/main/README.md)\n//!\n\n/// Holds auxiliary functions that calculate distances between two features.\n///\npub mod distance;\n\n/// Various auxiliary testing and example components\n///\npub mod examples;\n\n/// Frequently used components\n///\npub mod prelude;\n\n/// Holds basic abstractions for tracking - [Track](track::Track), auxiliary structures, traits, and functions.\n///\n/// It defines the track's look and feel, provides `Track` structure that holds track attributes and features,\n/// can accumulate track features and calculate feature distances between pair of tracks.\n///\npub mod track;\n\n/// Ready-to-use trackers - IoU SORT, Mahalanobis SORT, Hybrid Visual/Positional Sort\n///\npub mod trackers;\n\n/// Utility objects - bounding boxes, kalman_2d_box filter, polygon clipping, nms\n///\npub mod utils;\n\npub use track::store;\npub use track::voting;\n\nuse thiserror::Error;\n\n/// Package errors\n///\n#[derive(Error, Debug, Clone)]\npub enum Errors {\n    /// Compared tracks have incompatible attributes, so cannot be used in calculations.\n    #[error(\"Attributes are incompatible between tracks and cannot be used in calculations.\")]\n    IncompatibleAttributes,\n    /// One of tracks doesn't have features for specified class\n    ///\n    #[error(\"Requested observations for class={2} are missing in track={0} or track={1} - distance cannot be calculated.\")]\n    ObservationForClassNotFound(u64, u64, u64),\n    /// Requested track is not found in the store\n    ///\n    #[error(\"Missing track={0}.\")]\n    TrackNotFound(u64),\n\n    #[error(\"Missing requested tracks.\")]\n    TracksNotFound,\n\n    /// The distance is calculated against self. Ignore it.\n    ///\n    #[error(\"Calculation with self id={0} not permitted\")]\n    SameTrackCalculation(u64),\n\n    /// Track ID is duplicate\n    ///\n    #[error(\"Duplicate track id={0}\")]\n    DuplicateTrackId(u64),\n\n    /// Object cannot be converted\n    ///\n    #[error(\"Generic BBox cannot be converted to a requested type\")]\n    GenericBBoxConversionError,\n\n    /// Index is out of range\n    #[error(\"The index is out of range\")]\n    OutOfRange,\n}\n\npub const EPS: f32 = 0.00001;\n\n#[cfg(feature = \"python\")]\nmod python {\n    use crate::trackers::batch::python::PyPredictionBatchResult;\n    use crate::trackers::sort::batch_api::python::{PyBatchSort, PySortPredictionBatchRequest};\n    use crate::trackers::sort::python::{PyPositionalMetricType, PySortTrack, PyWastedSortTrack};\n    use crate::trackers::sort::simple_api::python::PySort;\n    use crate::trackers::spatio_temporal_constraints::python::PySpatioTemporalConstraints;\n    use crate::trackers::visual_sort::batch_api::python::{\n        PyBatchVisualSort, PyVisualSortPredictionBatchRequest,\n    };\n    use crate::trackers::visual_sort::metric::python::PyVisualSortMetricType;\n    use crate::trackers::visual_sort::options::python::PyVisualSortOptions;\n    use crate::trackers::visual_sort::python::{\n        PyVisualSortObservation, PyVisualSortObservationSet, PyWastedVisualSortTrack,\n    };\n    use crate::trackers::visual_sort::simple_api::python::PyVisualSort;\n    use crate::utils::bbox::python::{PyBoundingBox, PyUniversal2DBox};\n    use crate::utils::clipping::clipping_py::{\n        intersection_area_py, sutherland_hodgman_clip_py, PyPolygon,\n    };\n    use crate::utils::kalman::kalman_2d_box::python::{\n        PyUniversal2DBoxKalmanFilter, PyUniversal2DBoxKalmanFilterState,\n    };\n    use crate::utils::kalman::kalman_2d_point::python::{\n        PyPoint2DKalmanFilter, PyPoint2DKalmanFilterState,\n    };\n    use crate::utils::kalman::kalman_2d_point_vec::python::PyVec2DKalmanFilter;\n    use crate::utils::nms::nms_py::nms_py;\n    use pyo3::prelude::*;\n\n    #[pyfunction]\n    pub fn version() -> String {\n        env!(\"CARGO_PKG_VERSION\").to_string()\n    }\n\n    #[pymodule]\n    #[pyo3(name = \"similari\")]\n    fn similari(m: &Bound<'_, PyModule>) -> PyResult<()> {\n        pyo3_log::init();\n\n        m.add_class::<PyBoundingBox>()?;\n        m.add_class::<PyUniversal2DBox>()?;\n        m.add_class::<PyPolygon>()?;\n        m.add_class::<PySortTrack>()?;\n        m.add_class::<PyWastedSortTrack>()?;\n\n        m.add_class::<PyUniversal2DBoxKalmanFilterState>()?;\n        m.add_class::<PyUniversal2DBoxKalmanFilter>()?;\n\n        m.add_class::<PyPoint2DKalmanFilterState>()?;\n        m.add_class::<PyPoint2DKalmanFilter>()?;\n\n        m.add_class::<PyVec2DKalmanFilter>()?;\n\n        m.add_class::<PySortPredictionBatchRequest>()?;\n        m.add_class::<PySpatioTemporalConstraints>()?;\n        m.add_class::<PySort>()?;\n\n        m.add_class::<PyPositionalMetricType>()?;\n        m.add_class::<PyVisualSortMetricType>()?;\n        m.add_class::<PyVisualSortOptions>()?;\n        m.add_class::<PyVisualSortObservation>()?;\n        m.add_class::<PyVisualSortObservationSet>()?;\n        m.add_class::<PyVisualSortPredictionBatchRequest>()?;\n        m.add_class::<PyWastedVisualSortTrack>()?;\n        m.add_class::<PyVisualSort>()?;\n\n        m.add_class::<PyPredictionBatchResult>()?;\n\n        m.add_class::<PySortPredictionBatchRequest>()?;\n        m.add_class::<PyBatchSort>()?;\n\n        m.add_class::<PyBatchVisualSort>()?;\n\n        m.add_function(wrap_pyfunction!(version, m)?)?;\n        m.add_function(wrap_pyfunction!(nms_py, m)?)?;\n        m.add_function(wrap_pyfunction!(sutherland_hodgman_clip_py, m)?)?;\n        m.add_function(wrap_pyfunction!(intersection_area_py, m)?)?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/prelude.rs",
    "content": "use crate::track;\nuse crate::trackers;\nuse crate::utils;\n\npub use track::builder::{ObservationBuilder, TrackBuilder};\npub use track::notify::NoopNotifier;\npub use track::store::builder::TrackStoreBuilder;\n\npub use crate::trackers::sort::PositionalMetricType;\npub use trackers::sort::batch_api::BatchSort;\npub use trackers::sort::simple_api::Sort;\npub use trackers::sort::SortTrack;\npub use trackers::spatio_temporal_constraints::SpatioTemporalConstraints;\n\npub use crate::trackers::visual_sort::options::VisualSortOptions;\npub use trackers::visual_sort::metric::VisualSortMetricType;\npub use trackers::visual_sort::simple_api::VisualSort;\npub use trackers::visual_sort::VisualSortObservation;\n\npub use utils::bbox::BoundingBox;\npub use utils::bbox::Universal2DBox;\n\npub use utils::clipping::sutherland_hodgman_clip;\npub use utils::nms;\n"
  },
  {
    "path": "src/track/builder.rs",
    "content": "use crate::track::notify::{ChangeNotifier, NoopNotifier};\nuse crate::track::{Feature, ObservationAttributes, ObservationMetric, Track, TrackAttributes};\nuse anyhow::Result;\nuse rand::Rng;\n\ntype TrackBuilderObservationRepr<OA, TAU> = (u64, Option<OA>, Option<Feature>, Option<TAU>);\n\n/// Builder is used to build an observation\n///\npub struct ObservationBuilder<TAU, OA>\nwhere\n    OA: ObservationAttributes,\n{\n    feature_class: u64,\n    observation_attributes: Option<OA>,\n    observation: Option<Feature>,\n    track_attributes_update: Option<TAU>,\n}\n\nimpl<TAU, OA> ObservationBuilder<TAU, OA>\nwhere\n    OA: ObservationAttributes,\n{\n    /// Constructor method\n    ///\n    /// The observation is created for a certain feature class\n    ///\n    /// # Parameters\n    /// * `feature_cals` - feature class\n    ///\n    pub fn new(feature_class: u64) -> Self {\n        Self {\n            feature_class,\n            observation_attributes: None,\n            observation: None,\n            track_attributes_update: None,\n        }\n    }\n\n    /// Sets observation custom attributes\n    ///\n    pub fn observation_attributes(mut self, attrs: OA) -> Self {\n        self.observation_attributes = Some(attrs);\n        self\n    }\n\n    /// Sets a unified feature vector for observation\n    ///\n    pub fn observation(mut self, observation: Feature) -> Self {\n        self.observation = Some(observation);\n        self\n    }\n\n    /// Sets the track attributes update, connected to the observation\n    ///\n    pub fn track_attributes_update(mut self, upd: TAU) -> Self {\n        self.track_attributes_update = Some(upd);\n        self\n    }\n\n    /// Builds observation tuple suitable for [TrackBuilder::observation](TrackBuilder::observation) method\n    ///\n    pub fn build(self) -> TrackBuilderObservationRepr<OA, TAU> {\n        (\n            self.feature_class,\n            self.observation_attributes,\n            self.observation,\n            self.track_attributes_update,\n        )\n    }\n}\n\n/// Builder object for Track\n///\npub struct TrackBuilder<TA, M, OA, N = NoopNotifier>\nwhere\n    TA: TrackAttributes<TA, OA>,\n    M: ObservationMetric<TA, OA>,\n    OA: ObservationAttributes,\n    N: ChangeNotifier,\n{\n    id: u64,\n    track_attrs: Option<TA>,\n    metric: Option<M>,\n    notifier: Option<N>,\n    observations: Vec<TrackBuilderObservationRepr<OA, TA::Update>>,\n}\n\nimpl<TA, M, OA, N> Default for TrackBuilder<TA, M, OA, N>\nwhere\n    TA: TrackAttributes<TA, OA>,\n    M: ObservationMetric<TA, OA>,\n    OA: ObservationAttributes,\n    N: ChangeNotifier,\n{\n    fn default() -> Self {\n        let mut rng = rand::thread_rng();\n        TrackBuilder::new(rng.gen::<u64>())\n    }\n}\n\nimpl<TA, M, OA, N> TrackBuilder<TA, M, OA, N>\nwhere\n    TA: TrackAttributes<TA, OA>,\n    M: ObservationMetric<TA, OA>,\n    OA: ObservationAttributes,\n    N: ChangeNotifier,\n{\n    /// Empty track constructor\n    ///\n    /// # Parameters\n    /// * `id` - unique track ID\n    ///\n    pub fn new(id: u64) -> TrackBuilder<TA, M, OA, N> {\n        Self {\n            id,\n            track_attrs: None,\n            metric: None,\n            notifier: None,\n            observations: Vec::new(),\n        }\n    }\n\n    pub fn attributes(mut self, track_attrs: TA) -> Self {\n        assert!(\n            self.track_attrs.is_none(),\n            \"The method `attributes` must be called once.\"\n        );\n        self.track_attrs = Some(track_attrs);\n        self\n    }\n\n    pub fn metric(mut self, metric: M) -> Self {\n        assert!(\n            self.metric.is_none(),\n            \"The method `metric` must be called once.\"\n        );\n        self.metric = Some(metric);\n        self\n    }\n\n    pub fn notifier(mut self, notifier: N) -> Self {\n        assert!(\n            self.notifier.is_none(),\n            \"The method `notifier` must be called once.\"\n        );\n        self.notifier = Some(notifier);\n        self\n    }\n\n    /// Sets additional observation. The method can be called multiple times to add several observations.\n    ///\n    /// # Parameters\n    /// * `observation` is the tuple produced by [ObservationBuilder](ObservationBuilder)\n    ///\n    pub fn observation(mut self, observation: TrackBuilderObservationRepr<OA, TA::Update>) -> Self {\n        let (feature_class, observation_attributes, observation, track_attributes_update) =\n            observation;\n        self.observations.push((\n            feature_class,\n            observation_attributes,\n            observation,\n            track_attributes_update,\n        ));\n        self\n    }\n\n    pub fn build(self) -> Result<Track<TA, M, OA, N>> {\n        let mut track = Track::new(\n            self.id,\n            self.metric.unwrap(),\n            self.track_attrs.unwrap(),\n            self.notifier.unwrap(),\n        );\n        for (cls, oa, feat, upd) in self.observations {\n            track.add_observation(cls, oa, feat, upd)?;\n        }\n        Ok(track)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::examples::{UnboundAttributeUpdate, UnboundAttrs, UnboundMetric};\n    use crate::track::builder::{ObservationBuilder, TrackBuilder};\n    use crate::track::notify::NoopNotifier;\n    use crate::track::utils::FromVec;\n    use crate::track::Feature;\n    use anyhow::Result;\n\n    #[test]\n    fn builder_id() -> Result<()> {\n        let track = TrackBuilder::new(10)\n            .notifier(NoopNotifier)\n            .metric(UnboundMetric)\n            .attributes(UnboundAttrs)\n            .observation(\n                ObservationBuilder::new(0)\n                    .observation(Feature::from_vec(vec![0.0, 1.0]))\n                    .observation_attributes(0.1)\n                    .track_attributes_update(UnboundAttributeUpdate)\n                    .build(),\n            )\n            .build()?;\n        assert_eq!(track.get_track_id(), 10);\n        Ok(())\n    }\n\n    #[test]\n    fn builder_noid() -> Result<()> {\n        let track = TrackBuilder::default()\n            .notifier(NoopNotifier)\n            .metric(UnboundMetric)\n            .attributes(UnboundAttrs)\n            .observation(\n                ObservationBuilder::new(0)\n                    .observation(Feature::from_vec(vec![0.0, 1.0]))\n                    .observation_attributes(0.1)\n                    .track_attributes_update(UnboundAttributeUpdate)\n                    .build(),\n            )\n            .build()?;\n        assert!(track.get_track_id() > 0);\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/track/notify.rs",
    "content": "pub trait ChangeNotifier: Clone + Sync + Send + 'static {\n    fn send(&mut self, id: u64);\n}\n\n#[derive(Clone, Debug)]\npub struct NoopNotifier;\n\nimpl ChangeNotifier for NoopNotifier {\n    fn send(&mut self, _id: u64) {}\n}\n"
  },
  {
    "path": "src/track/store/builder.rs",
    "content": "use crate::store::TrackStore;\nuse crate::track::notify::{ChangeNotifier, NoopNotifier};\nuse crate::track::{ObservationAttributes, ObservationMetric, TrackAttributes};\nuse std::marker::PhantomData;\n\n/// Builder for TrackStore\n///\npub struct TrackStoreBuilder<TA, M, OA, N = NoopNotifier>\nwhere\n    TA: TrackAttributes<TA, OA>,\n    M: ObservationMetric<TA, OA>,\n    OA: ObservationAttributes,\n    N: ChangeNotifier,\n{\n    metric: Option<M>,\n    default_attributes: Option<TA>,\n    notifier: Option<N>,\n    shards: usize,\n    _phantom_oa: PhantomData<OA>,\n}\n\n/// Default builder\n/// shards count is set to number cpu cores (threads)\n///\nimpl<TA, M, OA, N> Default for TrackStoreBuilder<TA, M, OA, N>\nwhere\n    TA: TrackAttributes<TA, OA>,\n    M: ObservationMetric<TA, OA>,\n    OA: ObservationAttributes,\n    N: ChangeNotifier,\n{\n    fn default() -> TrackStoreBuilder<TA, M, OA, N> {\n        Self::new(num_cpus::get())\n    }\n}\n\nimpl<TA, M, OA, N> TrackStoreBuilder<TA, M, OA, N>\nwhere\n    TA: TrackAttributes<TA, OA>,\n    M: ObservationMetric<TA, OA>,\n    OA: ObservationAttributes,\n    N: ChangeNotifier,\n{\n    /// Creates a new builder\n    ///\n    /// # Parameters\n    /// * `shards` - number of shards to use (the parameter by default is equal to cores count), should be chosen experimentally, depending on tracker kind.\n    ///\n    pub fn new(shards: usize) -> Self {\n        TrackStoreBuilder {\n            shards,\n            metric: None,\n            default_attributes: None,\n            notifier: None,\n            _phantom_oa: PhantomData,\n        }\n    }\n\n    /// Sets the metric object to use\n    ///\n    pub fn metric(mut self, metric: M) -> Self {\n        assert!(\n            self.metric.is_none(),\n            \"The method `metric` must be called once.\"\n        );\n        self.metric = Some(metric);\n        self\n    }\n\n    /// Sets the notifier object to use\n    ///\n    pub fn notifier(mut self, notifier: N) -> Self {\n        assert!(\n            self.notifier.is_none(),\n            \"The method `notifier` must be called once.\"\n        );\n        self.notifier = Some(notifier);\n        self\n    }\n\n    /// Sets the default track attributes to use for new tracks\n    ///\n    pub fn default_attributes(mut self, attrs: TA) -> Self {\n        assert!(\n            self.default_attributes.is_none(),\n            \"The method `default_attributes` must be called once.\"\n        );\n        self.default_attributes = Some(attrs);\n        self\n    }\n\n    /// Builds the TrackStore\n    ///\n    pub fn build(self) -> TrackStore<TA, M, OA, N> {\n        TrackStore::new(\n            self.metric.unwrap(),\n            self.default_attributes.unwrap(),\n            self.notifier.unwrap(),\n            self.shards,\n        )\n    }\n}\n"
  },
  {
    "path": "src/track/store/store_tests.rs",
    "content": "#[cfg(test)]\nmod tests {\n    use crate::distance::euclidean;\n    use crate::examples::{current_time_ms, vec2};\n    use crate::prelude::TrackStoreBuilder;\n    use crate::track::store::TrackStore;\n    use crate::track::utils::feature_attributes_sort_dec;\n    use crate::track::{\n        LookupRequest, MetricOutput, MetricQuery, NoopLookup, NoopNotifier, Observation,\n        ObservationAttributes, ObservationMetric, ObservationsDb, Track, TrackAttributes,\n        TrackAttributesUpdate, TrackStatus,\n    };\n    use crate::EPS;\n    use anyhow::Result;\n    use std::thread;\n    use std::time::Duration;\n\n    #[derive(Default, Debug, Clone)]\n    pub struct TimeAttrs {\n        start_time: u128,\n        end_time: u128,\n        baked_period: u128,\n    }\n\n    #[derive(Default, Clone)]\n    pub struct TimeAttrUpdates {\n        time: u128,\n    }\n\n    impl TrackAttributesUpdate<TimeAttrs> for TimeAttrUpdates {\n        fn apply(&self, attrs: &mut TimeAttrs) -> Result<()> {\n            attrs.end_time = self.time;\n            if attrs.start_time == 0 {\n                attrs.start_time = self.time;\n            }\n            Ok(())\n        }\n    }\n\n    impl TrackAttributes<TimeAttrs, f32> for TimeAttrs {\n        type Update = TimeAttrUpdates;\n        type Lookup = NoopLookup<TimeAttrs, f32>;\n\n        fn compatible(&self, other: &TimeAttrs) -> bool {\n            self.end_time <= other.start_time\n        }\n\n        fn merge(&mut self, other: &TimeAttrs) -> Result<()> {\n            self.start_time = self.start_time.min(other.start_time);\n            self.end_time = self.end_time.max(other.end_time);\n            Ok(())\n        }\n\n        fn baked(&self, _observations: &ObservationsDb<f32>) -> Result<TrackStatus> {\n            if current_time_ms() >= self.baked_period + self.end_time {\n                Ok(TrackStatus::Ready)\n            } else {\n                Ok(TrackStatus::Pending)\n            }\n        }\n    }\n\n    #[derive(Default, Clone)]\n    struct TimeMetric {\n        max_length: usize,\n    }\n\n    impl ObservationMetric<TimeAttrs, f32> for TimeMetric {\n        fn metric(&self, mq: &MetricQuery<TimeAttrs, f32>) -> MetricOutput<f32> {\n            let (e1, e2) = (mq.candidate_observation, mq.track_observation);\n            Some((\n                f32::calculate_metric_object(&e1.attr().as_ref(), &e2.attr().as_ref()),\n                match (e1.feature().as_ref(), e2.feature().as_ref()) {\n                    (Some(x), Some(y)) => Some(euclidean(x, y)),\n                    _ => None,\n                },\n            ))\n        }\n\n        fn optimize(\n            &mut self,\n            _feature_class: u64,\n            _merge_history: &[u64],\n            _attrs: &mut TimeAttrs,\n            features: &mut Vec<Observation<f32>>,\n            _prev_length: usize,\n            _is_merge: bool,\n        ) -> Result<()> {\n            features.sort_by(feature_attributes_sort_dec);\n            features.truncate(self.max_length);\n            Ok(())\n        }\n    }\n\n    #[test]\n    fn new_default_store() -> Result<()> {\n        let default_store: TrackStore<TimeAttrs, TimeMetric, f32> = TrackStoreBuilder::default()\n            .default_attributes(TimeAttrs::default())\n            .metric(TimeMetric::default())\n            .notifier(NoopNotifier)\n            .build();\n        drop(default_store);\n        Ok(())\n    }\n\n    #[test]\n    fn new_store_10_shards() -> Result<()> {\n        let mut store = TrackStore::new(\n            TimeMetric { max_length: 20 },\n            TimeAttrs {\n                baked_period: 10,\n                ..Default::default()\n            },\n            NoopNotifier,\n            10,\n        );\n        store.add(\n            0,\n            0,\n            Some(0.9),\n            Some(vec2(0.0, 1.0)),\n            Some(TimeAttrUpdates {\n                time: current_time_ms(),\n            }),\n        )?;\n\n        Ok(())\n    }\n\n    fn time_attrs_current_ts() -> Option<TimeAttrUpdates> {\n        Some(TimeAttrUpdates {\n            time: current_time_ms(),\n        })\n    }\n\n    #[test]\n    fn sharding_n_fetch() -> Result<()> {\n        let mut store = TrackStore::new(\n            TimeMetric { max_length: 20 },\n            TimeAttrs {\n                baked_period: 10,\n                ..Default::default()\n            },\n            NoopNotifier,\n            2,\n        );\n\n        let stats = store.shard_stats();\n        assert_eq!(stats, vec![0, 0]);\n\n        store.add(\n            0,\n            0,\n            Some(0.9),\n            Some(vec2(0.0, 1.0)),\n            time_attrs_current_ts(),\n        )?;\n\n        let stats = store.shard_stats();\n        assert_eq!(stats, vec![1, 0]);\n\n        store.add(\n            1,\n            0,\n            Some(0.9),\n            Some(vec2(0.0, 1.0)),\n            time_attrs_current_ts(),\n        )?;\n\n        let stats = store.shard_stats();\n        assert_eq!(stats, vec![1, 1]);\n\n        let tracks = store.fetch_tracks(&[0, 1]);\n        assert_eq!(tracks.len(), 2);\n        assert_eq!(tracks[0].track_id, 0);\n        assert_eq!(tracks[1].track_id, 1);\n\n        Ok(())\n    }\n\n    #[test]\n    fn general_ops() -> Result<()> {\n        let mut store = TrackStore::new(\n            TimeMetric { max_length: 20 },\n            TimeAttrs {\n                baked_period: 10,\n                ..Default::default()\n            },\n            NoopNotifier,\n            1,\n        );\n        store.add(\n            0,\n            0,\n            Some(0.9),\n            Some(vec2(0.0, 1.0)),\n            time_attrs_current_ts(),\n        )?;\n        let baked = store.find_usable();\n        assert!(baked.is_empty());\n        thread::sleep(Duration::from_millis(30));\n        let baked = store.find_usable();\n        assert_eq!(baked.len(), 1);\n        assert_eq!(baked[0].0, 0);\n\n        let vectors = store.fetch_tracks(&baked.into_iter().map(|(t, _)| t).collect::<Vec<_>>());\n        assert_eq!(vectors.len(), 1);\n        assert_eq!(vectors[0].track_id, 0);\n        assert_eq!(vectors[0].observations.len(), 1);\n\n        store.add(\n            0,\n            0,\n            Some(0.9),\n            Some(vec2(0.0, 1.0)),\n            time_attrs_current_ts(),\n        )?;\n        let (dists, errs) = store.owned_track_distances(&[0], 0, false);\n        assert!(dists.all().is_empty());\n        assert!(errs.all().is_empty());\n        thread::sleep(Duration::from_millis(10));\n        store.add(\n            1,\n            0,\n            Some(0.7),\n            Some(vec2(1.0, 0.0)),\n            time_attrs_current_ts(),\n        )?;\n\n        let (dists, errs) = store.owned_track_distances(&[0], 0, false);\n        let dists = dists.all();\n        let errs = errs.all();\n\n        assert_eq!(dists.len(), 1);\n        assert_eq!(dists[0].to, 1);\n        assert!(dists[0].feature_distance.is_some());\n        assert!((dists[0].feature_distance.as_ref().unwrap() - 2.0_f32.sqrt()).abs() < EPS);\n        assert!(errs.is_empty());\n\n        let (dists, errs) = store.owned_track_distances(&[1], 0, false);\n        let dists = dists.all();\n        let errs = errs.all();\n\n        assert_eq!(dists.len(), 0);\n        assert_eq!(errs.len(), 0);\n\n        let mut v = store.fetch_tracks(&[0]);\n\n        let v = v.pop().unwrap();\n        let (dists, errs) = store.foreign_track_distances(vec![v.clone()], 0, false);\n        let dists = dists.all();\n        let errs = errs.all();\n\n        assert_eq!(dists.len(), 1);\n        assert_eq!(dists[0].to, 1);\n        assert!(dists[0].feature_distance.is_some());\n        assert!((dists[0].feature_distance.as_ref().unwrap() - 2.0_f32.sqrt()).abs() < EPS);\n        assert!(errs.is_empty());\n\n        // make it incompatible across the attributes\n        thread::sleep(Duration::from_millis(10));\n        let mut v = v;\n        v.attributes.end_time = current_time_ms();\n\n        let (dists, errs) = store.foreign_track_distances(vec![v.clone()], 0, false);\n        let dists = dists.all();\n        let errs = errs.all();\n\n        assert_eq!(dists.len(), 0);\n        assert_eq!(errs.len(), 0);\n\n        thread::sleep(Duration::from_millis(10));\n        store.add(\n            1,\n            0,\n            Some(0.7),\n            Some(vec2(1.0, 1.0)),\n            time_attrs_current_ts(),\n        )?;\n\n        let mut v = v;\n        v.attributes.end_time = store.get_store(1).get(&1).unwrap().attributes.start_time - 1;\n        let (dists, errs) = store.foreign_track_distances(vec![v], 0, false);\n        let dists = dists.all();\n        let errs = errs.all();\n\n        assert_eq!(dists.len(), 2);\n        assert_eq!(dists[0].to, 1);\n        assert!(dists[0].feature_distance.is_some());\n        assert!((dists[0].feature_distance.as_ref().unwrap() - 2.0_f32.sqrt()).abs() < EPS);\n        assert!((dists[1].feature_distance.as_ref().unwrap() - 1.0).abs() < EPS);\n        assert!(errs.is_empty());\n\n        Ok(())\n    }\n\n    #[test]\n    fn baked_similarity() -> Result<()> {\n        let mut store = TrackStore::new(\n            TimeMetric { max_length: 20 },\n            TimeAttrs {\n                baked_period: 10,\n                ..Default::default()\n            },\n            NoopNotifier,\n            2,\n        );\n        thread::sleep(Duration::from_millis(1));\n        store.add(\n            1,\n            0,\n            Some(0.9),\n            Some(vec2(0.0, 1.0)),\n            time_attrs_current_ts(),\n        )?;\n\n        let mut ext_track = Track::new(\n            2,\n            TimeMetric { max_length: 20 },\n            TimeAttrs {\n                baked_period: 10,\n                ..Default::default()\n            },\n            NoopNotifier,\n        );\n\n        //thread::sleep(Duration::from_millis(10));\n        ext_track.add_observation(\n            0,\n            Some(0.8),\n            Some(vec2(0.66, 0.33)),\n            Some(TimeAttrUpdates {\n                time: current_time_ms(),\n            }),\n        )?;\n\n        let (dists, errs) = store.foreign_track_distances(vec![ext_track.clone()], 0, true);\n        let dists = dists.all();\n        let errs = errs.all();\n\n        assert!(dists.is_empty());\n        assert!(errs.is_empty());\n        thread::sleep(Duration::from_millis(10));\n        store.add(\n            0,\n            0,\n            Some(0.9),\n            Some(vec2(0.0, 1.0)),\n            time_attrs_current_ts(),\n        )?;\n\n        let (dists, errs) = store.owned_track_distances(&[1], 0, true);\n        let dists = dists.all();\n        let errs = errs.all();\n\n        dbg!(&dists);\n        assert!(dists.is_empty());\n        assert!(errs.is_empty());\n\n        Ok(())\n    }\n\n    #[test]\n    fn all_similarity() -> Result<()> {\n        let mut ext_track = Track::new(\n            2,\n            TimeMetric { max_length: 20 },\n            TimeAttrs {\n                baked_period: 10,\n                ..Default::default()\n            },\n            NoopNotifier,\n        );\n\n        thread::sleep(Duration::from_millis(1));\n        ext_track.add_observation(\n            0,\n            Some(0.8),\n            Some(vec2(0.66, 0.33)),\n            time_attrs_current_ts(),\n        )?;\n\n        let mut store = TrackStore::new(\n            TimeMetric { max_length: 20 },\n            TimeAttrs {\n                baked_period: 10,\n                ..Default::default()\n            },\n            NoopNotifier,\n            2,\n        );\n        thread::sleep(Duration::from_millis(1));\n        store.add(\n            1,\n            0,\n            Some(0.9),\n            Some(vec2(0.0, 1.0)),\n            time_attrs_current_ts(),\n        )?;\n\n        let (dists, errs) = store.foreign_track_distances(vec![ext_track.clone()], 0, false);\n        let dists = dists.all();\n        let errs = errs.all();\n\n        assert_eq!(dists.len(), 1);\n        assert!(errs.is_empty());\n\n        thread::sleep(Duration::from_millis(1));\n        store.add(\n            3,\n            0,\n            Some(0.9),\n            Some(vec2(0.0, 1.0)),\n            time_attrs_current_ts(),\n        )?;\n\n        let (dists, errs) = store.owned_track_distances(&[1], 0, false);\n        let dists = dists.all();\n        let errs = errs.all();\n\n        assert_eq!(dists.len(), 1);\n        assert!(errs.is_empty());\n\n        Ok(())\n    }\n\n    #[test]\n    fn add_track_ok() -> Result<()> {\n        let mut ext_track = Track::new(\n            2,\n            TimeMetric { max_length: 20 },\n            TimeAttrs {\n                baked_period: 10,\n                ..Default::default()\n            },\n            NoopNotifier,\n        );\n\n        thread::sleep(Duration::from_millis(1));\n        ext_track.add_observation(\n            0,\n            Some(0.8),\n            Some(vec2(0.66, 0.33)),\n            time_attrs_current_ts(),\n        )?;\n\n        let mut store = TrackStore::new(\n            TimeMetric { max_length: 20 },\n            TimeAttrs {\n                baked_period: 10,\n                ..Default::default()\n            },\n            NoopNotifier,\n            1,\n        );\n        thread::sleep(Duration::from_millis(1));\n        store.add(\n            0,\n            0,\n            Some(0.9),\n            Some(vec2(0.0, 1.0)),\n            time_attrs_current_ts(),\n        )?;\n\n        store.add_track(ext_track)?;\n        Ok(())\n    }\n\n    #[test]\n    fn add_track_dup_id() -> Result<()> {\n        let mut ext_track = Track::new(\n            0, // duplicate track id\n            TimeMetric { max_length: 20 },\n            TimeAttrs {\n                baked_period: 10,\n                ..Default::default()\n            },\n            NoopNotifier,\n        );\n\n        thread::sleep(Duration::from_millis(1));\n        ext_track.add_observation(\n            0,\n            Some(0.8),\n            Some(vec2(0.66, 0.33)),\n            time_attrs_current_ts(),\n        )?;\n\n        let mut store = TrackStore::new(\n            TimeMetric { max_length: 20 },\n            TimeAttrs {\n                baked_period: 10,\n                ..Default::default()\n            },\n            NoopNotifier,\n            1,\n        );\n        thread::sleep(Duration::from_millis(1));\n        store.add(\n            0,\n            0,\n            Some(0.9),\n            Some(vec2(0.0, 1.0)),\n            time_attrs_current_ts(),\n        )?;\n\n        assert!(store.add_track(ext_track).is_err());\n\n        Ok(())\n    }\n\n    #[test]\n    fn merge_ext_tracks() -> Result<()> {\n        let mut ext_track = Track::new(\n            2,\n            TimeMetric { max_length: 20 },\n            TimeAttrs {\n                baked_period: 10,\n                ..Default::default()\n            },\n            NoopNotifier,\n        );\n\n        thread::sleep(Duration::from_millis(1));\n        ext_track.add_observation(\n            0,\n            Some(0.8),\n            Some(vec2(0.66, 0.33)),\n            time_attrs_current_ts(),\n        )?;\n\n        ext_track.add_observation(\n            1,\n            Some(0.8),\n            Some(vec2(0.65, 0.33)),\n            time_attrs_current_ts(),\n        )?;\n\n        let mut store = TrackStore::new(\n            TimeMetric { max_length: 20 },\n            TimeAttrs {\n                baked_period: 10,\n                ..Default::default()\n            },\n            NoopNotifier,\n            1,\n        );\n        thread::sleep(Duration::from_millis(1));\n        store.add(\n            0,\n            0,\n            Some(0.9),\n            Some(vec2(0.0, 1.0)),\n            time_attrs_current_ts(),\n        )?;\n\n        let res = store.merge_external(0, &ext_track, Some(&[0]), true);\n        assert!(res.is_ok());\n        let classes = store.get_store(0).get(&0).unwrap().get_feature_classes();\n        assert_eq!(classes, vec![0]);\n\n        let res = store.merge_external(0, &ext_track, None, true);\n        assert!(res.is_ok());\n        let mut classes = store.get_store(0).get(&0).unwrap().get_feature_classes();\n        classes.sort();\n        assert_eq!(classes, vec![0, 1]);\n\n        Ok(())\n    }\n\n    #[test]\n    fn merge_own_tracks() -> Result<()> {\n        let mut store = TrackStore::new(\n            TimeMetric { max_length: 20 },\n            TimeAttrs {\n                baked_period: 10,\n                ..Default::default()\n            },\n            NoopNotifier,\n            1,\n        );\n        thread::sleep(Duration::from_millis(1));\n        store.add(\n            0,\n            0,\n            Some(0.9),\n            Some(vec2(0.0, 1.0)),\n            time_attrs_current_ts(),\n        )?;\n\n        thread::sleep(Duration::from_millis(1));\n        store.add(\n            1,\n            1,\n            Some(0.9),\n            Some(vec2(0.0, 1.0)),\n            time_attrs_current_ts(),\n        )?;\n\n        let res = store.merge_owned(0, 1, None, false, true);\n        assert!(res.is_ok());\n\n        let res = store.merge_owned(0, 1, None, true, true);\n        if let Ok(Some(t)) = res {\n            assert_eq!(t.track_id, 1);\n        } else {\n            unreachable!();\n        }\n\n        Ok(())\n    }\n\n    #[test]\n    fn lookup() {\n        #[derive(Default, Clone)]\n        struct Lookup;\n        impl LookupRequest<LookupAttrs, f32> for Lookup {\n            fn lookup(\n                &self,\n                _attributes: &LookupAttrs,\n                _observations: &ObservationsDb<f32>,\n                _merge_history: &[u64],\n            ) -> bool {\n                true\n            }\n        }\n\n        #[derive(Debug, Clone, Default)]\n        struct LookupAttrs;\n\n        #[derive(Default, Clone)]\n        pub struct LookupAttributeUpdate;\n\n        impl TrackAttributesUpdate<LookupAttrs> for LookupAttributeUpdate {\n            fn apply(&self, _attrs: &mut LookupAttrs) -> Result<()> {\n                Ok(())\n            }\n        }\n\n        impl TrackAttributes<LookupAttrs, f32> for LookupAttrs {\n            type Update = LookupAttributeUpdate;\n            type Lookup = Lookup;\n\n            fn compatible(&self, _other: &LookupAttrs) -> bool {\n                true\n            }\n\n            fn merge(&mut self, _other: &LookupAttrs) -> Result<()> {\n                Ok(())\n            }\n\n            fn baked(&self, _observations: &ObservationsDb<f32>) -> Result<TrackStatus> {\n                Ok(TrackStatus::Ready)\n            }\n        }\n\n        #[derive(Default, Clone)]\n        pub struct LookupMetric;\n\n        impl ObservationMetric<LookupAttrs, f32> for LookupMetric {\n            fn metric(&self, mq: &MetricQuery<LookupAttrs, f32>) -> MetricOutput<f32> {\n                let (e1, e2) = (mq.candidate_observation, mq.track_observation);\n                Some((\n                    f32::calculate_metric_object(&e1.attr().as_ref(), &e2.attr().as_ref()),\n                    match (e1.feature().as_ref(), e2.feature().as_ref()) {\n                        (Some(x), Some(y)) => Some(euclidean(x, y)),\n                        _ => None,\n                    },\n                ))\n            }\n\n            fn optimize(\n                &mut self,\n                _feature_class: u64,\n                _merge_history: &[u64],\n                _attrs: &mut LookupAttrs,\n                _features: &mut Vec<Observation<f32>>,\n                _prev_length: usize,\n                _is_merge: bool,\n            ) -> Result<()> {\n                Ok(())\n            }\n        }\n\n        let mut store = TrackStoreBuilder::default()\n            .metric(LookupMetric::default())\n            .default_attributes(LookupAttrs::default())\n            .notifier(NoopNotifier)\n            .build();\n        const N: usize = 10;\n        for _ in 0..N {\n            let t = store.new_track_random_id().build().unwrap();\n            store.add_track(t).unwrap();\n        }\n        let res = store.lookup(Lookup);\n        assert_eq!(res.len(), N);\n    }\n}\n"
  },
  {
    "path": "src/track/store/track_distance.rs",
    "content": "use crate::store::{ObservationMetricErr, Results};\nuse crate::track::{ObservationAttributes, ObservationMetricOk};\nuse crossbeam::channel::Receiver;\nuse std::vec::IntoIter;\n\n/// Represents the response from the track distance computation.\n///\ntrait TrackDistanceResponse<OA>: IntoIterator\nwhere\n    OA: ObservationAttributes,\n{\n    type Output;\n    fn get_all(&self) -> Vec<Self::Output> {\n        let mut results = Vec::new();\n        for _ in 0..self.count() {\n            let res = self.channel().recv().unwrap();\n            Self::extend(&mut results, self.elt(res));\n        }\n        results\n    }\n\n    fn count(&self) -> usize;\n    fn elt(&self, res: Results<OA>) -> Vec<Self::Output>;\n    fn extend(output: &mut Vec<Self::Output>, elt: Vec<Self::Output>);\n    fn channel(&self) -> &Receiver<Results<OA>>;\n}\n\n/// Represents the ok response from the track distance computation.\n///\npub struct TrackDistanceOk<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    count: usize,\n    channel: Receiver<Results<OA>>,\n}\n\npub struct TrackDistanceOkIterator<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    iterator_count: usize,\n    channel: Receiver<Results<OA>>,\n    current_chunk: IntoIter<ObservationMetricOk<OA>>,\n}\n\npub struct TrackDistanceErrIterator<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    iterator_count: usize,\n    channel: Receiver<Results<OA>>,\n    current_chunk: IntoIter<ObservationMetricErr<OA>>,\n}\n\nimpl<OA> TrackDistanceOk<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    pub fn all(self) -> Vec<ObservationMetricOk<OA>> {\n        self.get_all()\n    }\n\n    pub(crate) fn new(count: usize, channel: Receiver<Results<OA>>) -> Self {\n        Self { count, channel }\n    }\n}\n\n/// Represents the error response from the track distance computation.\n///\npub struct TrackDistanceErr<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    count: usize,\n    channel: Receiver<Results<OA>>,\n}\n\nimpl<OA> TrackDistanceErr<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    pub fn all(self) -> Vec<ObservationMetricErr<OA>> {\n        self.get_all()\n    }\n\n    pub(crate) fn new(count: usize, channel: Receiver<Results<OA>>) -> Self {\n        Self { count, channel }\n    }\n}\n\nimpl<OA> Iterator for TrackDistanceOkIterator<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    type Item = ObservationMetricOk<OA>;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        loop {\n            let elt = self.current_chunk.next();\n            if elt.is_some() {\n                return elt;\n            } else if self.iterator_count == 0 {\n                return None;\n            } else {\n                self.iterator_count -= 1;\n                let elt = self.channel.recv().unwrap();\n                match elt {\n                    Results::DistanceOk(elt) => {\n                        self.current_chunk = elt.into_iter();\n                    }\n                    _ => unreachable!(),\n                }\n            }\n        }\n    }\n}\n\nimpl<OA> Iterator for TrackDistanceErrIterator<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    type Item = ObservationMetricErr<OA>;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        loop {\n            let elt = self.current_chunk.next();\n            if elt.is_some() {\n                return elt;\n            } else if self.iterator_count == 0 {\n                return None;\n            } else {\n                self.iterator_count -= 1;\n                let elt = self.channel.recv().unwrap();\n                match elt {\n                    Results::DistanceErr(elt) => {\n                        self.current_chunk = elt.into_iter();\n                    }\n                    _ => unreachable!(),\n                }\n            }\n        }\n    }\n}\n\nimpl<OA> IntoIterator for TrackDistanceOk<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    type Item = ObservationMetricOk<OA>;\n    type IntoIter = TrackDistanceOkIterator<OA>;\n\n    fn into_iter(self) -> Self::IntoIter {\n        TrackDistanceOkIterator {\n            iterator_count: self.count,\n            channel: self.channel,\n            current_chunk: Vec::default().into_iter(),\n        }\n    }\n}\n\nimpl<OA> IntoIterator for TrackDistanceErr<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    type Item = ObservationMetricErr<OA>;\n    type IntoIter = TrackDistanceErrIterator<OA>;\n\n    fn into_iter(self) -> Self::IntoIter {\n        TrackDistanceErrIterator {\n            iterator_count: self.count,\n            channel: self.channel,\n            current_chunk: Vec::default().into_iter(),\n        }\n    }\n}\n\nimpl<OA> TrackDistanceResponse<OA> for TrackDistanceOk<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    type Output = ObservationMetricOk<OA>;\n\n    fn count(&self) -> usize {\n        self.count\n    }\n\n    fn elt(&self, res: Results<OA>) -> Vec<Self::Output> {\n        match res {\n            Results::DistanceOk(r) => r,\n            _ => {\n                unreachable!();\n            }\n        }\n    }\n\n    fn extend(output: &mut Vec<Self::Output>, elt: Vec<Self::Output>) {\n        output.extend_from_slice(&elt);\n    }\n\n    fn channel(&self) -> &Receiver<Results<OA>> {\n        &self.channel\n    }\n}\n\nimpl<OA> TrackDistanceResponse<OA> for TrackDistanceErr<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    type Output = ObservationMetricErr<OA>;\n\n    fn count(&self) -> usize {\n        self.count\n    }\n\n    fn elt(&self, res: Results<OA>) -> Vec<Self::Output> {\n        match res {\n            Results::DistanceErr(r) => r,\n            _ => {\n                unreachable!();\n            }\n        }\n    }\n    fn extend(output: &mut Vec<Self::Output>, elt: Vec<Self::Output>) {\n        output.extend(elt);\n    }\n\n    fn channel(&self) -> &Receiver<Results<OA>> {\n        &self.channel\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::distance::euclidean;\n    use crate::examples::vec2;\n    use crate::prelude::{NoopNotifier, ObservationBuilder, TrackStoreBuilder};\n    use crate::track::{\n        MetricOutput, MetricQuery, NoopLookup, Observation, ObservationAttributes,\n        ObservationMetric, ObservationsDb, Track, TrackAttributes, TrackAttributesUpdate,\n        TrackStatus,\n    };\n    use anyhow::Result;\n\n    #[derive(Debug, Clone, Default)]\n    struct MockAttrs;\n\n    #[derive(Default, Clone)]\n    pub struct MockAttrsUpdate;\n\n    impl TrackAttributesUpdate<MockAttrs> for MockAttrsUpdate {\n        fn apply(&self, _attrs: &mut MockAttrs) -> Result<()> {\n            Ok(())\n        }\n    }\n\n    impl TrackAttributes<MockAttrs, f32> for MockAttrs {\n        type Update = MockAttrsUpdate;\n        type Lookup = NoopLookup<MockAttrs, f32>;\n\n        fn compatible(&self, _other: &MockAttrs) -> bool {\n            true\n        }\n\n        fn merge(&mut self, _other: &MockAttrs) -> Result<()> {\n            Ok(())\n        }\n\n        fn baked(&self, _observations: &ObservationsDb<f32>) -> Result<TrackStatus> {\n            Ok(TrackStatus::Ready)\n        }\n    }\n\n    #[derive(Default, Clone)]\n    pub struct MockMetric;\n\n    impl ObservationMetric<MockAttrs, f32> for MockMetric {\n        fn metric(&self, mq: &MetricQuery<MockAttrs, f32>) -> MetricOutput<f32> {\n            let (e1, e2) = (mq.candidate_observation, mq.track_observation);\n            Some((\n                f32::calculate_metric_object(&e1.attr().as_ref(), &e2.attr().as_ref()),\n                match (e1.feature().as_ref(), e2.feature().as_ref()) {\n                    (Some(x), Some(y)) => Some(euclidean(x, y)),\n                    _ => None,\n                },\n            ))\n        }\n\n        fn optimize(\n            &mut self,\n            _feature_class: u64,\n            _merge_history: &[u64],\n            _attrs: &mut MockAttrs,\n            _features: &mut Vec<Observation<f32>>,\n            _prev_length: usize,\n            _is_merge: bool,\n        ) -> Result<()> {\n            Ok(())\n        }\n    }\n\n    #[test]\n    fn result_iterators() {\n        let mut store = TrackStoreBuilder::default()\n            .default_attributes(MockAttrs)\n            .metric(MockMetric)\n            .notifier(NoopNotifier)\n            .build();\n        const N: usize = 10000;\n        for _ in 0..N {\n            let t = store\n                .new_track_random_id()\n                .observation(\n                    ObservationBuilder::new(0)\n                        .observation(vec2(1.0, 0.0))\n                        .build(),\n                )\n                .build()\n                .unwrap();\n            store.add_track(t).unwrap();\n        }\n\n        let t1: Track<MockAttrs, MockMetric, f32> = store\n            .new_track_random_id()\n            .observation(\n                ObservationBuilder::new(0)\n                    .observation(vec2(0.0, 0.0))\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        let t2: Track<MockAttrs, MockMetric, f32> = store\n            .new_track_random_id()\n            .observation(\n                ObservationBuilder::new(0)\n                    .observation(vec2(-1.0, 0.0))\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        let (dists, errs) = store.foreign_track_distances(vec![t1.clone(), t2.clone()], 0, false);\n        assert!(errs.all().is_empty());\n        assert_eq!(dists.all().len(), 2 * N);\n\n        let (dists, errs) = store.foreign_track_distances(vec![t1.clone(), t2.clone()], 0, false);\n        assert!(errs.into_iter().next().is_none());\n        assert_eq!(dists.into_iter().count(), 2 * N);\n\n        let (dists, errs) = store.foreign_track_distances(vec![t1, t2], 0, false);\n        drop(store);\n        drop(dists);\n        drop(errs);\n    }\n}\n"
  },
  {
    "path": "src/track/store.rs",
    "content": "pub mod builder;\nmod store_tests;\npub mod track_distance;\n\nuse crate::prelude::TrackBuilder;\nuse crate::track::notify::{ChangeNotifier, NoopNotifier};\nuse crate::track::{\n    Feature, Observation, ObservationAttributes, ObservationMetric, ObservationMetricOk, Track,\n    TrackAttributes, TrackStatus,\n};\nuse crate::Errors;\nuse anyhow::Result;\nuse crossbeam::channel::{Receiver, Sender};\nuse log::{error, warn};\nuse std::collections::HashMap;\nuse std::sync::{Arc, Mutex, MutexGuard};\nuse std::thread::JoinHandle;\nuse std::{mem, thread};\nuse track_distance::{TrackDistanceErr, TrackDistanceOk};\n\n#[derive(Clone)]\nenum Commands<TA, M, OA, N>\nwhere\n    TA: TrackAttributes<TA, OA>,\n    M: ObservationMetric<TA, OA>,\n    OA: ObservationAttributes,\n    N: ChangeNotifier,\n{\n    Drop(Sender<Results<OA>>),\n    FindBaked(Sender<Results<OA>>),\n    Distances(\n        Arc<Track<TA, M, OA, N>>,\n        u64,\n        bool,\n        Sender<Results<OA>>,\n        Sender<Results<OA>>,\n    ),\n    Lookup(TA::Lookup, Sender<Results<OA>>),\n    Merge(\n        u64,\n        Track<TA, M, OA, N>,\n        Vec<u64>,\n        bool,\n        Option<Sender<Results<OA>>>,\n    ),\n}\n\n/// The type that provides lock-ed access to certain shard store\n///\npub type StoreMutexGuard<'a, TA, M, FA, N> = MutexGuard<'a, HashMap<u64, Track<TA, M, FA, N>>>;\n\n/// The type that provides the initial track that was in the store before it was merged into\n/// target track\n///\npub type OwnedMergeResult<TA, M, FA, N> = Result<Option<Track<TA, M, FA, N>>>;\n\n#[derive(Debug)]\npub enum Results<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    DistanceOk(Vec<ObservationMetricOk<OA>>),\n    DistanceErr(Vec<ObservationMetricErr<OA>>),\n    BakedStatus(Vec<(u64, Result<TrackStatus>)>),\n    Dropped,\n    MergeResult(Result<()>),\n}\n\n/// Merge future result\n///\npub struct FutureMergeResponse<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    receiver: Receiver<Results<OA>>,\n    _sender: Sender<Results<OA>>,\n}\n\nimpl<OA> FutureMergeResponse<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    pub fn get(&self) -> Result<()> {\n        let res = self.receiver.recv();\n        if res.is_err() {\n            res?;\n            unreachable!();\n        }\n        Ok(())\n    }\n\n    pub fn is_ready(&self) -> bool {\n        !self.receiver.is_empty()\n    }\n}\n\n/// Auxiliary type to express distance calculation errors\npub type ObservationMetricErr<OA> = Result<Vec<ObservationMetricOk<OA>>>;\n\n/// Track store container with accelerated similarity operations.\n///\n/// TrackStore is implemented for certain attributes (A), attribute update (U), and metric (M), so\n/// it handles only such objects. You cannot store tracks with different attributes within the same\n/// TrackStore.\n///\n/// The metric is also defined for TrackStore, however Metric implementation may have various metric\n/// calculation options for concrete feature classes. E.g. FEAT1 may be calculated with Euclid distance,\n/// while FEAT2 may be calculated with cosine. It is up to Metric implementor how the metric works.\n///\n/// TrackStore examples can be found at:\n/// [examples/*](https://github.com/insight-platform/Similari/blob/main/examples).\n///\npub struct TrackStore<TA, M, OA, N = NoopNotifier>\nwhere\n    TA: TrackAttributes<TA, OA>,\n    M: ObservationMetric<TA, OA>,\n    OA: ObservationAttributes,\n    N: ChangeNotifier,\n{\n    default_attributes: TA,\n    metric: M,\n    notifier: N,\n    num_shards: usize,\n    #[allow(clippy::type_complexity)]\n    stores: Arc<Vec<Mutex<HashMap<u64, Track<TA, M, OA, N>>>>>,\n    // receiver: Receiver<Results<FA>>,\n    #[allow(clippy::type_complexity)]\n    executors: Vec<(Sender<Commands<TA, M, OA, N>>, JoinHandle<()>)>,\n}\n\nimpl<TA, M, OA, N> Drop for TrackStore<TA, M, OA, N>\nwhere\n    TA: TrackAttributes<TA, OA>,\n    M: ObservationMetric<TA, OA>,\n    OA: ObservationAttributes,\n    N: ChangeNotifier,\n{\n    fn drop(&mut self) {\n        let executors = mem::take(&mut self.executors);\n        let (results_sender, results_receiver) = crossbeam::channel::unbounded();\n        for (s, j) in executors {\n            s.send(Commands::Drop(results_sender.clone())).unwrap();\n            let res = results_receiver.recv().unwrap();\n            match res {\n                Results::Dropped => {\n                    j.join().unwrap();\n                    drop(s);\n                }\n                _ => {\n                    unreachable!();\n                }\n            }\n        }\n    }\n}\n\n/// The basic implementation should fit to most of needs.\n///\nimpl<TA, M, OA, N> TrackStore<TA, M, OA, N>\nwhere\n    TA: TrackAttributes<TA, OA>,\n    M: ObservationMetric<TA, OA>,\n    OA: ObservationAttributes,\n    N: ChangeNotifier,\n{\n    #[allow(clippy::type_complexity)]\n    fn handle_store_ops(\n        stores: Arc<Vec<Mutex<HashMap<u64, Track<TA, M, OA, N>>>>>,\n        store_id: usize,\n        commands_receiver: Receiver<Commands<TA, M, OA, N>>,\n    ) {\n        let store = stores.get(store_id).unwrap();\n        while let Ok(c) = commands_receiver.recv() {\n            match c {\n                Commands::Drop(channel) => {\n                    let _r = channel.send(Results::Dropped);\n                    return;\n                }\n                Commands::FindBaked(channel) => {\n                    let baked = store\n                        .lock()\n                        .unwrap()\n                        .iter()\n                        .flat_map(|(track_id, track)| {\n                            match track.get_attributes().baked(&track.observations) {\n                                Ok(status) => match status {\n                                    TrackStatus::Pending => None,\n                                    other => Some((*track_id, Ok(other))),\n                                },\n                                Err(e) => Some((*track_id, Err(e))),\n                            }\n                        })\n                        .collect();\n                    let r = channel.send(Results::BakedStatus(baked));\n                    if let Err(_e) = r {\n                        return;\n                    }\n                }\n                Commands::Distances(track, feature_class, only_baked, channel_ok, channel_err) => {\n                    let mut capacity = 0;\n                    let res = store\n                        .lock()\n                        .unwrap()\n                        .iter()\n                        .flat_map(|(_, other)| {\n                            if track.track_id == other.track_id {\n                                return None;\n                            }\n\n                            if !only_baked {\n                                let dists = track.distances(other, feature_class);\n                                match dists {\n                                    Ok(dists) => {\n                                        capacity += dists.len();\n                                        Some(Ok(track.metric.postprocess_distances(dists)))\n                                    }\n                                    Err(e) => match e.downcast_ref::<Errors>() {\n                                        Some(Errors::IncompatibleAttributes) => None,\n                                        _ => Some(Err(e)),\n                                    },\n                                }\n                            } else {\n                                match other.get_attributes().baked(&other.observations) {\n                                    Ok(TrackStatus::Ready) => {\n                                        let dists = track.distances(other, feature_class);\n                                        match dists {\n                                            Ok(dists) => {\n                                                capacity += dists.len();\n                                                Some(Ok(track.metric.postprocess_distances(dists)))\n                                            }\n                                            Err(e) => match e.downcast_ref::<Errors>() {\n                                                Some(Errors::IncompatibleAttributes) => None,\n                                                _ => Some(Err(e)),\n                                            },\n                                        }\n                                    }\n                                    _ => None,\n                                }\n                            }\n                        })\n                        .collect::<Vec<_>>();\n\n                    let mut distances = Vec::with_capacity(capacity);\n                    let mut errors = Vec::new();\n\n                    for r in res {\n                        match r {\n                            Ok(dists) => {\n                                distances.extend_from_slice(&dists);\n                            }\n                            e => errors.push(e),\n                        }\n                    }\n\n                    let r = channel_ok.send(Results::DistanceOk(distances));\n                    if let Err(e) = r {\n                        warn!(\"Unable to send data back to caller. Channel error: {:?}\", e);\n                    }\n\n                    let r = channel_err.send(Results::DistanceErr(errors));\n                    if let Err(e) = r {\n                        warn!(\"Unable to send data back to caller. Channel error: {:?}\", e);\n                    }\n                }\n                Commands::Merge(dest_id, src, classes, merge_history, channel_opt) => {\n                    let mut store = store.lock().unwrap();\n                    let dest = store.get_mut(&dest_id);\n\n                    let res = match dest {\n                        Some(dest) => {\n                            if dest_id == src.track_id {\n                                Err(Errors::SameTrackCalculation(dest_id).into())\n                            } else if !classes.is_empty() {\n                                dest.merge(&src, &classes, merge_history)\n                            } else {\n                                dest.merge(&src, &src.get_feature_classes(), merge_history)\n                            }\n                        }\n\n                        None => Err(Errors::TrackNotFound(dest_id).into()),\n                    };\n\n                    if let Some(channel) = channel_opt {\n                        if let Err(send_res) = channel.send(Results::MergeResult(res)) {\n                            warn!(\"Receiver channel was dropped before the data sent into it. Error is: {:?}\", send_res);\n                        }\n                    }\n                }\n                Commands::Lookup(q, channel) => {\n                    let store = store.lock().unwrap();\n                    let res = channel.send(Results::BakedStatus(\n                        store\n                            .values()\n                            .filter(|x| x.lookup(&q))\n                            .map(|x| (x.track_id, x.get_attributes().baked(&x.observations)))\n                            .collect(),\n                    ));\n\n                    if let Err(send_res) = res {\n                        warn!(\"Receiver channel was dropped before the data sent into it. Error is: {:?}\", send_res);\n                    }\n                }\n            }\n        }\n    }\n\n    /// Constructor method\n    ///\n    /// When you construct track store you may pass two initializer objects:\n    /// * Metric\n    /// * Attributes\n    ///\n    /// They will be used upon track creation to initialize per-track metric and attributes.\n    /// They are cloned when a certain track is created.\n    ///\n    /// If `None` is passed, `Default` initializers are used.\n    ///\n    pub fn new(metric: M, default_attributes: TA, notifier: N, shards: usize) -> Self {\n        let stores = Arc::new(\n            (0..shards)\n                .map(|_| Mutex::new(HashMap::default()))\n                .collect::<Vec<_>>(),\n        );\n        let my_stores = stores.clone();\n\n        Self {\n            //receiver: results_receiver,\n            num_shards: shards,\n            notifier,\n            default_attributes,\n            metric,\n            stores: my_stores,\n            executors: {\n                (0..shards)\n                    .map(|s| {\n                        let (commands_sender, commands_receiver) = crossbeam::channel::unbounded();\n                        let stores = stores.clone();\n                        let thread = thread::spawn(move || {\n                            Self::handle_store_ops(stores, s, commands_receiver);\n                        });\n                        (commands_sender, thread)\n                    })\n                    .collect()\n            },\n        }\n    }\n\n    /// Method is used to find ready to use tracks within the store.\n    ///\n    /// The search is parallelized with Rayon. The results returned for tracks with\n    /// * `TrackStatus::Ready`,\n    /// * `TrackStatus::Wasted` or\n    /// * `Err(e)`\n    ///\n    pub fn find_usable(&mut self) -> Vec<(u64, Result<TrackStatus>)> {\n        let mut results = Vec::with_capacity(self.shard_stats().iter().sum());\n        let (results_sender, results_receiver) = crossbeam::channel::unbounded();\n        for (cmd, _) in &mut self.executors {\n            cmd.send(Commands::FindBaked(results_sender.clone()))\n                .unwrap();\n        }\n        for (_, _) in &mut self.executors {\n            let res = results_receiver.recv().unwrap();\n            match res {\n                Results::BakedStatus(r) => {\n                    results.extend(r);\n                }\n                _ => {\n                    unreachable!();\n                }\n            }\n        }\n        results\n    }\n\n    /// Counts of objects per every store shard\n    ///\n    pub fn shard_stats(&self) -> Vec<usize> {\n        let mut result = Vec::new();\n        for s in self.stores.iter() {\n            result.push(s.lock().unwrap().len());\n        }\n        result\n    }\n\n    /// Pulls (and removes) requested tracks from the store.\n    ///\n    pub fn fetch_tracks(&mut self, tracks: &[u64]) -> Vec<Track<TA, M, OA, N>> {\n        let mut res = Vec::default();\n        for track_id in tracks {\n            let mut tracks_shard = self.get_store(*track_id as usize);\n            if let Some(t) = tracks_shard.remove(track_id) {\n                res.push(t);\n            }\n        }\n        res\n    }\n\n    /// Returns track builder object that can build new track compatible with the storage.\n    ///\n    /// Attributes, metric, notifier are cloned from store\n    ///\n    pub fn new_track(&self, track_id: u64) -> TrackBuilder<TA, M, OA, N> {\n        TrackBuilder::new(track_id)\n            .metric(self.metric.clone())\n            .attributes(self.default_attributes.clone())\n            .notifier(self.notifier.clone())\n    }\n\n    /// Returns track builder object that can build new track compatible with the storage.\n    ///\n    /// Attributes, metric, notifier are cloned from store\n    ///\n    pub fn new_track_random_id(&self) -> TrackBuilder<TA, M, OA, N> {\n        TrackBuilder::default()\n            .metric(self.metric.clone())\n            .attributes(self.default_attributes.clone())\n            .notifier(self.notifier.clone())\n    }\n\n    /// Calculates distances for external track (not in track store) to all tracks in DB which are\n    /// allowed.\n    ///\n    /// # Arguments\n    /// * `tracks` - batch external tracks that is used as distance subjects\n    /// * `feature_class` - what feature to use for distance calculation\n    /// * `only_baked` - calculate distances only across the tracks that have `TrackBakingStatus::Ready` status\n    ///\n    pub fn foreign_track_distances(\n        &mut self,\n        tracks: Vec<Track<TA, M, OA, N>>,\n        feature_class: u64,\n        only_baked: bool,\n    ) -> (TrackDistanceOk<OA>, TrackDistanceErr<OA>) {\n        let tracks_count = tracks.len();\n\n        let (results_ok_sender, results_ok_receiver) = crossbeam::channel::unbounded();\n        let (results_err_sender, results_err_receiver) = crossbeam::channel::unbounded();\n\n        for track in tracks {\n            let track = Arc::new(track);\n            for (cmd, _) in &mut self.executors {\n                cmd.send(Commands::Distances(\n                    track.clone(),\n                    feature_class,\n                    only_baked,\n                    results_ok_sender.clone(),\n                    results_err_sender.clone(),\n                ))\n                .unwrap();\n            }\n        }\n\n        let count = self.executors.len() * tracks_count;\n\n        (\n            TrackDistanceOk::new(count, results_ok_receiver),\n            TrackDistanceErr::new(count, results_err_receiver),\n        )\n    }\n\n    /// Calculates track distances for a track within the store\n    ///\n    /// The distances for (self, self) are not calculated.\n    ///\n    /// # Arguments\n    /// * `tracks` - batch of tracks that are used as distance subjects\n    /// * `feature_class` - what feature to use for distance calculation\n    /// * `only_baked` - calculate distances only across the tracks that have `TrackBakingStatus::Ready` status\n    ///\n    pub fn owned_track_distances(\n        &mut self,\n        tracks: &[u64],\n        feature_class: u64,\n        only_baked: bool,\n    ) -> (TrackDistanceOk<OA>, TrackDistanceErr<OA>) {\n        let tracks_vec = self.fetch_tracks(tracks);\n\n        let res = self.foreign_track_distances(tracks_vec.clone(), feature_class, only_baked);\n\n        for t in tracks_vec {\n            self.add_track(t).unwrap();\n        }\n\n        res\n    }\n\n    /// returns the store shard for id\n    ///\n    pub fn get_store(&self, id: usize) -> StoreMutexGuard<'_, TA, M, OA, N> {\n        let store_id = id % self.num_shards;\n        self.stores.as_ref().get(store_id).unwrap().lock().unwrap()\n    }\n\n    /// returns the store shard for id\n    ///\n    pub fn get_executor(&self, id: usize) -> usize {\n        id % self.num_shards\n    }\n\n    /// Adds external track into storage\n    ///\n    /// # Arguments\n    /// * `track` - track compatible with the storage.\n    ///\n    /// # Returns\n    /// * `Ok(track_id)` if added\n    /// * `Err(Errors::DuplicateTrackId(track_id))` if failed to add\n    ///\n    pub fn add_track(&mut self, track: Track<TA, M, OA, N>) -> Result<u64> {\n        let track_id = track.track_id;\n        let mut store = self.get_store(track_id as usize);\n        if store.get(&track_id).is_none() {\n            store.insert(track_id, track);\n            Ok(track_id)\n        } else {\n            Err(Errors::DuplicateTrackId(track_id).into())\n        }\n    }\n\n    /// Injects new feature observation for feature class into track\n    ///\n    /// # Arguments\n    /// * `track_id` - unique Id of the track within the store\n    /// * `feature_class` - where the observation will be placed within the track\n    /// * `feature_attribute` - feature quality parameter\n    /// * `feature` - feature observation\n    /// * `attributes_update` - the update to be applied to attributes upon the feature insert\n    ///\n    pub fn add(\n        &mut self,\n        track_id: u64,\n        feature_class: u64,\n        feature_attribute: Option<OA>,\n        feature: Option<Feature>,\n        attributes_update: Option<TA::Update>,\n    ) -> Result<()> {\n        let mut tracks = self.get_store(track_id as usize);\n        #[allow(clippy::significant_drop_in_scrutinee)]\n        match tracks.get_mut(&track_id) {\n            None => {\n                let mut t = Track {\n                    notifier: self.notifier.clone(),\n                    attributes: self.default_attributes.clone(),\n                    track_id,\n                    observations: HashMap::from([(\n                        feature_class,\n                        vec![Observation(feature_attribute, feature)],\n                    )]),\n                    metric: self.metric.clone(),\n                    merge_history: vec![track_id],\n                };\n                if let Some(attributes_update) = &attributes_update {\n                    t.update_attributes(attributes_update)?;\n                }\n\n                tracks.insert(track_id, t);\n            }\n            Some(track) => {\n                track.add_observation(\n                    feature_class,\n                    feature_attribute,\n                    feature,\n                    attributes_update,\n                )?;\n            }\n        }\n        Ok(())\n    }\n\n    /// Merge store owned tracks\n    ///\n    /// # Arguments\n    /// * `dest_id` - identifier of destination track\n    /// * `src_id` - identifier of source track\n    /// * `classes` - optional list of classes to merge (otherwise all defined in src are merged into dest)\n    /// * `remove_src_if_ok` - whether remove source track from store if merge completed or not\n    /// * `merge_history` - configures whether merge history is built upon track merging.\n    ///\n    /// # Return\n    /// * `Ok(Old_Source_Track)` - merge was successful\n    /// * `Err(e)` - merge met problems\n    ///\n    pub fn merge_owned(\n        &mut self,\n        dest_id: u64,\n        src_id: u64,\n        classes: Option<&[u64]>,\n        remove_src_if_ok: bool,\n        merge_history: bool,\n    ) -> OwnedMergeResult<TA, M, OA, N> {\n        let mut src = self.fetch_tracks(&[src_id]);\n        if src.is_empty() {\n            return Err(Errors::TrackNotFound(src_id).into());\n        }\n        let src = src.pop().unwrap();\n        match self.merge_external(dest_id, &src, classes, merge_history) {\n            Ok(_) => {\n                if !remove_src_if_ok {\n                    self.add_track(src).unwrap();\n                    return Ok(None);\n                }\n                Ok(Some(src))\n            }\n            err => {\n                self.add_track(src).unwrap();\n                err?;\n                unreachable!();\n            }\n        }\n    }\n\n    /// Merge external track with destination stored in store without blocking\n    ///\n    /// # Arguments\n    /// * `dest_id` - identifier of destination track\n    /// * `src` - source track\n    /// * `classes` - optional list of classes to merge (otherwise all defined in src are merged into dest)\n    /// * `merge_history` - configures whether merge history is built upon track merging.\n    ///\n    /// # Return\n    /// * `Ok(FutureMergeResponse<FA>)` - future object that contains the receiver channel to gen the result when it is complete\n    /// * `Err(e)` - error occurred\n    ///\n    pub fn merge_external_noblock(\n        &mut self,\n        dest_id: u64,\n        src: Track<TA, M, OA, N>,\n        classes: Option<&[u64]>,\n        merge_history: bool,\n    ) -> Result<FutureMergeResponse<OA>> {\n        let (results_sender, results_receiver) = crossbeam::channel::bounded(1);\n        let executor_id = self.get_executor(dest_id as usize);\n        let (cmd, _) = self.executors.get_mut(executor_id).unwrap();\n\n        let command = Commands::Merge(\n            dest_id,\n            src,\n            if let Some(c) = classes {\n                c.to_vec()\n            } else {\n                vec![]\n            },\n            merge_history,\n            Some(results_sender.clone()),\n        );\n\n        let res = cmd.send(command);\n\n        if res.is_err() {\n            error!(\n                \"Executor {} unable to accept the command. Error is: {:?}\",\n                executor_id, &res\n            );\n            res?;\n            unreachable!();\n        }\n\n        Ok(FutureMergeResponse {\n            _sender: results_sender,\n            receiver: results_receiver,\n        })\n    }\n\n    /// Merge external track with destination stored in store\n    ///\n    /// # Arguments\n    /// * `dest_id` - identifier of destination track\n    /// * `src` - source track\n    /// * `classes` - optional list of classes to merge (otherwise all defined in src are merged into dest)\n    /// * `merge_history` - configures whether merge history is built upon track merging.\n    ///\n    /// # Return\n    /// * `Ok(())` - merge was successful\n    /// * `Err(e)` - merge met problems\n    ///\n    pub fn merge_external(\n        &mut self,\n        dest_id: u64,\n        src: &Track<TA, M, OA, N>,\n        classes: Option<&[u64]>,\n        merge_history: bool,\n    ) -> Result<()> {\n        let res = self.merge_external_noblock(dest_id, src.clone(), classes, merge_history);\n        if let Ok(res) = res {\n            res.get()\n        } else {\n            res?;\n            unreachable!();\n        }\n    }\n\n    /// Method is used to find tracks that match lookup query.\n    ///\n    /// The search is parallelized with Rayon. The results returned for tracks with their statuses.\n    ///\n    pub fn lookup(&self, q: TA::Lookup) -> Vec<(u64, Result<TrackStatus>)> {\n        let mut results = Vec::with_capacity(self.shard_stats().iter().sum());\n        let (results_sender, results_receiver) = crossbeam::channel::unbounded();\n        for (cmd, _) in &self.executors {\n            cmd.send(Commands::Lookup(q.clone(), results_sender.clone()))\n                .unwrap();\n        }\n        for (_, _) in &self.executors {\n            let res = results_receiver.recv().unwrap();\n            match res {\n                Results::BakedStatus(r) => {\n                    results.extend(r);\n                }\n                _ => {\n                    unreachable!();\n                }\n            }\n        }\n        results\n    }\n\n    /// clears all the tracks from the store\n    ///\n    pub fn clear(&self) {\n        for s in self.stores.as_ref() {\n            let mut lock = s.lock().unwrap();\n            lock.clear();\n        }\n    }\n}\n"
  },
  {
    "path": "src/track/utils.rs",
    "content": "use crate::track::{Feature, Observation, ObservationAttributes, FEATURE_LANES_SIZE};\nuse std::cmp::Ordering;\nuse ultraviolet::f32x8;\n\n/// Utility function that can be used by [ObservationMetric](crate::track::ObservationMetric::metric) implementors to sort\n/// features by attributes decreasingly.\n///\npub fn feature_attributes_sort_dec<FA: ObservationAttributes + PartialOrd>(\n    e1: &Observation<FA>,\n    e2: &Observation<FA>,\n) -> Ordering {\n    e2.0.partial_cmp(&e1.0).unwrap()\n}\n\n/// Utility function that can be used by [ObservationMetric](crate::track::ObservationMetric::metric) implementors to sort\n/// features by attributes increasingly.\n///\npub fn feature_attributes_sort_inc<FA: ObservationAttributes + PartialOrd>(\n    e1: &Observation<FA>,\n    e2: &Observation<FA>,\n) -> Ordering {\n    e1.0.partial_cmp(&e2.0).unwrap()\n}\n\nimpl FromVec<&Feature, Vec<f32>> for Vec<f32> {\n    fn from_vec(vec: &Feature) -> Vec<f32> {\n        let mut res = Vec::with_capacity(vec.len() * FEATURE_LANES_SIZE);\n        for e in vec {\n            res.extend_from_slice(e.as_array_ref());\n        }\n        res\n    }\n}\n\n/// Feature from &Vec<f32>\n///\nimpl FromVec<Vec<f32>, Feature> for Feature {\n    fn from_vec(vec: Vec<f32>) -> Feature {\n        Feature::from_vec(&vec)\n    }\n}\n\n/// Feature from &Vec<f32>\n///\nimpl FromVec<&Vec<f32>, Feature> for Feature {\n    fn from_vec(vec: &Vec<f32>) -> Feature {\n        let mut feature = {\n            let one_more = usize::from(vec.len() % FEATURE_LANES_SIZE > 0);\n            Feature::with_capacity(vec.len() / FEATURE_LANES_SIZE + one_more)\n        };\n\n        let mut acc: [f32; FEATURE_LANES_SIZE] = [0.0; FEATURE_LANES_SIZE];\n        let mut part = 0;\n        for (counter, i) in vec.iter().enumerate() {\n            part = counter % FEATURE_LANES_SIZE;\n            if part == 0 {\n                acc = [0.0; FEATURE_LANES_SIZE];\n            }\n            acc[part] = *i;\n            if part == FEATURE_LANES_SIZE - 1 {\n                feature.push(f32x8::new(acc));\n                part = FEATURE_LANES_SIZE;\n            }\n        }\n\n        if part < FEATURE_LANES_SIZE {\n            feature.push(f32x8::new(acc));\n        }\n        feature\n    }\n}\n\n/// Utility trait to get conversion between feature vector representations\n///\npub trait FromVec<V, R> {\n    fn from_vec(vec: V) -> R;\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::track::utils::FromVec;\n    use crate::track::Feature;\n\n    #[test]\n    fn conv_tests() {\n        let v = vec![0.0, 0.2, 0.3];\n        let o = Feature::from_vec(v);\n        let v2 = Vec::from_vec(&o);\n        assert_eq!(v2, vec![0.0, 0.2, 0.3, 0.0, 0.0, 0.0, 0.0, 0.0]);\n    }\n}\n"
  },
  {
    "path": "src/track/voting/best.rs",
    "content": "use crate::track::{ObservationAttributes, ObservationMetricOk};\nuse crate::voting::topn::TopNVotingElt;\nuse crate::voting::Voting;\nuse itertools::Itertools;\nuse log::debug;\nuse std::collections::{HashMap, HashSet};\nuse std::marker::PhantomData;\n\n/// TopN winners voting engine that selects Top N vectors with most close distances.\n///\n/// It calculates winners as:\n/// 1. removes all distances that are greater than threshold\n/// 2. sorts remaining tracks according to their IDs\n/// 3. counts tracks by their ID's\n/// 4. sorts groups by frequency decreasingly\n/// 5. returns TopN\n///\npub struct BestFitVoting<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    max_distance: f32,\n    min_votes: usize,\n    _phony: PhantomData<OA>,\n}\n\nimpl<OA> BestFitVoting<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    /// Constructs new engine\n    ///\n    /// # Arguments\n    /// * `max_distance` - max distance permitted to participate\n    /// * `min_votes` - minimal amount of votes required the track to participate\n    ///\n    pub fn new(max_distance: f32, min_votes: usize) -> Self {\n        Self {\n            max_distance,\n            min_votes,\n            _phony: PhantomData,\n        }\n    }\n}\n\nimpl<OA> Voting<OA> for BestFitVoting<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    type WinnerObject = TopNVotingElt;\n\n    fn winners<T>(&self, distances: T) -> HashMap<u64, Vec<TopNVotingElt>>\n    where\n        T: IntoIterator<Item = ObservationMetricOk<OA>>,\n    {\n        let mut max_dist = -1.0_f32;\n        let mut candidates: Vec<_> = distances\n            .into_iter()\n            .filter(\n                |ObservationMetricOk {\n                     from: q,\n                     to: w,\n                     attribute_metric: _f_attr_dist,\n                     feature_distance: feat_dist,\n                 }| {\n                    debug!(\n                        \"Raw | Src: {:#?}, Dst: {:#?}, Metric: {:#?}\",\n                        q, w, feat_dist\n                    );\n                    match feat_dist {\n                        Some(e) => {\n                            if max_dist < *e {\n                                max_dist = *e;\n                            }\n                            *e <= self.max_distance\n                        }\n                        _ => false,\n                    }\n                },\n            )\n            .map(\n                |ObservationMetricOk {\n                     from: src_track,\n                     to: dest_track,\n                     attribute_metric: _,\n                     feature_distance: dist,\n                 }| { ((src_track, dest_track), dist.unwrap()) },\n            )\n            .into_group_map()\n            .into_iter()\n            .filter(|(_, count)| count.len() >= self.min_votes)\n            .map(|((src_track, dest_track), dists)| {\n                debug!(\n                    \"Group | Src: {:#?}, Dst: {:#?}, Dist: {:#?}\",\n                    src_track, dest_track, &dists\n                );\n                let weight = dists.into_iter().map(|d| (max_dist - d) as f64).sum();\n                TopNVotingElt {\n                    query_track: src_track,\n                    winner_track: dest_track,\n                    weight,\n                }\n            })\n            .collect::<Vec<_>>();\n\n        candidates.sort_by(|e1, e2| e2.weight.partial_cmp(&e1.weight).unwrap());\n\n        debug!(\"Candidates: {:#?}\", &candidates);\n\n        let mut results: HashSet<u64> = HashSet::new();\n\n        for c in &mut candidates {\n            let key = c.query_track;\n            let winner = c.winner_track;\n            if results.contains(&winner) {\n                c.winner_track = key;\n            } else {\n                results.insert(winner);\n            }\n        }\n\n        let res = candidates\n            .into_iter()\n            .map(|e| (e.query_track, e))\n            .into_group_map();\n        debug!(\"Results: {:#?}\", &res);\n        res\n    }\n}\n"
  },
  {
    "path": "src/track/voting/topn.rs",
    "content": "use crate::track::{ObservationAttributes, ObservationMetricOk};\nuse crate::voting::Voting;\nuse itertools::Itertools;\nuse std::collections::HashMap;\nuse std::marker::PhantomData;\n\n/// TopN winners voting engine that selects Top N vectors with most close distances.\n///\n/// It calculates winners as:\n/// 1. removes all distances that are greater than threshold\n/// 2. sorts remaining tracks according to their IDs\n/// 3. counts tracks by their ID's\n/// 4. sorts groups by frequency decreasingly\n/// 5. returns TopN\n///\npub struct TopNVoting<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    topn: usize,\n    max_distance: f32,\n    min_votes: usize,\n    _phony: PhantomData<OA>,\n}\n\nimpl<OA> TopNVoting<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    /// Constructs new engine\n    ///\n    /// # Arguments\n    /// * `topn` - top winners\n    /// * `max_distance` - max distance permitted to participate\n    /// * `min_votes` - minimal amount of votes required the track to participate\n    ///\n    pub fn new(topn: usize, max_distance: f32, min_votes: usize) -> Self {\n        Self {\n            topn,\n            max_distance,\n            min_votes,\n            _phony: PhantomData,\n        }\n    }\n}\n\n/// Return type fot TopN voting engine\n///\n#[derive(Default, Debug, PartialEq)]\npub struct TopNVotingElt {\n    pub query_track: u64,\n    /// winning track\n    pub winner_track: u64,\n    /// number of votes it gathered\n    pub weight: f64,\n}\n\nimpl TopNVotingElt {\n    pub fn new(query_track: u64, winner_track: u64, weight: f64) -> Self {\n        Self {\n            query_track,\n            winner_track,\n            weight,\n        }\n    }\n}\n\nimpl<OA> Voting<OA> for TopNVoting<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    type WinnerObject = TopNVotingElt;\n\n    fn winners<T>(&self, distances: T) -> HashMap<u64, Vec<TopNVotingElt>>\n    where\n        T: IntoIterator<Item = ObservationMetricOk<OA>>,\n    {\n        let mut max_dist = -1.0_f32;\n        let counts: Vec<_> = distances\n            .into_iter()\n            .filter(\n                |ObservationMetricOk {\n                     from: _,\n                     to: _track,\n                     attribute_metric: _f_attr_dist,\n                     feature_distance: feat_dist,\n                 }| match feat_dist {\n                    Some(e) => {\n                        if max_dist < *e {\n                            max_dist = *e;\n                        }\n                        *e <= self.max_distance\n                    }\n                    _ => false,\n                },\n            )\n            .map(\n                |ObservationMetricOk {\n                     from: src_track,\n                     to: dest_track,\n                     attribute_metric: _,\n                     feature_distance: dist,\n                 }| { ((src_track, dest_track), dist.unwrap()) },\n            )\n            .into_group_map()\n            .into_iter()\n            .filter(|(_, count)| count.len() >= self.min_votes)\n            .map(|((q, w), c)| {\n                let weight = c.into_iter().map(|d| (max_dist - d) as f64).sum();\n\n                TopNVotingElt {\n                    query_track: q,\n                    winner_track: w,\n                    weight,\n                }\n            })\n            .collect::<Vec<_>>();\n\n        let mut results: HashMap<u64, Vec<TopNVotingElt>> = HashMap::new();\n\n        for c in counts {\n            let key = c.query_track;\n            if let Some(val) = results.get_mut(&key) {\n                val.push(c);\n            } else {\n                results.insert(key, vec![c]);\n            }\n        }\n\n        for counts in results.values_mut() {\n            counts.sort_by(|l, r| r.weight.partial_cmp(&l.weight).unwrap());\n            counts.truncate(self.topn);\n        }\n\n        results\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::track::voting::topn::{TopNVoting, TopNVotingElt, Voting};\n    use crate::track::ObservationMetricOk;\n    use std::collections::HashMap;\n\n    #[test]\n    fn default_voting() {\n        let v: TopNVoting<()> = TopNVoting::new(5, 0.32, 1);\n\n        let candidates = v.winners([ObservationMetricOk::new(0, 1, None, Some(0.2))]);\n\n        assert_eq!(\n            candidates,\n            HashMap::from([(0, vec![TopNVotingElt::new(0, 1, 0.0)])])\n        );\n\n        let candidates = v.winners([\n            ObservationMetricOk::new(0, 1, None, Some(0.2)),\n            ObservationMetricOk::new(0, 1, None, Some(0.3)),\n        ]);\n\n        assert_eq!(\n            candidates,\n            HashMap::from([(0, vec![TopNVotingElt::new(0, 1, 0.10000000894069672)])])\n        );\n\n        let candidates = v.winners([\n            ObservationMetricOk::new(0, 1, None, Some(0.2)),\n            ObservationMetricOk::new(0, 1, None, Some(0.4)),\n        ]);\n\n        assert_eq!(\n            candidates,\n            HashMap::from([(0, vec![TopNVotingElt::new(0, 1, 0.20000000298023224)])])\n        );\n\n        let mut candidates = v.winners([\n            ObservationMetricOk::new(0, 1, None, Some(0.2)),\n            ObservationMetricOk::new(0, 2, None, Some(0.2)),\n        ]);\n\n        candidates\n            .get_mut(&0)\n            .unwrap()\n            .sort_by(|l, r| l.winner_track.partial_cmp(&r.winner_track).unwrap());\n\n        assert_eq!(\n            candidates,\n            HashMap::from([(\n                0,\n                vec![TopNVotingElt::new(0, 1, 0.0), TopNVotingElt::new(0, 2, 0.0)]\n            )])\n        );\n\n        let mut candidates = v.winners([\n            ObservationMetricOk::new(0, 1, None, Some(0.2)),\n            ObservationMetricOk::new(0, 1, None, Some(0.22)),\n            ObservationMetricOk::new(0, 2, None, Some(0.21)),\n            ObservationMetricOk::new(0, 2, None, Some(0.2)),\n            ObservationMetricOk::new(0, 3, None, Some(0.22)),\n            ObservationMetricOk::new(0, 3, None, Some(0.2)),\n            ObservationMetricOk::new(0, 4, None, Some(0.23)),\n            ObservationMetricOk::new(0, 4, None, Some(0.3)),\n            ObservationMetricOk::new(0, 5, None, Some(0.24)),\n            ObservationMetricOk::new(0, 5, None, Some(0.3)),\n            ObservationMetricOk::new(0, 6, None, Some(0.25)),\n            ObservationMetricOk::new(0, 6, None, Some(0.5)),\n        ]);\n\n        candidates\n            .get_mut(&0)\n            .unwrap()\n            .sort_by(|l, r| l.winner_track.partial_cmp(&r.winner_track).unwrap());\n\n        assert_eq!(\n            candidates,\n            HashMap::from([(\n                0,\n                vec![\n                    TopNVotingElt::new(0, 1, 0.5800000131130219),\n                    TopNVotingElt::new(0, 2, 0.5900000333786011),\n                    TopNVotingElt::new(0, 3, 0.5800000131130219),\n                    TopNVotingElt::new(0, 4, 0.4699999690055847),\n                    TopNVotingElt::new(0, 5, 0.4599999785423279)\n                ]\n            )])\n        );\n    }\n\n    #[test]\n    fn two_query_vecs() {\n        let v: TopNVoting<f32> = TopNVoting::new(5, 0.32, 1);\n\n        let mut candidates = v.winners([\n            ObservationMetricOk::new(0, 1, None, Some(0.2)),\n            ObservationMetricOk::new(0, 1, None, Some(0.22)),\n            ObservationMetricOk::new(0, 2, None, Some(0.21)),\n            ObservationMetricOk::new(0, 2, None, Some(0.2)),\n            ObservationMetricOk::new(0, 3, None, Some(0.22)),\n            ObservationMetricOk::new(0, 3, None, Some(0.2)),\n            ObservationMetricOk::new(7, 4, None, Some(0.23)),\n            ObservationMetricOk::new(7, 4, None, Some(0.3)),\n            ObservationMetricOk::new(7, 5, None, Some(0.24)),\n            ObservationMetricOk::new(7, 5, None, Some(0.3)),\n            ObservationMetricOk::new(7, 6, None, Some(0.25)),\n            ObservationMetricOk::new(7, 6, None, Some(0.5)),\n        ]);\n\n        candidates\n            .get_mut(&0)\n            .unwrap()\n            .sort_by(|l, r| l.winner_track.partial_cmp(&r.winner_track).unwrap());\n\n        candidates\n            .get_mut(&7)\n            .unwrap()\n            .sort_by(|l, r| l.winner_track.partial_cmp(&r.winner_track).unwrap());\n\n        assert_eq!(\n            candidates,\n            HashMap::from([\n                (\n                    0,\n                    vec![\n                        TopNVotingElt::new(0, 1, 0.5800000131130219),\n                        TopNVotingElt::new(0, 2, 0.5900000333786011),\n                        TopNVotingElt::new(0, 3, 0.5800000131130219),\n                    ]\n                ),\n                (\n                    7,\n                    vec![\n                        TopNVotingElt::new(7, 4, 0.4699999690055847),\n                        TopNVotingElt::new(7, 5, 0.4599999785423279),\n                        TopNVotingElt::new(7, 6, 0.250)\n                    ]\n                )\n            ])\n        );\n    }\n}\n"
  },
  {
    "path": "src/track/voting.rs",
    "content": "pub mod best;\npub mod topn;\n\nuse crate::track::{ObservationAttributes, ObservationMetricOk};\nuse std::collections::HashMap;\n\n/// Trait to implement distance voting engines.\n///\n/// Distance voting engine is used to select winning tracks among distances\n/// resulted from the distance calculation.\n///\npub trait Voting<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    type WinnerObject;\n    /// Method that selects winning tracks\n    ///\n    ///\n    /// # Arguments\n    /// * `distances` - distances resulted from the distance calculation.\n    ///\n    /// # Return\n    /// Map of track_ids -> Vec<Result>\n    ///\n    fn winners<T>(&self, distances: T) -> HashMap<u64, Vec<Self::WinnerObject>>\n    where\n        T: IntoIterator<Item = ObservationMetricOk<OA>>;\n}\n"
  },
  {
    "path": "src/track.rs",
    "content": "use crate::track::notify::{ChangeNotifier, NoopNotifier};\nuse crate::Errors;\nuse anyhow::Result;\nuse itertools::Itertools;\nuse std::collections::HashMap;\nuse std::fmt::Debug;\nuse std::marker::PhantomData;\nuse std::mem::take;\nuse ultraviolet::f32x8;\n\npub mod builder;\npub mod notify;\npub mod store;\npub mod utils;\npub mod voting;\n\n/// Return type for distance between the current track's and other track observation pair\n///\n#[derive(Debug, Clone)]\npub struct ObservationMetricOk<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    /// source track ID\n    pub from: u64,\n    /// compared track ID\n    pub to: u64,\n    /// custom feature attribute metric object calculated for pairwise feature attributes\n    pub attribute_metric: Option<OA::MetricObject>,\n    /// distance calculated for pairwise feature vectors\n    pub feature_distance: Option<f32>,\n}\n\nimpl<OA> ObservationMetricOk<OA>\nwhere\n    OA: ObservationAttributes,\n{\n    pub fn new(\n        from: u64,\n        to: u64,\n        attribute_metric: Option<OA::MetricObject>,\n        feature_distance: Option<f32>,\n    ) -> Self {\n        Self {\n            from,\n            to,\n            attribute_metric,\n            feature_distance,\n        }\n    }\n}\n\n/// Internal feature vector representation.\n///\npub type Feature = Vec<f32x8>;\n\n/// Number of SIMD lanes used to store observation parts internally\nconst FEATURE_LANES_SIZE: usize = 8;\n\n/// Observation specification.\n///\n/// It is a tuple struct of optional observation attributes (T) and optional feature vector itself.\n/// Observations are collected from the real world and placed into tracks. Later the observations are used\n/// to calculate the distances between tracks to make merging.\n///\n#[derive(Default, Clone)]\npub struct Observation<T>(pub(crate) Option<T>, pub(crate) Option<Feature>)\nwhere\n    T: Send + Sync + Clone + 'static;\n\nimpl<T> Observation<T>\nwhere\n    T: Send + Sync + Clone + 'static,\n{\n    pub fn new(attrs: Option<T>, feature: Option<Feature>) -> Self {\n        Self(attrs, feature)\n    }\n\n    /// Access to observation attributes\n    ///\n    pub fn attr(&self) -> &Option<T> {\n        &self.0\n    }\n\n    /// Access to observation attributes for modification purposes\n    ///\n    pub fn attr_mut(&mut self) -> &mut Option<T> {\n        &mut self.0\n    }\n\n    /// Access to observation feature\n    ///\n    pub fn feature(&self) -> &Option<Feature> {\n        &self.1\n    }\n\n    /// Access to observation feature for modification purposes\n    ///\n    pub fn feature_mut(&mut self) -> &mut Option<Feature> {\n        &mut self.1\n    }\n}\n\n/// HashTable that accumulates observations within the track.\n///\n/// The key is the feature class the value is the vector of observations collected.\n///\npub type ObservationsDb<T> = HashMap<u64, Vec<Observation<T>>>;\n\n/// Custom observation attributes object that is the part of the observation together with the feature vector.\n///\npub trait ObservationAttributes: Send + Sync + Clone + 'static {\n    type MetricObject: Debug + Send + Sync + Clone + 'static;\n    fn calculate_metric_object(l: &Option<&Self>, r: &Option<&Self>) -> Option<Self::MetricObject>;\n}\n\n/// Output result type used by metric when pairwise metric is calculated\n///\n/// `None` - no metric for that pair - the result will be dropped (optimization technique)\n/// `Some(Option<X>, Option<Y>)` - metric is calculated, values are inside.\n///  where\n///   * `Option<X>` is the metric object computed for observation attributes;\n///   * `Option<Y>` is the distance computed for feature vectors of the observation.\n///\npub type MetricOutput<T> = Option<(Option<T>, Option<f32>)>;\n\n/// Query object that is a parameter of the ``ObservationMetric::metric` method.\n///\n/// The query is used to make pairwise comparison of observations for two tracks.\n/// There is a\n///  * `candidate` track - the one, that is selected as a comparison subject\n///  * `track` track - the one, that is iterated over those kept in the store\n///\npub struct MetricQuery<'a, TA, OA: ObservationAttributes> {\n    /// * `feature_class` - class of currently used feature\n    pub feature_class: u64,\n    /// * `candidate_attrs` - candidate track attributes\n    pub candidate_attrs: &'a TA,\n    /// * `candidate_observation` - candidate track observation\n    pub candidate_observation: &'a Observation<OA>,\n    /// * `track_attrs` - track attributes\n    pub track_attrs: &'a TA,\n    /// * `track_observation` - track observation\n    pub track_observation: &'a Observation<OA>,\n}\n\n/// The trait that implements the methods for observations comparison, optimization and filtering.\n///\n/// This is the one of the most important elements of the track. It defines how track distances are\n/// computed, how track observations are compacted and transformed upon merging.\n///\npub trait ObservationMetric<TA, OA: ObservationAttributes>: Send + Sync + Clone + 'static {\n    /// calculates the distance between two features.\n    ///\n    /// # Parameters\n    /// * `mq` - query to calculate metric\n    ///\n    fn metric(&self, mq: &'_ MetricQuery<'_, TA, OA>) -> MetricOutput<OA::MetricObject>;\n\n    /// the method is used every time, when a new observation is added to the feature storage as well as when\n    /// two tracks are merged.\n    ///\n    /// # Arguments\n    ///\n    /// * `feature_class` - the feature class\n    /// * `merge_history` - the vector of track identifiers collected upon every merge\n    /// * `attributes` - mutable track attributes that can be updated or read during optimization\n    /// * `observations` - observations to optimize\n    /// * `prev_length` - previous length of observations (before the current observation was added or merge occurred)\n    /// * `is_merge` - true, when the op is for track merging, false when the observation is added to the track\n    ///\n    /// # Returns\n    /// * `Ok(())` if the optimization is successful\n    /// * `Err(e)` if the optimization failed\n    ///\n    fn optimize(\n        &mut self,\n        feature_class: u64,\n        merge_history: &[u64],\n        attributes: &mut TA,\n        observations: &mut Vec<Observation<OA>>,\n        prev_length: usize,\n        is_merge: bool,\n    ) -> Result<()>;\n\n    /// The postprocessing is run just before the executor returns calculated distances.\n    ///\n    /// The postprocessing is aimed to remove non-viable, invalid distances that can be skipped\n    /// to improve the performance or the quality of further track voting process.\n    ///\n    fn postprocess_distances(\n        &self,\n        unfiltered: Vec<ObservationMetricOk<OA>>,\n    ) -> Vec<ObservationMetricOk<OA>> {\n        unfiltered\n    }\n}\n\n/// Enum which specifies the status of feature tracks in storage. When the feature tracks are collected,\n/// eventually the track must be complete so it can be used for\n/// database search and later merge operations.\n///\n#[derive(Clone, Debug)]\npub enum TrackStatus {\n    /// The track is ready and can be used to find similar tracks for merge.\n    Ready,\n    /// The track is not ready and still being collected.\n    Pending,\n    /// The track is invalid because somehow became incorrect or outdated along the way.\n    Wasted,\n}\n\n/// The trait that must be implemented by a search query object to run searches over the store\n///\npub trait LookupRequest<TA, OA>: Send + Sync + Clone + 'static\nwhere\n    TA: TrackAttributes<TA, OA>,\n    OA: ObservationAttributes,\n{\n    fn lookup(\n        &self,\n        _attributes: &TA,\n        _observations: &ObservationsDb<OA>,\n        _merge_history: &[u64],\n    ) -> bool {\n        false\n    }\n}\n\n/// Do nothing lookup implementation that can be put anywhere lookup is required.\n///\n/// It is compatible with all TA, OA. Const parameter defines what lookup returns:\n/// * `false` - all lookup elements are ignored\n/// * `true` - all lookup elements are returned\n///\npub struct NoopLookup<TA, OA, const RES: bool = false>\nwhere\n    TA: TrackAttributes<TA, OA>,\n    OA: ObservationAttributes,\n{\n    _ta: PhantomData<TA>,\n    _oa: PhantomData<OA>,\n}\n\nimpl<TA, OA, const RES: bool> Clone for NoopLookup<TA, OA, RES>\nwhere\n    TA: TrackAttributes<TA, OA>,\n    OA: ObservationAttributes,\n{\n    fn clone(&self) -> Self {\n        NoopLookup {\n            _ta: PhantomData,\n            _oa: PhantomData,\n        }\n    }\n}\n\nimpl<TA, OA, const RES: bool> Default for NoopLookup<TA, OA, RES>\nwhere\n    TA: TrackAttributes<TA, OA>,\n    OA: ObservationAttributes,\n{\n    fn default() -> Self {\n        NoopLookup {\n            _ta: PhantomData,\n            _oa: PhantomData,\n        }\n    }\n}\n\nimpl<TA, OA, const RES: bool> LookupRequest<TA, OA> for NoopLookup<TA, OA, RES>\nwhere\n    TA: TrackAttributes<TA, OA>,\n    OA: ObservationAttributes,\n{\n    fn lookup(\n        &self,\n        _attributes: &TA,\n        _observations: &ObservationsDb<OA>,\n        _merge_history: &[u64],\n    ) -> bool {\n        RES\n    }\n}\n\n/// The trait represents user defined Track Attributes. It is used to define custom attributes that\n/// fit a domain field where tracking implemented.\n///\n/// When the user implements track attributes they has to implement this trait to create a valid attributes object.\n///\npub trait TrackAttributes<TA: TrackAttributes<TA, OA>, OA: ObservationAttributes>:\n    Send + Sync + Clone + 'static\n{\n    type Update: TrackAttributesUpdate<TA>;\n    type Lookup: LookupRequest<TA, OA>;\n    /// The method is used to evaluate attributes of two tracks to determine whether tracks are compatible\n    /// for distance calculation. When the attributes are compatible, the method returns `true`.\n    ///\n    /// E.g.\n    ///     Let's imagine the case when the track includes the attributes for track begin and end timestamps.\n    ///     The tracks are compatible their timeframes don't intersect between each other. The method `compatible`\n    ///     can decide that.\n    ///\n    fn compatible(&self, other: &TA) -> bool;\n\n    /// When the tracks are merged, their attributes are merged as well. The method defines the approach to merge attributes.\n    ///\n    /// E.g.\n    ///     Let's imagine the case when the track includes the attributes for track begin and end timestamps.\n    ///     Merge operation may look like `[b1; e1] + [b2; e2] -> [min(b1, b2); max(e1, e2)]`.\n    ///\n    fn merge(&mut self, other: &TA) -> Result<()>;\n\n    /// The method is used by storage to determine when track is ready/not ready/wasted. Look at [TrackStatus](TrackStatus).\n    ///\n    /// It uses attribute information collected across the track config.toml and features information.\n    ///\n    /// E.g.\n    ///     track is ready when\n    ///          `now - end_timestamp > 30s` (no features collected during the last 30 seconds).\n    ///\n    fn baked(&self, observations: &ObservationsDb<OA>) -> Result<TrackStatus>;\n}\n\n/// The attribute update information that is sent with new features to the track is represented by the trait.\n///\n/// The trait must be implemented for update struct for specific attributes struct implementation.\n///\npub trait TrackAttributesUpdate<TA>: Clone + Send + Sync + 'static {\n    /// Method is used to update track attributes from update structure.\n    ///\n    fn apply(&self, attrs: &mut TA) -> Result<()>;\n}\n\n/// Represents track of observations - it's a core concept of the library.\n///\n/// The track is created for specific attributes(A), Metric(M) and AttributeUpdate(U).\n/// * Attributes hold track meta information specific for certain domain.\n/// * Metric defines how to compare track features and optimize features when tracks are\n///   merged or collected\n/// * AttributeUpdate specifies how attributes are update from external sources.\n///\n#[derive(Default, Clone)]\npub struct Track<TA, M, OA, N = NoopNotifier>\nwhere\n    TA: TrackAttributes<TA, OA>,\n    M: ObservationMetric<TA, OA>,\n    OA: ObservationAttributes,\n    N: ChangeNotifier,\n{\n    attributes: TA,\n    track_id: u64,\n    observations: ObservationsDb<OA>,\n    metric: M,\n    merge_history: Vec<u64>,\n    notifier: N,\n}\n\n/// One and only parametrized track implementation.\n///\nimpl<TA, M, OA, N> Track<TA, M, OA, N>\nwhere\n    TA: TrackAttributes<TA, OA>,\n    M: ObservationMetric<TA, OA>,\n    OA: ObservationAttributes,\n    N: ChangeNotifier,\n{\n    /// Creates a new track with id `track_id` with `metric` initializer object and `attributes` initializer object.\n    ///\n    /// The `metric` and `attributes` are optional, if `None` is specified, then `Default` initializer is used.\n    ///\n    pub fn new(track_id: u64, metric: M, attributes: TA, notifier: N) -> Self {\n        let mut v = Self {\n            notifier,\n            attributes,\n            track_id,\n            metric,\n            observations: ObservationsDb::default(),\n            merge_history: vec![track_id],\n        };\n        v.notifier.send(track_id);\n        v\n    }\n\n    /// Returns track_id.\n    ///\n    pub fn get_track_id(&self) -> u64 {\n        self.track_id\n    }\n\n    /// Sets track_id.\n    ///\n    pub fn set_track_id(&mut self, track_id: u64) -> u64 {\n        let old = self.track_id;\n        self.track_id = track_id;\n        old\n    }\n\n    /// Returns current track attributes.\n    ///\n    pub fn get_attributes(&self) -> &TA {\n        &self.attributes\n    }\n\n    pub fn get_observations(&self, feature_class: u64) -> Option<&Vec<Observation<OA>>> {\n        self.observations.get(&feature_class)\n    }\n\n    pub fn get_mut_observations(\n        &mut self,\n        feature_class: u64,\n    ) -> Option<&mut Vec<Observation<OA>>> {\n        self.observations.get_mut(&feature_class)\n    }\n\n    /// Returns the current track merge history for the track\n    ///\n    pub fn get_merge_history(&self) -> &Vec<u64> {\n        &self.merge_history\n    }\n\n    /// Returns all classes present\n    ///\n    pub fn get_feature_classes(&self) -> Vec<u64> {\n        self.observations.keys().cloned().collect()\n    }\n\n    fn update_attributes(&mut self, update: &TA::Update) -> Result<()> {\n        update.apply(&mut self.attributes)\n    }\n\n    /// Adds new observation to track.\n    ///\n    /// When the method is called, the track attributes are updated according to `update` argument, and the feature\n    /// is placed into features for a specified feature class.\n    ///\n    /// # Arguments\n    /// * `feature_class` - class of observation\n    /// * `feature_attributes` - quality of the feature (confidence, or another parameter that defines how the observation is valuable across the observations).\n    /// * `feature` - observation to add to the track for specified `feature_class`.\n    /// * `track_attributes_update` - attribute update message\n    ///\n    /// # Returns\n    /// Returns `Result<()>` where `Ok(())` if attributes are updated without errors AND observation is added AND observations optimized without errors.\n    ///\n    ///\n    pub fn add_observation(\n        &mut self,\n        feature_class: u64,\n        feature_attributes: Option<OA>,\n        feature: Option<Feature>,\n        track_attributes_update: Option<TA::Update>,\n    ) -> Result<()> {\n        let last_attributes = self.attributes.clone();\n        let last_observations = self.observations.clone();\n        let last_metric = self.metric.clone();\n\n        if let Some(track_attributes_update) = &track_attributes_update {\n            let res = self.update_attributes(track_attributes_update);\n            if res.is_err() {\n                self.attributes = last_attributes;\n                res?;\n                unreachable!();\n            }\n        }\n\n        if feature.is_none() && feature_attributes.is_none() {\n            self.notifier.send(self.track_id);\n            return Ok(());\n        }\n\n        match self.observations.get_mut(&feature_class) {\n            None => {\n                self.observations.insert(\n                    feature_class,\n                    vec![Observation(feature_attributes, feature)],\n                );\n            }\n            Some(observations) => {\n                observations.push(Observation(feature_attributes, feature));\n            }\n        }\n        let observations = self.observations.get_mut(&feature_class).unwrap();\n        let prev_length = observations.len() - 1;\n\n        let res = self.metric.optimize(\n            feature_class,\n            &self.merge_history,\n            &mut self.attributes,\n            observations,\n            prev_length,\n            false,\n        );\n        if res.is_err() {\n            self.attributes = last_attributes;\n            self.observations = last_observations;\n            self.metric = last_metric;\n            res?;\n            unreachable!();\n        }\n        self.notifier.send(self.track_id);\n        Ok(())\n    }\n\n    /// Merges vector into current track across specified feature classes.\n    ///\n    /// The merge works across specified feature classes:\n    /// * step 1: attributes are merged\n    /// * step 2.0: features are merged for classes\n    /// * step 2.1: features are optimized for every class\n    ///\n    /// If feature class doesn't exist any of tracks it's skipped, otherwise:\n    ///\n    /// * both: `{S[class]} U {OTHER[class]}`\n    /// * self: `{S[class]}`\n    /// * other: `{OTHER[class]}`\n    ///\n    /// # Parameters\n    /// * `other` - track to merge into self\n    /// * `merge_history` - defines add merged track id into self merge history or not\n    ///\n    pub fn merge(&mut self, other: &Self, classes: &[u64], merge_history: bool) -> Result<()> {\n        let last_attributes = self.attributes.clone();\n        let res = self.attributes.merge(&other.attributes);\n        if res.is_err() {\n            self.attributes = last_attributes;\n            res?;\n            unreachable!();\n        }\n\n        let last_observations = self.observations.clone();\n        let last_metric = self.metric.clone();\n\n        for cls in classes {\n            let dest = self.observations.get_mut(cls);\n            let src = other.observations.get(cls);\n            let prev_length = match (dest, src) {\n                (Some(dest_observations), Some(src_observations)) => {\n                    let prev_length = dest_observations.len();\n                    dest_observations.extend(src_observations.iter().cloned());\n                    Some(prev_length)\n                }\n                (None, Some(src_observations)) => {\n                    self.observations.insert(*cls, src_observations.clone());\n                    Some(0)\n                }\n\n                (Some(dest_observations), None) => {\n                    let prev_length = dest_observations.len();\n                    Some(prev_length)\n                }\n\n                _ => None,\n            };\n            let merge_history = if merge_history {\n                self.merge_history\n                    .iter()\n                    .chain(other.merge_history.iter())\n                    .cloned()\n                    .collect::<Vec<_>>()\n            } else {\n                take(&mut self.merge_history)\n            };\n\n            if let Some(prev_length) = prev_length {\n                let res = self.metric.optimize(\n                    *cls,\n                    &merge_history,\n                    &mut self.attributes,\n                    self.observations.get_mut(cls).unwrap(),\n                    prev_length,\n                    true,\n                );\n\n                if res.is_err() {\n                    self.attributes = last_attributes;\n                    self.observations = last_observations;\n                    self.metric = last_metric;\n                    res?;\n                    unreachable!();\n                }\n                self.merge_history = merge_history;\n            }\n        }\n\n        self.notifier.send(self.track_id);\n        Ok(())\n    }\n\n    /// Calculates distances between all features for two tracks for a class.\n    ///\n    /// First it calculates cartesian product `S X O` and calculates the distance for every pair.\n    ///\n    /// Before it calculates the distance, it checks that attributes are compatible. If no,\n    /// [`Err(Errors::IncompatibleAttributes)`](Errors::IncompatibleAttributes) returned. Otherwise,\n    /// the vector of distances returned that holds `(other.track_id, Result<f32>)` pairs. `Track_id` is\n    /// the same for all results and used in higher level operations. `Result<f32>` is `Ok(f32)` when\n    /// the distance calculated by `Metric` well, `Err(e)` when `Metric` is unable to calculate the distance.\n    ///\n    /// # Parameters\n    /// * `other` - track to find distances to\n    /// * `feature_class` - what feature class to use to calculate distances\n    /// * `filter` - defines either results are filtered by distance before the output or not\n    pub fn distances(\n        &self,\n        other: &Self,\n        feature_class: u64,\n    ) -> Result<Vec<ObservationMetricOk<OA>>> {\n        if !self.attributes.compatible(&other.attributes) {\n            Err(Errors::IncompatibleAttributes.into())\n        } else {\n            match (\n                self.observations.get(&feature_class),\n                other.observations.get(&feature_class),\n            ) {\n                (Some(left), Some(right)) => Ok(left\n                    .iter()\n                    .cartesian_product(right.iter())\n                    .flat_map(|(l, r)| {\n                        let mq = MetricQuery {\n                            feature_class,\n                            candidate_attrs: self.get_attributes(),\n                            candidate_observation: l,\n                            track_attrs: other.get_attributes(),\n                            track_observation: r,\n                        };\n\n                        // let (attribute_metric, feature_distance) = self.metric.new_metric(&mq)?;\n                        let (attribute_metric, feature_distance) = self.metric.metric(\n                            &mq, // feature_class,\n                                // self.get_attributes(),\n                                // other.get_attributes(),\n                                // l,\n                                // r,\n                        )?;\n                        Some(ObservationMetricOk {\n                            from: self.track_id,\n                            to: other.track_id,\n                            attribute_metric,\n                            feature_distance,\n                        })\n                    })\n                    .collect()),\n                _ => Err(Errors::ObservationForClassNotFound(\n                    self.track_id,\n                    other.track_id,\n                    feature_class,\n                )\n                .into()),\n            }\n        }\n    }\n\n    pub fn lookup(&self, query: &TA::Lookup) -> bool {\n        query.lookup(&self.attributes, &self.observations, &self.merge_history)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::distance::euclidean;\n    use crate::examples::current_time_sec;\n    use crate::prelude::{NoopNotifier, TrackBuilder};\n    use crate::track::utils::{feature_attributes_sort_dec, FromVec};\n    use crate::track::{\n        Feature, LookupRequest, MetricOutput, MetricQuery, NoopLookup, Observation,\n        ObservationAttributes, ObservationMetric, ObservationsDb, Track, TrackAttributes,\n        TrackAttributesUpdate, TrackStatus,\n    };\n    use crate::EPS;\n    use anyhow::Result;\n\n    #[derive(Clone)]\n    pub struct DefaultAttrs;\n\n    #[derive(Clone)]\n    pub struct DefaultAttrUpdates;\n\n    impl TrackAttributesUpdate<DefaultAttrs> for DefaultAttrUpdates {\n        fn apply(&self, _attrs: &mut DefaultAttrs) -> Result<()> {\n            Ok(())\n        }\n    }\n\n    impl TrackAttributes<DefaultAttrs, f32> for DefaultAttrs {\n        type Update = DefaultAttrUpdates;\n        type Lookup = NoopLookup<DefaultAttrs, f32>;\n\n        fn compatible(&self, _other: &DefaultAttrs) -> bool {\n            true\n        }\n\n        fn merge(&mut self, _other: &DefaultAttrs) -> Result<()> {\n            Ok(())\n        }\n\n        fn baked(&self, _observations: &ObservationsDb<f32>) -> Result<TrackStatus> {\n            Ok(TrackStatus::Pending)\n        }\n    }\n\n    #[derive(Clone)]\n    struct DefaultMetric;\n    impl ObservationMetric<DefaultAttrs, f32> for DefaultMetric {\n        fn metric(&self, mq: &MetricQuery<'_, DefaultAttrs, f32>) -> MetricOutput<f32> {\n            let (e1, e2) = (mq.candidate_observation, mq.track_observation);\n            Some((\n                f32::calculate_metric_object(&e1.attr().as_ref(), &e2.attr().as_ref()),\n                match (e1.feature().as_ref(), e2.feature().as_ref()) {\n                    (Some(x), Some(y)) => Some(euclidean(x, y)),\n                    _ => None,\n                },\n            ))\n        }\n\n        fn optimize(\n            &mut self,\n            _feature_class: u64,\n            _merge_history: &[u64],\n            _attributes: &mut DefaultAttrs,\n            features: &mut Vec<Observation<f32>>,\n            _prev_length: usize,\n            _is_merge: bool,\n        ) -> Result<()> {\n            features.sort_by(feature_attributes_sort_dec);\n            features.truncate(20);\n            Ok(())\n        }\n    }\n\n    #[test]\n    fn init() {\n        let t1 = Track::new(3, DefaultMetric, DefaultAttrs, NoopNotifier);\n        assert_eq!(t1.get_track_id(), 3);\n    }\n\n    #[test]\n    fn track_distances() -> Result<()> {\n        let mut t1 = Track::new(1, DefaultMetric, DefaultAttrs, NoopNotifier);\n        t1.add_observation(\n            0,\n            Some(0.3),\n            Some(Feature::from_vec(vec![1f32, 0.0, 0.0])),\n            None,\n        )?;\n\n        let mut t2 = Track::new(2, DefaultMetric, DefaultAttrs, NoopNotifier);\n        t2.add_observation(\n            0,\n            Some(0.3),\n            Some(Feature::from_vec(vec![0f32, 1.0f32, 0.0])),\n            None,\n        )?;\n\n        let dists = t1.distances(&t1, 0);\n        let dists = dists.unwrap();\n        assert_eq!(dists.len(), 1);\n        assert!(*dists[0].feature_distance.as_ref().unwrap() < EPS);\n\n        let dists = t1.distances(&t2, 0);\n        let dists = dists.unwrap();\n        assert_eq!(dists.len(), 1);\n        assert!((*dists[0].feature_distance.as_ref().unwrap() - 2.0_f32.sqrt()).abs() < EPS);\n\n        t2.add_observation(\n            0,\n            Some(0.2),\n            Some(Feature::from_vec(vec![1f32, 1.0f32, 0.0])),\n            None,\n        )?;\n\n        assert_eq!(t2.observations.get(&0).unwrap().len(), 2);\n\n        let dists = t1.distances(&t2, 0);\n        let dists = dists.unwrap();\n        assert_eq!(dists.len(), 2);\n        assert!((*dists[0].feature_distance.as_ref().unwrap() - 2.0_f32.sqrt()).abs() < EPS);\n        assert!((*dists[1].feature_distance.as_ref().unwrap() - 1.0).abs() < EPS);\n        Ok(())\n    }\n\n    #[test]\n    fn merge_same() -> Result<()> {\n        let mut t1 = Track::new(1, DefaultMetric, DefaultAttrs, NoopNotifier);\n        t1.add_observation(\n            0,\n            Some(0.3),\n            Some(Feature::from_vec(vec![1f32, 0.0, 0.0])),\n            None,\n        )?;\n\n        let mut t2 = Track::new(2, DefaultMetric, DefaultAttrs, NoopNotifier);\n        t2.add_observation(\n            0,\n            Some(0.3),\n            Some(Feature::from_vec(vec![0f32, 1.0f32, 0.0])),\n            None,\n        )?;\n        let r = t1.merge(&t2, &[0], true);\n        assert!(r.is_ok());\n        assert_eq!(t1.observations.get(&0).unwrap().len(), 2);\n        Ok(())\n    }\n\n    #[test]\n    fn merge_other_feature_class() -> Result<()> {\n        let mut t1 = Track::new(1, DefaultMetric, DefaultAttrs, NoopNotifier);\n        t1.add_observation(\n            0,\n            Some(0.3),\n            Some(Feature::from_vec(vec![1f32, 0.0, 0.0])),\n            None,\n        )?;\n\n        let mut t2 = Track::new(2, DefaultMetric, DefaultAttrs, NoopNotifier);\n        t2.add_observation(\n            1,\n            Some(0.3),\n            Some(Feature::from_vec(vec![0f32, 1.0f32, 0.0])),\n            None,\n        )?;\n        let r = t1.merge(&t2, &[1], true);\n        assert!(r.is_ok());\n        assert_eq!(t1.observations.get(&0).unwrap().len(), 1);\n        assert_eq!(t1.observations.get(&1).unwrap().len(), 1);\n        Ok(())\n    }\n\n    #[test]\n    fn attribute_compatible_match() -> Result<()> {\n        #[derive(Default, Debug, Clone)]\n        pub struct TimeAttrs {\n            start_time: u64,\n            end_time: u64,\n        }\n\n        #[derive(Default, Clone)]\n        pub struct TimeAttrUpdates {\n            time: u64,\n        }\n\n        impl TrackAttributesUpdate<TimeAttrs> for TimeAttrUpdates {\n            fn apply(&self, attrs: &mut TimeAttrs) -> Result<()> {\n                attrs.end_time = self.time;\n                if attrs.start_time == 0 {\n                    attrs.start_time = self.time;\n                }\n                Ok(())\n            }\n        }\n\n        impl TrackAttributes<TimeAttrs, f32> for TimeAttrs {\n            type Update = TimeAttrUpdates;\n            type Lookup = NoopLookup<TimeAttrs, f32>;\n\n            fn compatible(&self, other: &TimeAttrs) -> bool {\n                self.end_time <= other.start_time\n            }\n\n            fn merge(&mut self, other: &TimeAttrs) -> Result<()> {\n                self.start_time = self.start_time.min(other.start_time);\n                self.end_time = self.end_time.max(other.end_time);\n                Ok(())\n            }\n\n            fn baked(&self, _observations: &ObservationsDb<f32>) -> Result<TrackStatus> {\n                if current_time_sec() - self.end_time > 30 {\n                    Ok(TrackStatus::Ready)\n                } else {\n                    Ok(TrackStatus::Pending)\n                }\n            }\n        }\n\n        #[derive(Default, Clone)]\n        struct TimeMetric;\n        impl ObservationMetric<TimeAttrs, f32> for TimeMetric {\n            fn metric(&self, mq: &MetricQuery<'_, TimeAttrs, f32>) -> MetricOutput<f32> {\n                let (e1, e2) = (mq.candidate_observation, mq.track_observation);\n                Some((\n                    f32::calculate_metric_object(&e1.attr().as_ref(), &e2.attr().as_ref()),\n                    match (e1.feature().as_ref(), e2.feature().as_ref()) {\n                        (Some(x), Some(y)) => Some(euclidean(x, y)),\n                        _ => None,\n                    },\n                ))\n            }\n\n            fn optimize(\n                &mut self,\n                _feature_class: u64,\n                _merge_history: &[u64],\n                _attributes: &mut TimeAttrs,\n                features: &mut Vec<Observation<f32>>,\n                _prev_length: usize,\n                _is_merge: bool,\n            ) -> Result<()> {\n                features.sort_by(feature_attributes_sort_dec);\n                features.truncate(20);\n                Ok(())\n            }\n        }\n\n        let mut t1 = Track::new(1, TimeMetric::default(), TimeAttrs::default(), NoopNotifier);\n        t1.add_observation(\n            0,\n            Some(0.3),\n            Some(Feature::from_vec(vec![1f32, 0.0, 0.0])),\n            Some(TimeAttrUpdates { time: 2 }),\n        )?;\n\n        let mut t2 = Track::new(2, TimeMetric::default(), TimeAttrs::default(), NoopNotifier);\n        t2.add_observation(\n            0,\n            Some(0.3),\n            Some(Feature::from_vec(vec![0f32, 1.0f32, 0.0])),\n            Some(TimeAttrUpdates { time: 3 }),\n        )?;\n\n        let dists = t1.distances(&t2, 0);\n        let dists = dists.unwrap();\n        assert_eq!(dists.len(), 1);\n        assert!((*dists[0].feature_distance.as_ref().unwrap() - 2.0_f32.sqrt()).abs() < EPS);\n        assert_eq!(dists[0].to, 2);\n\n        let mut t3 = Track::new(3, TimeMetric::default(), TimeAttrs::default(), NoopNotifier);\n        t3.add_observation(\n            0,\n            Some(0.3),\n            Some(Feature::from_vec(vec![0f32, 1.0f32, 0.0])),\n            Some(TimeAttrUpdates { time: 1 }),\n        )?;\n\n        let dists = t1.distances(&t3, 0);\n        assert!(dists.is_err());\n        Ok(())\n    }\n\n    #[test]\n    fn get_classes() -> Result<()> {\n        let mut t1 = Track::new(1, DefaultMetric, DefaultAttrs, NoopNotifier);\n        t1.add_observation(\n            0,\n            Some(0.3),\n            Some(Feature::from_vec(vec![1f32, 0.0, 0.0])),\n            None,\n        )?;\n\n        t1.add_observation(\n            1,\n            Some(0.3),\n            Some(Feature::from_vec(vec![0f32, 1.0f32, 0.0])),\n            None,\n        )?;\n        let mut classes = t1.get_feature_classes();\n        classes.sort();\n\n        assert_eq!(classes, vec![0, 1]);\n\n        Ok(())\n    }\n\n    #[test]\n    fn attr_metric_update_recover() {\n        use thiserror::Error;\n\n        #[derive(Error, Debug)]\n        enum TestError {\n            #[error(\"Update Error\")]\n            Update,\n            #[error(\"Unable to Merge\")]\n            Merge,\n            #[error(\"Unable to Optimize\")]\n            Optimize,\n        }\n\n        #[derive(Default, Clone, PartialEq, Eq, Debug)]\n        pub struct LocalAttrs {\n            pub count: u32,\n        }\n\n        #[derive(Clone)]\n        pub struct LocalAttrsUpdates {\n            ignore: bool,\n        }\n\n        impl TrackAttributesUpdate<LocalAttrs> for LocalAttrsUpdates {\n            fn apply(&self, attrs: &mut LocalAttrs) -> Result<()> {\n                if !self.ignore {\n                    attrs.count += 1;\n                    if attrs.count > 1 {\n                        Err(TestError::Update.into())\n                    } else {\n                        Ok(())\n                    }\n                } else {\n                    Ok(())\n                }\n            }\n        }\n\n        impl TrackAttributes<LocalAttrs, f32> for LocalAttrs {\n            type Update = LocalAttrsUpdates;\n            type Lookup = NoopLookup<LocalAttrs, f32>;\n\n            fn compatible(&self, _other: &LocalAttrs) -> bool {\n                true\n            }\n\n            fn merge(&mut self, _other: &LocalAttrs) -> Result<()> {\n                Err(TestError::Merge.into())\n            }\n\n            fn baked(&self, _observations: &ObservationsDb<f32>) -> Result<TrackStatus> {\n                Ok(TrackStatus::Pending)\n            }\n        }\n\n        #[derive(Clone)]\n        struct LocalMetric;\n        impl ObservationMetric<LocalAttrs, f32> for LocalMetric {\n            fn metric(&self, mq: &MetricQuery<LocalAttrs, f32>) -> MetricOutput<f32> {\n                let (e1, e2) = (mq.candidate_observation, mq.track_observation);\n                Some((\n                    f32::calculate_metric_object(&e1.attr().as_ref(), &e2.attr().as_ref()),\n                    match (e1.feature().as_ref(), e2.feature().as_ref()) {\n                        (Some(x), Some(y)) => Some(euclidean(x, y)),\n                        _ => None,\n                    },\n                ))\n            }\n\n            fn optimize(\n                &mut self,\n                _feature_class: u64,\n                _merge_history: &[u64],\n                _attributes: &mut LocalAttrs,\n                _features: &mut Vec<Observation<f32>>,\n                prev_length: usize,\n                _is_merge: bool,\n            ) -> Result<()> {\n                if prev_length == 1 {\n                    Err(TestError::Optimize.into())\n                } else {\n                    Ok(())\n                }\n            }\n        }\n\n        let mut t1 = Track::new(1, LocalMetric, LocalAttrs::default(), NoopNotifier);\n        assert_eq!(t1.attributes, LocalAttrs { count: 0 });\n        let res = t1.add_observation(\n            0,\n            Some(0.3),\n            Some(Feature::from_vec(vec![1f32, 0.0, 0.0])),\n            Some(LocalAttrsUpdates { ignore: false }),\n        );\n        assert!(res.is_ok());\n        assert_eq!(t1.attributes, LocalAttrs { count: 1 });\n\n        let res = t1.add_observation(\n            0,\n            Some(0.3),\n            Some(Feature::from_vec(vec![1f32, 0.0, 0.0])),\n            Some(LocalAttrsUpdates { ignore: true }),\n        );\n        assert!(res.is_err());\n        if let Err(e) = res {\n            match e.root_cause().downcast_ref::<TestError>().unwrap() {\n                TestError::Update | TestError::Merge => {\n                    unreachable!();\n                }\n                TestError::Optimize => {}\n            }\n        } else {\n            unreachable!();\n        }\n\n        assert_eq!(t1.attributes, LocalAttrs { count: 1 });\n\n        let mut t2 = Track::new(2, LocalMetric, LocalAttrs::default(), NoopNotifier);\n        assert_eq!(t2.attributes, LocalAttrs { count: 0 });\n        let res = t2.add_observation(\n            0,\n            Some(0.3),\n            Some(Feature::from_vec(vec![1f32, 0.0, 0.0])),\n            Some(LocalAttrsUpdates { ignore: false }),\n        );\n        assert!(res.is_ok());\n        assert_eq!(t2.attributes, LocalAttrs { count: 1 });\n\n        let res = t1.merge(&t2, &[0], true);\n        if let Err(e) = res {\n            match e.root_cause().downcast_ref::<TestError>().unwrap() {\n                TestError::Update | TestError::Optimize => {\n                    unreachable!();\n                }\n                TestError::Merge => {}\n            }\n        } else {\n            unreachable!();\n        }\n        assert_eq!(t1.attributes, LocalAttrs { count: 1 });\n    }\n\n    #[test]\n    fn merge_history() -> Result<()> {\n        let mut t1 = Track::new(0, DefaultMetric, DefaultAttrs, NoopNotifier);\n        let mut t2 = Track::new(1, DefaultMetric, DefaultAttrs, NoopNotifier);\n\n        t1.add_observation(\n            0,\n            Some(0.3),\n            Some(Feature::from_vec(vec![1f32, 0.0, 0.0])),\n            None,\n        )?;\n\n        t2.add_observation(\n            0,\n            Some(0.3),\n            Some(Feature::from_vec(vec![0f32, 1.0f32, 0.0])),\n            None,\n        )?;\n\n        let mut track_with_merge_history = t1.clone();\n        let _r = track_with_merge_history.merge(&t2, &[0], true);\n        assert_eq!(track_with_merge_history.merge_history, vec![0, 1]);\n\n        let _r = t1.merge(&t2, &[0], false);\n        assert_eq!(t1.merge_history, vec![0]);\n\n        Ok(())\n    }\n\n    #[test]\n    fn unit_track() {\n        #[derive(Clone)]\n        pub struct UnitAttrs;\n\n        #[derive(Clone)]\n        pub struct UnitAttrUpdates;\n\n        impl TrackAttributesUpdate<UnitAttrs> for UnitAttrUpdates {\n            fn apply(&self, _attrs: &mut UnitAttrs) -> Result<()> {\n                Ok(())\n            }\n        }\n\n        impl TrackAttributes<UnitAttrs, ()> for UnitAttrs {\n            type Update = UnitAttrUpdates;\n            type Lookup = NoopLookup<UnitAttrs, ()>;\n\n            fn compatible(&self, _other: &UnitAttrs) -> bool {\n                true\n            }\n\n            fn merge(&mut self, _other: &UnitAttrs) -> Result<()> {\n                Ok(())\n            }\n\n            fn baked(&self, _observations: &ObservationsDb<()>) -> Result<TrackStatus> {\n                Ok(TrackStatus::Pending)\n            }\n        }\n\n        #[derive(Clone)]\n        struct UnitMetric;\n        impl ObservationMetric<UnitAttrs, ()> for UnitMetric {\n            fn metric(&self, mq: &MetricQuery<UnitAttrs, ()>) -> MetricOutput<()> {\n                let (e1, e2) = (mq.candidate_observation, mq.track_observation);\n                Some((\n                    None,\n                    match (e1.1.as_ref(), e2.1.as_ref()) {\n                        (Some(x), Some(y)) => Some(euclidean(x, y)),\n                        _ => None,\n                    },\n                ))\n            }\n\n            fn optimize(\n                &mut self,\n                _feature_class: u64,\n                _merge_history: &[u64],\n                _attributes: &mut UnitAttrs,\n                features: &mut Vec<Observation<()>>,\n                _prev_length: usize,\n                _is_merge: bool,\n            ) -> Result<()> {\n                features.sort_by(feature_attributes_sort_dec);\n                features.truncate(20);\n                Ok(())\n            }\n        }\n\n        let _t1 = Track::new(1, UnitMetric, UnitAttrs, NoopNotifier);\n    }\n\n    #[test]\n    fn lookup() {\n        #[derive(Default, Clone)]\n        struct Lookup;\n        impl LookupRequest<LookupAttrs, f32> for Lookup {\n            fn lookup(\n                &self,\n                _attributes: &LookupAttrs,\n                _observations: &ObservationsDb<f32>,\n                _merge_history: &[u64],\n            ) -> bool {\n                true\n            }\n        }\n\n        #[derive(Clone, Default)]\n        struct LookupAttrs;\n\n        #[derive(Clone)]\n        pub struct LookupAttributeUpdate;\n\n        impl TrackAttributesUpdate<LookupAttrs> for LookupAttributeUpdate {\n            fn apply(&self, _attrs: &mut LookupAttrs) -> Result<()> {\n                Ok(())\n            }\n        }\n\n        impl TrackAttributes<LookupAttrs, f32> for LookupAttrs {\n            type Update = LookupAttributeUpdate;\n            type Lookup = Lookup;\n\n            fn compatible(&self, _other: &LookupAttrs) -> bool {\n                true\n            }\n\n            fn merge(&mut self, _other: &LookupAttrs) -> Result<()> {\n                Ok(())\n            }\n\n            fn baked(&self, _observations: &ObservationsDb<f32>) -> Result<TrackStatus> {\n                Ok(TrackStatus::Ready)\n            }\n        }\n\n        #[derive(Clone)]\n        pub struct LookupMetric;\n\n        impl ObservationMetric<LookupAttrs, f32> for LookupMetric {\n            fn metric(&self, mq: &MetricQuery<LookupAttrs, f32>) -> MetricOutput<f32> {\n                let (e1, e2) = (mq.candidate_observation, mq.track_observation);\n                Some((\n                    f32::calculate_metric_object(&e1.attr().as_ref(), &e2.attr().as_ref()),\n                    match (e1.feature().as_ref(), e2.feature().as_ref()) {\n                        (Some(x), Some(y)) => Some(euclidean(x, y)),\n                        _ => None,\n                    },\n                ))\n            }\n\n            fn optimize(\n                &mut self,\n                _feature_class: u64,\n                _merge_history: &[u64],\n                _attrs: &mut LookupAttrs,\n                _features: &mut Vec<Observation<f32>>,\n                _prev_length: usize,\n                _is_merge: bool,\n            ) -> Result<()> {\n                Ok(())\n            }\n        }\n\n        let t: Track<LookupAttrs, LookupMetric, f32> = TrackBuilder::default()\n            .metric(LookupMetric)\n            .attributes(LookupAttrs)\n            .notifier(NoopNotifier)\n            .build()\n            .unwrap();\n        assert!(t.lookup(&Lookup));\n    }\n}\n"
  },
  {
    "path": "src/trackers/batch.rs",
    "content": "use crate::prelude::SortTrack;\nuse crossbeam::channel::{Receiver, Sender};\nuse log::debug;\n\nuse std::collections::HashMap;\nuse std::sync::{Arc, Mutex};\n\npub type BatchRecords<T> = HashMap<u64, Vec<T>>;\npub type SceneTracks = (u64, Vec<SortTrack>);\n\n#[derive(Debug, Clone)]\npub struct PredictionBatchRequest<T> {\n    batch: BatchRecords<T>,\n    sender: Sender<SceneTracks>,\n    batch_size: Arc<Mutex<usize>>,\n}\n\n#[derive(Clone, Debug)]\npub struct PredictionBatchResult {\n    receiver: Receiver<SceneTracks>,\n    batch_size: Arc<Mutex<usize>>,\n}\n\nimpl PredictionBatchResult {\n    pub fn ready(&self) -> bool {\n        !self.receiver.is_empty()\n    }\n\n    pub fn get(&self) -> SceneTracks {\n        self.receiver\n            .recv()\n            .expect(\"Receiver must always receive batch computation result\")\n    }\n\n    pub fn batch_size(&self) -> usize {\n        *self.batch_size.lock().unwrap()\n    }\n}\n\n#[cfg(feature = \"python\")]\npub mod python {\n    use crate::trackers::sort::python::PySortTrack;\n\n    use super::PredictionBatchResult;\n    use pyo3::prelude::*;\n\n    pub type PySceneTracks = (u64, Vec<PySortTrack>);\n\n    #[pyclass]\n    #[derive(Clone, Debug)]\n    #[pyo3(name = \"PredictionBatchResult\")]\n    pub struct PyPredictionBatchResult(pub(crate) PredictionBatchResult);\n\n    #[pymethods]\n    impl PyPredictionBatchResult {\n        pub fn ready(&self) -> bool {\n            self.0.ready()\n        }\n\n        #[pyo3(signature = ())]\n        fn get(&self) -> PySceneTracks {\n            Python::with_gil(|py| py.allow_threads(|| unsafe { std::mem::transmute(self.0.get()) }))\n        }\n\n        pub fn batch_size(&self) -> usize {\n            self.0.batch_size()\n        }\n    }\n}\n\nimpl<T> PredictionBatchRequest<T> {\n    pub fn get_sender(&self) -> Sender<SceneTracks> {\n        self.sender.clone()\n    }\n\n    #[allow(dead_code)]\n    pub(crate) fn send(&self, res: SceneTracks) -> bool {\n        let res = self.sender.send(res);\n        if let Err(e) = res {\n            debug!(\n                \"Error occurred when sending results to the batch result object. Error is: {:?}\",\n                e\n            );\n            false\n        } else {\n            true\n        }\n    }\n\n    pub fn batch_size(&self) -> usize {\n        *self.batch_size.lock().unwrap()\n    }\n\n    pub fn add(&mut self, scene_id: u64, elt: T) {\n        let vec = self.batch.get_mut(&scene_id);\n        if let Some(vec) = vec {\n            vec.push(elt);\n        } else {\n            self.batch.insert(scene_id, vec![elt]);\n        }\n        let mut batch_size = self.batch_size.lock().unwrap();\n        *batch_size = self.batch.len();\n    }\n\n    pub fn new() -> (Self, PredictionBatchResult) {\n        let (sender, receiver) = crossbeam::channel::bounded(1);\n        let batch_size = Arc::new(Mutex::new(0));\n        (\n            Self {\n                batch: BatchRecords::default(),\n                sender,\n                batch_size: batch_size.clone(),\n            },\n            PredictionBatchResult {\n                receiver,\n                batch_size,\n            },\n        )\n    }\n\n    pub fn get_batch(&self) -> &BatchRecords<T> {\n        &self.batch\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::prelude::Universal2DBox;\n    use crate::trackers::batch::PredictionBatchRequest;\n\n    #[test]\n    fn test() {\n        let (mut request, result) = PredictionBatchRequest::<Universal2DBox>::new();\n        request.add(0, Universal2DBox::new(0.0, 0.0, Some(0.5), 1.0, 5.0));\n        request.add(0, Universal2DBox::new(5.0, 5.0, Some(0.0), 1.5, 10.0));\n        request.add(1, Universal2DBox::new(0.0, 0.0, Some(1.0), 0.7, 5.1));\n        let _batch = request.get_batch();\n        assert_eq!(result.batch_size(), 2);\n\n        assert!(request.send((0, vec![])));\n        assert_eq!(result.ready(), true);\n        let res = result.get();\n        assert_eq!(res.0, 0);\n        assert!(res.1.is_empty());\n        drop(result);\n        assert!(!request.send((0, vec![])));\n    }\n}\n"
  },
  {
    "path": "src/trackers/epoch_db.rs",
    "content": "use crate::track::TrackStatus;\nuse anyhow::Result;\nuse std::collections::HashMap;\nuse std::sync::RwLock;\n\npub trait EpochDb {\n    fn epoch_db(&self) -> &Option<RwLock<HashMap<u64, usize>>>;\n    fn max_idle_epochs(&self) -> usize;\n\n    fn skip_epochs_for_scene(&self, scene_id: u64, n: usize) {\n        if let Some(epoch_store) = self.epoch_db() {\n            let mut epoch_store = epoch_store.write().unwrap();\n            if let Some(epoch) = epoch_store.get_mut(&scene_id) {\n                *epoch += n;\n            } else {\n                epoch_store.insert(scene_id, n);\n            }\n        }\n    }\n\n    fn current_epoch_with_scene(&self, scene_id: u64) -> Option<usize> {\n        if let Some(epoch_store) = self.epoch_db() {\n            let mut epoch_store = epoch_store.write().unwrap();\n            let epoch = epoch_store.get_mut(&scene_id);\n            if let Some(epoch) = epoch {\n                Some(*epoch)\n            } else {\n                Some(0)\n            }\n        } else {\n            None\n        }\n    }\n\n    fn next_epoch(&self, scene_id: u64) -> Option<usize> {\n        if let Some(epoch_store) = self.epoch_db() {\n            let mut epoch_store = epoch_store.write().unwrap();\n            let epoch = epoch_store.get_mut(&scene_id);\n            if let Some(epoch) = epoch {\n                *epoch += 1;\n                Some(*epoch)\n            } else {\n                epoch_store.insert(scene_id, 1);\n                Some(1)\n            }\n        } else {\n            None\n        }\n    }\n\n    fn baked(&self, scene_id: u64, last_updated: usize) -> Result<TrackStatus> {\n        if let Some(current_epoch) = &self.epoch_db() {\n            let current_epoch = current_epoch.read().unwrap();\n            if last_updated + self.max_idle_epochs() < *current_epoch.get(&scene_id).unwrap_or(&0) {\n                Ok(TrackStatus::Wasted)\n            } else {\n                Ok(TrackStatus::Pending)\n            }\n        } else {\n            // If epoch expiration is not set the tracks are always ready.\n            // If set, then only when certain amount of epochs pass they are Wasted.\n            //\n            Ok(TrackStatus::Ready)\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::track::TrackStatus;\n    use crate::trackers::epoch_db::EpochDb;\n    use std::collections::HashMap;\n    use std::sync::RwLock;\n\n    #[test]\n    fn test_epoch_db() {\n        #[derive(Debug, Default)]\n        pub struct DbOptions {\n            /// The map that stores current epochs for the scene_id\n            epoch_db: Option<RwLock<HashMap<u64, usize>>>,\n            /// The maximum number of epochs without update while the track is alive\n            max_idle_epochs: usize,\n        }\n\n        impl EpochDb for DbOptions {\n            fn epoch_db(&self) -> &Option<RwLock<HashMap<u64, usize>>> {\n                &self.epoch_db\n            }\n\n            fn max_idle_epochs(&self) -> usize {\n                self.max_idle_epochs\n            }\n        }\n\n        let db = DbOptions {\n            epoch_db: Some(RwLock::new(HashMap::default())),\n            max_idle_epochs: 2,\n        };\n\n        assert_eq!(db.next_epoch(0), Some(1));\n        assert_eq!(db.next_epoch(0), Some(2));\n\n        assert_eq!(db.next_epoch(1), Some(1));\n        assert_eq!(db.next_epoch(1), Some(2));\n\n        assert_eq!(db.current_epoch_with_scene(0), Some(2));\n        assert_eq!(db.current_epoch_with_scene(1), Some(2));\n        assert_eq!(db.current_epoch_with_scene(2), Some(0));\n\n        db.skip_epochs_for_scene(0, 10);\n        db.skip_epochs_for_scene(1, 2);\n\n        assert!(matches!(db.baked(0, 2), Ok(TrackStatus::Wasted)));\n        assert!(matches!(db.baked(1, 2), Ok(TrackStatus::Pending)));\n\n        db.skip_epochs_for_scene(1, 1);\n        assert!(matches!(db.baked(1, 2), Ok(TrackStatus::Wasted)));\n    }\n}\n"
  },
  {
    "path": "src/trackers/kalman_prediction.rs",
    "content": "use crate::utils::bbox::Universal2DBox;\nuse crate::utils::kalman::kalman_2d_box::{Universal2DBoxKalmanFilter, DIM_2D_BOX_X2};\nuse crate::utils::kalman::KalmanState;\n\npub trait TrackAttributesKalmanPrediction {\n    fn get_state(&self) -> Option<KalmanState<{ DIM_2D_BOX_X2 }>>;\n    fn set_state(&mut self, state: KalmanState<{ DIM_2D_BOX_X2 }>);\n\n    fn get_position_weight(&self) -> f32;\n\n    fn get_velocity_weight(&self) -> f32;\n\n    fn make_prediction(&mut self, observation_bbox: &Universal2DBox) -> Universal2DBox {\n        let f =\n            Universal2DBoxKalmanFilter::new(self.get_position_weight(), self.get_velocity_weight());\n\n        let current_state = if let Some(state) = self.get_state() {\n            state\n        } else {\n            f.initiate(observation_bbox)\n        };\n\n        let prediction = f.predict(&current_state);\n\n        let new_state = f.update(&prediction, observation_bbox);\n        self.set_state(new_state);\n\n        let mut res = Universal2DBox::try_from(new_state).unwrap();\n        res.confidence = observation_bbox.confidence;\n\n        res\n    }\n}\n"
  },
  {
    "path": "src/trackers/sort/batch_api.rs",
    "content": "use crate::prelude::{\n    NoopNotifier, ObservationBuilder, PositionalMetricType, SortTrack, TrackStoreBuilder,\n    Universal2DBox,\n};\nuse crate::store::track_distance::TrackDistanceOkIterator;\nuse crate::store::TrackStore;\nuse crate::track::Track;\nuse crate::trackers::batch::{PredictionBatchRequest, PredictionBatchResult, SceneTracks};\nuse crate::trackers::epoch_db::EpochDb;\nuse crate::trackers::sort::metric::SortMetric;\nuse crate::trackers::sort::voting::SortVoting;\nuse crate::trackers::sort::{\n    AutoWaste, SortAttributes, SortAttributesOptions, SortAttributesUpdate, SortLookup,\n    DEFAULT_AUTO_WASTE_PERIODICITY, MAHALANOBIS_NEW_TRACK_THRESHOLD,\n};\n\nuse crate::trackers::spatio_temporal_constraints::SpatioTemporalConstraints;\nuse crate::trackers::tracker_api::TrackerAPI;\nuse crate::voting::Voting;\nuse crossbeam::channel::{Receiver, Sender};\nuse log::warn;\nuse rand::Rng;\nuse std::collections::HashMap;\nuse std::mem;\nuse std::sync::{Arc, Condvar, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};\nuse std::thread::{spawn, JoinHandle};\n\ntype VotingSenderChannel = Sender<VotingCommands>;\ntype VotingReceiverChannel = Receiver<VotingCommands>;\n\ntype MiddlewareSortTrackStore = TrackStore<SortAttributes, SortMetric, Universal2DBox>;\ntype MiddlewareSortTrack = Track<SortAttributes, SortMetric, Universal2DBox>;\ntype BatchBusyMonitor = Arc<(Mutex<usize>, Condvar)>;\n\nenum VotingCommands {\n    Distances {\n        scene_id: u64,\n        distances: TrackDistanceOkIterator<Universal2DBox>,\n        channel: Sender<SceneTracks>,\n        tracks: Vec<MiddlewareSortTrack>,\n        monitor: BatchBusyMonitor,\n    },\n    Exit,\n}\n\npub struct BatchSort {\n    monitor: Option<BatchBusyMonitor>,\n    store: Arc<RwLock<MiddlewareSortTrackStore>>,\n    wasted_store: RwLock<MiddlewareSortTrackStore>,\n    opts: Arc<SortAttributesOptions>,\n    voting_threads: Vec<(VotingSenderChannel, JoinHandle<()>)>,\n    auto_waste: AutoWaste,\n}\n\nimpl Drop for BatchSort {\n    fn drop(&mut self) {\n        let voting_threads = mem::take(&mut self.voting_threads);\n        for (tx, t) in voting_threads {\n            tx.send(VotingCommands::Exit)\n                .expect(\"Voting thread must be alive.\");\n            drop(tx);\n            t.join()\n                .expect(\"Voting thread is expected to shutdown successfully.\");\n        }\n    }\n}\n\nfn voting_thread(\n    store: Arc<RwLock<MiddlewareSortTrackStore>>,\n    rx: VotingReceiverChannel,\n    method: PositionalMetricType,\n    track_id: Arc<RwLock<u64>>,\n) {\n    while let Ok(command) = rx.recv() {\n        match command {\n            VotingCommands::Distances {\n                scene_id,\n                distances,\n                channel,\n                tracks,\n                monitor,\n            } => {\n                let candidates_num = tracks.len();\n                let tracks_num = {\n                    let store = store.read().expect(\"Access to store must always succeed\");\n                    store.shard_stats().iter().sum()\n                };\n\n                let voting = SortVoting::new(\n                    match method {\n                        PositionalMetricType::Mahalanobis => MAHALANOBIS_NEW_TRACK_THRESHOLD,\n                        PositionalMetricType::IoU(t) => t,\n                    },\n                    candidates_num,\n                    tracks_num,\n                );\n\n                let winners = voting.winners(distances);\n                let mut res = Vec::default();\n                for mut t in tracks {\n                    let source = t.get_track_id();\n                    let tid = {\n                        let mut track_id = track_id.write().unwrap();\n                        *track_id += 1;\n                        *track_id\n                    };\n                    let track_id: u64 = if let Some(dest) = winners.get(&source) {\n                        let dest = dest[0];\n                        if dest == source {\n                            t.set_track_id(tid);\n                            store\n                                .write()\n                                .expect(\"Access to store must always succeed\")\n                                .add_track(t)\n                                .unwrap();\n                            tid\n                        } else {\n                            store\n                                .write()\n                                .expect(\"Access to store must always succeed\")\n                                .merge_external(dest, &t, Some(&[0]), false)\n                                .unwrap();\n                            dest\n                        }\n                    } else {\n                        t.set_track_id(tid);\n                        store\n                            .write()\n                            .expect(\"Access to store must always succeed\")\n                            .add_track(t)\n                            .unwrap();\n                        tid\n                    };\n\n                    let store = store.read().expect(\"Access to store must always succeed\");\n                    let shard = store.get_store(track_id as usize);\n                    let track = shard.get(&track_id).unwrap();\n\n                    res.push(SortTrack::from(track))\n                }\n                let res = channel.send((scene_id, res));\n                if let Err(e) = res {\n                    warn!(\"Unable to send results to a caller, likely the caller already closed the channel. Error is: {:?}\", e);\n                }\n                let (lock, cvar) = &*monitor;\n                let mut lock = lock.lock().unwrap();\n                *lock -= 1;\n                cvar.notify_one();\n            }\n            VotingCommands::Exit => break,\n        }\n    }\n}\n\nimpl BatchSort {\n    #[allow(clippy::too_many_arguments)]\n    pub fn new(\n        distance_shards: usize,\n        voting_shards: usize,\n        bbox_history: usize,\n        max_idle_epochs: usize,\n        method: PositionalMetricType,\n        min_confidence: f32,\n        spatio_temporal_constraints: Option<SpatioTemporalConstraints>,\n        kalman_position_weight: f32,\n        kalman_velocity_weight: f32,\n    ) -> Self {\n        assert!(bbox_history > 0);\n        let epoch_db = RwLock::new(HashMap::default());\n        let opts = Arc::new(SortAttributesOptions::new(\n            Some(epoch_db),\n            max_idle_epochs,\n            bbox_history,\n            spatio_temporal_constraints.unwrap_or_default(),\n            kalman_position_weight,\n            kalman_velocity_weight,\n        ));\n\n        let store = Arc::new(RwLock::new(\n            TrackStoreBuilder::new(distance_shards)\n                .default_attributes(SortAttributes::new(opts.clone()))\n                .metric(SortMetric::new(method, min_confidence))\n                .notifier(NoopNotifier)\n                .build(),\n        ));\n\n        let wasted_store = RwLock::new(\n            TrackStoreBuilder::new(distance_shards)\n                .default_attributes(SortAttributes::new(opts.clone()))\n                .metric(SortMetric::new(method, min_confidence))\n                .notifier(NoopNotifier)\n                .build(),\n        );\n\n        let track_id = Arc::new(RwLock::new(0));\n\n        let voting_threads = (0..voting_shards)\n            .map(|_e| {\n                let (tx, rx) = crossbeam::channel::unbounded();\n                let thread_store = store.clone();\n                let thread_track_id = track_id.clone();\n                (\n                    tx,\n                    spawn(move || voting_thread(thread_store, rx, method, thread_track_id)),\n                )\n            })\n            .collect::<Vec<_>>();\n\n        Self {\n            monitor: None,\n            store,\n            wasted_store,\n            opts,\n            voting_threads,\n            auto_waste: AutoWaste {\n                periodicity: DEFAULT_AUTO_WASTE_PERIODICITY,\n                counter: DEFAULT_AUTO_WASTE_PERIODICITY,\n            },\n        }\n    }\n\n    pub fn predict(\n        &mut self,\n        batch_request: PredictionBatchRequest<(Universal2DBox, Option<i64>)>,\n    ) {\n        if self.auto_waste.counter == 0 {\n            self.auto_waste();\n            self.auto_waste.counter = self.auto_waste.periodicity;\n        } else {\n            self.auto_waste.counter -= 1;\n        }\n\n        if let Some(m) = &self.monitor {\n            let (lock, cvar) = &**m;\n            let _guard = cvar.wait_while(lock.lock().unwrap(), |v| *v > 0).unwrap();\n        }\n\n        self.monitor = Some(Arc::new((\n            Mutex::new(batch_request.batch_size()),\n            Condvar::new(),\n        )));\n\n        for (i, (scene_id, bboxes)) in batch_request.get_batch().iter().enumerate() {\n            let mut rng = rand::thread_rng();\n            let epoch = self.opts.next_epoch(*scene_id).unwrap();\n\n            let tracks = bboxes\n                .iter()\n                .map(|(bb, custom_object_id)| {\n                    self.store\n                        .read()\n                        .expect(\"Access to store must always succeed\")\n                        .new_track(rng.gen())\n                        .observation(\n                            ObservationBuilder::new(0)\n                                .observation_attributes(bb.clone())\n                                .track_attributes_update(SortAttributesUpdate::new_with_scene(\n                                    epoch,\n                                    *scene_id,\n                                    *custom_object_id,\n                                ))\n                                .build(),\n                        )\n                        .build()\n                        .expect(\"Track creation must always succeed!\")\n                })\n                .collect::<Vec<_>>();\n\n            let (dists, errs) = {\n                let mut store = self\n                    .store\n                    .write()\n                    .expect(\"Access to store must always succeed\");\n                store.foreign_track_distances(tracks.clone(), 0, false)\n            };\n\n            assert!(errs.all().is_empty());\n            let thread_id = i % self.voting_threads.len();\n            self.voting_threads[thread_id]\n                .0\n                .send(VotingCommands::Distances {\n                    monitor: self.monitor.as_ref().unwrap().clone(),\n                    scene_id: *scene_id,\n                    distances: dists.into_iter(),\n                    channel: batch_request.get_sender(),\n                    tracks,\n                })\n                .expect(\"Sending voting request to voting thread must not fail\");\n        }\n    }\n\n    pub fn idle_tracks(&mut self) -> Vec<SortTrack> {\n        self.idle_tracks_with_scene(0)\n    }\n\n    pub fn idle_tracks_with_scene(&mut self, scene_id: u64) -> Vec<SortTrack> {\n        let store = self.store.read().unwrap();\n\n        store\n            .lookup(SortLookup::IdleLookup(scene_id))\n            .iter()\n            .map(|(track_id, _status)| {\n                let shard = store.get_store(*track_id as usize);\n                let track = shard.get(track_id).unwrap();\n                SortTrack::from(track)\n            })\n            .collect()\n    }\n}\n\nimpl TrackerAPI<SortAttributes, SortMetric, Universal2DBox, SortAttributesOptions, NoopNotifier>\n    for BatchSort\n{\n    fn get_auto_waste_obj_mut(&mut self) -> &mut AutoWaste {\n        &mut self.auto_waste\n    }\n\n    fn get_opts(&self) -> &SortAttributesOptions {\n        &self.opts\n    }\n\n    fn get_main_store_mut(&mut self) -> RwLockWriteGuard<MiddlewareSortTrackStore> {\n        self.store.write().unwrap()\n    }\n\n    fn get_wasted_store_mut(&mut self) -> RwLockWriteGuard<MiddlewareSortTrackStore> {\n        self.wasted_store.write().unwrap()\n    }\n\n    fn get_main_store(&self) -> RwLockReadGuard<MiddlewareSortTrackStore> {\n        self.store.read().unwrap()\n    }\n\n    fn get_wasted_store(&self) -> RwLockReadGuard<MiddlewareSortTrackStore> {\n        self.wasted_store.read().unwrap()\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct SortPredictionBatchRequest {\n    pub batch: PredictionBatchRequest<(Universal2DBox, Option<i64>)>,\n    pub result: Option<PredictionBatchResult>,\n}\n\nimpl SortPredictionBatchRequest {\n    pub fn new() -> Self {\n        let (batch, result) = PredictionBatchRequest::new();\n\n        Self {\n            batch,\n            result: Some(result),\n        }\n    }\n\n    pub fn add(&mut self, scene_id: u64, bbox: Universal2DBox, custom_object_id: Option<i64>) {\n        self.batch.add(scene_id, (bbox, custom_object_id))\n    }\n}\n\nimpl Default for SortPredictionBatchRequest {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\n#[cfg(feature = \"python\")]\npub mod python {\n    use crate::{\n        trackers::{\n            batch::python::PyPredictionBatchResult,\n            sort::{\n                python::{PyPositionalMetricType, PySortTrack, PyWastedSortTrack},\n                WastedSortTrack,\n            },\n            spatio_temporal_constraints::python::PySpatioTemporalConstraints,\n            tracker_api::TrackerAPI,\n        },\n        utils::bbox::python::PyUniversal2DBox,\n    };\n\n    use super::{BatchSort, SortPredictionBatchRequest};\n    use pyo3::prelude::*;\n\n    #[pyclass]\n    #[pyo3(name = \"BatchSort\")]\n    pub struct PyBatchSort(pub(crate) BatchSort);\n\n    #[pymethods]\n    impl PyBatchSort {\n        #[new]\n        #[pyo3(signature = (\n        distance_shards = 4,\n        voting_shards = 4,\n        bbox_history = 1,\n        max_idle_epochs = 5,\n        method = None,\n        min_confidence = 0.05,\n        spatio_temporal_constraints = None,\n        kalman_position_weight = 1.0 / 20.0,\n        kalman_velocity_weight = 1.0 / 160.0\n    ))]\n        #[allow(clippy::too_many_arguments)]\n        pub fn new(\n            distance_shards: i64,\n            voting_shards: i64,\n            bbox_history: i64,\n            max_idle_epochs: i64,\n            method: Option<PyPositionalMetricType>,\n            min_confidence: f32,\n            spatio_temporal_constraints: Option<PySpatioTemporalConstraints>,\n            kalman_position_weight: f32,\n            kalman_velocity_weight: f32,\n        ) -> Self {\n            Self(BatchSort::new(\n                distance_shards\n                    .try_into()\n                    .expect(\"Positive number expected\"),\n                voting_shards.try_into().expect(\"Positive number expected\"),\n                bbox_history.try_into().expect(\"Positive number expected\"),\n                max_idle_epochs\n                    .try_into()\n                    .expect(\"Positive number expected\"),\n                method.unwrap_or(PyPositionalMetricType::maha()).0,\n                min_confidence,\n                spatio_temporal_constraints.map(|x| x.0),\n                kalman_position_weight,\n                kalman_velocity_weight,\n            ))\n        }\n\n        #[pyo3(signature = (n))]\n        fn skip_epochs(&mut self, n: i64) {\n            assert!(n > 0);\n            self.0.skip_epochs(n.try_into().unwrap())\n        }\n\n        #[pyo3(signature = (scene_id, n))]\n        fn skip_epochs_for_scene(&mut self, scene_id: i64, n: i64) {\n            assert!(n > 0 && scene_id >= 0);\n            self.0\n                .skip_epochs_for_scene(scene_id.try_into().unwrap(), n.try_into().unwrap())\n        }\n\n        /// Get the amount of stored tracks per shard\n        ///\n        #[pyo3(signature = ())]\n        fn shard_stats(&self) -> Vec<i64> {\n            Python::with_gil(|py| {\n                py.allow_threads(|| {\n                    self.0\n                        .store\n                        .read()\n                        .unwrap()\n                        .shard_stats()\n                        .into_iter()\n                        .map(|e| i64::try_from(e).unwrap())\n                        .collect()\n                })\n            })\n        }\n\n        /// Get the current epoch for `scene_id` == 0\n        ///\n        #[pyo3(signature = ())]\n        fn current_epoch(&self) -> i64 {\n            self.0.current_epoch_with_scene(0).try_into().unwrap()\n        }\n\n        /// Get the current epoch for `scene_id`\n        ///\n        /// # Parameters\n        /// * `scene_id` - scene id\n        ///\n        #[pyo3(\n        signature = (scene_id)\n    )]\n        fn current_epoch_with_scene(&self, scene_id: i64) -> isize {\n            assert!(scene_id >= 0);\n            self.0\n                .current_epoch_with_scene(scene_id.try_into().unwrap())\n                .try_into()\n                .unwrap()\n        }\n\n        /// Receive tracking information for observed bboxes of `scene_id` == 0\n        ///\n        /// # Parameters\n        /// * `bboxes` - bounding boxes received from a detector\n        ///\n        #[pyo3(signature = (batch))]\n        fn predict(&mut self, mut batch: PySortPredictionBatchRequest) -> PyPredictionBatchResult {\n            self.0.predict(batch.0.batch);\n            PyPredictionBatchResult(batch.0.result.take().unwrap())\n        }\n\n        /// Remove all the tracks with expired life\n        ///\n        #[pyo3(signature = ())]\n        fn wasted(&mut self) -> Vec<PyWastedSortTrack> {\n            Python::with_gil(|py| {\n                py.allow_threads(|| {\n                    self.0\n                        .wasted()\n                        .into_iter()\n                        .map(WastedSortTrack::from)\n                        .map(PyWastedSortTrack)\n                        .collect()\n                })\n            })\n        }\n\n        /// Clear all tracks with expired life\n        ///\n        #[pyo3(signature = ())]\n        pub fn clear_wasted(&mut self) {\n            Python::with_gil(|py| {\n                py.allow_threads(|| self.0.clear_wasted());\n            })\n        }\n\n        /// Get idle tracks with not expired life\n        ///\n        #[pyo3(signature = (scene_id))]\n        pub fn idle_tracks(&mut self, scene_id: i64) -> Vec<PySortTrack> {\n            Python::with_gil(|py| {\n                py.allow_threads(|| unsafe {\n                    std::mem::transmute(self.0.idle_tracks_with_scene(scene_id.try_into().unwrap()))\n                })\n            })\n        }\n    }\n\n    #[pyclass]\n    #[pyo3(name = \"SortPredictionBatchRequest\")]\n    #[derive(Debug, Clone)]\n    pub struct PySortPredictionBatchRequest(pub(crate) SortPredictionBatchRequest);\n\n    #[pymethods]\n    impl PySortPredictionBatchRequest {\n        #[new]\n        fn new() -> Self {\n            Self(SortPredictionBatchRequest::new())\n        }\n\n        #[pyo3(signature = (scene_id, bbox, custom_object_id=None))]\n        fn add(&mut self, scene_id: u64, bbox: PyUniversal2DBox, custom_object_id: Option<i64>) {\n            self.0.add(scene_id, bbox.0, custom_object_id)\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::prelude::BoundingBox;\n    use crate::prelude::PositionalMetricType::Mahalanobis;\n    use crate::trackers::batch::PredictionBatchRequest;\n    use crate::trackers::sort::batch_api::BatchSort;\n    use crate::trackers::sort::metric::DEFAULT_MINIMAL_SORT_CONFIDENCE;\n\n    #[test]\n    fn new_drop() {\n        let mut bs = BatchSort::new(\n            1,\n            1,\n            1,\n            1,\n            Mahalanobis,\n            DEFAULT_MINIMAL_SORT_CONFIDENCE,\n            None,\n            1.0 / 20.0,\n            1.0 / 160.0,\n        );\n        let (mut batch, res) = PredictionBatchRequest::new();\n        batch.add(0, (BoundingBox::new(0.0, 0.0, 5.0, 10.0).into(), Some(1)));\n        batch.add(1, (BoundingBox::new(0.0, 0.0, 5.0, 10.0).into(), Some(2)));\n\n        bs.predict(batch);\n\n        for _ in 0..res.batch_size() {\n            let data = res.get();\n            dbg!(data);\n        }\n    }\n}\n"
  },
  {
    "path": "src/trackers/sort/metric.rs",
    "content": "use crate::track::{\n    MetricOutput, MetricQuery, Observation, ObservationAttributes, ObservationMetric,\n    ObservationMetricOk,\n};\nuse crate::trackers::kalman_prediction::TrackAttributesKalmanPrediction;\nuse crate::trackers::sort::PositionalMetricType;\nuse crate::trackers::sort::{SortAttributes, DEFAULT_SORT_IOU_THRESHOLD};\nuse crate::utils::bbox::Universal2DBox;\nuse crate::utils::kalman::kalman_2d_box::Universal2DBoxKalmanFilter;\n\npub const DEFAULT_MINIMAL_SORT_CONFIDENCE: f32 = 0.05;\n\n#[derive(Clone)]\npub struct SortMetric {\n    method: PositionalMetricType,\n    min_confidence: f32,\n}\n\nimpl Default for SortMetric {\n    fn default() -> Self {\n        Self::new(\n            PositionalMetricType::IoU(DEFAULT_SORT_IOU_THRESHOLD),\n            DEFAULT_MINIMAL_SORT_CONFIDENCE,\n        )\n    }\n}\n\nimpl SortMetric {\n    pub fn new(method: PositionalMetricType, min_confidence: f32) -> Self {\n        Self {\n            method,\n            min_confidence,\n        }\n    }\n}\n\nimpl ObservationMetric<SortAttributes, Universal2DBox> for SortMetric {\n    fn metric(&self, mq: &MetricQuery<SortAttributes, Universal2DBox>) -> MetricOutput<f32> {\n        let (candidate_bbox, track_bbox) = (\n            mq.candidate_observation.attr().as_ref().unwrap(),\n            mq.track_observation.attr().as_ref().unwrap(),\n        );\n        let conf = if candidate_bbox.confidence < self.min_confidence {\n            self.min_confidence\n        } else {\n            candidate_bbox.confidence\n        };\n\n        if Universal2DBox::too_far(candidate_bbox, track_bbox) {\n            None\n        } else {\n            Some(match self.method {\n                PositionalMetricType::Mahalanobis => {\n                    let state = mq.track_attrs.get_state().unwrap();\n                    let f = Universal2DBoxKalmanFilter::new(\n                        mq.track_attrs.get_position_weight(),\n                        mq.track_attrs.get_velocity_weight(),\n                    );\n                    let dist = f.distance(state, candidate_bbox);\n                    (\n                        Some(Universal2DBoxKalmanFilter::calculate_cost(dist, true) / conf),\n                        None,\n                    )\n                }\n                PositionalMetricType::IoU(threshold) => {\n                    let box_m_opt = Universal2DBox::calculate_metric_object(\n                        &Some(candidate_bbox),\n                        &Some(track_bbox),\n                    );\n                    (\n                        box_m_opt.map(|e| e * conf).filter(|e| *e >= threshold),\n                        None,\n                    )\n                }\n            })\n        }\n    }\n\n    fn optimize(\n        &mut self,\n        _feature_class: u64,\n        _merge_history: &[u64],\n        attrs: &mut SortAttributes,\n        features: &mut Vec<Observation<Universal2DBox>>,\n        _prev_length: usize,\n        _is_merge: bool,\n    ) -> anyhow::Result<()> {\n        let mut observation = features.pop().unwrap();\n        let observation_bbox = observation.attr().as_ref().unwrap();\n        features.clear();\n\n        let mut predicted_bbox = attrs.make_prediction(observation_bbox);\n        attrs.update_history(observation_bbox, &predicted_bbox);\n\n        *observation.attr_mut() = Some(match self.method {\n            PositionalMetricType::Mahalanobis => predicted_bbox,\n            PositionalMetricType::IoU(_) => {\n                predicted_bbox.gen_vertices();\n                predicted_bbox\n            }\n        });\n\n        features.push(observation);\n        Ok(())\n    }\n\n    fn postprocess_distances(\n        &self,\n        unfiltered: Vec<ObservationMetricOk<Universal2DBox>>,\n    ) -> Vec<ObservationMetricOk<Universal2DBox>> {\n        unfiltered\n            .into_iter()\n            .filter(|res| res.attribute_metric.is_some())\n            .collect()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::prelude::{BoundingBox, PositionalMetricType};\n    use crate::track::{MetricQuery, Observation, ObservationMetric};\n    use crate::trackers::sort::metric::{SortMetric, DEFAULT_MINIMAL_SORT_CONFIDENCE};\n    use crate::trackers::sort::{\n        SortAttributes, SortAttributesOptions, DEFAULT_SORT_IOU_THRESHOLD,\n    };\n    use crate::trackers::spatio_temporal_constraints::SpatioTemporalConstraints;\n    use crate::EPS;\n    use std::sync::Arc;\n\n    #[test]\n    fn confidence_preserved_during_optimization() {\n        let mut attrs = SortAttributes::new(Arc::new(SortAttributesOptions::new(\n            None,\n            0,\n            5,\n            SpatioTemporalConstraints::default(),\n            1.0 / 20.0,\n            1.0 / 160.0,\n        )));\n\n        let mut metric = SortMetric::new(\n            PositionalMetricType::IoU(DEFAULT_SORT_IOU_THRESHOLD),\n            DEFAULT_MINIMAL_SORT_CONFIDENCE,\n        );\n\n        let mut obs = vec![Observation::new(\n            Some(BoundingBox::new_with_confidence(0.0, 0.0, 8.0, 10.0, 0.8).as_xyaah()),\n            None,\n        )];\n\n        metric\n            .optimize(0, &[], &mut attrs, &mut obs, 0, true)\n            .unwrap();\n\n        assert_eq!(\n            obs[0].0.as_ref().unwrap().confidence,\n            0.8,\n            \"Confidence must be preserved during optimization\"\n        );\n    }\n\n    #[test]\n    fn confidence_used_in_distance_calculation() {\n        let attr_opts = Arc::new(SortAttributesOptions::new(\n            None,\n            0,\n            5,\n            SpatioTemporalConstraints::default(),\n            1.0 / 20.0,\n            1.0 / 160.0,\n        ));\n\n        let candidate_attrs = SortAttributes::new(attr_opts.clone());\n        let track_attrs = SortAttributes::new(attr_opts.clone());\n\n        let metric = SortMetric::new(\n            PositionalMetricType::IoU(DEFAULT_SORT_IOU_THRESHOLD),\n            DEFAULT_MINIMAL_SORT_CONFIDENCE,\n        );\n\n        let candidate_obs = Observation::new(\n            Some(BoundingBox::new_with_confidence(0.0, 0.0, 8.0, 10.0, 0.8).as_xyaah()),\n            None,\n        );\n\n        let track_obs = Observation::new(\n            Some(BoundingBox::new_with_confidence(0.0, 0.0, 8.0, 10.0, 1.0).as_xyaah()),\n            None,\n        );\n\n        let mq = MetricQuery {\n            feature_class: 0,\n            candidate_attrs: &candidate_attrs,\n            candidate_observation: &candidate_obs,\n            track_attrs: &track_attrs,\n            track_observation: &track_obs,\n        };\n\n        let res = metric.metric(&mq);\n        assert!(\n            (res.unwrap().0.unwrap() - 0.8).abs() < EPS,\n            \"Confidence value in candidate box must be used.\"\n        );\n\n        let mq = MetricQuery {\n            feature_class: 0,\n            candidate_attrs: &track_attrs,\n            candidate_observation: &track_obs,\n            track_attrs: &candidate_attrs,\n            track_observation: &candidate_obs,\n        };\n\n        let res = metric.metric(&mq);\n        assert!(\n            (res.unwrap().0.unwrap() - 1.0).abs() < EPS,\n            \"Confidence in track box must NOT be used.\"\n        );\n    }\n}\n"
  },
  {
    "path": "src/trackers/sort/simple_api.rs",
    "content": "use std::collections::HashMap;\nuse std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};\n\nuse rand::Rng;\n\nuse crate::prelude::{NoopNotifier, ObservationBuilder, TrackStoreBuilder};\nuse crate::store::TrackStore;\nuse crate::track::Track;\nuse crate::trackers::epoch_db::EpochDb;\nuse crate::trackers::sort::{\n    metric::SortMetric, voting::SortVoting, AutoWaste, PositionalMetricType, SortAttributes,\n    SortAttributesOptions, SortAttributesUpdate, SortLookup, SortTrack, VotingType,\n    DEFAULT_AUTO_WASTE_PERIODICITY, MAHALANOBIS_NEW_TRACK_THRESHOLD,\n};\nuse crate::trackers::spatio_temporal_constraints::SpatioTemporalConstraints;\nuse crate::trackers::tracker_api::TrackerAPI;\nuse crate::utils::bbox::Universal2DBox;\nuse crate::voting::Voting;\n\n/// Easy to use SORT tracker implementation\n///\npub struct Sort {\n    store: RwLock<TrackStore<SortAttributes, SortMetric, Universal2DBox>>,\n    wasted_store: RwLock<TrackStore<SortAttributes, SortMetric, Universal2DBox>>,\n    method: PositionalMetricType,\n    opts: Arc<SortAttributesOptions>,\n    auto_waste: AutoWaste,\n    track_id: u64,\n}\n\nimpl Sort {\n    /// Creates new tracker\n    ///\n    /// # Parameters\n    /// * `shards` - amount of cpu threads to process the data, keep 1 for up to 100 simultaneously tracked objects, try it before setting high - higher numbers may lead to unexpected latencies.\n    /// * `bbox_history` - how many last bboxes are kept within stored track (valuable for offline trackers), for online - keep 1\n    /// * `max_idle_epochs` - how long track survives without being updated\n    /// * `threshold` - how low IoU must be to establish a new track (default from the authors of SORT is 0.3)\n    ///\n    #[allow(clippy::too_many_arguments)]\n    pub fn new(\n        shards: usize,\n        bbox_history: usize,\n        max_idle_epochs: usize,\n        method: PositionalMetricType,\n        min_confidence: f32,\n        spatio_temporal_constraints: Option<SpatioTemporalConstraints>,\n        kalman_position_weight: f32,\n        kalman_velocity_weight: f32,\n    ) -> Self {\n        assert!(bbox_history > 0);\n        let epoch_db = RwLock::new(HashMap::default());\n        let opts = Arc::new(SortAttributesOptions::new(\n            Some(epoch_db),\n            max_idle_epochs,\n            bbox_history,\n            spatio_temporal_constraints.unwrap_or_default(),\n            kalman_position_weight,\n            kalman_velocity_weight,\n        ));\n        let store = RwLock::new(\n            TrackStoreBuilder::new(shards)\n                .default_attributes(SortAttributes::new(opts.clone()))\n                .metric(SortMetric::new(method, min_confidence))\n                .notifier(NoopNotifier)\n                .build(),\n        );\n\n        let wasted_store = RwLock::new(\n            TrackStoreBuilder::new(shards)\n                .default_attributes(SortAttributes::new(opts.clone()))\n                .metric(SortMetric::new(method, min_confidence))\n                .notifier(NoopNotifier)\n                .build(),\n        );\n\n        Self {\n            store,\n            track_id: 0,\n            wasted_store,\n            method,\n            opts,\n            auto_waste: AutoWaste {\n                periodicity: DEFAULT_AUTO_WASTE_PERIODICITY,\n                counter: DEFAULT_AUTO_WASTE_PERIODICITY,\n            },\n        }\n    }\n\n    /// Receive tracking information for observed bboxes of `scene_id` == 0\n    ///\n    /// # Parameters\n    /// * `bboxes` - bounding boxes received from a detector\n    ///\n    pub fn predict(&mut self, bboxes: &[(Universal2DBox, Option<i64>)]) -> Vec<SortTrack> {\n        self.predict_with_scene(0, bboxes)\n    }\n\n    fn gen_track_id(&mut self) -> u64 {\n        self.track_id += 1;\n        self.track_id\n    }\n\n    /// Receive tracking information for observed bboxes of `scene_id`\n    ///\n    /// # Parameters\n    /// * `scene_id` - scene id provided by a user (class, camera id, etc...)\n    /// * `bboxes` - bounding boxes received from a detector\n    ///\n    pub fn predict_with_scene(\n        &mut self,\n        scene_id: u64,\n        bboxes: &[(Universal2DBox, Option<i64>)],\n    ) -> Vec<SortTrack> {\n        if self.auto_waste.counter == 0 {\n            self.auto_waste();\n            self.auto_waste.counter = self.auto_waste.periodicity;\n        } else {\n            self.auto_waste.counter -= 1;\n        }\n\n        let mut rng = rand::thread_rng();\n        let epoch = self.opts.next_epoch(scene_id).unwrap();\n\n        let tracks = bboxes\n            .iter()\n            .map(|(bb, custom_object_id)| {\n                self.store\n                    .read()\n                    .unwrap()\n                    .new_track(rng.gen())\n                    .observation(\n                        ObservationBuilder::new(0)\n                            .observation_attributes(bb.clone())\n                            .track_attributes_update(SortAttributesUpdate::new_with_scene(\n                                epoch,\n                                scene_id,\n                                *custom_object_id,\n                            ))\n                            .build(),\n                    )\n                    .build()\n                    .unwrap()\n            })\n            .collect::<Vec<_>>();\n        let num_candidates = tracks.len();\n        let (dists, errs) =\n            self.store\n                .write()\n                .unwrap()\n                .foreign_track_distances(tracks.clone(), 0, false);\n        assert!(errs.all().is_empty());\n        let dists = dists.all();\n        let voting = SortVoting::new(\n            match self.method {\n                PositionalMetricType::Mahalanobis => MAHALANOBIS_NEW_TRACK_THRESHOLD,\n                PositionalMetricType::IoU(t) => t,\n            },\n            num_candidates,\n            self.store.read().unwrap().shard_stats().iter().sum(),\n        );\n        let winners = voting.winners(dists);\n        let mut res = Vec::default();\n\n        for mut t in tracks {\n            let source = t.get_track_id();\n            let track_id: u64 = if let Some(dest) = winners.get(&source) {\n                let dest = dest[0];\n                if dest == source {\n                    let track_id = self.gen_track_id();\n                    t.set_track_id(track_id);\n                    self.store.write().unwrap().add_track(t).unwrap();\n                    track_id\n                } else {\n                    self.store\n                        .write()\n                        .unwrap()\n                        .merge_external(dest, &t, Some(&[0]), false)\n                        .unwrap();\n                    dest\n                }\n            } else {\n                let track_id = self.gen_track_id();\n                t.set_track_id(track_id);\n                self.store.write().unwrap().add_track(t).unwrap();\n                track_id\n            };\n\n            let lock = self.store.read().unwrap();\n            let store = lock.get_store(track_id as usize);\n            let track = store.get(&track_id).unwrap();\n            res.push(SortTrack::from(track));\n        }\n\n        res\n    }\n\n    pub fn idle_tracks(&mut self) -> Vec<SortTrack> {\n        self.idle_tracks_with_scene(0)\n    }\n\n    pub fn idle_tracks_with_scene(&mut self, scene_id: u64) -> Vec<SortTrack> {\n        let store = self.store.read().unwrap();\n\n        store\n            .lookup(SortLookup::IdleLookup(scene_id))\n            .iter()\n            .map(|(track_id, _status)| {\n                let shard = store.get_store(*track_id as usize);\n                let track = shard.get(track_id).unwrap();\n                SortTrack::from(track)\n            })\n            .collect()\n    }\n}\n\nimpl TrackerAPI<SortAttributes, SortMetric, Universal2DBox, SortAttributesOptions, NoopNotifier>\n    for Sort\n{\n    fn get_auto_waste_obj_mut(&mut self) -> &mut AutoWaste {\n        &mut self.auto_waste\n    }\n\n    fn get_opts(&self) -> &SortAttributesOptions {\n        &self.opts\n    }\n\n    fn get_main_store_mut(\n        &mut self,\n    ) -> RwLockWriteGuard<TrackStore<SortAttributes, SortMetric, Universal2DBox, NoopNotifier>>\n    {\n        self.store.write().unwrap()\n    }\n\n    fn get_wasted_store_mut(\n        &mut self,\n    ) -> RwLockWriteGuard<TrackStore<SortAttributes, SortMetric, Universal2DBox, NoopNotifier>>\n    {\n        self.wasted_store.write().unwrap()\n    }\n\n    fn get_main_store(\n        &self,\n    ) -> RwLockReadGuard<TrackStore<SortAttributes, SortMetric, Universal2DBox, NoopNotifier>> {\n        self.store.read().unwrap()\n    }\n\n    fn get_wasted_store(\n        &self,\n    ) -> RwLockReadGuard<TrackStore<SortAttributes, SortMetric, Universal2DBox, NoopNotifier>> {\n        self.wasted_store.read().unwrap()\n    }\n}\n\nimpl From<&Track<SortAttributes, SortMetric, Universal2DBox>> for SortTrack {\n    fn from(track: &Track<SortAttributes, SortMetric, Universal2DBox>) -> Self {\n        let attrs = track.get_attributes();\n        SortTrack {\n            id: track.get_track_id(),\n            custom_object_id: attrs.custom_object_id,\n            voting_type: VotingType::Positional,\n            epoch: attrs.last_updated_epoch,\n            scene_id: attrs.scene_id,\n            observed_bbox: attrs.observed_boxes.back().unwrap().clone(),\n            predicted_bbox: attrs.predicted_boxes.back().unwrap().clone(),\n            length: attrs.track_length,\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::trackers::sort::metric::DEFAULT_MINIMAL_SORT_CONFIDENCE;\n    use crate::trackers::sort::simple_api::Sort;\n    use crate::trackers::sort::PositionalMetricType::IoU;\n    use crate::trackers::sort::DEFAULT_SORT_IOU_THRESHOLD;\n    use crate::trackers::tracker_api::TrackerAPI;\n    use crate::utils::bbox::BoundingBox;\n\n    #[test]\n    fn sort() {\n        let mut t = Sort::new(\n            1,\n            10,\n            2,\n            IoU(DEFAULT_SORT_IOU_THRESHOLD),\n            DEFAULT_MINIMAL_SORT_CONFIDENCE,\n            None,\n            1.0 / 20.0,\n            1.0 / 160.0,\n        );\n        assert_eq!(t.current_epoch(), 0);\n        let bb = BoundingBox::new(0.0, 0.0, 10.0, 20.0);\n        let v = t.predict(&[(bb.into(), None)]);\n        let wasted = t.wasted();\n        assert!(wasted.is_empty());\n        assert_eq!(v.len(), 1);\n        let v = v[0].clone();\n        let track_id = v.id;\n        assert_eq!(v.custom_object_id, None);\n        assert_eq!(v.length, 1);\n        assert_eq!(v.observed_bbox, bb.into());\n        assert_eq!(v.epoch, 1);\n        assert_eq!(t.current_epoch(), 1);\n\n        let bb = BoundingBox::new(0.1, 0.1, 10.1, 20.0);\n        let v = t.predict(&[(bb.into(), Some(2))]);\n        let wasted = t.wasted();\n        assert!(wasted.is_empty());\n        assert_eq!(v.len(), 1);\n        let v = v[0].clone();\n        assert_eq!(v.custom_object_id, Some(2));\n        assert_eq!(v.id, track_id);\n        assert_eq!(v.length, 2);\n        assert_eq!(v.observed_bbox, bb.into());\n        assert_eq!(v.epoch, 2);\n        assert_eq!(t.current_epoch(), 2);\n\n        let bb = BoundingBox::new(10.1, 10.1, 10.1, 20.0);\n        let v = t.predict(&[(bb.into(), Some(3))]);\n        assert_eq!(v.len(), 1);\n        let v = v[0].clone();\n        assert_eq!(v.custom_object_id, Some(3));\n        assert_ne!(v.id, track_id);\n        let wasted = t.wasted();\n        assert!(wasted.is_empty());\n        assert_eq!(t.current_epoch(), 3);\n\n        let bb = t.predict(&[]);\n        assert!(bb.is_empty());\n        let wasted = t.wasted();\n        assert!(wasted.is_empty());\n        assert_eq!(t.current_epoch(), 4);\n        assert_eq!(t.current_epoch(), 4);\n\n        let bb = t.predict(&[]);\n        assert!(bb.is_empty());\n        let wasted = t.wasted();\n        assert_eq!(wasted.len(), 1);\n        assert_eq!(wasted[0].get_track_id(), track_id);\n        assert_eq!(t.current_epoch(), 5);\n    }\n\n    #[test]\n    fn sort_with_scenes() {\n        let mut t = Sort::new(\n            1,\n            10,\n            2,\n            IoU(DEFAULT_SORT_IOU_THRESHOLD),\n            DEFAULT_MINIMAL_SORT_CONFIDENCE,\n            None,\n            1.0 / 20.0,\n            1.0 / 160.0,\n        );\n        let bb = BoundingBox::new(0.0, 0.0, 10.0, 20.0);\n        assert_eq!(t.current_epoch_with_scene(1), 0);\n        assert_eq!(t.current_epoch_with_scene(2), 0);\n\n        let _v = t.predict_with_scene(1, &[(bb.into(), Some(4))]);\n        let _v = t.predict_with_scene(1, &[(bb.into(), Some(5))]);\n\n        assert_eq!(t.current_epoch_with_scene(1), 2);\n        assert_eq!(t.current_epoch_with_scene(2), 0);\n\n        let _v = t.predict_with_scene(2, &[(bb.into(), Some(6))]);\n\n        assert_eq!(t.current_epoch_with_scene(1), 2);\n        assert_eq!(t.current_epoch_with_scene(2), 1);\n    }\n\n    #[test]\n    fn idle_tracks() {\n        let mut t = Sort::new(\n            1,\n            10,\n            2,\n            IoU(DEFAULT_SORT_IOU_THRESHOLD),\n            DEFAULT_MINIMAL_SORT_CONFIDENCE,\n            None,\n            1.0 / 20.0,\n            1.0 / 160.0,\n        );\n        let bb = BoundingBox::new(0.0, 0.0, 10.0, 20.0);\n\n        let _v = t.predict_with_scene(1, &[(bb.into(), Some(4))]);\n        let idle = t.idle_tracks_with_scene(1);\n        assert!(idle.is_empty());\n\n        let _v = t.predict_with_scene(1, &[]);\n\n        let idle = t.idle_tracks_with_scene(1);\n        assert_eq!(idle.len(), 1);\n        assert_eq!(idle[0].id, 1);\n    }\n\n    #[test]\n    fn clear_wasted_tracks() {\n        let mut t = Sort::new(\n            1,\n            10,\n            2,\n            IoU(DEFAULT_SORT_IOU_THRESHOLD),\n            DEFAULT_MINIMAL_SORT_CONFIDENCE,\n            None,\n            1.0 / 20.0,\n            1.0 / 160.0,\n        );\n        let bb = BoundingBox::new(0.0, 0.0, 10.0, 20.0);\n\n        let _v = t.predict_with_scene(1, &[(bb.into(), Some(4))]);\n        t.skip_epochs_for_scene(1, 3);\n        assert_eq!(\n            t.wasted_store\n                .read()\n                .unwrap()\n                .shard_stats()\n                .iter()\n                .sum::<usize>(),\n            1\n        );\n        t.clear_wasted();\n        assert_eq!(\n            t.wasted_store\n                .read()\n                .unwrap()\n                .shard_stats()\n                .iter()\n                .sum::<usize>(),\n            0\n        );\n    }\n}\n\n#[cfg(feature = \"python\")]\npub mod python {\n    use pyo3::prelude::*;\n\n    use crate::{\n        prelude::Universal2DBox,\n        trackers::{\n            sort::{\n                python::{PyPositionalMetricType, PySortTrack, PyWastedSortTrack},\n                WastedSortTrack,\n            },\n            spatio_temporal_constraints::python::PySpatioTemporalConstraints,\n            tracker_api::TrackerAPI,\n        },\n        utils::bbox::python::PyUniversal2DBox,\n    };\n\n    use super::Sort;\n\n    #[pyclass]\n    #[pyo3(name = \"Sort\")]\n    pub struct PySort(pub Sort);\n\n    #[pymethods]\n    impl PySort {\n        #[new]\n        #[pyo3(signature = (\n            shards = 4,\n            bbox_history = 1,\n            max_idle_epochs = 5,\n            method = None,\n            min_confidence = 0.05,\n            spatio_temporal_constraints = None,\n            kalman_position_weight = 1.0 / 20.0,\n            kalman_velocity_weight = 1.0 / 160.0\n        ))]\n        #[allow(clippy::too_many_arguments)]\n        pub fn new_py(\n            shards: i64,\n            bbox_history: i64,\n            max_idle_epochs: i64,\n            method: Option<PyPositionalMetricType>,\n            min_confidence: f32,\n            spatio_temporal_constraints: Option<PySpatioTemporalConstraints>,\n            kalman_position_weight: f32,\n            kalman_velocity_weight: f32,\n        ) -> Self {\n            Self(Sort::new(\n                shards.try_into().expect(\"Positive number expected\"),\n                bbox_history.try_into().expect(\"Positive number expected\"),\n                max_idle_epochs\n                    .try_into()\n                    .expect(\"Positive number expected\"),\n                method.unwrap_or(PyPositionalMetricType::maha()).0,\n                min_confidence,\n                spatio_temporal_constraints.map(|x| x.0),\n                kalman_position_weight,\n                kalman_velocity_weight,\n            ))\n        }\n\n        #[pyo3(signature = (n))]\n        pub fn skip_epochs(&mut self, n: i64) {\n            assert!(n > 0);\n            self.0.skip_epochs(n.try_into().unwrap())\n        }\n\n        #[pyo3(signature = (scene_id, n))]\n        pub fn skip_epochs_for_scene(&mut self, scene_id: i64, n: i64) {\n            assert!(n > 0 && scene_id >= 0);\n            self.0\n                .skip_epochs_for_scene(scene_id.try_into().unwrap(), n.try_into().unwrap())\n        }\n\n        /// Get the amount of stored tracks per shard\n        ///\n        #[pyo3(signature = ())]\n        pub fn shard_stats(&self) -> Vec<i64> {\n            Python::with_gil(|py| {\n                py.allow_threads(|| {\n                    self.0\n                        .store\n                        .read()\n                        .unwrap()\n                        .shard_stats()\n                        .into_iter()\n                        .map(|e| i64::try_from(e).unwrap())\n                        .collect()\n                })\n            })\n        }\n\n        /// Get the current epoch for `scene_id` == 0\n        ///\n        #[pyo3(signature = ())]\n        pub fn current_epoch(&self) -> i64 {\n            self.0.current_epoch_with_scene(0).try_into().unwrap()\n        }\n\n        /// Get the current epoch for `scene_id`\n        ///\n        /// # Parameters\n        /// * `scene_id` - scene id\n        ///\n        #[pyo3(signature = (scene_id))]\n        pub fn current_epoch_with_scene(&self, scene_id: i64) -> isize {\n            assert!(scene_id >= 0);\n            self.0\n                .current_epoch_with_scene(scene_id.try_into().unwrap())\n                .try_into()\n                .unwrap()\n        }\n\n        /// Receive tracking information for observed bboxes of `scene_id` == 0\n        ///\n        /// # Parameters\n        /// * `bboxes` - bounding boxes received from a detector\n        ///\n        #[pyo3(signature = (bboxes))]\n        pub fn predict(\n            &mut self,\n            bboxes: Vec<(PyUniversal2DBox, Option<i64>)>,\n        ) -> Vec<PySortTrack> {\n            self.predict_with_scene(0, bboxes)\n        }\n\n        /// Receive tracking information for observed bboxes of `scene_id`\n        ///\n        /// # Parameters\n        /// * `scene_id` - scene id provided by a user (class, camera id, etc...)\n        /// * `bboxes` - bounding boxes received from a detector\n        ///\n        #[pyo3(signature = (scene_id, bboxes))]\n        pub fn predict_with_scene(\n            &mut self,\n            scene_id: i64,\n            bboxes: Vec<(PyUniversal2DBox, Option<i64>)>,\n        ) -> Vec<PySortTrack> {\n            assert!(scene_id >= 0);\n            let bboxes: Vec<(Universal2DBox, Option<i64>)> = unsafe { std::mem::transmute(bboxes) };\n\n            Python::with_gil(|py| {\n                py.allow_threads(|| unsafe {\n                    std::mem::transmute(\n                        self.0\n                            .predict_with_scene(scene_id.try_into().unwrap(), &bboxes),\n                    )\n                })\n            })\n        }\n\n        /// Fetch and remove all the tracks with expired life\n        ///\n        #[pyo3(signature = ())]\n        pub fn wasted(&mut self) -> Vec<PyWastedSortTrack> {\n            Python::with_gil(|py| {\n                py.allow_threads(|| {\n                    self.0\n                        .wasted()\n                        .into_iter()\n                        .map(WastedSortTrack::from)\n                        .map(PyWastedSortTrack)\n                        .collect()\n                })\n            })\n        }\n\n        /// Clear all tracks with expired life\n        ///\n        #[pyo3(signature = ())]\n        pub fn clear_wasted(&mut self) {\n            Python::with_gil(|py| {\n                py.allow_threads(|| self.0.clear_wasted());\n            })\n        }\n\n        /// Get idle tracks with not expired life\n        ///\n        #[pyo3(signature = ())]\n        pub fn idle_tracks(&mut self) -> Vec<PySortTrack> {\n            self.idle_tracks_with_scene(0)\n        }\n\n        /// Get idle tracks with not expired life\n        ///\n        #[pyo3(signature = (scene_id))]\n        pub fn idle_tracks_with_scene(&mut self, scene_id: i64) -> Vec<PySortTrack> {\n            Python::with_gil(|py| {\n                py.allow_threads(|| unsafe {\n                    std::mem::transmute(self.0.idle_tracks_with_scene(scene_id.try_into().unwrap()))\n                })\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "src/trackers/sort/voting.rs",
    "content": "use crate::track::ObservationMetricOk;\nuse crate::utils::bbox::Universal2DBox;\nuse crate::voting::Voting;\nuse core::option::Option::{None, Some};\nuse pathfinding::kuhn_munkres::kuhn_munkres;\nuse pathfinding::matrix::Matrix;\nuse std::collections::HashMap;\n\nconst F32_U64_MULT: f32 = 1_000_000.0;\n\npub struct SortVoting {\n    threshold: i64,\n    candidate_num: usize,\n    track_num: usize,\n}\n\nimpl SortVoting {\n    pub fn new(threshold: f32, candidates_num: usize, tracks_num: usize) -> Self {\n        Self {\n            threshold: (threshold * F32_U64_MULT) as i64,\n            candidate_num: candidates_num,\n            track_num: tracks_num,\n        }\n    }\n}\n\nimpl Voting<Universal2DBox> for SortVoting {\n    type WinnerObject = u64;\n\n    fn winners<T>(&self, distances: T) -> HashMap<u64, Vec<Self::WinnerObject>>\n    where\n        T: IntoIterator<Item = ObservationMetricOk<Universal2DBox>>,\n    {\n        let mut candidates_index: usize = 0;\n\n        if self.track_num == 0 {\n            return HashMap::default();\n        }\n\n        let mut tracks_index: Vec<u64> = Vec::default();\n        tracks_index.resize(self.candidate_num, 0);\n        let mut tracks_r_index: HashMap<u64, usize> = HashMap::default();\n\n        let mut cost_matrix = Matrix::new(\n            self.candidate_num,\n            self.candidate_num + self.track_num,\n            0i64,\n        );\n\n        for ObservationMetricOk {\n            from,\n            to,\n            attribute_metric,\n            feature_distance: _,\n        } in distances\n        {\n            assert!(from > 0 && to > 0);\n\n            let weight = (attribute_metric.unwrap_or(0.0) * F32_U64_MULT) as i64;\n\n            let row = tracks_r_index.get(&from).copied().unwrap_or_else(|| {\n                let index = candidates_index;\n                candidates_index += 1;\n\n                tracks_index[index] = from;\n                tracks_r_index.insert(from, index);\n                index\n            });\n\n            let col = tracks_r_index.get(&to).copied().unwrap_or_else(|| {\n                let index = tracks_index.len();\n                tracks_index.push(to);\n                tracks_r_index.insert(to, index);\n                index\n            });\n\n            let v = cost_matrix.get_mut((row, col)).unwrap();\n            *v = weight;\n        }\n\n        for i in 0..self.candidate_num {\n            let v = cost_matrix.get_mut((i, i)).unwrap();\n            *v = self.threshold;\n        }\n\n        let (_, solution) = kuhn_munkres(&cost_matrix);\n\n        solution\n            .into_iter()\n            .enumerate()\n            .flat_map(|(i, e)| {\n                let (from, to) = (tracks_index[i], tracks_index[e]);\n                if from > 0 && to > 0 {\n                    Some((from, vec![to]))\n                } else {\n                    None\n                }\n            })\n            .collect()\n    }\n}\n\n#[cfg(test)]\nmod voting_tests {\n    use crate::track::ObservationMetricOk;\n    use crate::trackers::sort::voting::SortVoting;\n    use crate::voting::Voting;\n    use std::collections::HashMap;\n\n    #[test]\n    fn test_voting() {\n        let v = SortVoting::new(0.3, 3, 3);\n        let winners = v.winners([\n            ObservationMetricOk {\n                from: 10,\n                to: 20,\n                attribute_metric: Some(0.6),\n                feature_distance: None,\n            },\n            ObservationMetricOk {\n                from: 10,\n                to: 25,\n                attribute_metric: Some(0.4),\n                feature_distance: None,\n            },\n            ObservationMetricOk {\n                from: 10,\n                to: 30,\n                attribute_metric: Some(0.4),\n                feature_distance: None,\n            },\n            ObservationMetricOk {\n                from: 11,\n                to: 20,\n                attribute_metric: Some(0.5),\n                feature_distance: None,\n            },\n            ObservationMetricOk {\n                from: 11,\n                to: 25,\n                attribute_metric: Some(0.69),\n                feature_distance: None,\n            },\n            ObservationMetricOk {\n                from: 11,\n                to: 30,\n                attribute_metric: Some(0.4),\n                feature_distance: None,\n            },\n            ObservationMetricOk {\n                from: 12,\n                to: 20,\n                attribute_metric: Some(0.2),\n                feature_distance: None,\n            },\n            ObservationMetricOk {\n                from: 12,\n                to: 25,\n                attribute_metric: Some(0.27),\n                feature_distance: None,\n            },\n            ObservationMetricOk {\n                from: 12,\n                to: 30,\n                attribute_metric: Some(0.28),\n                feature_distance: None,\n            },\n        ]);\n\n        assert_eq!(\n            winners,\n            HashMap::from([(10, vec![20]), (11, vec![25]), (12, vec![12])])\n        );\n    }\n}\n"
  },
  {
    "path": "src/trackers/sort.rs",
    "content": "use crate::track::{\n    LookupRequest, ObservationsDb, Track, TrackAttributes, TrackAttributesUpdate, TrackStatus,\n};\nuse crate::trackers::epoch_db::EpochDb;\nuse crate::trackers::kalman_prediction::TrackAttributesKalmanPrediction;\nuse crate::trackers::spatio_temporal_constraints::SpatioTemporalConstraints;\nuse crate::utils::bbox::Universal2DBox;\nuse crate::utils::kalman::kalman_2d_box::DIM_2D_BOX_X2;\nuse crate::utils::kalman::KalmanState;\nuse anyhow::Result;\n\nuse std::collections::{HashMap, VecDeque};\nuse std::sync::{Arc, RwLock};\n\nuse self::metric::SortMetric;\n\n/// SORT metric implementation with IoU and Mahalanobis distances\npub mod metric;\n\n/// SORT implementation with a very tiny interface\npub mod simple_api;\n\n/// Voting engine with Hungarian algorithm\n///\npub mod voting;\n\n/// SORT tracker with Batch API\npub mod batch_api;\n\n/// Default IoU threshold that is defined by SORT author in the original repo\npub const DEFAULT_SORT_IOU_THRESHOLD: f32 = 0.3;\n\n#[derive(Debug)]\npub struct SortAttributesOptions {\n    /// The map that stores current epochs for the scene_id\n    epoch_db: Option<RwLock<HashMap<u64, usize>>>,\n    /// The maximum number of epochs without update while the track is alive\n    max_idle_epochs: usize,\n    /// The maximum length of collected objects for the track\n    pub history_length: usize,\n    pub spatio_temporal_constraints: SpatioTemporalConstraints,\n    pub position_weight: f32,\n    pub velocity_weight: f32,\n}\n\nimpl Default for SortAttributesOptions {\n    fn default() -> Self {\n        Self {\n            epoch_db: None,\n            max_idle_epochs: 0,\n            history_length: 0,\n            spatio_temporal_constraints: SpatioTemporalConstraints::default(),\n            position_weight: 1.0 / 20.0,\n            velocity_weight: 1.0 / 160.0,\n        }\n    }\n}\n\nimpl EpochDb for SortAttributesOptions {\n    fn epoch_db(&self) -> &Option<RwLock<HashMap<u64, usize>>> {\n        &self.epoch_db\n    }\n\n    fn max_idle_epochs(&self) -> usize {\n        self.max_idle_epochs\n    }\n}\n\nimpl SortAttributesOptions {\n    pub fn new(\n        epoch_db: Option<RwLock<HashMap<u64, usize>>>,\n        max_idle_epochs: usize,\n        history_length: usize,\n        spatio_temporal_constraints: SpatioTemporalConstraints,\n        position_weight: f32,\n        velocity_weight: f32,\n    ) -> Self {\n        Self {\n            epoch_db,\n            max_idle_epochs,\n            history_length,\n            spatio_temporal_constraints,\n            position_weight,\n            velocity_weight,\n        }\n    }\n}\n\n/// Attributes associated with SORT track\n///\n#[derive(Debug, Clone)]\npub struct SortAttributes {\n    /// The lastly predicted boxes\n    pub predicted_boxes: VecDeque<Universal2DBox>,\n    /// The lastly observed boxes\n    pub observed_boxes: VecDeque<Universal2DBox>,\n    /// The epoch when the track was lastly updated\n    pub last_updated_epoch: usize,\n    /// The length of the track\n    pub track_length: usize,\n    /// Customer-specific scene identifier that splits the objects by classes, realms, etc.\n    pub scene_id: u64,\n    /// Custom object id\n    pub custom_object_id: Option<i64>,\n\n    /// Kalman filter predicted state\n    state: Option<KalmanState<{ DIM_2D_BOX_X2 }>>,\n    opts: Arc<SortAttributesOptions>,\n}\n\nimpl TrackAttributesKalmanPrediction for SortAttributes {\n    fn get_state(&self) -> Option<KalmanState<{ DIM_2D_BOX_X2 }>> {\n        self.state\n    }\n\n    fn set_state(&mut self, state: KalmanState<{ DIM_2D_BOX_X2 }>) {\n        self.state = Some(state);\n    }\n\n    fn get_position_weight(&self) -> f32 {\n        self.opts.position_weight\n    }\n\n    fn get_velocity_weight(&self) -> f32 {\n        self.opts.velocity_weight\n    }\n}\n\nimpl Default for SortAttributes {\n    fn default() -> Self {\n        Self {\n            predicted_boxes: VecDeque::default(),\n            observed_boxes: VecDeque::default(),\n            last_updated_epoch: 0,\n            track_length: 0,\n            scene_id: 0,\n            state: None,\n            custom_object_id: None,\n            opts: Arc::new(SortAttributesOptions::default()),\n        }\n    }\n}\n\nimpl SortAttributes {\n    /// Creates new attributes with limited history\n    ///\n    /// # Parameters\n    /// * `opts` - options\n    ///\n    pub fn new(opts: Arc<SortAttributesOptions>) -> Self {\n        Self {\n            opts,\n            ..Default::default()\n        }\n    }\n\n    fn update_history(\n        &mut self,\n        observation_bbox: &Universal2DBox,\n        predicted_bbox: &Universal2DBox,\n    ) {\n        self.track_length += 1;\n\n        self.observed_boxes.push_back(observation_bbox.clone());\n        self.predicted_boxes.push_back(predicted_bbox.clone());\n\n        if self.opts.history_length > 0 && self.observed_boxes.len() > self.opts.history_length {\n            self.observed_boxes.pop_front();\n            self.predicted_boxes.pop_front();\n        }\n    }\n}\n\n/// Update object for SortAttributes\n///\n#[derive(Clone, Debug, Default)]\npub struct SortAttributesUpdate {\n    epoch: usize,\n    scene_id: u64,\n    custom_object_id: Option<i64>,\n}\n\n/// Lookup object for SortAttributes\n///\n#[derive(Clone, Debug)]\npub enum SortLookup {\n    IdleLookup(u64),\n}\n\nimpl LookupRequest<SortAttributes, Universal2DBox> for SortLookup {\n    fn lookup(\n        &self,\n        attributes: &SortAttributes,\n        _observations: &ObservationsDb<Universal2DBox>,\n        _merge_history: &[u64],\n    ) -> bool {\n        match self {\n            SortLookup::IdleLookup(scene_id) => {\n                *scene_id == attributes.scene_id\n                    && attributes.last_updated_epoch\n                        != attributes\n                            .opts\n                            .current_epoch_with_scene(attributes.scene_id)\n                            .unwrap()\n            }\n        }\n    }\n}\n\nimpl SortAttributesUpdate {\n    /// update epoch with scene_id == 0\n    ///\n    /// # Parameters\n    /// * `epoch` - epoch update\n    ///\n    pub fn new(epoch: usize, custom_object_id: Option<i64>) -> Self {\n        Self {\n            epoch,\n            scene_id: 0,\n            custom_object_id,\n        }\n    }\n    /// update epoch for a specific scene_id\n    ///\n    /// # Parameters\n    /// * `epoch` - epoch\n    /// * `scene_id` - scene_id\n    pub fn new_with_scene(epoch: usize, scene_id: u64, custom_object_id: Option<i64>) -> Self {\n        Self {\n            epoch,\n            scene_id,\n            custom_object_id,\n        }\n    }\n}\n\nimpl TrackAttributesUpdate<SortAttributes> for SortAttributesUpdate {\n    fn apply(&self, attrs: &mut SortAttributes) -> Result<()> {\n        attrs.last_updated_epoch = self.epoch;\n        attrs.scene_id = self.scene_id;\n        attrs.custom_object_id = self.custom_object_id;\n        Ok(())\n    }\n}\n\nimpl TrackAttributes<SortAttributes, Universal2DBox> for SortAttributes {\n    type Update = SortAttributesUpdate;\n    type Lookup = SortLookup;\n\n    fn compatible(&self, other: &SortAttributes) -> bool {\n        if self.scene_id == other.scene_id {\n            let o1 = self.predicted_boxes.back().unwrap();\n            let o2 = other.predicted_boxes.back().unwrap();\n\n            let epoch_delta = (self.last_updated_epoch as i128 - other.last_updated_epoch as i128)\n                .abs()\n                .try_into()\n                .unwrap();\n\n            let center_dist = Universal2DBox::dist_in_2r(o1, o2);\n\n            self.opts.max_idle_epochs() >= epoch_delta\n                && self\n                    .opts\n                    .spatio_temporal_constraints\n                    .validate(epoch_delta, center_dist)\n        } else {\n            false\n        }\n    }\n\n    fn merge(&mut self, other: &SortAttributes) -> Result<()> {\n        self.last_updated_epoch = other.last_updated_epoch;\n        self.custom_object_id = other.custom_object_id;\n        Ok(())\n    }\n\n    fn baked(&self, _observations: &ObservationsDb<Universal2DBox>) -> Result<TrackStatus> {\n        self.opts.baked(self.scene_id, self.last_updated_epoch)\n    }\n}\n\n/// Online track structure that contains tracking information for the last tracker epoch\n///\n#[derive(Debug, Clone)]\npub struct SortTrack {\n    /// id of the track\n    ///\n    pub id: u64,\n    /// when the track was lastly updated\n    ///\n    pub epoch: usize,\n    /// the bbox predicted by KF\n    ///\n    pub predicted_bbox: Universal2DBox,\n    /// the bbox passed by detector\n    ///\n    pub observed_bbox: Universal2DBox,\n    /// user-defined scene id that splits tracking space on isolated realms\n    ///\n    pub scene_id: u64,\n    /// current track length\n    ///\n    pub length: usize,\n    /// what kind of voting was led to the current merge\n    ///\n    pub voting_type: VotingType,\n    /// custom object id passed by the user to find the track easily\n    ///\n    pub custom_object_id: Option<i64>,\n}\n\n/// Online track structure that contains tracking information for the last tracker epoch\n///\n#[derive(Debug, Clone)]\npub struct WastedSortTrack {\n    /// id of the track\n    ///\n    pub id: u64,\n    /// when the track was lastly updated\n    ///\n    pub epoch: usize,\n    /// the bbox predicted by KF\n    ///\n    pub predicted_bbox: Universal2DBox,\n    /// the bbox passed by detector\n    ///\n    pub observed_bbox: Universal2DBox,\n    /// user-defined scene id that splits tracking space on isolated realms\n    ///\n    pub scene_id: u64,\n    /// current track length\n    ///\n    pub length: usize,\n    /// history of predicted boxes\n    ///\n    pub predicted_boxes: Vec<Universal2DBox>,\n    /// history of observed boxes\n    ///\n    pub observed_boxes: Vec<Universal2DBox>,\n}\n\nimpl From<Track<SortAttributes, SortMetric, Universal2DBox>> for WastedSortTrack {\n    fn from(track: Track<SortAttributes, SortMetric, Universal2DBox>) -> Self {\n        let attrs = track.get_attributes();\n        WastedSortTrack {\n            id: track.get_track_id(),\n            epoch: attrs.last_updated_epoch,\n            scene_id: attrs.scene_id,\n            length: attrs.track_length,\n            observed_bbox: attrs.observed_boxes.back().unwrap().clone(),\n            predicted_bbox: attrs.predicted_boxes.back().unwrap().clone(),\n            predicted_boxes: attrs.predicted_boxes.clone().into_iter().collect(),\n            observed_boxes: attrs.observed_boxes.clone().into_iter().collect(),\n        }\n    }\n}\n\n#[derive(Default, Debug, Clone, Copy)]\npub enum VotingType {\n    #[default]\n    Visual,\n    Positional,\n}\n\n#[derive(Clone, Default, Copy, Debug)]\npub enum PositionalMetricType {\n    #[default]\n    Mahalanobis,\n    IoU(f32),\n}\n\npub struct AutoWaste {\n    pub periodicity: usize,\n    pub counter: usize,\n}\n\npub(crate) const DEFAULT_AUTO_WASTE_PERIODICITY: usize = 100;\npub(crate) const MAHALANOBIS_NEW_TRACK_THRESHOLD: f32 = 1.0;\n\n#[cfg(feature = \"python\")]\npub mod python {\n    use pyo3::prelude::*;\n\n    use crate::utils::bbox::python::PyUniversal2DBox;\n\n    use super::{PositionalMetricType, SortTrack, VotingType, WastedSortTrack};\n\n    #[pyclass]\n    #[pyo3(name = \"PositionalMetricType\")]\n    #[derive(Clone, Debug)]\n    pub struct PyPositionalMetricType(pub PositionalMetricType);\n\n    #[pymethods]\n    impl PyPositionalMetricType {\n        #[staticmethod]\n        pub fn maha() -> Self {\n            PyPositionalMetricType(PositionalMetricType::Mahalanobis)\n        }\n\n        #[staticmethod]\n        pub fn iou(threshold: f32) -> Self {\n            assert!(\n                threshold > 0.0 && threshold < 1.0,\n                \"Threshold must lay between (0.0 and 1.0)\"\n            );\n\n            PyPositionalMetricType(PositionalMetricType::IoU(threshold))\n        }\n\n        #[classattr]\n        const __hash__: Option<Py<PyAny>> = None;\n\n        fn __repr__(&self) -> String {\n            format!(\"{self:?}\")\n        }\n\n        fn __str__(&self) -> String {\n            format!(\"{self:#?}\")\n        }\n    }\n\n    #[pyclass]\n    #[pyo3(name = \"SortTrack\")]\n    #[derive(Debug, Clone)]\n    #[repr(transparent)]\n    pub struct PySortTrack(pub(crate) SortTrack);\n\n    #[pymethods]\n    impl PySortTrack {\n        #[classattr]\n        const __hash__: Option<Py<PyAny>> = None;\n\n        fn __repr__(&self) -> String {\n            format!(\"{self:?}\")\n        }\n\n        fn __str__(&self) -> String {\n            format!(\"{self:#?}\")\n        }\n\n        #[getter]\n        fn get_id(&self) -> u64 {\n            self.0.id\n        }\n\n        #[getter]\n        fn get_epoch(&self) -> usize {\n            self.0.epoch\n        }\n\n        #[getter]\n        fn get_predicted_bbox(&self) -> PyUniversal2DBox {\n            PyUniversal2DBox(self.0.predicted_bbox.clone())\n        }\n\n        #[getter]\n        fn get_observed_bbox(&self) -> PyUniversal2DBox {\n            PyUniversal2DBox(self.0.observed_bbox.clone())\n        }\n\n        #[getter]\n        fn get_scene_id(&self) -> u64 {\n            self.0.scene_id\n        }\n\n        #[getter]\n        fn get_length(&self) -> usize {\n            self.0.length\n        }\n\n        #[getter]\n        fn get_voting_type(&self) -> PyVotingType {\n            PyVotingType(self.0.voting_type)\n        }\n\n        #[getter]\n        fn get_custom_object_id(&self) -> Option<i64> {\n            self.0.custom_object_id\n        }\n    }\n\n    #[pyclass]\n    #[pyo3(name = \"WastedSortTrack\")]\n    #[derive(Debug, Clone)]\n    #[repr(transparent)]\n    pub struct PyWastedSortTrack(pub(crate) WastedSortTrack);\n\n    #[pymethods]\n    impl PyWastedSortTrack {\n        #[classattr]\n        const __hash__: Option<Py<PyAny>> = None;\n\n        fn __repr__(&self) -> String {\n            format!(\"{:?}\", self.0)\n        }\n\n        fn __str__(&self) -> String {\n            format!(\"{:#?}\", self.0)\n        }\n\n        #[getter]\n        fn id(&self) -> u64 {\n            self.0.id\n        }\n\n        #[getter]\n        fn epoch(&self) -> usize {\n            self.0.epoch\n        }\n\n        #[getter]\n        fn predicted_bbox(&self) -> PyUniversal2DBox {\n            PyUniversal2DBox(self.0.predicted_bbox.clone())\n        }\n\n        #[getter]\n        fn observed_bbox(&self) -> PyUniversal2DBox {\n            PyUniversal2DBox(self.0.observed_bbox.clone())\n        }\n\n        #[getter]\n        fn scene_id(&self) -> u64 {\n            self.0.scene_id\n        }\n\n        #[getter]\n        fn length(&self) -> usize {\n            self.0.length\n        }\n\n        #[getter]\n        fn predicted_boxes(&self) -> Vec<PyUniversal2DBox> {\n            unsafe { std::mem::transmute(self.0.predicted_boxes.clone()) }\n        }\n\n        #[getter]\n        fn observed_boxes(&self) -> Vec<PyUniversal2DBox> {\n            unsafe { std::mem::transmute(self.0.observed_boxes.clone()) }\n        }\n    }\n\n    #[pyclass]\n    #[pyo3(name = \"VotingType\")]\n    #[derive(Default, Debug, Clone, Copy)]\n    pub struct PyVotingType(pub VotingType);\n\n    #[pymethods]\n    impl PyVotingType {\n        #[classattr]\n        const __hash__: Option<Py<PyAny>> = None;\n\n        fn __repr__(&self) -> String {\n            format!(\"{self:?}\")\n        }\n\n        fn __str__(&self) -> String {\n            format!(\"{self:#?}\")\n        }\n    }\n}\n\n#[cfg(test)]\nmod track_tests {\n    use crate::prelude::{NoopNotifier, ObservationBuilder, TrackBuilder};\n    use crate::trackers::sort::metric::{SortMetric, DEFAULT_MINIMAL_SORT_CONFIDENCE};\n    use crate::trackers::sort::PositionalMetricType::IoU;\n    use crate::trackers::sort::{SortAttributes, DEFAULT_SORT_IOU_THRESHOLD};\n    use crate::utils::bbox::BoundingBox;\n    use crate::utils::kalman::kalman_2d_box::Universal2DBoxKalmanFilter;\n\n    #[test]\n    fn construct() {\n        let observation_bb_0 = BoundingBox::new(1.0, 1.0, 10.0, 15.0);\n        let observation_bb_1 = BoundingBox::new(1.1, 1.3, 10.0, 15.0);\n\n        let f = Universal2DBoxKalmanFilter::default();\n        let init_state = f.initiate(&observation_bb_0.into());\n\n        let mut t1 = TrackBuilder::new(1)\n            .attributes(SortAttributes::default())\n            .metric(SortMetric::new(\n                IoU(DEFAULT_SORT_IOU_THRESHOLD),\n                DEFAULT_MINIMAL_SORT_CONFIDENCE,\n            ))\n            .notifier(NoopNotifier)\n            .observation(\n                ObservationBuilder::new(0)\n                    .observation_attributes(observation_bb_0.into())\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        assert!(t1.get_attributes().state.is_some());\n        assert_eq!(t1.get_attributes().predicted_boxes.len(), 1);\n        assert_eq!(t1.get_attributes().observed_boxes.len(), 1);\n        assert_eq!(t1.get_merge_history().len(), 1);\n        assert_eq!(\n            t1.get_attributes().predicted_boxes[0],\n            observation_bb_0.into()\n        );\n\n        let predicted_state = f.predict(&init_state);\n        assert_eq!(\n            BoundingBox::try_from(predicted_state).unwrap(),\n            observation_bb_0\n        );\n\n        let t2 = TrackBuilder::new(2)\n            .attributes(SortAttributes::default())\n            .metric(SortMetric::new(\n                IoU(DEFAULT_SORT_IOU_THRESHOLD),\n                DEFAULT_MINIMAL_SORT_CONFIDENCE,\n            ))\n            .notifier(NoopNotifier)\n            .observation(\n                ObservationBuilder::new(0)\n                    .observation_attributes(observation_bb_1.into())\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        t1.merge(&t2, &[0], false).unwrap();\n\n        assert!(t1.get_attributes().state.is_some());\n        assert_eq!(t1.get_attributes().predicted_boxes.len(), 2);\n        assert_eq!(t1.get_attributes().observed_boxes.len(), 2);\n    }\n}\n"
  },
  {
    "path": "src/trackers/spatio_temporal_constraints.rs",
    "content": "/// The struct allows defining the constraints for objects comprared across different epochs.\n///\n/// When the new objects batch is passed to the tracker it has a newer epoch that the tracks that are kept\n/// within the trackers. It may happen that epoch difference is 1 when the track was updated in the previous\n/// epoch or more than one, if the track wasn't updated lastly.\n///\n/// The constraint defines how far the object may be from the track for certain epoch difference to still count\n/// that it can relate to it. The distance is measured in Nx(R_Obj+R_Track), where\n/// * `N` is the float number that defines the expected maximal distance;\n/// * `R_Obj` - radius of the circle surrounding the candidate object;\n/// * `R_Track` - radius of the circle surrounding the last bounding box of the track.\n///\n\n#[derive(Default, Debug, Clone)]\npub struct SpatioTemporalConstraints {\n    constraints: Vec<(usize, f32)>,\n}\n\nimpl SpatioTemporalConstraints {\n    /// Allows adding new constraints to the constraints engine\n    ///\n    /// # Parameters\n    /// * `constraints` - slice of tuples (epoch_delta, max_allowed_distance)\n    ///\n    pub fn constraints(mut self, constraints: &[(usize, f32)]) -> Self {\n        self.add_constraints(constraints.to_vec());\n        self\n    }\n}\n\nimpl SpatioTemporalConstraints {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    pub fn add_constraints(&mut self, constraints: Vec<(usize, f32)>) {\n        for (delta, max_distance) in constraints {\n            assert!(\n                max_distance > 0.0,\n                \"The distance is expected to be a positive float\"\n            );\n            self.constraints.push((delta, max_distance));\n        }\n        self.constraints.sort_by(|(e1, _), (e2, _)| e1.cmp(e2));\n        self.constraints.dedup_by(|(e1, _), (e2, _)| *e1 == *e2);\n    }\n\n    pub fn validate(&self, epoch_delta: usize, dist: f32) -> bool {\n        assert!(\n            dist >= 0.0,\n            \"The distance is expected to be a positive float\"\n        );\n        let constraint = self.constraints.iter().find(|(d, _)| *d >= epoch_delta);\n\n        match constraint {\n            None => true,\n            Some((_, max_dist)) => dist <= *max_dist,\n        }\n    }\n}\n\n#[cfg(feature = \"python\")]\npub mod python {\n    use pyo3::prelude::*;\n\n    use super::SpatioTemporalConstraints;\n\n    #[pyclass]\n    #[derive(Default, Debug, Clone)]\n    #[pyo3(name = \"SpatioTemporalConstraints\")]\n    pub struct PySpatioTemporalConstraints(pub(crate) SpatioTemporalConstraints);\n\n    #[pymethods]\n    impl PySpatioTemporalConstraints {\n        #[new]\n        pub fn new() -> Self {\n            Self(SpatioTemporalConstraints::default())\n        }\n\n        /// Allows adding new constraints to the constraints engine\n        ///\n        /// # Parameters\n        /// * `constraints` - Vec of tuples (epoch_delta, max_allowed_distance)\n        ///\n        #[pyo3(text_signature = \"($self, l: [(epoch_delta, max_allowed_distance)]\")]\n        pub fn add_constraints(&mut self, constraints: Vec<(usize, f32)>) {\n            self.0.add_constraints(constraints)\n        }\n\n        /// Validates the distance for specified epoch delta\n        ///\n        #[pyo3(text_signature = \"($self, epoch_delta, dist)\")]\n        pub fn validate(&self, epoch_delta: usize, dist: f32) -> bool {\n            self.0.validate(epoch_delta, dist)\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::trackers::spatio_temporal_constraints::SpatioTemporalConstraints;\n\n    #[test]\n    fn test() {\n        let mut spc = SpatioTemporalConstraints::default();\n        spc.add_constraints(vec![(1, 0.5), (2, 1.0), (3, 2.0), (4, 4.0)]);\n        spc.add_constraints(vec![(3, 2.5), (4, 4.5), (7, 8.5)]);\n\n        assert!(spc.validate(1, 0.4));\n        assert!(!spc.validate(1, 0.6));\n\n        assert!(spc.validate(6, 7.0));\n        assert!(!spc.validate(6, 9.0));\n\n        assert!(spc.validate(7, 8.4));\n        assert!(spc.validate(7, 8.5));\n        assert!(!spc.validate(7, 8.7));\n\n        assert!(spc.validate(9, 8.7));\n        assert!(spc.validate(9, 100.0));\n    }\n}\n"
  },
  {
    "path": "src/trackers/tracker_api.rs",
    "content": "use crate::store::TrackStore;\nuse crate::track::notify::ChangeNotifier;\nuse crate::track::TrackStatus;\nuse crate::track::{ObservationAttributes, ObservationMetric, Track, TrackAttributes};\nuse crate::trackers::epoch_db::EpochDb;\nuse crate::trackers::sort::AutoWaste;\nuse std::sync::{RwLockReadGuard, RwLockWriteGuard};\n\npub trait TrackerAPI<TA, M, OA, E, N>\nwhere\n    TA: TrackAttributes<TA, OA>,\n    M: ObservationMetric<TA, OA>,\n    OA: ObservationAttributes,\n    N: ChangeNotifier,\n    E: EpochDb,\n{\n    fn get_auto_waste_obj_mut(&mut self) -> &mut AutoWaste;\n    fn get_opts(&self) -> &E;\n    fn get_main_store_mut(&mut self) -> RwLockWriteGuard<TrackStore<TA, M, OA, N>>;\n    fn get_wasted_store_mut(&mut self) -> RwLockWriteGuard<TrackStore<TA, M, OA, N>>;\n\n    fn get_main_store(&self) -> RwLockReadGuard<TrackStore<TA, M, OA, N>>;\n    fn get_wasted_store(&self) -> RwLockReadGuard<TrackStore<TA, M, OA, N>>;\n\n    /// change auto waste job periodicity\n    ///\n    fn set_auto_waste(&mut self, periodicity: usize) {\n        let obj = self.get_auto_waste_obj_mut();\n        obj.periodicity = periodicity;\n        obj.counter = 0;\n    }\n\n    /// Skip number of epochs to force tracks to turn to terminal state\n    ///\n    /// # Parameters\n    /// * `n` - number of epochs to skip for `scene_id` == 0\n    ///\n    fn skip_epochs(&mut self, n: usize) {\n        self.skip_epochs_for_scene(0, n)\n    }\n\n    /// Skip number of epochs to force tracks to turn to terminal state\n    ///\n    /// # Parameters\n    /// * `n` - number of epochs to skip for `scene_id`\n    /// * `scene_id` - scene to skip epochs\n    ///\n    fn skip_epochs_for_scene(&mut self, scene_id: u64, n: usize) {\n        self.get_opts().skip_epochs_for_scene(scene_id, n);\n        self.auto_waste();\n    }\n\n    /// Get the current epoch for `scene_id` == 0\n    ///\n    fn current_epoch(&self) -> usize {\n        self.current_epoch_with_scene(0)\n    }\n\n    /// Get the current epoch for `scene_id`\n    ///\n    /// # Parameters\n    /// * `scene_id` - scene id\n    ///\n    fn current_epoch_with_scene(&self, scene_id: u64) -> usize {\n        self.get_opts().current_epoch_with_scene(scene_id).unwrap()\n    }\n\n    /// Receive all the tracks with expired life from the main store\n    ///\n    fn get_main_store_wasted(&mut self) -> Vec<Track<TA, M, OA, N>> {\n        let tracks = self.get_main_store_mut().find_usable();\n        let wasted = tracks\n            .into_iter()\n            .filter(|(_, status)| matches!(status, Ok(TrackStatus::Wasted)))\n            .map(|(track, _)| track)\n            .collect::<Vec<_>>();\n\n        self.get_main_store_mut().fetch_tracks(&wasted)\n    }\n\n    fn auto_waste(&mut self) {\n        let tracks = self.get_main_store_wasted();\n        for t in tracks {\n            self.get_wasted_store_mut()\n                .add_track(t)\n                .expect(\"Cannot be a error, copying track to wasted store\");\n        }\n    }\n\n    fn wasted(&mut self) -> Vec<Track<TA, M, OA, N>> {\n        self.auto_waste();\n        let tracks = self.get_wasted_store_mut().find_usable();\n        let wasted = tracks\n            .into_iter()\n            .filter(|(_, status)| matches!(status, Ok(TrackStatus::Wasted)))\n            .map(|(track, _)| track)\n            .collect::<Vec<_>>();\n\n        self.get_wasted_store_mut().fetch_tracks(&wasted)\n    }\n\n    /// Get the amount of tracks kept in main store per shard\n    ///\n    fn active_shard_stats(&self) -> Vec<usize> {\n        self.get_main_store().shard_stats()\n    }\n\n    /// Get the amount of tracks kept in wasted store per shard\n    ///\n    fn wasted_shard_stats(&self) -> Vec<usize> {\n        self.get_main_store().shard_stats()\n    }\n\n    /// Clears wasted tracks\n    fn clear_wasted(&self) {\n        self.get_wasted_store().clear();\n    }\n}\n"
  },
  {
    "path": "src/trackers/visual_sort/batch_api.rs",
    "content": "use crate::prelude::{\n    NoopNotifier, ObservationBuilder, PositionalMetricType, SortTrack, TrackStoreBuilder,\n    VisualSortObservation, VisualSortOptions,\n};\nuse crate::store::track_distance::TrackDistanceOkIterator;\nuse crate::store::TrackStore;\nuse crate::track::utils::FromVec;\nuse crate::track::{Feature, Track};\nuse crate::trackers::batch::{PredictionBatchRequest, PredictionBatchResult, SceneTracks};\nuse crate::trackers::epoch_db::EpochDb;\nuse crate::trackers::sort::{\n    AutoWaste, SortAttributesOptions, DEFAULT_AUTO_WASTE_PERIODICITY,\n    MAHALANOBIS_NEW_TRACK_THRESHOLD,\n};\nuse crate::trackers::tracker_api::TrackerAPI;\nuse crate::trackers::visual_sort::metric::{VisualMetric, VisualMetricOptions};\nuse crate::trackers::visual_sort::observation_attributes::VisualObservationAttributes;\nuse crate::trackers::visual_sort::track_attributes::{\n    VisualAttributes, VisualAttributesUpdate, VisualSortLookup,\n};\nuse crate::trackers::visual_sort::voting::VisualVoting;\nuse crate::utils::clipping::bbox_own_areas::{\n    exclusively_owned_areas, exclusively_owned_areas_normalized_shares,\n};\nuse crate::voting::Voting;\nuse crossbeam::channel::{Receiver, Sender};\nuse log::warn;\nuse rand::Rng;\nuse std::mem;\nuse std::sync::{Arc, Condvar, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};\nuse std::thread::{spawn, JoinHandle};\n\ntype VotingSenderChannel = Sender<VotingCommands>;\ntype VotingReceiverChannel = Receiver<VotingCommands>;\n\ntype MiddlewareVisualSortTrackStore =\n    TrackStore<VisualAttributes, VisualMetric, VisualObservationAttributes>;\ntype MiddlewareSortTrack = Track<VisualAttributes, VisualMetric, VisualObservationAttributes>;\ntype BatchBusyMonitor = Arc<(Mutex<usize>, Condvar)>;\n\nenum VotingCommands {\n    Distances {\n        scene_id: u64,\n        distances: TrackDistanceOkIterator<VisualObservationAttributes>,\n        channel: Sender<SceneTracks>,\n        tracks: Vec<MiddlewareSortTrack>,\n        monitor: BatchBusyMonitor,\n    },\n    Exit,\n}\n\n// /// Easy to use Visual SORT tracker implementation\n// ///\npub struct BatchVisualSort {\n    monitor: Option<BatchBusyMonitor>,\n    store: Arc<RwLock<MiddlewareVisualSortTrackStore>>,\n    wasted_store: RwLock<MiddlewareVisualSortTrackStore>,\n    metric_opts: Arc<VisualMetricOptions>,\n    track_opts: Arc<SortAttributesOptions>,\n    voting_threads: Vec<(VotingSenderChannel, JoinHandle<()>)>,\n    auto_waste: AutoWaste,\n}\n\nimpl Drop for BatchVisualSort {\n    fn drop(&mut self) {\n        let voting_threads = mem::take(&mut self.voting_threads);\n        for (tx, t) in voting_threads {\n            tx.send(VotingCommands::Exit)\n                .expect(\"Voting thread must be alive.\");\n            drop(tx);\n            t.join()\n                .expect(\"Voting thread is expected to shutdown successfully.\");\n        }\n    }\n}\n\nfn voting_thread(\n    store: Arc<RwLock<MiddlewareVisualSortTrackStore>>,\n    rx: VotingReceiverChannel,\n    metric_opts: Arc<VisualMetricOptions>,\n    track_id: Arc<RwLock<u64>>,\n) {\n    while let Ok(command) = rx.recv() {\n        match command {\n            VotingCommands::Distances {\n                scene_id,\n                distances,\n                channel,\n                tracks,\n                monitor,\n            } => {\n                let voting = VisualVoting::new(\n                    match metric_opts.positional_kind {\n                        PositionalMetricType::Mahalanobis => MAHALANOBIS_NEW_TRACK_THRESHOLD,\n                        PositionalMetricType::IoU(t) => t,\n                    },\n                    f32::MAX,\n                    metric_opts.visual_min_votes,\n                );\n                let winners = voting.winners(distances);\n                let mut res = Vec::default();\n                for mut t in tracks {\n                    let source = t.get_track_id();\n\n                    let tid = {\n                        let mut track_id = track_id.write().unwrap();\n                        *track_id += 1;\n                        *track_id\n                    };\n\n                    let track_id: u64 = if let Some(dest) = winners.get(&source) {\n                        let (dest, vt) = dest[0];\n                        if dest == source {\n                            t.set_track_id(tid);\n                            store.write().unwrap().add_track(t).unwrap();\n                            tid\n                        } else {\n                            t.add_observation(\n                                0,\n                                None,\n                                None,\n                                Some(VisualAttributesUpdate::new_voting_type(vt)),\n                            )\n                            .unwrap();\n                            store\n                                .write()\n                                .unwrap()\n                                .merge_external(dest, &t, Some(&[0]), false)\n                                .unwrap();\n                            dest\n                        }\n                    } else {\n                        t.set_track_id(tid);\n                        store.write().unwrap().add_track(t).unwrap();\n                        tid\n                    };\n\n                    let lock = store.read().unwrap();\n                    let store = lock.get_store(track_id as usize);\n                    let track = store.get(&track_id).unwrap();\n\n                    res.push(SortTrack::from(track))\n                }\n\n                let res = channel.send((scene_id, res));\n                if let Err(e) = res {\n                    warn!(\"Unable to send results to a caller, likely the caller already closed the channel. Error is: {:?}\", e);\n                }\n\n                let (lock, cvar) = &*monitor;\n                let mut lock = lock.lock().unwrap();\n                *lock -= 1;\n                cvar.notify_one();\n            }\n            VotingCommands::Exit => break,\n        }\n    }\n}\n\nimpl BatchVisualSort {\n    pub fn new(distance_shards: usize, voting_shards: usize, opts: &VisualSortOptions) -> Self {\n        let (track_opts, metric) = opts.clone().build();\n        let track_opts = Arc::new(track_opts);\n        let metric_opts = metric.opts.clone();\n        let store = Arc::new(RwLock::new(\n            TrackStoreBuilder::new(distance_shards)\n                .default_attributes(VisualAttributes::new(track_opts.clone()))\n                .metric(metric.clone())\n                .notifier(NoopNotifier)\n                .build(),\n        ));\n\n        let wasted_store = RwLock::new(\n            TrackStoreBuilder::new(distance_shards)\n                .default_attributes(VisualAttributes::new(track_opts.clone()))\n                .metric(metric)\n                .notifier(NoopNotifier)\n                .build(),\n        );\n\n        let track_id = Arc::new(RwLock::new(0));\n\n        let voting_threads = (0..voting_shards)\n            .map(|_e| {\n                let (tx, rx) = crossbeam::channel::unbounded();\n                let thread_store = store.clone();\n                let thread_track_id = track_id.clone();\n                let thread_metric_opts = metric_opts.clone();\n\n                (\n                    tx,\n                    spawn(move || {\n                        voting_thread(thread_store, rx, thread_metric_opts, thread_track_id)\n                    }),\n                )\n            })\n            .collect::<Vec<_>>();\n\n        Self {\n            monitor: None,\n            store,\n            wasted_store,\n            track_opts,\n            metric_opts,\n            voting_threads,\n            auto_waste: AutoWaste {\n                periodicity: DEFAULT_AUTO_WASTE_PERIODICITY,\n                counter: DEFAULT_AUTO_WASTE_PERIODICITY,\n            },\n        }\n    }\n\n    pub fn predict(&mut self, batch_request: PredictionBatchRequest<VisualSortObservation>) {\n        if self.auto_waste.counter == 0 {\n            self.auto_waste();\n            self.auto_waste.counter = self.auto_waste.periodicity;\n        } else {\n            self.auto_waste.counter -= 1;\n        }\n\n        if let Some(m) = &self.monitor {\n            let (lock, cvar) = &**m;\n            let _guard = cvar.wait_while(lock.lock().unwrap(), |v| *v > 0).unwrap();\n        }\n\n        self.monitor = Some(Arc::new((\n            Mutex::new(batch_request.batch_size()),\n            Condvar::new(),\n        )));\n\n        for (i, (scene_id, observations)) in batch_request.get_batch().iter().enumerate() {\n            let mut percentages = Vec::default();\n            let use_own_area_percentage =\n                self.metric_opts.visual_minimal_own_area_percentage_collect\n                    + self.metric_opts.visual_minimal_own_area_percentage_use\n                    > 0.0;\n\n            if use_own_area_percentage {\n                percentages.reserve(observations.len());\n                let boxes = observations\n                    .iter()\n                    .map(|o| &o.bounding_box)\n                    .collect::<Vec<_>>();\n\n                percentages = exclusively_owned_areas_normalized_shares(\n                    boxes.as_ref(),\n                    exclusively_owned_areas(boxes.as_ref()).as_ref(),\n                );\n            }\n\n            let mut rng = rand::thread_rng();\n            let epoch = self.track_opts.next_epoch(*scene_id).unwrap();\n\n            let tracks = observations\n                .iter()\n                .enumerate()\n                .map(|(i, o)| {\n                    self.store\n                        .read()\n                        .expect(\"Access to store must always succeed\")\n                        .new_track(rng.gen())\n                        .observation({\n                            let mut obs = ObservationBuilder::new(0).observation_attributes(\n                                if use_own_area_percentage {\n                                    VisualObservationAttributes::with_own_area_percentage(\n                                        o.feature_quality.unwrap_or(1.0),\n                                        o.bounding_box.clone(),\n                                        percentages[i],\n                                    )\n                                } else {\n                                    VisualObservationAttributes::new(\n                                        o.feature_quality.unwrap_or(1.0),\n                                        o.bounding_box.clone(),\n                                    )\n                                },\n                            );\n\n                            if let Some(feature) = &o.feature {\n                                obs = obs.observation(Feature::from_vec(feature.to_vec()));\n                            }\n\n                            obs.track_attributes_update(\n                                VisualAttributesUpdate::new_init_with_scene(\n                                    epoch,\n                                    *scene_id,\n                                    o.custom_object_id,\n                                ),\n                            )\n                            .build()\n                        })\n                        .build()\n                        .expect(\"Track creation must always succeed!\")\n                })\n                .collect::<Vec<_>>();\n\n            let (dists, errs) = {\n                let mut store = self\n                    .store\n                    .write()\n                    .expect(\"Access to store must always succeed\");\n                store.foreign_track_distances(tracks.clone(), 0, false)\n            };\n\n            assert!(errs.all().is_empty());\n            let thread_id = i % self.voting_threads.len();\n            self.voting_threads[thread_id]\n                .0\n                .send(VotingCommands::Distances {\n                    monitor: self.monitor.as_ref().unwrap().clone(),\n                    scene_id: *scene_id,\n                    distances: dists.into_iter(),\n                    channel: batch_request.get_sender(),\n                    tracks,\n                })\n                .expect(\"Sending voting request to voting thread must not fail\");\n        }\n    }\n\n    pub fn idle_tracks(&mut self) -> Vec<SortTrack> {\n        self.idle_tracks_with_scene(0)\n    }\n\n    pub fn idle_tracks_with_scene(&mut self, scene_id: u64) -> Vec<SortTrack> {\n        let store = self.store.read().unwrap();\n\n        store\n            .lookup(VisualSortLookup::IdleLookup(scene_id))\n            .iter()\n            .map(|(track_id, _status)| {\n                let shard = store.get_store(*track_id as usize);\n                let track = shard.get(track_id).unwrap();\n                SortTrack::from(track)\n            })\n            .collect()\n    }\n}\n\nimpl\n    TrackerAPI<\n        VisualAttributes,\n        VisualMetric,\n        VisualObservationAttributes,\n        SortAttributesOptions,\n        NoopNotifier,\n    > for BatchVisualSort\n{\n    fn get_auto_waste_obj_mut(&mut self) -> &mut AutoWaste {\n        &mut self.auto_waste\n    }\n\n    fn get_opts(&self) -> &SortAttributesOptions {\n        &self.track_opts\n    }\n\n    fn get_main_store_mut(&mut self) -> RwLockWriteGuard<MiddlewareVisualSortTrackStore> {\n        self.store.write().unwrap()\n    }\n\n    fn get_wasted_store_mut(&mut self) -> RwLockWriteGuard<MiddlewareVisualSortTrackStore> {\n        self.wasted_store.write().unwrap()\n    }\n\n    fn get_main_store(&self) -> RwLockReadGuard<MiddlewareVisualSortTrackStore> {\n        self.store.read().unwrap()\n    }\n\n    fn get_wasted_store(&self) -> RwLockReadGuard<MiddlewareVisualSortTrackStore> {\n        self.wasted_store.read().unwrap()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::prelude::{\n        BoundingBox, PositionalMetricType, VisualSortMetricType, VisualSortObservation,\n        VisualSortOptions,\n    };\n    use crate::trackers::batch::PredictionBatchRequest;\n    use crate::trackers::visual_sort::batch_api::BatchVisualSort;\n\n    #[test]\n    fn test() {\n        let opts = VisualSortOptions::default()\n            .max_idle_epochs(3)\n            .kept_history_length(3)\n            .visual_metric(VisualSortMetricType::Euclidean(1.0))\n            .positional_metric(PositionalMetricType::Mahalanobis)\n            .visual_minimal_track_length(2)\n            .visual_minimal_area(5.0)\n            .visual_minimal_quality_use(0.45)\n            .visual_minimal_quality_collect(0.7)\n            .visual_max_observations(3)\n            .visual_min_votes(2);\n\n        let mut tracker = BatchVisualSort::new(1, 1, &opts);\n        let (mut batch, predictions) = PredictionBatchRequest::<VisualSortObservation>::new();\n        let vec = &vec![1.0, 1.0];\n        batch.add(\n            1,\n            VisualSortObservation::new(\n                Some(vec),\n                Some(0.9),\n                BoundingBox::new(1.0, 1.0, 3.0, 5.0).as_xyaah(),\n                Some(13),\n            ),\n        );\n        tracker.predict(batch);\n        for _ in 0..predictions.batch_size() {\n            let (scene, tracks) = predictions.get();\n            assert_eq!(scene, 1);\n            assert_eq!(tracks.len(), 1);\n            dbg!(tracks);\n        }\n\n        let (mut batch, predictions) = PredictionBatchRequest::<VisualSortObservation>::new();\n        let vec1 = &vec![1.0, 1.0];\n        let vec2 = &vec![0.1, 0.15];\n        batch.add(\n            1,\n            VisualSortObservation::new(\n                Some(vec1),\n                Some(0.9),\n                BoundingBox::new(1.0, 1.0, 3.0, 5.0).as_xyaah(),\n                Some(13),\n            ),\n        );\n\n        batch.add(\n            2,\n            VisualSortObservation::new(\n                Some(vec2),\n                Some(0.87),\n                BoundingBox::new(5.0, 10.0, 3.0, 5.0).as_xyaah(),\n                Some(23),\n            ),\n        );\n\n        batch.add(\n            2,\n            VisualSortObservation::new(\n                None,\n                None,\n                BoundingBox::new(25.0, 15.0, 3.0, 5.0).as_xyaah(),\n                Some(33),\n            ),\n        );\n\n        tracker.predict(batch);\n        for _ in 0..predictions.batch_size() {\n            let (scene, tracks) = predictions.get();\n            dbg!(scene, tracks);\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct VisualSortPredictionBatchRequest<'a> {\n    pub batch: PredictionBatchRequest<VisualSortObservation<'a>>,\n    pub result: Option<PredictionBatchResult>,\n}\n\nimpl<'a> VisualSortPredictionBatchRequest<'a> {\n    pub fn new() -> Self {\n        let (batch, result) = PredictionBatchRequest::new();\n        Self {\n            batch,\n            result: Some(result),\n        }\n    }\n\n    pub fn prediction(&mut self) -> Option<PredictionBatchResult> {\n        self.result.take()\n    }\n\n    pub fn add(&mut self, scene_id: u64, elt: VisualSortObservation<'a>) {\n        self.batch.add(scene_id, elt);\n    }\n}\n\nimpl Default for VisualSortPredictionBatchRequest<'_> {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\n#[cfg(feature = \"python\")]\npub mod python {\n    use pyo3::prelude::*;\n\n    use crate::{\n        prelude::VisualSortObservation,\n        trackers::{\n            batch::{python::PyPredictionBatchResult, PredictionBatchRequest},\n            sort::python::PySortTrack,\n            tracker_api::TrackerAPI,\n            visual_sort::{\n                options::python::PyVisualSortOptions,\n                python::{PyVisualSortObservation, PyWastedVisualSortTrack},\n                WastedVisualSortTrack,\n            },\n        },\n    };\n\n    use super::{BatchVisualSort, VisualSortPredictionBatchRequest};\n\n    #[pyclass]\n    #[pyo3(name = \"BatchVisualSort\")]\n    pub struct PyBatchVisualSort(pub(crate) BatchVisualSort);\n\n    #[pymethods]\n    impl PyBatchVisualSort {\n        #[new]\n        #[pyo3(signature = (distance_shards, voting_shards, opts))]\n        pub fn new(distance_shards: i64, voting_shards: i64, opts: &PyVisualSortOptions) -> Self {\n            Self(BatchVisualSort::new(\n                distance_shards\n                    .try_into()\n                    .expect(\"Positive number expected\"),\n                voting_shards.try_into().expect(\"Positive number expected\"),\n                &opts.0,\n            ))\n        }\n\n        #[pyo3(signature = (n))]\n        fn skip_epochs(&mut self, n: i64) {\n            assert!(n > 0);\n            self.0.skip_epochs(n.try_into().unwrap())\n        }\n\n        #[pyo3(signature = (scene_id, n))]\n        fn skip_epochs_for_scene(&mut self, scene_id: i64, n: i64) {\n            assert!(n > 0 && scene_id >= 0);\n            self.0\n                .skip_epochs_for_scene(scene_id.try_into().unwrap(), n.try_into().unwrap())\n        }\n\n        /// Get the amount of stored tracks per shard\n        ///\n        #[pyo3(signature = ())]\n        fn shard_stats(&self) -> Vec<i64> {\n            Python::with_gil(|py| {\n                py.allow_threads(|| {\n                    self.0\n                        .store\n                        .read()\n                        .unwrap()\n                        .shard_stats()\n                        .into_iter()\n                        .map(|e| i64::try_from(e).unwrap())\n                        .collect()\n                })\n            })\n        }\n\n        /// Get the current epoch for `scene_id` == 0\n        ///\n        #[pyo3( signature = ())]\n        fn current_epoch(&self) -> i64 {\n            self.current_epoch_with_scene(0).try_into().unwrap()\n        }\n\n        /// Get the current epoch for `scene_id`\n        ///\n        /// # Parameters\n        /// * `scene_id` - scene id\n        ///\n        #[pyo3(\n        signature = (scene_id)\n    )]\n        fn current_epoch_with_scene(&self, scene_id: i64) -> isize {\n            assert!(scene_id >= 0);\n            self.0\n                .current_epoch_with_scene(scene_id.try_into().unwrap())\n                .try_into()\n                .unwrap()\n        }\n\n        /// Receive tracking information for observed bboxes of `scene_id` == 0\n        ///\n        /// # Parameters\n        /// * `bboxes` - bounding boxes received from a detector\n        ///\n        #[pyo3(signature = (py_batch))]\n        fn predict(\n            &mut self,\n            py_batch: PyVisualSortPredictionBatchRequest,\n        ) -> PyPredictionBatchResult {\n            let (mut batch, res) = PredictionBatchRequest::<VisualSortObservation>::new();\n            for (scene_id, observations) in py_batch.0.batch.get_batch() {\n                for o in observations {\n                    let f = o.feature.as_ref();\n                    batch.add(\n                        *scene_id,\n                        VisualSortObservation::new(\n                            f.map(|x| x.as_ref()),\n                            o.feature_quality,\n                            o.bounding_box.clone(),\n                            o.custom_object_id,\n                        ),\n                    );\n                }\n            }\n            self.0.predict(batch);\n\n            PyPredictionBatchResult(res)\n        }\n\n        /// Remove all the tracks with expired life\n        ///\n        #[pyo3(signature = ())]\n        fn wasted(&mut self) -> Vec<PyWastedVisualSortTrack> {\n            Python::with_gil(|py| {\n                py.allow_threads(|| {\n                    self.0\n                        .wasted()\n                        .into_iter()\n                        .map(WastedVisualSortTrack::from)\n                        .map(PyWastedVisualSortTrack)\n                        .collect()\n                })\n            })\n        }\n\n        /// Clear all tracks with expired life\n        ///\n        #[pyo3(signature = ())]\n        pub fn clear_wasted(&mut self) {\n            Python::with_gil(|py| py.allow_threads(|| self.0.clear_wasted()));\n        }\n\n        /// Get idle tracks with not expired life\n        ///\n        #[pyo3(signature = (scene_id))]\n        pub fn idle_tracks(&mut self, scene_id: i64) -> Vec<PySortTrack> {\n            Python::with_gil(|py| {\n                py.allow_threads(|| unsafe {\n                    std::mem::transmute(self.0.idle_tracks_with_scene(scene_id.try_into().unwrap()))\n                })\n            })\n        }\n    }\n\n    #[derive(Debug, Clone)]\n    #[pyclass]\n    #[pyo3(name = \"VisualSortPredictionBatchRequest\")]\n    pub(crate) struct PyVisualSortPredictionBatchRequest(\n        pub(crate) VisualSortPredictionBatchRequest<'static>,\n    );\n\n    #[pymethods]\n    impl PyVisualSortPredictionBatchRequest {\n        #[new]\n        fn new() -> Self {\n            Self(VisualSortPredictionBatchRequest::new())\n        }\n\n        fn prediction(&mut self) -> Option<PyPredictionBatchResult> {\n            self.0.prediction().map(PyPredictionBatchResult)\n        }\n\n        fn add(&mut self, scene_id: u64, elt: PyVisualSortObservation) {\n            self.0.add(scene_id, elt.0)\n        }\n    }\n}\n"
  },
  {
    "path": "src/trackers/visual_sort/metric/builder.rs",
    "content": "use crate::trackers::sort::PositionalMetricType;\nuse crate::trackers::visual_sort::metric::{\n    VisualMetric, VisualMetricOptions, VisualSortMetricType,\n};\n\nuse std::sync::Arc;\n\n#[derive(Debug, Clone)]\npub struct VisualMetricBuilder {\n    visual_kind: VisualSortMetricType,\n    positional_kind: PositionalMetricType,\n    visual_minimal_track_length: usize,\n    visual_minimal_area: f32,\n    visual_minimal_quality_use: f32,\n    visual_minimal_quality_collect: f32,\n    visual_max_observations: usize,\n    visual_min_votes: usize,\n    visual_minimal_own_area_percentage_use: f32,\n    visual_minimal_own_area_percentage_collect: f32,\n    positional_min_confidence: f32,\n}\n\n/// By default the metric object is constructed with: Euclidean visual_sort metric, IoU(0.3) positional metric\n/// and minimal visual_sort track length = 3\n///\nimpl Default for VisualMetricBuilder {\n    fn default() -> Self {\n        VisualMetricBuilder {\n            visual_kind: VisualSortMetricType::Euclidean(f32::MAX),\n            positional_kind: PositionalMetricType::IoU(0.3),\n            visual_minimal_track_length: 3,\n            visual_minimal_area: 0.0,\n            visual_minimal_quality_use: 0.0,\n            visual_minimal_quality_collect: 0.0,\n            visual_max_observations: 5,\n            visual_min_votes: 1,\n            visual_minimal_own_area_percentage_use: 0.0,\n            visual_minimal_own_area_percentage_collect: 0.0,\n            positional_min_confidence: 0.1,\n        }\n    }\n}\n\nimpl VisualMetricBuilder {\n    pub fn visual_minimal_own_area_percentage_use(mut self, area: f32) -> Self {\n        assert!(\n            (0.0..=1.0).contains(&area),\n            \"Argument must be contained within (0.0..=1.0)\"\n        );\n        self.visual_minimal_own_area_percentage_use = area;\n        self\n    }\n\n    pub fn visual_minimal_own_area_percentage_collect(mut self, area: f32) -> Self {\n        assert!(\n            (0.0..=1.0).contains(&area),\n            \"Argument must be contained within (0.0..=1.0)\"\n        );\n        self.visual_minimal_own_area_percentage_collect = area;\n        self\n    }\n\n    pub fn visual_min_votes(mut self, n: usize) -> Self {\n        self.visual_min_votes = n;\n        self\n    }\n\n    pub fn positional_min_confidence(mut self, conf: f32) -> Self {\n        assert!(\n            (0.01..=1.0).contains(&conf),\n            \"Confidence must lay between (0.01 and 1.0)\"\n        );\n        self.positional_min_confidence = conf;\n        self\n    }\n\n    pub fn visual_metric(mut self, metric: VisualSortMetricType) -> Self {\n        self.visual_kind = metric;\n        self\n    }\n\n    pub fn positional_metric(mut self, metric: PositionalMetricType) -> Self {\n        if let PositionalMetricType::IoU(t) = metric {\n            assert!(\n                t > 0.0 && t < 1.0,\n                \"Threshold must lay between (0.0 and 1.0)\"\n            );\n        }\n        self.positional_kind = metric;\n        self\n    }\n\n    pub fn visual_minimal_track_length(mut self, length: usize) -> Self {\n        assert!(\n            length > 0,\n            \"The minimum amount of visual_sort features collected before visual_sort metric is applied should be greater than 0.\"\n        );\n        self.visual_minimal_track_length = length;\n        self\n    }\n\n    pub fn visual_minimal_area(mut self, area: f32) -> Self {\n        assert!(\n            area >= 0.0,\n            \"The minimum area of bbox for visual_sort feature distance calculated and feature collected should be greater than 0.\"\n        );\n        self.visual_minimal_area = area;\n        self\n    }\n\n    pub fn visual_minimal_quality_use(mut self, q: f32) -> Self {\n        assert!(\n            q >= 0.0,\n            \"The minimum quality of visual_sort feature should be greater than or equal to 0.0.\"\n        );\n        self.visual_minimal_quality_use = q;\n        self\n    }\n\n    pub fn visual_max_observations(mut self, n: usize) -> Self {\n        self.visual_max_observations = n;\n        self\n    }\n\n    pub fn visual_minimal_quality_collect(mut self, q: f32) -> Self {\n        assert!(\n            q >= 0.0,\n            \"The minimum quality of visual_sort feature should be greater than or equal to 0.0.\"\n        );\n        self.visual_minimal_quality_collect = q;\n        self\n    }\n\n    pub fn build(self) -> VisualMetric {\n        assert!(\n            0 < self.visual_min_votes\n                && 0 < self.visual_minimal_track_length\n                && self.visual_minimal_track_length <= self.visual_max_observations,\n            \"Ratios for (visual_min_votes, visual_minimal_track_length, visual_max_observations) are broken\"\n        );\n        VisualMetric {\n            opts: Arc::new(VisualMetricOptions {\n                positional_min_confidence: self.positional_min_confidence,\n                visual_kind: self.visual_kind,\n                positional_kind: self.positional_kind,\n                visual_minimal_track_length: self.visual_minimal_track_length,\n                visual_minimal_area: self.visual_minimal_area,\n                visual_minimal_quality_use: self.visual_minimal_quality_use,\n                visual_minimal_quality_collect: self.visual_minimal_quality_collect,\n                visual_max_observations: self.visual_max_observations,\n                visual_min_votes: self.visual_min_votes,\n                visual_minimal_own_area_percentage_use: self.visual_minimal_own_area_percentage_use,\n                visual_minimal_own_area_percentage_collect: self\n                    .visual_minimal_own_area_percentage_collect,\n            }),\n        }\n    }\n\n    #[inline]\n    pub fn set_visual_minimal_area(&mut self, visual_minimal_area: f32) {\n        self.visual_minimal_area = visual_minimal_area;\n    }\n\n    #[inline]\n    pub fn set_positional_kind(&mut self, positional_kind: PositionalMetricType) {\n        self.positional_kind = positional_kind;\n    }\n\n    #[inline]\n    pub fn set_visual_minimal_track_length(&mut self, visual_minimal_track_length: usize) {\n        self.visual_minimal_track_length = visual_minimal_track_length;\n    }\n\n    #[inline]\n    pub fn set_visual_minimal_quality_use(&mut self, visual_minimal_quality_use: f32) {\n        self.visual_minimal_quality_use = visual_minimal_quality_use;\n    }\n\n    #[inline]\n    pub fn set_visual_minimal_quality_collect(&mut self, visual_minimal_quality_collect: f32) {\n        self.visual_minimal_quality_collect = visual_minimal_quality_collect;\n    }\n\n    #[inline]\n    pub fn set_visual_max_observations(&mut self, visual_max_observations: usize) {\n        self.visual_max_observations = visual_max_observations;\n    }\n\n    #[inline]\n    pub fn set_visual_min_votes(&mut self, visual_min_votes: usize) {\n        self.visual_min_votes = visual_min_votes;\n    }\n\n    #[inline]\n    pub fn set_visual_minimal_own_area_percentage_use(\n        &mut self,\n        visual_minimal_own_area_percentage_use: f32,\n    ) {\n        self.visual_minimal_own_area_percentage_use = visual_minimal_own_area_percentage_use;\n    }\n    #[inline]\n    pub fn set_visual_minimal_own_area_percentage_collect(\n        &mut self,\n        visual_minimal_own_area_percentage_collect: f32,\n    ) {\n        self.visual_minimal_own_area_percentage_collect =\n            visual_minimal_own_area_percentage_collect;\n    }\n\n    #[inline]\n    pub fn set_positional_min_confidence(&mut self, positional_min_confidence: f32) {\n        self.positional_min_confidence = positional_min_confidence;\n    }\n\n    pub fn set_visual_kind(&mut self, visual_kind: VisualSortMetricType) {\n        self.visual_kind = visual_kind;\n    }\n}\n"
  },
  {
    "path": "src/trackers/visual_sort/metric.rs",
    "content": "/// Auxiliary class that helps to build a metric object\npub mod builder;\n\nuse crate::distance::{cosine, euclidean};\nuse crate::track::{Feature, MetricQuery, ObservationAttributes, ObservationMetricOk};\nuse crate::track::{MetricOutput, Observation, ObservationMetric};\nuse crate::trackers::kalman_prediction::TrackAttributesKalmanPrediction;\nuse crate::trackers::sort::PositionalMetricType;\nuse crate::trackers::visual_sort::metric::builder::VisualMetricBuilder;\nuse crate::trackers::visual_sort::metric::VisualSortMetricType::{Cosine, Euclidean};\nuse crate::trackers::visual_sort::observation_attributes::VisualObservationAttributes;\nuse crate::trackers::visual_sort::track_attributes::VisualAttributes;\nuse crate::utils::bbox::Universal2DBox;\nuse crate::utils::kalman::kalman_2d_box::Universal2DBoxKalmanFilter;\nuse anyhow::Result;\nuse std::default::Default;\nuse std::iter::Iterator;\nuse std::sync::Arc;\n\n#[derive(Clone, Copy, Debug)]\npub enum VisualSortMetricType {\n    Euclidean(f32),\n    Cosine(f32),\n}\n\nimpl Default for VisualSortMetricType {\n    fn default() -> Self {\n        Euclidean(f32::MAX)\n    }\n}\n\nimpl VisualSortMetricType {\n    pub fn euclidean(threshold: f32) -> Self {\n        assert!(threshold > 0.0, \"Threshold must be a positive number\");\n        VisualSortMetricType::Euclidean(threshold)\n    }\n\n    pub fn cosine(threshold: f32) -> Self {\n        assert!(\n            (-1.0..=1.0).contains(&threshold),\n            \"Threshold must lay within [-1.0:1:0]\"\n        );\n        VisualSortMetricType::Cosine(threshold)\n    }\n\n    pub fn threshold(&self) -> f32 {\n        match self {\n            Euclidean(t) | Cosine(t) => *t,\n        }\n    }\n\n    pub fn is_ok(&self, dist: f32) -> bool {\n        match self {\n            Euclidean(t) => dist <= *t,\n            Cosine(t) => dist >= *t,\n        }\n    }\n\n    pub fn distance_to_weight(&self, dist: f32) -> f32 {\n        match self {\n            Euclidean(_) => dist,\n            Cosine(_) => 1.0 - dist,\n        }\n    }\n}\n\n#[cfg(feature = \"python\")]\npub mod python {\n    use super::VisualSortMetricType;\n    use pyo3::prelude::*;\n\n    #[pyclass]\n    #[pyo3(name = \"VisualSortMetricType\")]\n    #[derive(Clone, Debug)]\n    pub struct PyVisualSortMetricType(pub VisualSortMetricType);\n\n    #[pymethods]\n    impl PyVisualSortMetricType {\n        #[staticmethod]\n        pub fn euclidean(threshold: f32) -> Self {\n            PyVisualSortMetricType(VisualSortMetricType::euclidean(threshold))\n        }\n\n        #[staticmethod]\n        pub fn cosine(threshold: f32) -> Self {\n            PyVisualSortMetricType(VisualSortMetricType::cosine(threshold))\n        }\n\n        #[classattr]\n        const __hash__: Option<Py<PyAny>> = None;\n\n        fn __repr__(&self) -> String {\n            format!(\"{self:?}\")\n        }\n\n        fn __str__(&self) -> String {\n            format!(\"{self:#?}\")\n        }\n    }\n}\n\n#[derive(Debug)]\npub struct VisualMetricOptions {\n    pub visual_max_observations: usize,\n    pub visual_min_votes: usize,\n    pub visual_kind: VisualSortMetricType,\n    pub positional_kind: PositionalMetricType,\n    pub visual_minimal_track_length: usize,\n    pub visual_minimal_area: f32,\n    pub visual_minimal_quality_use: f32,\n    pub visual_minimal_quality_collect: f32,\n    pub visual_minimal_own_area_percentage_use: f32,\n    pub visual_minimal_own_area_percentage_collect: f32,\n    pub positional_min_confidence: f32,\n}\n\n#[derive(Clone, Debug)]\npub struct VisualMetric {\n    pub opts: Arc<VisualMetricOptions>,\n}\n\nimpl Default for VisualMetric {\n    fn default() -> Self {\n        VisualMetricBuilder::default().build()\n    }\n}\n\nimpl VisualMetric {\n    fn optimize_observations(\n        &self,\n        observations: &mut Vec<Observation<VisualObservationAttributes>>,\n    ) {\n        observations.retain(|e| e.feature().is_some());\n\n        // remove all old bboxes\n        observations.iter_mut().for_each(|f| {\n            if let Some(e) = &mut f.attr_mut() {\n                e.drop_bbox();\n            }\n        });\n\n        observations.sort_by(|e1, e2| {\n            e2.attr()\n                .as_ref()\n                .unwrap()\n                .visual_quality()\n                .partial_cmp(&e1.attr().as_ref().unwrap().visual_quality())\n                .unwrap()\n        });\n\n        if observations.len() >= self.opts.visual_max_observations {\n            observations.truncate(observations.len() - 1);\n        }\n    }\n\n    fn positional_metric(\n        &self,\n        candidate_observation_bbox_opt: &Option<Universal2DBox>,\n        track_observation_bbox_opt: &Option<Universal2DBox>,\n        track_attributes: &VisualAttributes,\n    ) -> Option<f32> {\n        if let (Some(candidate_observation_bbox), Some(track_observation_bbox)) =\n            (candidate_observation_bbox_opt, track_observation_bbox_opt)\n        {\n            if Universal2DBox::too_far(candidate_observation_bbox, track_observation_bbox) {\n                None\n            } else {\n                let conf = if candidate_observation_bbox.confidence\n                    < self.opts.positional_min_confidence\n                {\n                    self.opts.positional_min_confidence\n                } else {\n                    candidate_observation_bbox.confidence\n                };\n\n                match self.opts.positional_kind {\n                    PositionalMetricType::Mahalanobis => {\n                        let state = track_attributes.get_state().unwrap();\n                        let f = Universal2DBoxKalmanFilter::new(\n                            track_attributes.get_position_weight(),\n                            track_attributes.get_velocity_weight(),\n                        );\n                        let dist = f.distance(state, candidate_observation_bbox);\n                        Some(Universal2DBoxKalmanFilter::calculate_cost(dist, true) / conf)\n                    }\n                    PositionalMetricType::IoU(threshold) => {\n                        let box_m_opt = Universal2DBox::calculate_metric_object(\n                            &candidate_observation_bbox_opt.as_ref(),\n                            &track_observation_bbox_opt.as_ref(),\n                        );\n                        box_m_opt.map(|e| e * conf).filter(|e| *e >= threshold)\n                    }\n                }\n            }\n        } else {\n            None\n        }\n    }\n\n    fn visual_metric(\n        &self,\n        candidate_observation_feature: &Feature,\n        track_observation_feature: &Feature,\n        track_attributes: &VisualAttributes,\n    ) -> Option<f32> {\n        if track_attributes.visual_features_collected_count >= self.opts.visual_minimal_track_length\n        {\n            let d = match self.opts.visual_kind {\n                VisualSortMetricType::Euclidean(_) => {\n                    euclidean(candidate_observation_feature, track_observation_feature)\n                }\n                VisualSortMetricType::Cosine(_) => {\n                    cosine(candidate_observation_feature, track_observation_feature)\n                }\n            };\n\n            if self.opts.visual_kind.is_ok(d) {\n                Some(self.opts.visual_kind.distance_to_weight(d))\n            } else {\n                None\n            }\n        } else {\n            None\n        }\n    }\n\n    fn feature_can_be_used(\n        &self,\n        bbox_opt: &Option<&Universal2DBox>,\n        feature_quality: f32,\n        visual_minimal_quality: f32,\n        visual_own_area_percentage: &Option<f32>,\n        visual_minimal_area_percentage: f32,\n    ) -> bool {\n        let quality_is_ok = feature_quality >= visual_minimal_quality;\n\n        let percentage_is_ok = visual_own_area_percentage\n            .map(|p| p >= visual_minimal_area_percentage)\n            .unwrap_or(true);\n\n        let bbox_is_ok = if let Some(bbox) = bbox_opt {\n            let area = bbox.area();\n            area >= self.opts.visual_minimal_area\n        } else {\n            unreachable!(\"The bbox must always present for candidate track\");\n        };\n\n        bbox_is_ok && quality_is_ok && percentage_is_ok\n    }\n}\n\nimpl ObservationMetric<VisualAttributes, VisualObservationAttributes> for VisualMetric {\n    fn metric(\n        &self,\n        mq: &MetricQuery<VisualAttributes, VisualObservationAttributes>,\n    ) -> MetricOutput<f32> {\n        let candidate_obs_attrs = mq\n            .candidate_observation\n            .attr()\n            .as_ref()\n            .expect(\"Observation attributes must always present.\");\n\n        let track_obs_attrs = mq\n            .track_observation\n            .attr()\n            .as_ref()\n            .expect(\"Observation attributes must always present.\");\n\n        let candidate_bbox_opt = candidate_obs_attrs.bbox_opt();\n        let candidate_feature_q = candidate_obs_attrs.visual_quality();\n        let candidate_own_area_percentage_opt = candidate_obs_attrs.own_area_percentage_opt();\n\n        let track_bbox_opt = track_obs_attrs.bbox_opt();\n\n        let candidate_feature_opt = mq.candidate_observation.feature().as_ref();\n        let track_feature_opt = mq.track_observation.feature().as_ref();\n\n        Some((\n            self.positional_metric(candidate_bbox_opt, track_bbox_opt, mq.track_attrs),\n            if self.feature_can_be_used(\n                &candidate_bbox_opt.as_ref(),\n                candidate_feature_q,\n                self.opts.visual_minimal_quality_use,\n                candidate_own_area_percentage_opt,\n                self.opts.visual_minimal_own_area_percentage_use,\n            ) {\n                match (candidate_feature_opt, track_feature_opt) {\n                    (Some(c), Some(t)) => self.visual_metric(c, t, mq.track_attrs),\n                    _ => None,\n                }\n            } else {\n                None\n            },\n        ))\n    }\n\n    fn optimize(\n        &mut self,\n        _feature_class: u64,\n        _merge_history: &[u64],\n        attrs: &mut VisualAttributes,\n        observations: &mut Vec<Observation<VisualObservationAttributes>>,\n        _prev_length: usize,\n        is_merge: bool,\n    ) -> Result<()> {\n        let mut observation = observations\n            .pop()\n            .expect(\"At least one observation must present in the track.\");\n\n        let obs_attrs = observation\n            .attr()\n            .as_ref()\n            .expect(\"Observation attributes must always present.\");\n\n        let observation_bbox = obs_attrs.unchecked_bbox_ref();\n        let feature_quality = obs_attrs.visual_quality();\n        let own_area_percentage_opt = *obs_attrs.own_area_percentage_opt();\n\n        let mut predicted_bbox = attrs.make_prediction(observation_bbox);\n        attrs.update_history(\n            observation_bbox,\n            &predicted_bbox,\n            observation.feature().clone(),\n        );\n\n        if is_merge\n            && !self.feature_can_be_used(\n                &Some(observation_bbox),\n                feature_quality,\n                self.opts.visual_minimal_quality_collect,\n                &own_area_percentage_opt,\n                self.opts.visual_minimal_own_area_percentage_collect,\n            )\n        {\n            *observation.feature_mut() = None;\n        }\n\n        *observation.attr_mut() = Some(if let Some(percentage) = own_area_percentage_opt {\n            VisualObservationAttributes::with_own_area_percentage(\n                feature_quality,\n                match self.opts.positional_kind {\n                    PositionalMetricType::Mahalanobis => predicted_bbox,\n                    PositionalMetricType::IoU(_) => {\n                        predicted_bbox.gen_vertices();\n                        predicted_bbox\n                    }\n                },\n                percentage,\n            )\n        } else {\n            VisualObservationAttributes::new(\n                feature_quality,\n                match self.opts.positional_kind {\n                    PositionalMetricType::Mahalanobis => predicted_bbox,\n                    PositionalMetricType::IoU(_) => {\n                        predicted_bbox.gen_vertices();\n                        predicted_bbox\n                    }\n                },\n            )\n        });\n\n        self.optimize_observations(observations);\n        observations.push(observation);\n        let current_len = observations.len();\n        observations.swap(0, current_len - 1);\n\n        attrs.visual_features_collected_count = observations\n            .iter()\n            .filter(|f| f.feature().is_some())\n            .count();\n\n        Ok(())\n    }\n\n    fn postprocess_distances(\n        &self,\n        unfiltered: Vec<ObservationMetricOk<VisualObservationAttributes>>,\n    ) -> Vec<ObservationMetricOk<VisualObservationAttributes>> {\n        unfiltered\n            .into_iter()\n            .filter(|res| res.feature_distance.is_some() || res.attribute_metric.is_some())\n            .collect()\n    }\n}\n\n#[cfg(test)]\nmod optimize {\n    use crate::examples::vec2;\n    use crate::track::{Observation, ObservationMetric};\n    use crate::trackers::sort::{PositionalMetricType, SortAttributesOptions};\n    use crate::trackers::spatio_temporal_constraints::SpatioTemporalConstraints;\n    use crate::trackers::visual_sort::metric::builder::VisualMetricBuilder;\n    use crate::trackers::visual_sort::metric::VisualSortMetricType;\n    use crate::trackers::visual_sort::observation_attributes::VisualObservationAttributes;\n    use crate::trackers::visual_sort::track_attributes::VisualAttributes;\n    use crate::utils::bbox::{BoundingBox, Universal2DBox};\n    use std::sync::Arc;\n\n    #[test]\n    fn optimization_regular() {\n        let mut metric = VisualMetricBuilder::default()\n            .positional_metric(PositionalMetricType::IoU(0.3))\n            .visual_metric(VisualSortMetricType::Euclidean(f32::MAX))\n            .build();\n\n        let mut attrs = VisualAttributes::new(Arc::new(SortAttributesOptions::new(\n            None,\n            0,\n            5,\n            SpatioTemporalConstraints::default(),\n            1.0 / 20.0,\n            1.0 / 160.0,\n        )));\n\n        let mut obs = vec![Observation::new(\n            Some(VisualObservationAttributes::new(\n                1.0,\n                BoundingBox::new(0.0, 0.0, 5.0, 10.0).as_xyaah(),\n            )),\n            Some(vec2(0.0, 1.0)),\n        )];\n\n        metric\n            .optimize(0, &[], &mut attrs, &mut obs, 0, false)\n            .unwrap();\n\n        assert_eq!(attrs.observed_features.len(), 1);\n        assert_eq!(attrs.observed_boxes.len(), 1);\n        assert_eq!(attrs.predicted_boxes.len(), 1);\n        assert_eq!(attrs.track_length, 1);\n        assert_eq!(obs.len(), 1);\n\n        let mut obs = vec![\n            Observation::new(\n                Some(VisualObservationAttributes::new(\n                    1.0,\n                    BoundingBox::new(0.0, 0.0, 5.0, 10.0).as_xyaah(),\n                )),\n                Some(vec2(0.0, 1.0)),\n            ),\n            Observation::new(\n                Some(VisualObservationAttributes::new(\n                    1.0,\n                    BoundingBox::new(0.2, 0.2, 5.0, 10.0).as_xyaah(),\n                )),\n                None,\n            ),\n        ];\n\n        metric\n            .optimize(0, &[], &mut attrs, &mut obs, 0, false)\n            .unwrap();\n\n        assert_eq!(attrs.observed_features.len(), 2);\n        assert_eq!(attrs.observed_boxes.len(), 2);\n        assert_eq!(attrs.predicted_boxes.len(), 2);\n        assert_eq!(attrs.track_length, 2);\n        assert_eq!(attrs.visual_features_collected_count, 1);\n        assert_eq!(obs.len(), 2);\n        assert!(\n            {\n                let e = &obs[0];\n                e.feature().is_none()\n                    && matches!(\n                        e.attr().as_ref().unwrap().bbox_opt(),\n                        Some(Universal2DBox { .. })\n                    )\n            } && {\n                let e = &obs[1];\n                e.feature().is_some() && matches!(e.attr().as_ref().unwrap().bbox_opt(), None)\n            }\n        );\n\n        let mut obs = vec![\n            Observation::new(\n                Some(VisualObservationAttributes::new(\n                    0.8,\n                    BoundingBox::new(0.0, 0.0, 5.0, 10.0).as_xyaah(),\n                )),\n                Some(vec2(0.0, 1.0)),\n            ),\n            Observation::new(\n                Some(VisualObservationAttributes::new(\n                    0.7,\n                    BoundingBox::new(0.2, 0.2, 5.0, 10.0).as_xyaah(),\n                )),\n                None,\n            ),\n            Observation::new(\n                Some(VisualObservationAttributes::new(\n                    1.0,\n                    BoundingBox::new(0.3, 0.3, 5.1, 10.0).as_xyaah(),\n                )),\n                Some(vec2(0.1, 1.1)),\n            ),\n            Observation::new(\n                Some(VisualObservationAttributes::new(\n                    0.6,\n                    BoundingBox::new(0.3, 0.3, 5.1, 10.0).as_xyaah(),\n                )),\n                Some(vec2(0.0, 1.1)),\n            ),\n        ];\n\n        metric\n            .optimize(0, &[], &mut attrs, &mut obs, 0, false)\n            .unwrap();\n\n        assert_eq!(attrs.observed_features.len(), 3);\n        assert_eq!(attrs.observed_boxes.len(), 3);\n        assert_eq!(attrs.predicted_boxes.len(), 3);\n        assert_eq!(attrs.track_length, 3);\n        assert_eq!(attrs.visual_features_collected_count, 3);\n        assert_eq!(obs.len(), 3);\n        assert!(matches!(\n            &obs[2],\n            Observation(Some(a), Some(o)) if a.bbox_opt().is_none() && a.visual_quality() == 1.0 && o[0].to_array()[..2] == [0.1 , 1.1]\n        ));\n        assert!(matches!(\n            &obs[1],\n            Observation(Some(a), Some(o)) if a.bbox_opt().is_none() && a.visual_quality() == 0.8 && o[0].to_array()[..2] == [0.0 , 1.0]\n        ));\n        assert!(matches!(\n            &obs[0],\n            Observation(Some(a), Some(o)) if a.bbox_opt().is_some() && a.visual_quality() == 0.6 && o[0].to_array()[..2] == [0.0 , 1.1]\n        ));\n    }\n\n    #[test]\n    fn optimize_low_quality() {\n        let mut metric = VisualMetricBuilder::default()\n            .positional_metric(PositionalMetricType::IoU(0.3))\n            .visual_metric(VisualSortMetricType::Euclidean(f32::MAX))\n            .visual_minimal_quality_collect(0.3)\n            .build();\n\n        let mut attrs = VisualAttributes::new(Arc::new(SortAttributesOptions::new(\n            None,\n            0,\n            5,\n            SpatioTemporalConstraints::default(),\n            1.0 / 20.0,\n            1.0 / 160.0,\n        )));\n\n        let mut obs = vec![Observation::new(\n            Some(VisualObservationAttributes::new(\n                0.25,\n                BoundingBox::new(0.0, 0.0, 5.0, 10.0).as_xyaah(),\n            )),\n            Some(vec2(0.0, 1.0)),\n        )];\n\n        metric\n            .optimize(0, &[], &mut attrs, &mut obs, 0, true)\n            .unwrap();\n\n        assert!(\n            obs[0].feature().is_none(),\n            \"Feature must be removed because the quality is lower than minimal required quality for collected features\"\n        );\n    }\n\n    #[test]\n    fn optimize_small_box() {\n        let mut metric = VisualMetricBuilder::default()\n            .positional_metric(PositionalMetricType::IoU(0.3))\n            .visual_metric(VisualSortMetricType::Euclidean(f32::MAX))\n            .visual_minimal_area(1.0)\n            .build();\n\n        let mut attrs = VisualAttributes::new(Arc::new(SortAttributesOptions::new(\n            None,\n            0,\n            5,\n            SpatioTemporalConstraints::default(),\n            1.0 / 20.0,\n            1.0 / 160.0,\n        )));\n\n        let mut obs = vec![Observation::new(\n            Some(VisualObservationAttributes::new(\n                0.25,\n                BoundingBox::new(0.0, 0.0, 0.8, 1.0).as_xyaah(),\n            )),\n            Some(vec2(0.0, 1.0)),\n        )];\n\n        metric\n            .optimize(0, &[], &mut attrs, &mut obs, 0, true)\n            .unwrap();\n\n        assert!(\n            obs[0].feature().is_none(),\n            \"Feature must be removed because the box area is lower than minimal area required for collected features\"\n        );\n    }\n\n    #[test]\n    fn optimize_overlap() {\n        let mut metric = VisualMetricBuilder::default()\n            .positional_metric(PositionalMetricType::IoU(0.3))\n            .visual_metric(VisualSortMetricType::Euclidean(f32::MAX))\n            .visual_minimal_area(1.0)\n            .visual_minimal_own_area_percentage_collect(0.6)\n            .build();\n\n        let mut attrs = VisualAttributes::new(Arc::new(SortAttributesOptions::new(\n            None,\n            0,\n            5,\n            SpatioTemporalConstraints::default(),\n            1.0 / 20.0,\n            1.0 / 160.0,\n        )));\n\n        let mut obs = vec![Observation::new(\n            Some(VisualObservationAttributes::with_own_area_percentage(\n                0.8,\n                BoundingBox::new(0.0, 0.0, 8.0, 10.0).as_xyaah(),\n                0.5,\n            )),\n            Some(vec2(0.0, 1.0)),\n        )];\n\n        metric\n            .optimize(0, &[], &mut attrs, &mut obs, 0, true)\n            .unwrap();\n\n        assert!(\n            obs[0].feature().is_none(),\n            \"Feature must be removed because the minimum own area percentage is lower than specified in metric options\"\n        );\n    }\n}\n\n#[cfg(test)]\nmod metric_tests {\n    use crate::examples::vec2;\n    use crate::prelude::{NoopNotifier, ObservationBuilder, TrackStoreBuilder};\n    use crate::store::TrackStore;\n    use crate::track::ObservationMetricOk;\n    use crate::trackers::sort::{PositionalMetricType, SortAttributesOptions};\n    use crate::trackers::spatio_temporal_constraints::SpatioTemporalConstraints;\n    use crate::trackers::visual_sort::metric::builder::VisualMetricBuilder;\n    use crate::trackers::visual_sort::metric::{VisualMetric, VisualSortMetricType};\n    use crate::trackers::visual_sort::observation_attributes::VisualObservationAttributes;\n    use crate::trackers::visual_sort::track_attributes::VisualAttributes;\n    use crate::utils::bbox::BoundingBox;\n    use crate::EPS;\n    use std::sync::Arc;\n\n    fn default_attrs() -> VisualAttributes {\n        VisualAttributes::new(Arc::new(SortAttributesOptions::new(\n            None,\n            0,\n            5,\n            SpatioTemporalConstraints::default(),\n            1.0 / 20.0,\n            1.0 / 160.0,\n        )))\n    }\n\n    fn default_store(\n        metric: VisualMetric,\n    ) -> TrackStore<VisualAttributes, VisualMetric, VisualObservationAttributes, NoopNotifier> {\n        TrackStoreBuilder::default()\n            .metric(metric)\n            .notifier(NoopNotifier)\n            .default_attributes(default_attrs())\n            .build()\n    }\n\n    #[test]\n    fn pos_metric_far() {\n        let metric = VisualMetricBuilder::default()\n            .positional_metric(PositionalMetricType::Mahalanobis)\n            .visual_minimal_track_length(1)\n            .build();\n\n        let store = default_store(metric);\n\n        let track1 = store\n            .new_track(1)\n            .observation(\n                ObservationBuilder::new(0)\n                    .observation(vec2(0.1, 1.1))\n                    .observation_attributes(VisualObservationAttributes::with_own_area_percentage(\n                        1.0,\n                        BoundingBox::new(100.3, 0.3, 5.1, 10.0).as_xyaah(),\n                        0.1,\n                    ))\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        let track2 = store\n            .new_track(2)\n            .observation(\n                ObservationBuilder::new(0)\n                    .observation(vec2(0.1, 1.0))\n                    .observation_attributes(VisualObservationAttributes::with_own_area_percentage(\n                        1.0,\n                        BoundingBox::new(0.3, 0.3, 5.1, 10.0).as_xyaah(),\n                        0.2,\n                    ))\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        let dists = track1.distances(&track2, 0).unwrap();\n        assert_eq!(dists.len(), 1);\n        assert!(matches!(\n            dists[0],\n            ObservationMetricOk {\n                from: 1,\n                to: 2,\n                attribute_metric: None, // ignored because objects are too far\n                feature_distance: Some(x)\n            } if x > 0.0));\n    }\n\n    #[test]\n    fn metric_iou() {\n        let metric = VisualMetricBuilder::default()\n            .positional_metric(PositionalMetricType::IoU(0.3))\n            .visual_metric(VisualSortMetricType::cosine(1.0))\n            .visual_minimal_track_length(1)\n            .build();\n        let store = default_store(metric);\n\n        let track1 = store\n            .new_track(1)\n            .observation(\n                ObservationBuilder::new(0)\n                    .observation(vec2(1.0, 0.0))\n                    .observation_attributes(VisualObservationAttributes::new(\n                        1.0,\n                        BoundingBox::new(0.3, 0.3, 5.1, 10.0).as_xyaah(),\n                    ))\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        let track2 = store\n            .new_track(2)\n            .observation(\n                ObservationBuilder::new(0)\n                    .observation(vec2(1.0, 0.0))\n                    .observation_attributes(VisualObservationAttributes::new(\n                        1.0,\n                        BoundingBox::new(0.3, 0.3, 5.1, 10.0).as_xyaah(),\n                    ))\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        let dists = track1.distances(&track2, 0).unwrap();\n        assert_eq!(dists.len(), 1);\n        assert!(matches!(\n            dists[0],\n            ObservationMetricOk {\n                from: 1,\n                to: 2,\n                attribute_metric: Some(x),\n                feature_distance: Some(y)\n            } if (x - 1.0).abs() < EPS && y.abs() < EPS));\n    }\n\n    #[test]\n    fn metric_maha() {\n        let metric = VisualMetricBuilder::default()\n            .positional_metric(PositionalMetricType::Mahalanobis)\n            .visual_metric(VisualSortMetricType::Euclidean(10.0))\n            .visual_minimal_track_length(1)\n            .build();\n        let store = default_store(metric);\n\n        let track1 = store\n            .new_track(1)\n            .observation(\n                ObservationBuilder::new(0)\n                    .observation(vec2(1.0, 0.0))\n                    .observation_attributes(VisualObservationAttributes::new(\n                        1.0,\n                        BoundingBox::new(0.3, 0.3, 5.1, 10.0).as_xyaah(),\n                    ))\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        let track2 = store\n            .new_track(2)\n            .observation(\n                ObservationBuilder::new(0)\n                    .observation(vec2(1.0, 0.0))\n                    .observation_attributes(VisualObservationAttributes::new(\n                        1.0,\n                        BoundingBox::new(0.3, 0.3, 5.1, 10.0).as_xyaah(),\n                    ))\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        let dists = track1.distances(&track2, 0).unwrap();\n        assert_eq!(dists.len(), 1);\n        dbg!(&dists[0]);\n        assert!(matches!(\n            dists[0],\n            ObservationMetricOk {\n                from: 1,\n                to: 2,\n                attribute_metric: Some(x),\n                feature_distance: Some(y)\n            } if (x - 100.0).abs() < EPS && y.abs() < EPS));\n    }\n\n    #[test]\n    fn visual_track_too_short() {\n        let metric = VisualMetricBuilder::default()\n            .positional_metric(PositionalMetricType::IoU(0.3))\n            .visual_metric(VisualSortMetricType::Euclidean(f32::MAX))\n            .visual_minimal_track_length(3)\n            .build();\n\n        let store = default_store(metric);\n\n        let track1 = store\n            .new_track(1)\n            .observation(\n                ObservationBuilder::new(0)\n                    .observation(vec2(0.1, 1.1))\n                    .observation_attributes(VisualObservationAttributes::new(\n                        1.0,\n                        BoundingBox::new(0.3, 0.3, 5.1, 10.0).as_xyaah(),\n                    ))\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        let track2 = store\n            .new_track(2)\n            .observation(\n                ObservationBuilder::new(0)\n                    .observation(vec2(0.1, 1.0))\n                    .observation_attributes(VisualObservationAttributes::new(\n                        1.0,\n                        BoundingBox::new(0.3, 0.3, 5.1, 10.0).as_xyaah(),\n                    ))\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        let dists = track1.distances(&track2, 0).unwrap();\n        assert_eq!(dists.len(), 1);\n        assert!(matches!(\n            dists[0],\n            ObservationMetricOk {\n                from: 1,\n                to: 2,\n                attribute_metric: Some(x),\n                feature_distance: None     // track too short\n            } if (x - 1.0).abs() < EPS));\n    }\n\n    #[test]\n    fn visual_track_long_enough() {\n        let metric = VisualMetricBuilder::default()\n            .positional_metric(PositionalMetricType::IoU(0.3))\n            .visual_metric(VisualSortMetricType::Euclidean(f32::MAX))\n            .visual_minimal_track_length(2)\n            .build();\n\n        let store = default_store(metric);\n\n        let track1 = store\n            .new_track(1)\n            .observation(\n                ObservationBuilder::new(0)\n                    .observation(vec2(0.1, 1.0))\n                    .observation_attributes(VisualObservationAttributes::new(\n                        1.0,\n                        BoundingBox::new(0.3, 0.3, 5.1, 10.0).as_xyaah(),\n                    ))\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        let track2 = store\n            .new_track(2)\n            .observation(\n                ObservationBuilder::new(0)\n                    .observation(vec2(0.1, 1.0))\n                    .observation_attributes(VisualObservationAttributes::new(\n                        1.0,\n                        BoundingBox::new(0.3, 0.3, 5.1, 10.0).as_xyaah(),\n                    ))\n                    .build(),\n            )\n            .observation(\n                ObservationBuilder::new(0)\n                    .observation(vec2(0.1, 1.0))\n                    .observation_attributes(VisualObservationAttributes::new(\n                        1.0,\n                        BoundingBox::new(0.3, 0.3, 5.1, 10.0).as_xyaah(),\n                    ))\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        let dists = track1.distances(&track2, 0).unwrap();\n        assert_eq!(dists.len(), 2);\n        assert!(matches!(\n            dists[0],\n            ObservationMetricOk {\n                from: 1,\n                to: 2,\n                attribute_metric: Some(x),\n                feature_distance: Some(y)     // track too short\n            } if (x - 1.0).abs() < EPS && y.abs() < EPS));\n\n        assert!(matches!(\n            dists[1],\n            ObservationMetricOk {\n                from: 1,\n                to: 2,\n                attribute_metric: None,\n                feature_distance: Some(y)     // track too short\n            } if y.abs() < EPS));\n    }\n\n    #[test]\n    fn visual_track_small_bbox() {\n        let metric = VisualMetricBuilder::default()\n            .positional_metric(PositionalMetricType::IoU(0.3))\n            .visual_metric(VisualSortMetricType::Euclidean(f32::MAX))\n            .visual_minimal_track_length(1)\n            .visual_minimal_area(1.0)\n            .build();\n\n        let store = default_store(metric);\n\n        let track1 = store\n            .new_track(1)\n            .observation(\n                ObservationBuilder::new(0)\n                    .observation(vec2(0.1, 1.0))\n                    .observation_attributes(VisualObservationAttributes::new(\n                        1.0,\n                        BoundingBox::new(0.3, 0.3, 0.8, 1.0).as_xyaah(),\n                    ))\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        let track2 = store\n            .new_track(2)\n            .observation(\n                ObservationBuilder::new(0)\n                    .observation(vec2(0.1, 1.0))\n                    .observation_attributes(VisualObservationAttributes::new(\n                        1.0,\n                        BoundingBox::new(0.3, 0.3, 5.1, 10.0).as_xyaah(),\n                    ))\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        let dists = track1.distances(&track2, 0).unwrap();\n        assert_eq!(dists.len(), 1);\n        dbg!(&dists);\n        assert!(matches!(\n            dists[0],\n            ObservationMetricOk {\n                from: 1,\n                to: 2,\n                attribute_metric: None,\n                feature_distance: None // feature box is too small to use feature\n            }\n        ));\n    }\n\n    #[test]\n    fn visual_quality_low() {\n        let metric = VisualMetricBuilder::default()\n            .positional_metric(PositionalMetricType::IoU(0.3))\n            .visual_metric(VisualSortMetricType::Euclidean(f32::MAX))\n            .visual_minimal_quality_use(0.3)\n            .visual_minimal_track_length(1)\n            .build();\n\n        let store = default_store(metric);\n\n        let track1 = store\n            .new_track(1)\n            .observation(\n                ObservationBuilder::new(0)\n                    .observation(vec2(0.1, 1.0))\n                    .observation_attributes(VisualObservationAttributes::new(\n                        0.2,\n                        BoundingBox::new(0.3, 0.3, 5.1, 10.0).as_xyaah(),\n                    ))\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        let track2 = store\n            .new_track(2)\n            .observation(\n                ObservationBuilder::new(0)\n                    .observation(vec2(0.1, 1.0))\n                    .observation_attributes(VisualObservationAttributes::new(\n                        1.0,\n                        BoundingBox::new(0.3, 0.3, 5.1, 10.0).as_xyaah(),\n                    ))\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        let dists = track1.distances(&track2, 0).unwrap();\n        assert_eq!(dists.len(), 1);\n        assert!(matches!(\n            dists[0],\n            ObservationMetricOk {\n                from: 1,\n                to: 2,\n                attribute_metric: Some(x),\n                feature_distance: None     // quality is low\n            } if (x - 1.0).abs() < EPS));\n    }\n\n    #[test]\n    fn visual_own_area_percentage_low() {\n        let metric = VisualMetricBuilder::default()\n            .positional_metric(PositionalMetricType::IoU(0.3))\n            .visual_metric(VisualSortMetricType::Euclidean(f32::MAX))\n            .visual_minimal_own_area_percentage_use(0.6)\n            .visual_minimal_track_length(1)\n            .build();\n\n        let store = default_store(metric);\n\n        let track1 = store\n            .new_track(1)\n            .observation(\n                ObservationBuilder::new(0)\n                    .observation(vec2(0.1, 1.0))\n                    .observation_attributes(VisualObservationAttributes::with_own_area_percentage(\n                        1.0,\n                        BoundingBox::new(0.3, 0.3, 5.1, 10.0).as_xyaah(),\n                        0.5,\n                    ))\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        let track2 = store\n            .new_track(2)\n            .observation(\n                ObservationBuilder::new(0)\n                    .observation(vec2(0.1, 1.0))\n                    .observation_attributes(VisualObservationAttributes::new(\n                        1.0,\n                        BoundingBox::new(0.3, 0.3, 5.1, 10.0).as_xyaah(),\n                    ))\n                    .build(),\n            )\n            .build()\n            .unwrap();\n\n        let dists = track1.distances(&track2, 0).unwrap();\n        dbg!(&dists);\n        assert_eq!(dists.len(), 1);\n        assert!(matches!(\n            dists[0],\n            ObservationMetricOk {\n                from: 1,\n                to: 2,\n                attribute_metric: Some(x),\n                feature_distance: None     // own area percentage is low\n            } if (x - 1.0).abs() < EPS));\n    }\n}\n"
  },
  {
    "path": "src/trackers/visual_sort/observation_attributes.rs",
    "content": "use crate::track::{Observation, ObservationAttributes};\nuse crate::utils::bbox::Universal2DBox;\nuse crate::EPS;\nuse std::fmt::Formatter;\n\n#[derive(Clone, Debug, Default)]\npub struct VisualObservationAttributes {\n    bbox: Option<Universal2DBox>,\n    visual_quality: f32,\n    own_area_percentage: Option<f32>,\n}\n\nimpl VisualObservationAttributes {\n    pub fn new(q: f32, b: Universal2DBox) -> Self {\n        Self {\n            visual_quality: q,\n            bbox: Some(b),\n            own_area_percentage: None,\n        }\n    }\n\n    pub fn with_own_area_percentage(q: f32, b: Universal2DBox, own_area_percentage: f32) -> Self {\n        assert!(\n            (0.0..=1.0).contains(&own_area_percentage),\n            \"Own area percentage must be contained in (0.0..=1.0)\"\n        );\n\n        Self {\n            visual_quality: q,\n            bbox: Some(b),\n            own_area_percentage: Some(own_area_percentage),\n        }\n    }\n\n    pub fn unchecked_bbox_ref(&self) -> &Universal2DBox {\n        self.bbox.as_ref().unwrap()\n    }\n\n    pub fn bbox_opt(&self) -> &Option<Universal2DBox> {\n        &self.bbox\n    }\n\n    pub fn own_area_percentage_opt(&self) -> &Option<f32> {\n        &self.own_area_percentage\n    }\n\n    pub fn drop_bbox(&mut self) {\n        self.bbox = None;\n    }\n\n    pub fn visual_quality(&self) -> f32 {\n        self.visual_quality\n    }\n}\n\nimpl std::fmt::Debug for Observation<VisualObservationAttributes> {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"({:?}, {:?})\", self.attr(), self.feature())\n    }\n}\n\nimpl ObservationAttributes for VisualObservationAttributes {\n    type MetricObject = f32;\n\n    fn calculate_metric_object(l: &Option<&Self>, r: &Option<&Self>) -> Option<Self::MetricObject> {\n        if let (Some(l), Some(r)) = (l, r) {\n            if let (Some(l), Some(r)) = (&l.bbox, &r.bbox) {\n                let intersection = Universal2DBox::intersection(l, r);\n                if intersection == 0.0 {\n                    None\n                } else {\n                    let union = (l.height * l.height * l.aspect + r.height * r.height * r.aspect)\n                        as f64\n                        - intersection;\n                    let res = intersection / union;\n                    Some(res as f32)\n                }\n            } else {\n                None\n            }\n        } else {\n            None\n        }\n    }\n}\n\nimpl PartialEq<Self> for VisualObservationAttributes {\n    fn eq(&self, other: &Self) -> bool {\n        if let (Some(my_bbox), Some(other_bbox)) = (&self.bbox, &other.bbox) {\n            my_bbox.eq(other_bbox) && (self.visual_quality - other.visual_quality).abs() < EPS\n        } else {\n            false\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::track::ObservationAttributes;\n    use crate::trackers::visual_sort::observation_attributes::VisualObservationAttributes;\n    use crate::utils::bbox::BoundingBox;\n    use crate::EPS;\n\n    #[test]\n    fn operations() {\n        let mut attrs1 =\n            VisualObservationAttributes::new(0.7, BoundingBox::new(0.0, 0.0, 3.0, 5.0).as_xyaah());\n        let attrs2 =\n            VisualObservationAttributes::new(0.7, BoundingBox::new(0.0, 0.0, 3.0, 5.0).as_xyaah());\n\n        let dist =\n            VisualObservationAttributes::calculate_metric_object(&Some(&attrs1), &Some(&attrs2))\n                .unwrap();\n        assert!((dist - 1.0).abs() < EPS);\n\n        assert_eq!(&attrs1, &attrs2);\n\n        attrs1.bbox = None;\n        let dist =\n            VisualObservationAttributes::calculate_metric_object(&Some(&attrs1), &Some(&attrs2));\n        assert_eq!(dist, None);\n\n        let dist = VisualObservationAttributes::calculate_metric_object(&None, &Some(&attrs2));\n        assert_eq!(dist, None);\n\n        let dist = VisualObservationAttributes::calculate_metric_object(&Some(&attrs1), &None);\n        assert_eq!(dist, None);\n    }\n}\n"
  },
  {
    "path": "src/trackers/visual_sort/options.rs",
    "content": "use crate::trackers::sort::{PositionalMetricType, SortAttributesOptions};\nuse crate::trackers::spatio_temporal_constraints::SpatioTemporalConstraints;\nuse crate::trackers::visual_sort::metric::builder::VisualMetricBuilder;\nuse crate::trackers::visual_sort::metric::{VisualMetric, VisualSortMetricType};\nuse std::collections::HashMap;\nuse std::sync::RwLock;\n\n/// Class that is used to configure the Visual Tracker\n#[derive(Debug, Clone)]\npub struct VisualSortOptions {\n    max_idle_epochs: usize,\n    kept_history_length: usize,\n    spatio_temporal_constraints: SpatioTemporalConstraints,\n    metric_builder: VisualMetricBuilder,\n    kalman_position_weight: f32,\n    kalman_velocity_weight: f32,\n}\n\nimpl VisualSortOptions {\n    pub(crate) fn build(self) -> (SortAttributesOptions, VisualMetric) {\n        (\n            SortAttributesOptions::new(\n                Some(RwLock::new(HashMap::default())),\n                self.max_idle_epochs,\n                self.kept_history_length,\n                self.spatio_temporal_constraints,\n                self.kalman_position_weight,\n                self.kalman_velocity_weight,\n            ),\n            self.metric_builder.build(),\n        )\n    }\n\n    /// The number of epochs the track remains active.\n    ///\n    /// Lets the Frame Rate per second is `30`, setting `max_idle_epochs` to `30` means that the\n    /// track in store will be active even if only one new observation was merged with it during the\n    /// second. If during `30` invocations of `predict` for the scene, where the track is defined,\n    /// no observations are merged with it, the track will be marked as wasted, and no further\n    /// observations will be merged with it.\n    ///\n    pub fn max_idle_epochs(mut self, n: usize) -> Self {\n        self.max_idle_epochs = n;\n        self\n    }\n\n    /// The number of last observations, predictions, and features kept within the track attributes.\n    ///\n    /// The track's attributes may accumulate last observations, predictions, and features for the\n    /// caller's purpose. To protect the system from overflow `kept_history_length` parameter is used.\n    /// It forces the track to keep only the last `N` values instead of unlimited history. The parameter\n    /// is important when one uses the tracker in offline mode -  when the wasted tracks are used to get\n    /// the history. If the tracker is an online tracker, setting `1` is a reasonable value to keep memory\n    /// utilization low.\n    ///\n    pub fn kept_history_length(mut self, n: usize) -> Self {\n        assert!(n > 0, \"History length must be a positive number\");\n        self.kept_history_length = n;\n        self\n    }\n\n    /// The method is used to calculate the distance for visual_sort feature vectors.\n    ///\n    /// Currently, cosine and euclidean metrics are supported. The one you choose\n    /// is defined by the ReID model used.\n    ///    \n    pub fn visual_metric(mut self, metric: VisualSortMetricType) -> Self {\n        self.metric_builder = self.metric_builder.visual_metric(metric);\n        self\n    }\n\n    /// The minimal number of votes that is required to allow a track candidate to surpass the enabling\n    /// threshold of the visual_sort voting. The maximum allowed number of visual_sort features kept for the track\n    /// is defined by `visual_max_observations`.\n    ///\n    /// _Don't confuse `visual_max_observations` with `kept_history_length` - they have no relation.\n    /// The later is only used for caller purposes, not for track prediction._\n    ///\n    /// When the track candidate consisting of the single observation is compared versus tracks kept in\n    /// the store the system calculates up to `N` distances (`1 X N`), where `N` at most is equal to\n    /// `visual_max_observations`, but can be less if the track is short or previous observations were\n    /// ignored due to quality or other constraints.\n    ///\n    /// Only when `N >= visual_min_votes`, the track candidate is used in leader selection.\n    ///\n    pub fn visual_min_votes(mut self, n: usize) -> Self {\n        self.metric_builder = self.metric_builder.visual_min_votes(n);\n        self\n    }\n\n    /// The maximum number of visual_sort observations kept in the track for visual_sort estimations. The features\n    /// are collected in the track from the candidates, and when the `visual_max_observations` is\n    /// reached, the features with lower quality are wiped from the track.\n    ///\n    pub fn visual_max_observations(mut self, n: usize) -> Self {\n        self.metric_builder = self.metric_builder.visual_max_observations(n);\n        self\n    }\n\n    /// Minimal allowed confidence for bounding boxes. If the confidence is less than specified it is\n    /// corrected to be the minimal\n    ///\n    pub fn positional_min_confidence(mut self, conf: f32) -> Self {\n        self.metric_builder = self.metric_builder.positional_min_confidence(conf);\n        self\n    }\n\n    /// The constraints define how far the candidate is allowed to be from a track’s last box to\n    /// participate in the selection for the track. If the track candidate is too far from the\n    /// track kept in the store, it is skipped from the comparison.\n    ///\n    pub fn spatio_temporal_constraints(mut self, constraints: SpatioTemporalConstraints) -> Self {\n        self.spatio_temporal_constraints = constraints;\n        self\n    }\n\n    /// The parameter defines which positional metric is used to calculate distances between the track\n    /// candidate and tracks kept in the store. There are two metrics are supported - the Mahalanobis metric\n    /// and the IoU metric.\n    ///\n    pub fn positional_metric(mut self, metric: PositionalMetricType) -> Self {\n        self.metric_builder = self.metric_builder.positional_metric(metric);\n        self\n    }\n\n    /// The minimally required number of visual_sort features in the track that enables their usage in\n    /// candidates estimation. If the track is short and there are fewer features collected than\n    /// `visual_minimal_track_length` then candidates are estimated against it only by positional\n    /// distance. Keep in mind that this parameter must be less than or equal\n    /// to `visual_max_observations` to have sense.\n    ///\n    pub fn visual_minimal_track_length(mut self, length: usize) -> Self {\n        self.metric_builder = self.metric_builder.visual_minimal_track_length(length);\n        self\n    }\n\n    /// The minimal required area of track candidate's bounding box to use the visual_sort feature in estimation.\n    /// This parameter protects from the low-quality features received from the smallish boxes.\n    ///\n    pub fn visual_minimal_area(mut self, area: f32) -> Self {\n        self.metric_builder = self.metric_builder.visual_minimal_area(area);\n        self\n    }\n\n    /// The visual_sort quality threshold of a feature that activates the visual_sort estimation of a candidate\n    /// versus the tracks kept in the store.\n    ///\n    pub fn visual_minimal_quality_use(mut self, q: f32) -> Self {\n        self.metric_builder = self.metric_builder.visual_minimal_quality_use(q);\n        self\n    }\n\n    /// The visual_sort quality threshold of a feature that activates the adding of the visual_sort feature\n    /// to the track's visual_sort features.\n    ///\n    pub fn visual_minimal_quality_collect(mut self, q: f32) -> Self {\n        self.metric_builder = self.metric_builder.visual_minimal_quality_collect(q);\n        self\n    }\n\n    /// The threshold is calculated as `solely_owned_area / all_area` of the bounding box that\n    /// prevents low-quality visual_sort features received in a messy environment from being used in\n    /// visual_sort predictions.\n    ///\n    pub fn visual_minimal_own_area_percentage_use(mut self, area: f32) -> Self {\n        self.metric_builder = self\n            .metric_builder\n            .visual_minimal_own_area_percentage_use(area);\n        self\n    }\n\n    /// The threshold is calculated as `solely_owned_area / all_area` of the bounding box that prevents\n    /// low-quality visual_sort features received in a messy environment from being collected to a track\n    /// for making visual_sort predictions.\n    ///\n    pub fn visual_minimal_own_area_percentage_collect(mut self, area: f32) -> Self {\n        self.metric_builder = self\n            .metric_builder\n            .visual_minimal_own_area_percentage_collect(area);\n        self\n    }\n\n    pub fn kalman_position_weight(mut self, weight: f32) -> Self {\n        self.kalman_position_weight = weight;\n        self\n    }\n\n    pub fn kalman_velocity_weight(mut self, weight: f32) -> Self {\n        self.kalman_velocity_weight = weight;\n        self\n    }\n}\n\nimpl Default for VisualSortOptions {\n    fn default() -> Self {\n        Self {\n            max_idle_epochs: 2,\n            kept_history_length: 10,\n            metric_builder: VisualMetricBuilder::default(),\n            spatio_temporal_constraints: SpatioTemporalConstraints::default(),\n            kalman_position_weight: 1.0 / 20.0,\n            kalman_velocity_weight: 1.0 / 160.0,\n        }\n    }\n}\n\n#[cfg(feature = \"python\")]\npub mod python {\n    use crate::trackers::sort::python::PyPositionalMetricType;\n    use crate::trackers::spatio_temporal_constraints::python::PySpatioTemporalConstraints;\n    use crate::trackers::visual_sort::metric::python::PyVisualSortMetricType;\n\n    use super::VisualSortOptions;\n    use pyo3::prelude::*;\n\n    #[pyclass]\n    #[pyo3(name = \"VisualSortOptions\")]\n    pub struct PyVisualSortOptions(pub(crate) VisualSortOptions);\n\n    #[pymethods]\n    impl PyVisualSortOptions {\n        #[new]\n        pub(crate) fn new() -> Self {\n            Self(VisualSortOptions::default())\n        }\n\n        #[pyo3(text_signature = \"($self, n)\")]\n        pub(crate) fn max_idle_epochs(&mut self, n: i64) {\n            self.0.max_idle_epochs = n.try_into().expect(\"Parameter must be a positive number\");\n        }\n\n        #[pyo3(text_signature = \"($self, n)\")]\n        pub(crate) fn kept_history_length(&mut self, n: i64) {\n            self.0.kept_history_length = n.try_into().expect(\"Parameter must be a positive number\");\n        }\n\n        #[pyo3(text_signature = \"($self, n)\")]\n        pub(crate) fn visual_min_votes(&mut self, n: i64) {\n            self.0.metric_builder.set_visual_min_votes(n as _);\n        }\n\n        #[pyo3(text_signature = \"($self, metric)\")]\n        pub(crate) fn visual_metric(&mut self, metric: PyVisualSortMetricType) {\n            self.0.metric_builder.set_visual_kind(metric.0);\n        }\n\n        #[pyo3(text_signature = \"($self, constraints)\")]\n        pub(crate) fn spatio_temporal_constraints(\n            &mut self,\n            constraints: PySpatioTemporalConstraints,\n        ) {\n            self.0.spatio_temporal_constraints = constraints.0;\n        }\n\n        #[pyo3(text_signature = \"($self, metric)\")]\n        pub(crate) fn positional_metric(&mut self, metric: PyPositionalMetricType) {\n            self.0.metric_builder.set_positional_kind(metric.0);\n        }\n\n        #[pyo3(text_signature = \"($self, length)\")]\n        pub(crate) fn visual_minimal_track_length(&mut self, length: i64) {\n            self.0.metric_builder.set_visual_minimal_track_length(\n                length\n                    .try_into()\n                    .expect(\"Parameter must be a positive number\"),\n            );\n        }\n\n        #[pyo3(text_signature = \"($self, area)\")]\n        pub(crate) fn visual_minimal_area(&mut self, area: f32) {\n            self.0.metric_builder.set_visual_minimal_area(area);\n        }\n\n        #[pyo3(text_signature = \"($self, q)\")]\n        pub(crate) fn visual_minimal_quality_use(&mut self, q: f32) {\n            self.0.metric_builder.set_visual_minimal_quality_use(q);\n        }\n\n        #[pyo3(text_signature = \"($self, conf)\")]\n        pub(crate) fn positional_min_confidence(&mut self, conf: f32) {\n            self.0.metric_builder.set_positional_min_confidence(conf);\n        }\n\n        #[pyo3(text_signature = \"($self, n)\")]\n        pub(crate) fn visual_max_observations(&mut self, n: i64) {\n            self.0.metric_builder.set_visual_max_observations(\n                n.try_into().expect(\"Parameter must be a positive number\"),\n            );\n        }\n\n        #[pyo3(text_signature = \"($self, q)\")]\n        pub(crate) fn visual_minimal_quality_collect(&mut self, q: f32) {\n            self.0.metric_builder.set_visual_minimal_quality_collect(q);\n        }\n\n        #[pyo3(text_signature = \"($self, area)\")]\n        pub(crate) fn visual_minimal_own_area_percentage_use(&mut self, area: f32) {\n            self.0\n                .metric_builder\n                .set_visual_minimal_own_area_percentage_use(area);\n        }\n\n        #[pyo3(text_signature = \"($self, area)\")]\n        pub(crate) fn visual_minimal_own_area_percentage_collect(&mut self, area: f32) {\n            self.0\n                .metric_builder\n                .set_visual_minimal_own_area_percentage_collect(area);\n        }\n\n        #[pyo3(text_signature = \"($self, weight)\")]\n        pub(crate) fn kalman_position_weight(&mut self, weight: f32) {\n            self.0.kalman_position_weight = weight;\n        }\n\n        #[pyo3(text_signature = \"($self, weight)\")]\n        pub(crate) fn kalman_velocity_weight(&mut self, weight: f32) {\n            self.0.kalman_velocity_weight = weight;\n        }\n\n        #[classattr]\n        const __hash__: Option<Py<PyAny>> = None;\n\n        fn __repr__(&self) -> String {\n            format!(\"{:?}\", self.0)\n        }\n\n        fn __str__(&self) -> String {\n            format!(\"{:#?}\", self.0)\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::trackers::sort::python::PyPositionalMetricType;\n    use crate::trackers::sort::PositionalMetricType;\n    use crate::trackers::spatio_temporal_constraints::python::PySpatioTemporalConstraints;\n    use crate::trackers::spatio_temporal_constraints::SpatioTemporalConstraints;\n    use crate::trackers::visual_sort::metric::python::PyVisualSortMetricType;\n    use crate::trackers::visual_sort::metric::VisualSortMetricType;\n    use crate::trackers::visual_sort::options::python::PyVisualSortOptions;\n    use crate::trackers::visual_sort::options::VisualSortOptions;\n\n    #[test]\n    fn visual_sort_options_builder() {\n        let (opts, metric) = dbg!(VisualSortOptions::default()\n            .max_idle_epochs(3)\n            .kept_history_length(10)\n            .visual_metric(VisualSortMetricType::Euclidean(100.0))\n            .positional_metric(PositionalMetricType::Mahalanobis)\n            .visual_minimal_track_length(3)\n            .visual_minimal_area(5.0)\n            .visual_minimal_quality_use(0.45)\n            .visual_minimal_quality_collect(0.5)\n            .visual_max_observations(25)\n            .visual_min_votes(5)\n            .positional_min_confidence(0.13)\n            .visual_minimal_own_area_percentage_use(0.1)\n            .visual_minimal_own_area_percentage_collect(0.2)\n            .spatio_temporal_constraints(\n                SpatioTemporalConstraints::default().constraints(&[(5, 7.0)])\n            )\n            .build());\n\n        let mut opts_builder = PyVisualSortOptions::new();\n        opts_builder.max_idle_epochs(3);\n        opts_builder.kept_history_length(10);\n        opts_builder.visual_metric(PyVisualSortMetricType::euclidean(100.0));\n        opts_builder.positional_metric(PyPositionalMetricType::maha());\n        opts_builder.visual_minimal_track_length(3);\n        opts_builder.visual_minimal_area(5.0);\n        opts_builder.visual_minimal_quality_use(0.45);\n        opts_builder.visual_minimal_quality_collect(0.5);\n        opts_builder.visual_max_observations(25);\n        opts_builder.positional_min_confidence(0.13);\n        opts_builder.visual_minimal_own_area_percentage_use(0.1);\n        opts_builder.visual_minimal_own_area_percentage_collect(0.2);\n        opts_builder.visual_min_votes(5);\n        let mut constraints = PySpatioTemporalConstraints::new();\n        constraints.add_constraints(vec![(5, 7.0)]);\n        opts_builder.spatio_temporal_constraints(constraints);\n        let (opts_py, metric_py) = dbg!(opts_builder.0.build());\n\n        assert_eq!(format!(\"{:?}\", opts), format!(\"{:?}\", opts_py));\n        assert_eq!(format!(\"{:?}\", metric), format!(\"{:?}\", metric_py));\n    }\n}\n"
  },
  {
    "path": "src/trackers/visual_sort/simple_api.rs",
    "content": "use crate::prelude::{NoopNotifier, ObservationBuilder, SortTrack, TrackStoreBuilder};\nuse crate::store::TrackStore;\nuse crate::track::utils::FromVec;\nuse crate::track::{Feature, Track};\nuse crate::trackers::epoch_db::EpochDb;\nuse crate::trackers::sort::VotingType::Positional;\nuse crate::trackers::sort::{\n    AutoWaste, PositionalMetricType, SortAttributesOptions, DEFAULT_AUTO_WASTE_PERIODICITY,\n    MAHALANOBIS_NEW_TRACK_THRESHOLD,\n};\nuse crate::trackers::tracker_api::TrackerAPI;\nuse crate::trackers::visual_sort::metric::{VisualMetric, VisualMetricOptions};\nuse crate::trackers::visual_sort::observation_attributes::VisualObservationAttributes;\nuse crate::trackers::visual_sort::options::VisualSortOptions;\nuse crate::trackers::visual_sort::track_attributes::{\n    VisualAttributes, VisualAttributesUpdate, VisualSortLookup,\n};\nuse crate::trackers::visual_sort::voting::VisualVoting;\nuse crate::trackers::visual_sort::VisualSortObservation;\nuse crate::utils::clipping::bbox_own_areas::{\n    exclusively_owned_areas, exclusively_owned_areas_normalized_shares,\n};\nuse crate::voting::Voting;\nuse rand::Rng;\nuse std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};\n\n// /// Easy to use Visual SORT tracker implementation\n// ///\npub struct VisualSort {\n    store: RwLock<TrackStore<VisualAttributes, VisualMetric, VisualObservationAttributes>>,\n    wasted_store: RwLock<TrackStore<VisualAttributes, VisualMetric, VisualObservationAttributes>>,\n    metric_opts: Arc<VisualMetricOptions>,\n    track_opts: Arc<SortAttributesOptions>,\n    auto_waste: AutoWaste,\n    track_id: u64,\n}\n\nimpl VisualSort {\n    /// Creates new tracker\n    ///\n    /// # Parameters\n    /// * `shards` - amount of cpu threads to process the data, keep 1 for up to 100 simultaneously tracked objects, try it before setting high - higher numbers may lead to unexpected latencies.\n    /// * `opts` - tracker options\n    ///\n    pub fn new(shards: usize, opts: &VisualSortOptions) -> Self {\n        let (track_opts, metric) = opts.clone().build();\n        let track_opts = Arc::new(track_opts);\n        let metric_opts = metric.opts.clone();\n        let store = RwLock::new(\n            TrackStoreBuilder::new(shards)\n                .default_attributes(VisualAttributes::new(track_opts.clone()))\n                .metric(metric.clone())\n                .notifier(NoopNotifier)\n                .build(),\n        );\n\n        let wasted_store = RwLock::new(\n            TrackStoreBuilder::new(shards)\n                .default_attributes(VisualAttributes::new(track_opts.clone()))\n                .metric(metric)\n                .notifier(NoopNotifier)\n                .build(),\n        );\n\n        Self {\n            store,\n            wasted_store,\n            track_opts,\n            track_id: 0,\n            metric_opts,\n            auto_waste: AutoWaste {\n                periodicity: DEFAULT_AUTO_WASTE_PERIODICITY,\n                counter: DEFAULT_AUTO_WASTE_PERIODICITY,\n            },\n        }\n    }\n\n    /// Receive tracking information for observed bboxes of `scene_id == 0`\n    ///\n    /// # Parameters\n    /// * `scene_id` - custom identifier for the group of observed objects;\n    /// * `observations` - object observations with (feature, feature_quality and bounding box).\n    ///\n    pub fn predict(&mut self, observations: &[VisualSortObservation]) -> Vec<SortTrack> {\n        self.predict_with_scene(0, observations)\n    }\n\n    fn gen_track_id(&mut self) -> u64 {\n        self.track_id += 1;\n        self.track_id\n    }\n\n    /// Receive tracking information for observed bboxes of `scene_id`\n    ///\n    /// # Parameters\n    /// * `scene_id` - custom identifier for the group of observed objects;\n    /// * `observations` - object observations with (feature, feature_quality and bounding box).\n    ///\n    pub fn predict_with_scene(\n        &mut self,\n        scene_id: u64,\n        observations: &[VisualSortObservation],\n    ) -> Vec<SortTrack> {\n        if self.auto_waste.counter == 0 {\n            self.auto_waste();\n            self.auto_waste.counter = self.auto_waste.periodicity;\n        } else {\n            self.auto_waste.counter -= 1;\n        }\n\n        let mut percentages = Vec::default();\n        let use_own_area_percentage = self.metric_opts.visual_minimal_own_area_percentage_collect\n            + self.metric_opts.visual_minimal_own_area_percentage_use\n            > 0.0;\n\n        if use_own_area_percentage {\n            percentages.reserve(observations.len());\n            let boxes = observations\n                .iter()\n                .map(|e| &e.bounding_box)\n                .collect::<Vec<_>>();\n\n            percentages = exclusively_owned_areas_normalized_shares(\n                boxes.as_ref(),\n                exclusively_owned_areas(boxes.as_ref()).as_ref(),\n            );\n        }\n\n        let mut rng = rand::thread_rng();\n        let epoch = self.track_opts.next_epoch(scene_id).unwrap();\n\n        let mut tracks = observations\n            .iter()\n            .enumerate()\n            .map(|(i, o)| {\n                self.store\n                    .read()\n                    .unwrap()\n                    .new_track(rng.gen())\n                    .observation({\n                        let mut obs = ObservationBuilder::new(0).observation_attributes(\n                            if use_own_area_percentage {\n                                VisualObservationAttributes::with_own_area_percentage(\n                                    o.feature_quality.unwrap_or(1.0),\n                                    o.bounding_box.clone(),\n                                    percentages[i],\n                                )\n                            } else {\n                                VisualObservationAttributes::new(\n                                    o.feature_quality.unwrap_or(1.0),\n                                    o.bounding_box.clone(),\n                                )\n                            },\n                        );\n\n                        if let Some(feature) = &o.feature {\n                            obs = obs.observation(Feature::from_vec(feature.to_vec()));\n                        }\n\n                        obs.track_attributes_update(VisualAttributesUpdate::new_init_with_scene(\n                            epoch,\n                            scene_id,\n                            o.custom_object_id,\n                        ))\n                        .build()\n                    })\n                    .build()\n                    .unwrap()\n            })\n            .collect::<Vec<_>>();\n\n        let (dists, errs) =\n            self.store\n                .write()\n                .unwrap()\n                .foreign_track_distances(tracks.clone(), 0, false);\n\n        assert!(errs.all().is_empty());\n        let voting = VisualVoting::new(\n            match self.metric_opts.positional_kind {\n                PositionalMetricType::Mahalanobis => MAHALANOBIS_NEW_TRACK_THRESHOLD,\n                PositionalMetricType::IoU(t) => t,\n            },\n            f32::MAX,\n            self.metric_opts.visual_min_votes,\n        );\n        let winners = voting.winners(dists);\n        let mut res = Vec::default();\n        for t in &mut tracks {\n            let source = t.get_track_id();\n            let track_id: u64 = if let Some(dest) = winners.get(&source) {\n                let (dest, vt) = dest[0];\n                if dest == source {\n                    let mut t = t.clone();\n                    let track_id = self.gen_track_id();\n                    t.set_track_id(track_id);\n                    self.store.write().unwrap().add_track(t).unwrap();\n                    track_id\n                } else {\n                    t.add_observation(\n                        0,\n                        None,\n                        None,\n                        Some(VisualAttributesUpdate::new_voting_type(vt)),\n                    )\n                    .unwrap();\n                    self.store\n                        .write()\n                        .unwrap()\n                        .merge_external(dest, t, Some(&[0]), false)\n                        .unwrap();\n                    dest\n                }\n            } else {\n                let mut t = t.clone();\n                let track_id = self.gen_track_id();\n                t.set_track_id(track_id);\n                self.store.write().unwrap().add_track(t).unwrap();\n                track_id\n            };\n\n            let lock = self.store.read().unwrap();\n            let store = lock.get_store(track_id as usize);\n            let track = store.get(&track_id).unwrap();\n\n            res.push(SortTrack::from(track))\n        }\n\n        res\n    }\n\n    pub fn idle_tracks(&mut self) -> Vec<SortTrack> {\n        self.idle_tracks_with_scene(0)\n    }\n\n    pub fn idle_tracks_with_scene(&mut self, scene_id: u64) -> Vec<SortTrack> {\n        let store = self.store.read().unwrap();\n        store\n            .lookup(VisualSortLookup::IdleLookup(scene_id))\n            .iter()\n            .map(|(track_id, _status)| {\n                let shard = store.get_store(*track_id as usize);\n                let track = shard.get(track_id).unwrap();\n                SortTrack::from(track)\n            })\n            .collect()\n    }\n}\n\nimpl\n    TrackerAPI<\n        VisualAttributes,\n        VisualMetric,\n        VisualObservationAttributes,\n        SortAttributesOptions,\n        NoopNotifier,\n    > for VisualSort\n{\n    fn get_auto_waste_obj_mut(&mut self) -> &mut AutoWaste {\n        &mut self.auto_waste\n    }\n\n    fn get_opts(&self) -> &SortAttributesOptions {\n        &self.track_opts\n    }\n\n    fn get_main_store_mut(\n        &mut self,\n    ) -> RwLockWriteGuard<\n        TrackStore<VisualAttributes, VisualMetric, VisualObservationAttributes, NoopNotifier>,\n    > {\n        self.store.write().unwrap()\n    }\n\n    fn get_wasted_store_mut(\n        &mut self,\n    ) -> RwLockWriteGuard<\n        TrackStore<VisualAttributes, VisualMetric, VisualObservationAttributes, NoopNotifier>,\n    > {\n        self.wasted_store.write().unwrap()\n    }\n\n    fn get_main_store(\n        &self,\n    ) -> RwLockReadGuard<\n        TrackStore<VisualAttributes, VisualMetric, VisualObservationAttributes, NoopNotifier>,\n    > {\n        self.store.read().unwrap()\n    }\n\n    fn get_wasted_store(\n        &self,\n    ) -> RwLockReadGuard<\n        TrackStore<VisualAttributes, VisualMetric, VisualObservationAttributes, NoopNotifier>,\n    > {\n        self.wasted_store.read().unwrap()\n    }\n}\n\nimpl From<&Track<VisualAttributes, VisualMetric, VisualObservationAttributes>> for SortTrack {\n    fn from(track: &Track<VisualAttributes, VisualMetric, VisualObservationAttributes>) -> Self {\n        let attrs = track.get_attributes();\n        SortTrack {\n            id: track.get_track_id(),\n            custom_object_id: attrs.custom_object_id,\n            voting_type: attrs.voting_type.unwrap_or(Positional),\n            epoch: attrs.last_updated_epoch,\n            scene_id: attrs.scene_id,\n            observed_bbox: attrs.observed_boxes.back().unwrap().clone(),\n            predicted_bbox: attrs.predicted_boxes.back().unwrap().clone(),\n            length: attrs.track_length,\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::track::Observation;\n    use crate::trackers::sort::{PositionalMetricType, VotingType};\n    use crate::trackers::tracker_api::TrackerAPI;\n    use crate::trackers::visual_sort::metric::VisualSortMetricType;\n    use crate::trackers::visual_sort::observation_attributes::VisualObservationAttributes;\n    use crate::trackers::visual_sort::options::VisualSortOptions;\n    use crate::trackers::visual_sort::simple_api::VisualSort;\n    use crate::trackers::visual_sort::{VisualSortObservation, WastedVisualSortTrack};\n    use crate::utils::bbox::BoundingBox;\n\n    #[test]\n    fn visual_sort() {\n        let opts = VisualSortOptions::default()\n            .max_idle_epochs(3)\n            .kept_history_length(3)\n            .visual_metric(VisualSortMetricType::Euclidean(1.0))\n            .positional_metric(PositionalMetricType::Mahalanobis)\n            .visual_minimal_track_length(2)\n            .visual_minimal_area(5.0)\n            .visual_minimal_quality_use(0.45)\n            .visual_minimal_quality_collect(0.7)\n            .visual_max_observations(3)\n            .visual_min_votes(2);\n\n        let mut tracker = VisualSort::new(1, &opts);\n\n        // new track to be initialized\n        //\n        let tracks = tracker.predict_with_scene(\n            10,\n            &[VisualSortObservation::new(\n                Some(&vec![1.0, 1.0]),\n                Some(0.9),\n                BoundingBox::new(1.0, 1.0, 3.0, 5.0).as_xyaah(),\n                Some(13),\n            )],\n        );\n        let t = &tracks[0];\n        assert_eq!(t.custom_object_id, Some(13));\n        assert_eq!(t.scene_id, 10);\n        assert!(matches!(t.voting_type, VotingType::Positional));\n        assert!(matches!(t.epoch, 1));\n        let attrs = {\n            let lock = tracker.store.read().unwrap();\n            let store = lock.get_store(t.id as usize);\n            let track = store.get(&t.id).unwrap();\n            track.get_attributes().clone()\n        };\n        assert_eq!(attrs.visual_features_collected_count, 1);\n        assert_eq!(attrs.track_length, 1);\n        assert_eq!(attrs.observed_boxes.len(), 1);\n        assert_eq!(attrs.predicted_boxes.len(), 1);\n        assert_eq!(attrs.observed_features.len(), 1);\n        let first_track_id = t.id;\n\n        {\n            // another scene - new track\n            let tracks = tracker.predict_with_scene(\n                1,\n                &[VisualSortObservation::new(\n                    Some(&vec![1.0, 1.0]),\n                    Some(0.9),\n                    BoundingBox::new(1.0, 1.0, 3.0, 5.0).as_xyaah(),\n                    Some(133),\n                )],\n            );\n            let t = &tracks[0];\n            assert_eq!(t.custom_object_id, Some(133));\n            assert_eq!(t.scene_id, 1);\n            assert!(matches!(t.voting_type, VotingType::Positional));\n            assert!(matches!(t.epoch, 1));\n            let attrs = {\n                let lock = tracker.store.read().unwrap();\n                let store = lock.get_store(t.id as usize);\n                let track = store.get(&t.id).unwrap();\n                track.get_attributes().clone()\n            };\n            assert_eq!(attrs.visual_features_collected_count, 1);\n            assert_eq!(attrs.track_length, 1);\n            assert_eq!(attrs.observed_boxes.len(), 1);\n            assert_eq!(attrs.predicted_boxes.len(), 1);\n            assert_eq!(attrs.observed_features.len(), 1);\n        }\n\n        // add the segment to the track (merge by bbox pos)\n        //\n        let tracks = tracker.predict_with_scene(\n            10,\n            &[VisualSortObservation::new(\n                Some(&vec![0.95, 0.95]),\n                Some(0.93),\n                BoundingBox::new(1.1, 1.1, 3.05, 5.01).as_xyaah(),\n                Some(15),\n            )],\n        );\n        let t = &tracks[0];\n        assert_eq!(t.id, first_track_id);\n        assert_eq!(t.custom_object_id, Some(15));\n        assert_eq!(t.scene_id, 10);\n        assert!(matches!(t.voting_type, VotingType::Positional));\n        assert!(matches!(t.epoch, 2));\n        let attrs = {\n            let lock = tracker.store.read().unwrap();\n            let store = lock.get_store(t.id as usize);\n            let track = store.get(&t.id).unwrap();\n            track.get_attributes().clone()\n        };\n        assert_eq!(attrs.visual_features_collected_count, 2);\n        assert_eq!(attrs.track_length, 2);\n        assert_eq!(attrs.observed_boxes.len(), 2);\n        assert_eq!(attrs.predicted_boxes.len(), 2);\n        assert_eq!(attrs.observed_features.len(), 2);\n\n        // add the segment to the track (no visual_sort feature)\n        //\n        let tracks = tracker.predict_with_scene(\n            10,\n            &[VisualSortObservation::new(\n                None,\n                Some(0.93),\n                BoundingBox::new(1.11, 1.15, 3.15, 5.05).as_xyaah(),\n                Some(25),\n            )],\n        );\n        let t = &tracks[0];\n        assert_eq!(t.id, first_track_id);\n        assert_eq!(t.custom_object_id, Some(25));\n        assert_eq!(t.scene_id, 10);\n        assert!(matches!(t.voting_type, VotingType::Positional));\n        assert!(matches!(t.epoch, 3));\n        let attrs = {\n            let lock = tracker.store.read().unwrap();\n            let store = lock.get_store(t.id as usize);\n            let track = store.get(&t.id).unwrap();\n            track.get_attributes().clone()\n        };\n        assert_eq!(attrs.visual_features_collected_count, 2);\n        assert_eq!(attrs.track_length, 3);\n        assert_eq!(attrs.observed_boxes.len(), 3);\n        assert_eq!(attrs.predicted_boxes.len(), 3);\n        assert_eq!(attrs.observed_features.len(), 3);\n        assert!(attrs.observed_features.back().unwrap().is_none());\n\n        // add the segment to the track (no visual_sort feature)\n        //\n        let tracks = tracker.predict_with_scene(\n            10,\n            &[VisualSortObservation::new(\n                None,\n                Some(0.93),\n                BoundingBox::new(1.15, 1.25, 3.10, 5.05).as_xyaah(),\n                Some(2),\n            )],\n        );\n        let t = &tracks[0];\n        assert_eq!(t.id, first_track_id);\n        assert!(matches!(t.voting_type, VotingType::Positional));\n        assert!(matches!(t.epoch, 4));\n        let attrs = {\n            let lock = tracker.store.read().unwrap();\n            let store = lock.get_store(t.id as usize);\n            let track = store.get(&t.id).unwrap();\n            track.get_attributes().clone()\n        };\n        assert_eq!(attrs.visual_features_collected_count, 2);\n        assert_eq!(attrs.track_length, 4);\n        assert_eq!(attrs.observed_boxes.len(), 3);\n        assert_eq!(attrs.predicted_boxes.len(), 3);\n        assert_eq!(attrs.observed_features.len(), 3);\n        assert!(attrs.observed_features.back().unwrap().is_none());\n\n        // add the segment to the track (with visual_sort feature but low quality - no use, no collect)\n        //\n        let tracks = tracker.predict_with_scene(\n            10,\n            &[VisualSortObservation::new(\n                Some(&vec![0.97, 0.97]),\n                Some(0.44),\n                BoundingBox::new(1.15, 1.25, 3.10, 5.05).as_xyaah(),\n                Some(2),\n            )],\n        );\n        let t = &tracks[0];\n        assert_eq!(t.id, first_track_id);\n        assert!(matches!(t.voting_type, VotingType::Positional));\n        let attrs = {\n            let lock = tracker.store.read().unwrap();\n            let store = lock.get_store(t.id as usize);\n            let track = store.get(&t.id).unwrap();\n            track.get_attributes().clone()\n        };\n        assert_eq!(attrs.visual_features_collected_count, 2);\n        assert_eq!(attrs.track_length, 5);\n        assert!(attrs.observed_features.back().unwrap().is_some());\n\n        // add the segment to the track (with visual_sort feature but low quality - use, but no collect)\n        //\n        let tracks = tracker.predict_with_scene(\n            10,\n            &[VisualSortObservation::new(\n                Some(&vec![0.97, 0.97]),\n                Some(0.6),\n                BoundingBox::new(1.15, 1.25, 3.10, 5.05).as_xyaah(),\n                Some(2),\n            )],\n        );\n        let t = &tracks[0];\n        assert_eq!(t.id, first_track_id);\n        assert!(matches!(t.voting_type, VotingType::Visual));\n        let attrs = {\n            let lock = tracker.store.read().unwrap();\n            let store = lock.get_store(t.id as usize);\n            let track = store.get(&t.id).unwrap();\n            track.get_attributes().clone()\n        };\n        assert_eq!(attrs.visual_features_collected_count, 2);\n        assert_eq!(attrs.track_length, 6);\n        assert!(attrs.observed_features.back().unwrap().is_some());\n\n        // add the segment to the track (with visual_sort feature of normal quality - use, collect)\n        //\n        let tracks = tracker.predict_with_scene(\n            10,\n            &[VisualSortObservation::new(\n                Some(&vec![0.97, 0.97]),\n                Some(0.8),\n                BoundingBox::new(1.15, 1.25, 3.10, 5.05).as_xyaah(),\n                Some(2),\n            )],\n        );\n        let t = &tracks[0];\n        assert_eq!(t.id, first_track_id);\n        assert!(matches!(t.voting_type, VotingType::Visual));\n        let attrs = {\n            let lock = tracker.store.read().unwrap();\n            let store = lock.get_store(t.id as usize);\n            let track = store.get(&t.id).unwrap();\n            let observations = track.get_observations(0).unwrap();\n\n            fn bbox_is(b: &Observation<VisualObservationAttributes>) -> bool {\n                b.attr().as_ref().unwrap().bbox_opt().is_some()\n            }\n\n            assert!(bbox_is(&observations[0]) && observations[0].feature().is_some());\n            assert!(!bbox_is(&observations[1]) && observations[1].feature().is_some());\n            assert!(!bbox_is(&observations[2]) && observations[2].feature().is_some());\n\n            track.get_attributes().clone()\n        };\n        assert_eq!(attrs.visual_features_collected_count, 3);\n        assert_eq!(attrs.track_length, 7);\n        assert!(attrs.observed_features.back().unwrap().is_some());\n\n        // new track to be initialized\n        //\n        let tracks = tracker.predict_with_scene(\n            10,\n            &[VisualSortObservation::new(\n                Some(&vec![0.1, 0.1]),\n                Some(0.9),\n                BoundingBox::new(10.0, 10.0, 3.0, 5.0).as_xyaah(),\n                Some(33),\n            )],\n        );\n        let t = &tracks[0];\n        assert_eq!(t.custom_object_id, Some(33));\n        assert_eq!(t.scene_id, 10);\n        assert!(matches!(t.voting_type, VotingType::Positional));\n        assert!(matches!(t.epoch, 8));\n        assert_ne!(t.id, first_track_id);\n        let attrs = {\n            let lock = tracker.store.read().unwrap();\n            let store = lock.get_store(t.id as usize);\n            let track = store.get(&t.id).unwrap();\n            track.get_attributes().clone()\n        };\n        assert_eq!(attrs.visual_features_collected_count, 1);\n        assert_eq!(attrs.track_length, 1);\n        assert_eq!(attrs.observed_boxes.len(), 1);\n        assert_eq!(attrs.predicted_boxes.len(), 1);\n        assert_eq!(attrs.observed_features.len(), 1);\n        let other_track_id = t.id;\n\n        // add segment to be initialized\n        //\n        let tracks = tracker.predict_with_scene(\n            10,\n            &[VisualSortObservation::new(\n                Some(&vec![0.12, 0.15]),\n                Some(0.88),\n                BoundingBox::new(10.1, 10.1, 3.0, 5.0).as_xyaah(),\n                Some(35),\n            )],\n        );\n        let t = &tracks[0];\n        assert_eq!(t.custom_object_id, Some(35));\n        assert_eq!(t.scene_id, 10);\n        assert!(matches!(t.voting_type, VotingType::Positional));\n        assert!(matches!(t.epoch, 9));\n        assert_eq!(t.id, other_track_id);\n        let attrs = {\n            let lock = tracker.store.read().unwrap();\n            let store = lock.get_store(t.id as usize);\n            let track = store.get(&t.id).unwrap();\n            track.get_attributes().clone()\n        };\n        assert_eq!(attrs.visual_features_collected_count, 2);\n        assert_eq!(attrs.track_length, 2);\n        assert_eq!(attrs.observed_boxes.len(), 2);\n        assert_eq!(attrs.predicted_boxes.len(), 2);\n        assert_eq!(attrs.observed_features.len(), 2);\n\n        // add segment to be initialized\n        //\n        let tracks = tracker.predict_with_scene(\n            10,\n            &[VisualSortObservation::new(\n                Some(&vec![0.12, 0.14]),\n                Some(0.87),\n                BoundingBox::new(10.1, 10.1, 3.0, 5.0).as_xyaah(),\n                Some(31),\n            )],\n        );\n        let t = &tracks[0];\n        assert_eq!(t.custom_object_id, Some(31));\n        assert_eq!(t.scene_id, 10);\n        assert!(matches!(t.voting_type, VotingType::Visual));\n        assert!(matches!(t.epoch, 10));\n        assert_eq!(t.id, other_track_id);\n        let attrs = {\n            let lock = tracker.store.read().unwrap();\n            let store = lock.get_store(t.id as usize);\n            let track = store.get(&t.id).unwrap();\n            track.get_attributes().clone()\n        };\n        assert_eq!(attrs.visual_features_collected_count, 3);\n        assert_eq!(attrs.track_length, 3);\n        assert_eq!(attrs.observed_boxes.len(), 3);\n        assert_eq!(attrs.predicted_boxes.len(), 3);\n        assert_eq!(attrs.observed_features.len(), 3);\n\n        tracker.skip_epochs_for_scene(10, 5);\n        let tracks = tracker\n            .wasted()\n            .into_iter()\n            .map(WastedVisualSortTrack::from)\n            .collect::<Vec<_>>();\n        dbg!(&tracks);\n    }\n}\n\n#[cfg(feature = \"python\")]\npub mod python {\n    use pyo3::prelude::*;\n\n    use crate::{\n        prelude::VisualSortObservation,\n        trackers::{\n            sort::python::PySortTrack,\n            tracker_api::TrackerAPI,\n            visual_sort::{\n                options::python::PyVisualSortOptions,\n                python::{PyVisualSortObservationSet, PyWastedVisualSortTrack},\n                WastedVisualSortTrack,\n            },\n        },\n    };\n\n    use super::VisualSort;\n\n    #[pyclass]\n    #[pyo3(name = \"VisualSort\")]\n    pub struct PyVisualSort(pub(crate) VisualSort);\n\n    #[pymethods]\n    impl PyVisualSort {\n        #[new]\n        pub fn new(shards: i64, opts: &PyVisualSortOptions) -> Self {\n            assert!(shards > 0);\n            Self(VisualSort::new(shards.try_into().unwrap(), &opts.0))\n        }\n\n        #[pyo3(signature = (n))]\n        pub fn skip_epochs(&mut self, n: i64) {\n            assert!(n > 0);\n            self.0.skip_epochs(n.try_into().unwrap())\n        }\n\n        #[pyo3(signature = (scene_id, n))]\n        pub fn skip_epochs_for_scene(&mut self, scene_id: i64, n: i64) {\n            assert!(n > 0 && scene_id >= 0);\n            self.0\n                .skip_epochs_for_scene(scene_id.try_into().unwrap(), n.try_into().unwrap())\n        }\n\n        /// Get the amount of stored tracks per shard\n        ///\n        #[pyo3(signature = ())]\n        pub fn shard_stats(&self) -> Vec<i64> {\n            Python::with_gil(|py| {\n                py.allow_threads(|| {\n                    self.0\n                        .active_shard_stats()\n                        .into_iter()\n                        .map(|e| i64::try_from(e).unwrap())\n                        .collect()\n                })\n            })\n        }\n\n        /// Get the current epoch for `scene_id` == 0\n        ///\n        #[pyo3(signature = ())]\n        pub fn current_epoch(&self) -> i64 {\n            self.0.current_epoch_with_scene(0).try_into().unwrap()\n        }\n\n        /// Get the current epoch for `scene_id`\n        ///\n        /// # Parameters\n        /// * `scene_id` - scene id\n        ///\n        #[pyo3(signature = (scene_id))]\n        pub fn current_epoch_with_scene(&self, scene_id: i64) -> isize {\n            assert!(scene_id >= 0);\n            self.0\n                .current_epoch_with_scene(scene_id.try_into().unwrap())\n                .try_into()\n                .unwrap()\n        }\n\n        /// Receive tracking information for observed bboxes of `scene_id` == 0\n        ///\n        /// # Parameters\n        /// * `bboxes` - bounding boxes received from a detector\n        ///\n        #[pyo3(signature = (observation_set))]\n        pub fn predict(\n            &mut self,\n            observation_set: &PyVisualSortObservationSet,\n        ) -> Vec<PySortTrack> {\n            unsafe { std::mem::transmute(self.0.predict_with_scene(0, &observation_set.0.inner)) }\n        }\n\n        /// Receive tracking information for observed bboxes of `scene_id`\n        ///\n        /// # Parameters\n        /// * `scene_id` - scene id provided by a user (class, camera id, etc...)\n        /// * `observation_set` - observation set\n        ///\n        #[pyo3(signature = (scene_id, observation_set))]\n        pub fn predict_with_scene(\n            &mut self,\n            scene_id: i64,\n            observation_set: &PyVisualSortObservationSet,\n        ) -> Vec<PySortTrack> {\n            assert!(scene_id >= 0);\n            let observations = observation_set\n                .0\n                .inner\n                .iter()\n                .map(|e| {\n                    VisualSortObservation::new(\n                        e.feature.as_deref(),\n                        e.feature_quality,\n                        e.bounding_box.clone(),\n                        e.custom_object_id,\n                    )\n                })\n                .collect::<Vec<_>>();\n\n            Python::with_gil(|py| {\n                py.allow_threads(|| unsafe {\n                    std::mem::transmute(\n                        self.0\n                            .predict_with_scene(scene_id.try_into().unwrap(), &observations),\n                    )\n                })\n            })\n        }\n\n        /// Remove all the tracks with expired life\n        ///\n        #[pyo3(signature = ())]\n        pub fn wasted(&mut self) -> Vec<PyWastedVisualSortTrack> {\n            Python::with_gil(|py| {\n                py.allow_threads(|| {\n                    self.0\n                        .wasted()\n                        .into_iter()\n                        .map(WastedVisualSortTrack::from)\n                        .map(PyWastedVisualSortTrack)\n                        .collect()\n                })\n            })\n        }\n\n        /// Clear all tracks with expired life\n        ///\n        #[pyo3(signature = ())]\n        pub fn clear_wasted(&mut self) {\n            Python::with_gil(|py| py.allow_threads(|| self.0.clear_wasted()));\n        }\n\n        /// Get idle tracks with not expired life\n        ///\n        #[pyo3(signature = ())]\n        pub fn idle_tracks(&mut self) -> Vec<PySortTrack> {\n            unsafe { std::mem::transmute(self.0.idle_tracks_with_scene(0)) }\n        }\n\n        /// Get idle tracks with not expired life\n        ///\n        #[pyo3(signature = (scene_id))]\n        pub fn idle_tracks_with_scene_py(&mut self, scene_id: i64) -> Vec<PySortTrack> {\n            Python::with_gil(|py| {\n                py.allow_threads(|| unsafe {\n                    std::mem::transmute(self.0.idle_tracks_with_scene(scene_id.try_into().unwrap()))\n                })\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "src/trackers/visual_sort/track_attributes.rs",
    "content": "use crate::track::{\n    Feature, LookupRequest, ObservationsDb, TrackAttributes, TrackAttributesUpdate, TrackStatus,\n};\nuse crate::trackers::epoch_db::EpochDb;\nuse crate::trackers::kalman_prediction::TrackAttributesKalmanPrediction;\nuse crate::trackers::sort::{SortAttributesOptions, VotingType};\nuse crate::trackers::visual_sort::observation_attributes::VisualObservationAttributes;\nuse crate::utils::bbox::Universal2DBox;\nuse crate::utils::kalman::kalman_2d_box::DIM_2D_BOX_X2;\nuse crate::utils::kalman::KalmanState;\nuse anyhow::Result;\nuse std::collections::VecDeque;\nuse std::sync::Arc;\n\n/// Universal visual_sort attributes for visual_sort trackers\n///\n#[derive(Debug, Clone)]\npub struct VisualAttributes {\n    /// Boxes predicted by Kalman filter\n    pub predicted_boxes: VecDeque<Universal2DBox>,\n    /// Boxes observed by detector\n    pub observed_boxes: VecDeque<Universal2DBox>,\n    /// Features observed by feature extractor model\n    pub observed_features: VecDeque<Option<Feature>>,\n    /// The last epoch when attributes were updated\n    pub last_updated_epoch: usize,\n    /// The length of the track\n    pub track_length: usize,\n    /// Visual track elements amount collected\n    pub visual_features_collected_count: usize,\n    /// Custom scene id provided by the user\n    pub scene_id: u64,\n    /// Custom object id provided for the bbox and observation\n    pub custom_object_id: Option<i64>,\n    /// Last voting type\n    pub voting_type: Option<VotingType>,\n\n    state: Option<KalmanState<{ DIM_2D_BOX_X2 }>>,\n    opts: Arc<SortAttributesOptions>,\n}\n\nimpl Default for VisualAttributes {\n    fn default() -> Self {\n        Self {\n            voting_type: None,\n            predicted_boxes: VecDeque::default(),\n            observed_boxes: VecDeque::default(),\n            observed_features: VecDeque::default(),\n            last_updated_epoch: 0,\n            track_length: 0,\n            visual_features_collected_count: 0,\n            scene_id: 0,\n            custom_object_id: None,\n            state: None,\n            opts: Arc::new(SortAttributesOptions::default()),\n        }\n    }\n}\n\nimpl VisualAttributes {\n    /// Creates new attributes with limited history\n    ///\n    /// # Parameters\n    /// * `opts` - attribute options.\n    ///\n    pub fn new(opts: Arc<SortAttributesOptions>) -> Self {\n        Self {\n            opts,\n            ..Default::default()\n        }\n    }\n\n    pub fn update_history(\n        &mut self,\n        observation_bbox: &Universal2DBox,\n        predicted_bbox: &Universal2DBox,\n        observation_feature: Option<Feature>,\n    ) {\n        self.track_length += 1;\n\n        self.observed_boxes.push_back(observation_bbox.clone());\n        self.predicted_boxes.push_back(predicted_bbox.clone());\n        self.observed_features.push_back(observation_feature);\n\n        if self.opts.history_length > 0 && self.observed_boxes.len() > self.opts.history_length {\n            self.observed_boxes.pop_front();\n            self.predicted_boxes.pop_front();\n            self.observed_features.pop_front();\n        }\n    }\n}\n\nimpl TrackAttributesKalmanPrediction for VisualAttributes {\n    fn get_state(&self) -> Option<KalmanState<{ DIM_2D_BOX_X2 }>> {\n        self.state\n    }\n\n    fn set_state(&mut self, state: KalmanState<{ DIM_2D_BOX_X2 }>) {\n        self.state = Some(state);\n    }\n\n    fn get_position_weight(&self) -> f32 {\n        self.opts.position_weight\n    }\n\n    fn get_velocity_weight(&self) -> f32 {\n        self.opts.velocity_weight\n    }\n}\n\n#[derive(Clone, Debug)]\npub enum VisualSortLookup {\n    IdleLookup(u64),\n}\n\nimpl LookupRequest<VisualAttributes, VisualObservationAttributes> for VisualSortLookup {\n    fn lookup(\n        &self,\n        attributes: &VisualAttributes,\n        _observations: &ObservationsDb<VisualObservationAttributes>,\n        _merge_history: &[u64],\n    ) -> bool {\n        match self {\n            VisualSortLookup::IdleLookup(scene_id) => {\n                *scene_id == attributes.scene_id\n                    && attributes.last_updated_epoch\n                        != attributes\n                            .opts\n                            .current_epoch_with_scene(attributes.scene_id)\n                            .unwrap()\n            }\n        }\n    }\n}\n\n#[derive(Clone, Debug)]\npub enum VisualAttributesUpdate {\n    Init {\n        epoch: usize,\n        scene_id: u64,\n        custom_object_id: Option<i64>,\n    },\n    VotingType(VotingType),\n}\n\nimpl VisualAttributesUpdate {\n    pub fn new_init(epoch: usize, custom_object_id: Option<i64>) -> Self {\n        Self::new_init_with_scene(epoch, 0, custom_object_id)\n    }\n\n    pub fn new_init_with_scene(epoch: usize, scene_id: u64, custom_object_id: Option<i64>) -> Self {\n        Self::Init {\n            epoch,\n            scene_id,\n            custom_object_id,\n        }\n    }\n\n    pub fn new_voting_type(vt: VotingType) -> Self {\n        Self::VotingType(vt)\n    }\n}\n\nimpl TrackAttributesUpdate<VisualAttributes> for VisualAttributesUpdate {\n    fn apply(&self, attrs: &mut VisualAttributes) -> Result<()> {\n        match self {\n            Self::Init {\n                epoch,\n                scene_id,\n                custom_object_id,\n            } => {\n                attrs.last_updated_epoch = *epoch;\n                attrs.scene_id = *scene_id;\n                attrs.custom_object_id = *custom_object_id;\n            }\n            VisualAttributesUpdate::VotingType(vt) => {\n                attrs.voting_type = Some(*vt);\n            }\n        }\n        Ok(())\n    }\n}\n\nimpl TrackAttributes<VisualAttributes, VisualObservationAttributes> for VisualAttributes {\n    type Update = VisualAttributesUpdate;\n    type Lookup = VisualSortLookup;\n\n    fn compatible(&self, other: &VisualAttributes) -> bool {\n        if self.scene_id == other.scene_id {\n            let o1 = self.predicted_boxes.back().unwrap();\n            let o2 = other.predicted_boxes.back().unwrap();\n\n            let epoch_delta = (self.last_updated_epoch as i128 - other.last_updated_epoch as i128)\n                .abs()\n                .try_into()\n                .unwrap();\n\n            let center_dist = Universal2DBox::dist_in_2r(o1, o2);\n\n            self.opts.max_idle_epochs() >= epoch_delta\n                && self\n                    .opts\n                    .spatio_temporal_constraints\n                    .validate(epoch_delta, center_dist)\n        } else {\n            false\n        }\n    }\n\n    fn merge(&mut self, other: &VisualAttributes) -> Result<()> {\n        self.last_updated_epoch = other.last_updated_epoch;\n        self.custom_object_id = other.custom_object_id;\n        self.voting_type = other.voting_type;\n        Ok(())\n    }\n\n    fn baked(\n        &self,\n        _observations: &ObservationsDb<VisualObservationAttributes>,\n    ) -> Result<TrackStatus> {\n        self.opts.baked(self.scene_id, self.last_updated_epoch)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::trackers::sort::SortAttributesOptions;\n    use crate::trackers::spatio_temporal_constraints::SpatioTemporalConstraints;\n    use crate::trackers::visual_sort::track_attributes::VisualAttributes;\n    use crate::utils::bbox::BoundingBox;\n    use std::collections::HashMap;\n    use std::sync::{Arc, RwLock};\n\n    #[test]\n    fn attribute_operations() {\n        let opts = SortAttributesOptions::new(\n            Some(RwLock::new(HashMap::default())),\n            5,\n            1,\n            SpatioTemporalConstraints::default(),\n            1.0 / 20.0,\n            1.0 / 160.0,\n        );\n        let mut attributes = VisualAttributes::new(Arc::new(opts));\n        attributes.update_history(\n            &BoundingBox::new(0.0, 3.0, 5.0, 7.0).as_xyaah(),\n            &BoundingBox::new(0.1, 3.1, 5.1, 7.1).as_xyaah(),\n            None,\n        );\n\n        assert_eq!(attributes.observed_boxes.len(), 1);\n        assert_eq!(attributes.predicted_boxes.len(), 1);\n        assert_eq!(attributes.observed_features.len(), 1);\n        assert_eq!(attributes.track_length, 1);\n\n        attributes.update_history(\n            &BoundingBox::new(0.0, 3.0, 5.0, 7.0).as_xyaah(),\n            &BoundingBox::new(0.1, 3.1, 5.1, 7.1).as_xyaah(),\n            None,\n        );\n\n        assert_eq!(attributes.observed_boxes.len(), 1);\n        assert_eq!(attributes.predicted_boxes.len(), 1);\n        assert_eq!(attributes.observed_features.len(), 1);\n        assert_eq!(attributes.track_length, 2);\n    }\n}\n"
  },
  {
    "path": "src/trackers/visual_sort/visual_sort_py.rs",
    "content": "use crate::prelude::Universal2DBox;\nuse crate::trackers::batch::{PredictionBatchRequest, PredictionBatchResult};\nuse pyo3::prelude::*;\n\n#[derive(Debug, Clone)]\n#[pyclass]\n#[pyo3(name = \"VisualSortPredictionBatchRequest\")]\npub(crate) struct PyVisualSortPredictionBatchRequest {\n    pub(crate) batch: PredictionBatchRequest<PyVisualSortObservation>,\n    result: Option<PredictionBatchResult>,\n}\n\n#[pymethods]\nimpl PyVisualSortPredictionBatchRequest {\n    #[new]\n    fn new() -> Self {\n        let (batch, result) = PredictionBatchRequest::new();\n        Self {\n            batch,\n            result: Some(result),\n        }\n    }\n\n    fn prediction(&mut self) -> Option<PredictionBatchResult> {\n        self.result.take()\n    }\n\n    fn add(&mut self, scene_id: u64, elt: PyVisualSortObservation) {\n        self.batch.add(scene_id, elt);\n    }\n}\n\n#[pyclass(\n    text_signature = \"(feature_opt, feature_quality_opt, bounding_box, custom_object_id_opt)\"\n)]\n#[derive(Debug, Clone)]\n#[pyo3(name = \"VisualSortObservation\")]\npub struct PyVisualSortObservation {\n    pub feature: Option<Vec<f32>>,\n    pub feature_quality: Option<f32>,\n    pub bounding_box: Universal2DBox,\n    pub custom_object_id: Option<i64>,\n}\n\n#[pymethods]\nimpl PyVisualSortObservation {\n    #[new]\n    #[pyo3(signature = (feature, feature_quality, bounding_box, custom_object_id))]\n    pub fn new(\n        feature: Option<Vec<f32>>,\n        feature_quality: Option<f32>,\n        bounding_box: Universal2DBox,\n        custom_object_id: Option<i64>,\n    ) -> Self {\n        Self {\n            feature,\n            feature_quality,\n            bounding_box,\n            custom_object_id,\n        }\n    }\n\n    #[classattr]\n    const __hash__: Option<Py<PyAny>> = None;\n\n    fn __repr__(&self) -> String {\n        format!(\"{self:?}\")\n    }\n\n    fn __str__(&self) -> String {\n        format!(\"{self:#?}\")\n    }\n}\n\n#[pyclass(\n    text_signature = \"(feature_opt, feature_quality_opt, bounding_box, custom_object_id_opt)\"\n)]\n#[derive(Debug)]\n#[pyo3(name = \"VisualSortObservationSet\")]\npub struct PyVisualSortObservationSet {\n    pub inner: Vec<PyVisualSortObservation>,\n}\n\n#[pymethods]\nimpl PyVisualSortObservationSet {\n    #[new]\n    fn new() -> Self {\n        Self {\n            inner: Vec::default(),\n        }\n    }\n\n    #[pyo3(text_signature = \"($self, observation)\")]\n    fn add(&mut self, observation: PyVisualSortObservation) {\n        self.inner.push(observation);\n    }\n\n    #[classattr]\n    const __hash__: Option<Py<PyAny>> = None;\n\n    fn __repr__(&self) -> String {\n        format!(\"{self:?}\")\n    }\n\n    fn __str__(&self) -> String {\n        format!(\"{self:#?}\")\n    }\n}\n"
  },
  {
    "path": "src/trackers/visual_sort/voting.rs",
    "content": "use crate::track::ObservationMetricOk;\nuse crate::trackers::sort::voting::SortVoting;\nuse crate::trackers::sort::VotingType;\nuse crate::trackers::visual_sort::observation_attributes::VisualObservationAttributes;\nuse crate::utils::bbox::Universal2DBox;\nuse crate::voting::best::BestFitVoting;\nuse crate::voting::Voting;\nuse itertools::Itertools;\nuse log::debug;\nuse std::collections::{HashMap, HashSet};\n\npub struct VisualVoting {\n    positional_threshold: f32,\n    max_allowed_feature_distance: f32,\n    min_winner_feature_votes: usize,\n}\n\nimpl VisualVoting {\n    pub fn new(\n        positional_threshold: f32,\n        max_allowed_feature_distance: f32,\n        min_winner_feature_votes: usize,\n    ) -> Self {\n        Self {\n            positional_threshold,\n            max_allowed_feature_distance,\n            min_winner_feature_votes,\n        }\n    }\n}\n\nimpl From<ObservationMetricOk<VisualObservationAttributes>>\n    for ObservationMetricOk<Universal2DBox>\n{\n    fn from(e: ObservationMetricOk<VisualObservationAttributes>) -> Self {\n        ObservationMetricOk {\n            from: e.from,\n            to: e.to,\n            attribute_metric: e.attribute_metric,\n            feature_distance: e.feature_distance,\n        }\n    }\n}\n\nimpl Voting<VisualObservationAttributes> for VisualVoting {\n    type WinnerObject = (u64, VotingType);\n\n    fn winners<T>(&self, distances: T) -> HashMap<u64, Vec<Self::WinnerObject>>\n    where\n        T: IntoIterator<Item = ObservationMetricOk<VisualObservationAttributes>>,\n    {\n        let topn_feature_voting: BestFitVoting<VisualObservationAttributes> = BestFitVoting::new(\n            self.max_allowed_feature_distance,\n            self.min_winner_feature_votes,\n        );\n\n        let (distances, distances_clone) = distances.into_iter().tee();\n\n        let feature_winners = topn_feature_voting.winners(distances);\n        debug!(\"TopN winners: {:#?}\", &feature_winners);\n\n        let mut excluded_tracks = HashSet::new();\n        let mut feature_winners = feature_winners\n            .into_iter()\n            .map(|(from, w)| {\n                let winner_track = w[0].winner_track;\n                excluded_tracks.insert(winner_track);\n                (from, vec![(winner_track, VotingType::Visual)])\n            })\n            .collect::<HashMap<_, _>>();\n\n        let mut remaining_candidates = HashSet::new();\n        let mut remaining_tracks = HashSet::new();\n        let remaining_distances = distances_clone\n            .into_iter()\n            .filter(|e: &ObservationMetricOk<VisualObservationAttributes>| {\n                (!(feature_winners.contains_key(&e.from) || excluded_tracks.contains(&e.to)))\n                    && e.attribute_metric.is_some()\n            })\n            .map(|e| {\n                remaining_candidates.insert(e.from);\n                remaining_tracks.insert(e.to);\n                e.into()\n            })\n            .collect::<Vec<_>>();\n\n        let positional_voting = SortVoting::new(\n            self.positional_threshold,\n            remaining_candidates.len(),\n            remaining_tracks.len(),\n        );\n\n        let positional_winners = positional_voting\n            .winners(remaining_distances)\n            .into_iter()\n            .map(|(from, winner)| (from, vec![(winner[0], VotingType::Positional)]));\n\n        feature_winners.extend(positional_winners);\n        feature_winners\n    }\n}\n\n#[cfg(test)]\nmod voting_tests {\n    use crate::track::ObservationMetricOk;\n    use crate::trackers::visual_sort::voting::{VisualVoting, VotingType};\n    use crate::voting::Voting;\n\n    #[test]\n    fn test_visual_match() {\n        let v = VisualVoting::new(0.3, 0.7, 1);\n        let w = v.winners(vec![ObservationMetricOk::new(1, 2, Some(0.7), Some(0.7))]);\n        assert!(matches!(w.get(&1).unwrap()[0].1, VotingType::Visual));\n    }\n\n    #[test]\n    fn test_positional_match() {\n        let v = VisualVoting::new(0.3, 0.7, 2);\n        let w = v.winners(vec![ObservationMetricOk::new(1, 2, Some(0.7), Some(0.7))]);\n        assert!(matches!(w.get(&1).unwrap()[0].1, VotingType::Positional));\n    }\n\n    #[test]\n    fn test_visual_competitive_match() {\n        let v = VisualVoting::new(0.3, 0.7, 2);\n        let w = v.winners(vec![\n            ObservationMetricOk::new(1, 2, Some(0.7), Some(0.7)),\n            ObservationMetricOk::new(1, 2, None, Some(0.68)),\n            ObservationMetricOk::new(1, 2, None, Some(0.65)),\n            ObservationMetricOk::new(1, 3, Some(0.7), Some(0.7)),\n            ObservationMetricOk::new(1, 3, None, Some(0.64)),\n        ]);\n        let res = w.get(&1).unwrap();\n        assert_eq!(res.len(), 1);\n        assert!(matches!(res[0].1, VotingType::Visual));\n        assert!(matches!(res[0].0, 2));\n    }\n\n    #[test]\n    fn test_visual_competitive_match_2() {\n        let v = VisualVoting::new(0.3, 0.7, 2);\n        let w = v.winners(vec![\n            ObservationMetricOk::new(1, 2, Some(0.7), Some(0.7)),\n            ObservationMetricOk::new(1, 2, None, Some(0.68)),\n            ObservationMetricOk::new(1, 2, None, Some(0.65)),\n            ObservationMetricOk::new(4, 3, Some(0.7), Some(0.7)),\n            ObservationMetricOk::new(4, 3, None, Some(0.64)),\n        ]);\n        let res = w.get(&1).unwrap();\n        assert_eq!(res.len(), 1);\n        assert!(matches!(res[0].1, VotingType::Visual));\n        assert!(matches!(res[0].0, 2));\n\n        let res = w.get(&4).unwrap();\n        assert_eq!(res.len(), 1);\n        assert!(matches!(res[0].1, VotingType::Visual));\n        assert!(matches!(res[0].0, 3));\n    }\n\n    #[test]\n    fn test_visual_positional_competitive_match_2() {\n        let v = VisualVoting::new(0.3, 0.7, 2);\n        let w = v.winners(vec![\n            ObservationMetricOk::new(1, 2, Some(0.7), Some(0.7)),\n            ObservationMetricOk::new(1, 2, None, Some(0.68)),\n            ObservationMetricOk::new(1, 2, None, Some(0.65)),\n            ObservationMetricOk::new(1, 3, Some(0.7), Some(0.7)),\n            ObservationMetricOk::new(1, 3, None, Some(0.64)),\n            ObservationMetricOk::new(11, 2, Some(0.8), Some(0.7)),\n            ObservationMetricOk::new(11, 3, Some(0.6), Some(0.64)),\n        ]);\n        let res = w.get(&1).unwrap();\n        assert_eq!(res.len(), 1);\n        assert!(matches!(res[0].1, VotingType::Visual));\n        assert!(matches!(res[0].0, 2));\n\n        let res = w.get(&11).unwrap();\n        assert_eq!(res.len(), 1);\n        assert!(matches!(res[0].1, VotingType::Positional));\n        assert!(matches!(res[0].0, 3));\n    }\n\n    #[test]\n    fn test_visual_positional_competitive_match_no_pos_metric() {\n        let v = VisualVoting::new(0.3, 0.7, 2);\n        let w = v.winners(vec![\n            ObservationMetricOk::new(1, 2, Some(0.7), Some(0.7)),\n            ObservationMetricOk::new(1, 2, None, Some(0.68)),\n            ObservationMetricOk::new(1, 2, None, Some(0.65)),\n            ObservationMetricOk::new(1, 3, Some(0.7), Some(0.7)),\n            ObservationMetricOk::new(1, 3, None, Some(0.64)),\n            ObservationMetricOk::new(11, 2, Some(0.8), Some(0.7)), // will be excluded by visual_sort voting (1>2)\n            ObservationMetricOk::new(11, 3, None, Some(0.64)), // no pos metric, as visual_sort votes\n                                                               // less 2 will go to pos voting, but no pos metric.\n        ]);\n        let res = w.get(&1).unwrap();\n        assert_eq!(res.len(), 1);\n        assert!(matches!(res[0].1, VotingType::Visual));\n        assert!(matches!(res[0].0, 2));\n\n        assert!(w.get(&11).is_none());\n    }\n}\n"
  },
  {
    "path": "src/trackers/visual_sort.rs",
    "content": "use std::borrow::Cow;\n\nuse crate::{\n    track::{utils::FromVec, Track},\n    utils::bbox::Universal2DBox,\n};\n\nuse self::{\n    metric::VisualMetric, observation_attributes::VisualObservationAttributes,\n    track_attributes::VisualAttributes,\n};\n\n/// Track metric implementation\npub mod metric;\n\n/// Cascade voting engine for visual_sort tracker. Combines TopN voting first for features and\n/// Hungarian voting for the rest of unmatched (objects, tracks)\npub mod voting;\n\n/// Track attributes for visual_sort tracker\npub mod track_attributes;\n\n/// Observation attributes for visual_sort tracker\npub mod observation_attributes;\n\n/// Implementation of Visual tracker with simple API\npub mod simple_api;\n\n/// Batched API that accepts the batch with multiple scenes at once\npub mod batch_api;\n/// Options object to configure the tracker\npub mod options;\n\n#[derive(Debug, Clone)]\npub struct VisualSortObservation<'a> {\n    feature: Option<Cow<'a, [f32]>>,\n    feature_quality: Option<f32>,\n    bounding_box: Universal2DBox,\n    custom_object_id: Option<i64>,\n}\n\nimpl<'a> VisualSortObservation<'a> {\n    pub fn new(\n        feature: Option<&'a [f32]>,\n        feature_quality: Option<f32>,\n        bounding_box: Universal2DBox,\n        custom_object_id: Option<i64>,\n    ) -> Self {\n        Self {\n            feature: feature.map(Cow::Borrowed),\n            feature_quality,\n            bounding_box,\n            custom_object_id,\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct VisualSortObservationSet<'a> {\n    pub inner: Vec<VisualSortObservation<'a>>,\n}\n\nimpl<'a> VisualSortObservationSet<'a> {\n    pub fn new() -> Self {\n        Self {\n            inner: Vec::default(),\n        }\n    }\n\n    pub fn add(&mut self, observation: VisualSortObservation<'a>) {\n        self.inner.push(observation);\n    }\n}\n\nimpl Default for VisualSortObservationSet<'_> {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\n/// Online track structure that contains tracking information for the last tracker epoch\n///\n#[derive(Debug, Clone)]\npub struct WastedVisualSortTrack {\n    /// id of the track\n    ///\n    pub id: u64,\n\n    /// when the track was lastly updated\n    ///\n    pub epoch: usize,\n\n    /// the bbox predicted by KF\n    ///\n    pub predicted_bbox: Universal2DBox,\n\n    /// the bbox passed by detector\n    ///\n    pub observed_bbox: Universal2DBox,\n\n    /// user-defined scene id that splits tracking space on isolated realms\n    ///\n    pub scene_id: u64,\n\n    /// current track length\n    ///\n    pub length: usize,\n\n    /// history of predicted boxes\n    ///\n    pub predicted_boxes: Vec<Universal2DBox>,\n\n    /// history of observed boxes\n    ///\n    pub observed_boxes: Vec<Universal2DBox>,\n\n    /// history of features\n    ///\n    pub observed_features: Vec<Option<Vec<f32>>>,\n}\n\nimpl From<Track<VisualAttributes, VisualMetric, VisualObservationAttributes>>\n    for WastedVisualSortTrack\n{\n    fn from(track: Track<VisualAttributes, VisualMetric, VisualObservationAttributes>) -> Self {\n        let attrs = track.get_attributes();\n        WastedVisualSortTrack {\n            id: track.get_track_id(),\n            epoch: attrs.last_updated_epoch,\n            scene_id: attrs.scene_id,\n            length: attrs.track_length,\n            observed_bbox: attrs.observed_boxes.back().unwrap().clone(),\n            predicted_bbox: attrs.predicted_boxes.back().unwrap().clone(),\n            predicted_boxes: attrs.predicted_boxes.clone().into_iter().collect(),\n            observed_boxes: attrs.observed_boxes.clone().into_iter().collect(),\n            observed_features: attrs\n                .observed_features\n                .clone()\n                .iter()\n                .map(|f_opt| f_opt.as_ref().map(Vec::from_vec))\n                .collect(),\n        }\n    }\n}\n\n#[cfg(feature = \"python\")]\npub mod python {\n    use super::{VisualSortObservation, VisualSortObservationSet, WastedVisualSortTrack};\n    use crate::utils::bbox::python::PyUniversal2DBox;\n    use pyo3::prelude::*;\n    use std::borrow::Cow;\n\n    #[pyclass]\n    #[pyo3(name = \"WastedVisualSortTrack\")]\n    pub struct PyWastedVisualSortTrack(pub(crate) WastedVisualSortTrack);\n\n    #[pymethods]\n    impl PyWastedVisualSortTrack {\n        #[classattr]\n        const __hash__: Option<Py<PyAny>> = None;\n\n        fn __repr__(&self) -> String {\n            format!(\"{:?}\", self.0)\n        }\n\n        fn __str__(&self) -> String {\n            format!(\"{:#?}\", self.0)\n        }\n\n        #[getter]\n        fn id(&self) -> u64 {\n            self.0.id\n        }\n\n        #[getter]\n        fn epoch(&self) -> usize {\n            self.0.epoch\n        }\n\n        #[getter]\n        fn predicted_bbox(&self) -> PyUniversal2DBox {\n            PyUniversal2DBox(self.0.predicted_bbox.clone())\n        }\n\n        #[getter]\n        fn observed_bbox(&self) -> PyUniversal2DBox {\n            PyUniversal2DBox(self.0.observed_bbox.clone())\n        }\n\n        #[getter]\n        fn scene_id(&self) -> u64 {\n            self.0.scene_id\n        }\n\n        #[getter]\n        fn length(&self) -> usize {\n            self.0.length\n        }\n\n        #[getter]\n        fn predicted_boxes(&self) -> Vec<PyUniversal2DBox> {\n            unsafe { std::mem::transmute(self.0.predicted_boxes.clone()) }\n        }\n\n        #[getter]\n        fn observed_boxes(&self) -> Vec<PyUniversal2DBox> {\n            unsafe { std::mem::transmute(self.0.observed_boxes.clone()) }\n        }\n\n        #[getter]\n        fn observed_features(&self) -> Vec<Option<Vec<f32>>> {\n            self.0.observed_features.clone()\n        }\n    }\n\n    #[pyclass]\n    #[derive(Debug, Clone)]\n    #[pyo3(name = \"VisualSortObservation\")]\n    pub struct PyVisualSortObservation(pub(crate) VisualSortObservation<'static>);\n\n    #[pymethods]\n    impl PyVisualSortObservation {\n        #[new]\n        #[pyo3(signature = (feature, feature_quality, bounding_box, custom_object_id))]\n        pub fn new(\n            feature: Option<Vec<f32>>,\n            feature_quality: Option<f32>,\n            bounding_box: PyUniversal2DBox,\n            custom_object_id: Option<i64>,\n        ) -> Self {\n            Self(VisualSortObservation {\n                feature: feature.map(Cow::Owned),\n                feature_quality,\n                bounding_box: bounding_box.0,\n                custom_object_id,\n            })\n        }\n\n        #[classattr]\n        const __hash__: Option<Py<PyAny>> = None;\n\n        fn __repr__(&self) -> String {\n            format!(\"{self:?}\")\n        }\n\n        fn __str__(&self) -> String {\n            format!(\"{self:#?}\")\n        }\n    }\n\n    #[pyclass]\n    #[derive(Debug)]\n    #[pyo3(name = \"VisualSortObservationSet\")]\n    pub struct PyVisualSortObservationSet(pub(crate) VisualSortObservationSet<'static>);\n\n    #[pymethods]\n    impl PyVisualSortObservationSet {\n        #[new]\n        fn new() -> Self {\n            Self(VisualSortObservationSet::new())\n        }\n\n        #[pyo3(text_signature = \"($self, observation)\")]\n        fn add(&mut self, observation: PyVisualSortObservation) {\n            self.0.add(observation.0);\n        }\n\n        #[classattr]\n        const __hash__: Option<Py<PyAny>> = None;\n\n        fn __repr__(&self) -> String {\n            format!(\"{self:?}\")\n        }\n\n        fn __str__(&self) -> String {\n            format!(\"{self:#?}\")\n        }\n    }\n}\n"
  },
  {
    "path": "src/trackers.rs",
    "content": "/// SORT tracker implementations (middleware and simple sort implementation - IoU and Mahalanobis)\n///\npub mod sort;\n\n/// Trait that implements epoch db management\npub mod epoch_db;\n\n/// Visual tracker implementations\npub mod visual_sort;\n\n/// Trait that implements kalman_2d_box prediction for attributes\npub mod kalman_prediction;\n\n/// The object that implements the constraints for space when objects from various epochs are compared.\n/// It helps to decrease the brute-force space\n///\npub mod spatio_temporal_constraints;\n\n/// Prediction batch request implementation\n///\npub mod batch;\n\n/// Trait to implement tracker API\npub mod tracker_api;\n"
  },
  {
    "path": "src/utils/bbox.rs",
    "content": "use crate::track::ObservationAttributes;\nuse crate::utils::clipping::sutherland_hodgman_clip;\nuse crate::Errors::GenericBBoxConversionError;\nuse crate::{Errors, EPS};\nuse geo::{Area, Coord, LineString, Polygon};\nuse std::f32::consts::PI;\n\n/// Bounding box in the format (left, top, width, height)\n///\n#[derive(Clone, Default, Debug, Copy)]\npub struct BoundingBox {\n    pub left: f32,\n    pub top: f32,\n    pub width: f32,\n    pub height: f32,\n    pub confidence: f32,\n}\n\nimpl BoundingBox {\n    pub fn new(left: f32, top: f32, width: f32, height: f32) -> Self {\n        Self {\n            left,\n            top,\n            width,\n            height,\n            confidence: 1.0,\n        }\n    }\n\n    pub fn new_with_confidence(\n        left: f32,\n        top: f32,\n        width: f32,\n        height: f32,\n        confidence: f32,\n    ) -> Self {\n        assert!(\n            (0.0..=1.0).contains(&confidence),\n            \"Confidence must lay between 0.0 and 1.0\"\n        );\n        Self {\n            left,\n            top,\n            width,\n            height,\n            confidence,\n        }\n    }\n\n    pub fn as_xyaah(&self) -> Universal2DBox {\n        Universal2DBox::from(self)\n    }\n\n    pub fn intersection(l: &BoundingBox, r: &BoundingBox) -> f64 {\n        assert!(l.width > 0.0);\n        assert!(l.height > 0.0);\n        assert!(r.width > 0.0);\n        assert!(r.height > 0.0);\n\n        let (ax0, ay0, ax1, ay1) = (l.left, l.top, l.left + l.width, l.top + l.height);\n        let (bx0, by0, bx1, by1) = (r.left, r.top, r.left + r.width, r.top + r.height);\n\n        let (x1, y1) = (ax0.max(bx0), ay0.max(by0));\n        let (x2, y2) = (ax1.min(bx1), ay1.min(by1));\n\n        let int_width = x2 - x1;\n        let int_height = y2 - y1;\n\n        if int_width > 0.0 && int_height > 0.0 {\n            (int_width * int_height) as f64\n        } else {\n            0.0_f64\n        }\n    }\n}\n\n/// Bounding box in the format (x, y, angle, aspect, height)\n#[derive(Default, Debug)]\npub struct Universal2DBox {\n    pub xc: f32,\n    pub yc: f32,\n    pub angle: Option<f32>,\n    pub aspect: f32,\n    pub height: f32,\n    pub confidence: f32,\n    _vertex_cache: Option<Polygon<f64>>,\n}\n\nimpl Clone for Universal2DBox {\n    fn clone(&self) -> Self {\n        Universal2DBox::new_with_confidence(\n            self.xc,\n            self.yc,\n            self.angle,\n            self.aspect,\n            self.height,\n            self.confidence,\n        )\n    }\n}\n\nimpl Universal2DBox {\n    pub fn new(xc: f32, yc: f32, angle: Option<f32>, aspect: f32, height: f32) -> Self {\n        Self {\n            xc,\n            yc,\n            angle,\n            aspect,\n            height,\n            confidence: 1.0,\n            _vertex_cache: None,\n        }\n    }\n\n    pub fn new_with_confidence(\n        xc: f32,\n        yc: f32,\n        angle: Option<f32>,\n        aspect: f32,\n        height: f32,\n        confidence: f32,\n    ) -> Self {\n        assert!(\n            (0.0..=1.0).contains(&confidence),\n            \"Confidence must lay between 0.0 and 1.0\"\n        );\n\n        Self {\n            xc,\n            yc,\n            angle,\n            aspect,\n            height,\n            confidence,\n            _vertex_cache: None,\n        }\n    }\n\n    pub fn ltwh(left: f32, top: f32, width: f32, height: f32) -> Self {\n        Self::from(BoundingBox::new_with_confidence(\n            left, top, width, height, 1.0,\n        ))\n    }\n\n    pub fn ltwh_with_confidence(\n        left: f32,\n        top: f32,\n        width: f32,\n        height: f32,\n        confidence: f32,\n    ) -> Self {\n        Self::from(BoundingBox::new_with_confidence(\n            left, top, width, height, confidence,\n        ))\n    }\n\n    pub fn get_radius(&self) -> f32 {\n        let hw = self.aspect * self.height / 2.0_f32;\n        let hh = self.height / 2.0_f32;\n        (hw * hw + hh * hh).sqrt()\n    }\n\n    pub fn area(&self) -> f32 {\n        let w = self.height * self.aspect;\n        w * self.height\n    }\n\n    #[inline]\n    pub fn get_vertices(&self) -> Polygon {\n        Polygon::from(self)\n    }\n\n    #[inline]\n    pub fn get_cached_vertices(&self) -> &Option<Polygon<f64>> {\n        &self._vertex_cache\n    }\n\n    #[inline]\n    pub fn gen_vertices(&mut self) -> &Self {\n        if self.angle.is_some() {\n            self._vertex_cache = Some(self.get_vertices());\n        }\n        self\n    }\n\n    /// Sets the angle\n    ///\n    pub fn rotate(self, angle: f32) -> Self {\n        Self {\n            xc: self.xc,\n            yc: self.yc,\n            angle: Some(angle),\n            aspect: self.aspect,\n            height: self.height,\n            confidence: self.confidence,\n            _vertex_cache: None,\n        }\n    }\n\n    /// Sets the angle\n    ///\n    pub fn rotate_mut(&mut self, angle: f32) {\n        self.angle = Some(angle)\n    }\n\n    /// Sets the angle\n    ///\n    pub fn set_confidence(&mut self, confidence: f32) {\n        assert!(\n            (0.0..=1.0).contains(&confidence),\n            \"Confidence must lay between 0.0 and 1.0\"\n        );\n        self.confidence = confidence;\n    }\n\n    pub fn sutherland_hodgman_clip(mut self, mut clipping: Universal2DBox) -> Polygon<f64> {\n        if self.angle.is_none() {\n            self.rotate_mut(0.0);\n        }\n\n        if clipping.angle.is_none() {\n            clipping.rotate_mut(0.0);\n        }\n\n        if self.get_cached_vertices().is_none() {\n            self.gen_vertices();\n        }\n\n        if clipping.get_cached_vertices().is_none() {\n            clipping.gen_vertices();\n        }\n\n        sutherland_hodgman_clip(\n            self.get_cached_vertices().as_ref().unwrap(),\n            clipping.get_cached_vertices().as_ref().unwrap(),\n        )\n    }\n}\n\nimpl From<BoundingBox> for Universal2DBox {\n    fn from(f: BoundingBox) -> Self {\n        Self::from(&f)\n    }\n}\n\nimpl From<&BoundingBox> for Universal2DBox {\n    fn from(f: &BoundingBox) -> Self {\n        Universal2DBox {\n            xc: f.left + f.width / 2.0,\n            yc: f.top + f.height / 2.0,\n            angle: None,\n            aspect: f.width / f.height,\n            height: f.height,\n            confidence: f.confidence,\n            _vertex_cache: None,\n        }\n    }\n}\n\nimpl TryFrom<Universal2DBox> for BoundingBox {\n    type Error = Errors;\n\n    fn try_from(value: Universal2DBox) -> Result<Self, Self::Error> {\n        BoundingBox::try_from(&value)\n    }\n}\n\nimpl TryFrom<&Universal2DBox> for BoundingBox {\n    type Error = Errors;\n\n    fn try_from(f: &Universal2DBox) -> Result<Self, Self::Error> {\n        if f.angle.is_some() {\n            Err(GenericBBoxConversionError)\n        } else {\n            let width = f.height * f.aspect;\n            Ok(BoundingBox {\n                left: f.xc - width / 2.0,\n                top: f.yc - f.height / 2.0,\n                width,\n                height: f.height,\n                confidence: f.confidence,\n            })\n        }\n    }\n}\n\nimpl From<&Universal2DBox> for Polygon<f64> {\n    fn from(b: &Universal2DBox) -> Self {\n        let angle = b.angle.unwrap_or(0.0) as f64;\n        let height = b.height as f64;\n        let aspect = b.aspect as f64;\n\n        let c = angle.cos();\n        let s = angle.sin();\n\n        let half_width = height * aspect / 2.0;\n        let half_height = height / 2.0;\n\n        let r1x = -half_width * c - half_height * s;\n        let r1y = -half_width * s + half_height * c;\n\n        let r2x = half_width * c - half_height * s;\n        let r2y = half_width * s + half_height * c;\n\n        let x = b.xc as f64;\n        let y = b.yc as f64;\n\n        Polygon::new(\n            LineString(vec![\n                Coord {\n                    x: x + r1x,\n                    y: y + r1y,\n                },\n                Coord {\n                    x: x + r2x,\n                    y: y + r2y,\n                },\n                Coord {\n                    x: x - r1x,\n                    y: y - r1y,\n                },\n                Coord {\n                    x: x - r2x,\n                    y: y - r2y,\n                },\n            ]),\n            vec![],\n        )\n    }\n}\n\n#[cfg(test)]\nmod polygons {\n    use crate::track::ObservationAttributes;\n    use crate::utils::bbox::Universal2DBox;\n    use crate::utils::clipping::sutherland_hodgman_clip;\n    use crate::EPS;\n    use geo::{Area, BooleanOps, Polygon};\n    use std::f32::consts::PI;\n\n    #[test]\n    fn transform() {\n        let bbox1 = Universal2DBox::new(0.0, 0.0, Some(2.0), 0.5, 2.0);\n        let polygon1 = Polygon::from(&bbox1);\n        let bbox2 = Universal2DBox::new(0.0, 0.0, Some(2.0 + PI / 2.0), 0.5, 2.0);\n        let polygon2 = Polygon::from(&bbox2);\n        let clip = sutherland_hodgman_clip(&polygon1, &polygon2);\n        let int_area = clip.unsigned_area();\n        let int = polygon1.intersection(&polygon2).unsigned_area();\n        assert!((int - int_area).abs() < EPS as f64);\n\n        let union = polygon1.union(&polygon2).unsigned_area();\n        assert!((union - 3.0).abs() < EPS as f64);\n\n        let res =\n            Universal2DBox::calculate_metric_object(&Some(&bbox1), &Some(&bbox2)).unwrap() as f64;\n        assert!((res - int / union).abs() < EPS as f64);\n\n        let bbox3 = Universal2DBox::new(10.0, 0.0, Some(2.0 + PI / 2.0), 0.5, 2.0);\n        let polygon3 = Polygon::from(&bbox3);\n\n        let int = polygon1.intersection(&polygon3).unsigned_area();\n        assert!((int - 0.0).abs() < EPS as f64);\n\n        let union = polygon1.union(&polygon3).unsigned_area();\n        assert!((union - 4.0).abs() < EPS as f64);\n\n        assert!(Universal2DBox::calculate_metric_object(&Some(&bbox1), &Some(&bbox3)).is_none());\n    }\n\n    #[test]\n    fn corner_case_f32() {\n        let x = Universal2DBox::new(8044.315, 8011.0454, Some(2.678_774_8), 1.00801, 49.8073);\n        let polygon_x = Polygon::from(&x);\n\n        let y = Universal2DBox::new(8044.455, 8011.338, Some(2.678_774_8), 1.0083783, 49.79979);\n        let polygon_y = Polygon::from(&y);\n\n        dbg!(&polygon_x, &polygon_y);\n    }\n}\n\n// impl From<&Universal2DBox> for BoundingBox {\n//     /// This is a lossy translation. It is valid only when the angle is 0\n//     fn from(f: &Universal2DBox) -> Self {\n//         let width = f.height * f.aspect;\n//         BoundingBox {\n//             left: f.xc - width / 2.0,\n//             top: f.yc - f.height / 2.0,\n//             width,\n//             height: f.height,\n//             confidence: f.confidence,\n//         }\n//     }\n// }\n\nimpl ObservationAttributes for BoundingBox {\n    type MetricObject = f32;\n\n    fn calculate_metric_object(\n        left: &Option<&Self>,\n        right: &Option<&Self>,\n    ) -> Option<Self::MetricObject> {\n        match (left, right) {\n            (Some(l), Some(r)) => {\n                let intersection = BoundingBox::intersection(l, r);\n                let union = (l.height * l.width + r.height * r.width) as f64 - intersection;\n                let res = intersection / union;\n                Some(res as f32)\n            }\n            _ => None,\n        }\n    }\n}\n\nimpl PartialEq<Self> for BoundingBox {\n    fn eq(&self, other: &Self) -> bool {\n        (self.left - other.left).abs() < EPS\n            && (self.top - other.top).abs() < EPS\n            && (self.width - other.width) < EPS\n            && (self.height - other.height) < EPS\n            && (self.confidence - other.confidence) < EPS\n    }\n}\n\npub fn normalize_angle(a: f32) -> f32 {\n    let pix2 = 2.0 * PI;\n    let n = (a / pix2).floor();\n    let a = a - n * pix2;\n    if a < 0.0 {\n        a + pix2\n    } else {\n        a\n    }\n}\n\n#[cfg(test)]\nmod tests_normalize_angle {\n    use crate::utils::bbox::normalize_angle;\n    use crate::EPS;\n\n    #[test]\n    fn normalize() {\n        assert!((normalize_angle(0.3) - 0.3).abs() < EPS);\n        assert!((normalize_angle(-0.3) - 5.983184).abs() < EPS);\n        assert!((normalize_angle(-0.3) - 5.983184).abs() < EPS);\n        assert!((normalize_angle(6.583184) - 0.3).abs() < EPS);\n    }\n}\n\nimpl Universal2DBox {\n    pub fn too_far(l: &Universal2DBox, r: &Universal2DBox) -> bool {\n        assert!(l.aspect > 0.0);\n        assert!(l.height > 0.0);\n        assert!(r.aspect > 0.0);\n        assert!(r.height > 0.0);\n\n        let max_distance = l.get_radius() + r.get_radius();\n        let x = l.xc - r.xc;\n        let y = l.yc - r.yc;\n        x * x + y * y > max_distance * max_distance\n    }\n\n    pub fn dist_in_2r(l: &Universal2DBox, r: &Universal2DBox) -> f32 {\n        assert!(l.aspect > 0.0);\n        assert!(l.height > 0.0);\n        assert!(r.aspect > 0.0);\n        assert!(r.height > 0.0);\n\n        let radial_distance = l.get_radius() + r.get_radius();\n        let x = l.xc - r.xc;\n        let y = l.yc - r.yc;\n        (x * x + y * y).sqrt() / (radial_distance * radial_distance + EPS).sqrt()\n    }\n\n    pub fn intersection(l: &Universal2DBox, r: &Universal2DBox) -> f64 {\n        // REMOVED DUE TO: Github #84\n        // need to implement better way to run simplified IoU\n        // now it runs in a general way\n        //\n        // if (normalize_angle(l.angle.unwrap_or(0.0)) - normalize_angle(r.angle.unwrap_or(0.0))).abs()\n        //     < EPS\n        // {\n        //     BoundingBox::intersection(&new_l.try_into().unwrap(), &new_r.try_into().unwrap())\n        // } else\n        if Universal2DBox::too_far(l, r) {\n            0.0\n        } else {\n            let mut l = l.clone();\n            let mut r = r.clone();\n\n            if l.get_cached_vertices().is_none() {\n                let angle = l.angle.unwrap_or(0.0);\n                l.rotate_mut(angle);\n                l.gen_vertices();\n            }\n\n            if r.get_cached_vertices().is_none() {\n                let angle = r.angle.unwrap_or(0.0);\n                r.rotate_mut(angle);\n                r.gen_vertices();\n            }\n\n            let p1 = l.get_cached_vertices().as_ref().unwrap();\n            let p2 = r.get_cached_vertices().as_ref().unwrap();\n\n            sutherland_hodgman_clip(p1, p2).unsigned_area()\n        }\n    }\n}\n\nimpl ObservationAttributes for Universal2DBox {\n    type MetricObject = f32;\n\n    fn calculate_metric_object(\n        left: &Option<&Self>,\n        right: &Option<&Self>,\n    ) -> Option<Self::MetricObject> {\n        match (left, right) {\n            (Some(l), Some(r)) => {\n                let intersection = Universal2DBox::intersection(l, r);\n                if intersection == 0.0 {\n                    None\n                } else {\n                    let union = (l.height * l.height * l.aspect + r.height * r.height * r.aspect)\n                        as f64\n                        - intersection;\n                    let res = intersection / union;\n                    Some(res as f32)\n                }\n            }\n            _ => None,\n        }\n    }\n}\n\nimpl PartialEq<Self> for Universal2DBox {\n    fn eq(&self, other: &Self) -> bool {\n        (self.xc - other.xc).abs() < EPS\n            && (self.yc - other.yc).abs() < EPS\n            && (self.angle.unwrap_or(0.0) - other.angle.unwrap_or(0.0)) < EPS\n            && (self.aspect - other.aspect) < EPS\n            && (self.height - other.height) < EPS\n    }\n}\n\n#[cfg(feature = \"python\")]\npub mod python {\n    use crate::utils::clipping::clipping_py::PyPolygon;\n\n    use super::{BoundingBox, Universal2DBox};\n    use pyo3::{exceptions::PyAttributeError, prelude::*};\n\n    #[derive(Clone, Default, Debug, Copy)]\n    #[repr(transparent)]\n    #[pyclass]\n    #[pyo3(name = \"BoundingBox\")]\n    pub struct PyBoundingBox(pub(crate) BoundingBox);\n\n    #[pymethods]\n    impl PyBoundingBox {\n        #[classattr]\n        const __hash__: Option<Py<PyAny>> = None;\n\n        fn __repr__(&self) -> String {\n            format!(\"{:?}\", self.0)\n        }\n\n        fn __str__(&self) -> String {\n            self.__repr__()\n        }\n\n        #[getter]\n        pub fn get_left(&self) -> f32 {\n            self.0.left\n        }\n\n        #[setter]\n        pub fn set_left(&mut self, left: f32) {\n            self.0.left = left;\n        }\n\n        #[getter]\n        pub fn get_top(&self) -> f32 {\n            self.0.top\n        }\n\n        #[setter]\n        pub fn set_top(&mut self, top: f32) {\n            self.0.top = top;\n        }\n\n        #[getter]\n        pub fn get_width(&mut self) -> f32 {\n            self.0.width\n        }\n\n        #[setter]\n        pub fn set_width(&mut self, width: f32) {\n            self.0.width = width;\n        }\n\n        #[getter]\n        pub fn get_height(&mut self) -> f32 {\n            self.0.height\n        }\n\n        #[setter]\n        pub fn set_height(&mut self, height: f32) {\n            self.0.height = height;\n        }\n\n        #[getter]\n        pub fn get_confidence(&mut self) -> f32 {\n            self.0.confidence\n        }\n\n        #[setter]\n        pub fn set_confidence(&mut self, confidence: f32) {\n            self.0.confidence = confidence;\n        }\n\n        pub fn as_xyaah(&self) -> PyUniversal2DBox {\n            PyUniversal2DBox(self.0.as_xyaah())\n        }\n        /// Constructor. Confidence is set to 1.0\n        ///\n        #[new]\n        pub fn new(left: f32, top: f32, width: f32, height: f32) -> Self {\n            Self(BoundingBox::new(left, top, width, height))\n        }\n\n        /// Creates the bbox with custom confidence\n        ///\n        #[staticmethod]\n        pub fn new_with_confidence(\n            left: f32,\n            top: f32,\n            width: f32,\n            height: f32,\n            confidence: f32,\n        ) -> Self {\n            Self(BoundingBox::new_with_confidence(\n                left, top, width, height, confidence,\n            ))\n        }\n    }\n\n    #[derive(Default, Debug, Clone)]\n    #[repr(transparent)]\n    #[pyclass]\n    #[pyo3(name = \"Universal2DBox\")]\n    pub struct PyUniversal2DBox(pub(crate) Universal2DBox);\n\n    #[pymethods]\n    impl PyUniversal2DBox {\n        #[classattr]\n        const __hash__: Option<Py<PyAny>> = None;\n\n        fn __repr__(&self) -> String {\n            format!(\"{:?}\", self.0)\n        }\n\n        fn __str__(&self) -> String {\n            self.__repr__()\n        }\n\n        pub fn get_radius(&self) -> f32 {\n            self.0.get_radius()\n        }\n\n        pub fn as_ltwh(&self) -> PyResult<PyBoundingBox> {\n            let r = BoundingBox::try_from(&self.0);\n            if let Ok(res) = r {\n                Ok(PyBoundingBox(res))\n            } else {\n                Err(PyAttributeError::new_err(format!(\"{r:?}\")))\n            }\n        }\n\n        pub fn gen_vertices(&mut self) {\n            self.0.gen_vertices();\n        }\n\n        pub fn get_vertices(&self) -> PyPolygon {\n            PyPolygon(self.0.get_vertices())\n        }\n\n        /// Sets the angle\n        ///\n        #[pyo3(signature = (angle))]\n        pub fn rotate(&mut self, angle: f32) {\n            self.0.rotate_mut(angle)\n        }\n\n        #[getter]\n        pub fn get_confidence(&self) -> f32 {\n            self.0.confidence\n        }\n\n        #[setter]\n        pub fn set_confidence(&mut self, confidence: f32) {\n            self.0.set_confidence(confidence)\n        }\n\n        #[getter]\n        pub fn get_xc(&self) -> f32 {\n            self.0.xc\n        }\n\n        #[setter]\n        pub fn set_xc(&mut self, xc: f32) {\n            self.0.xc = xc;\n        }\n\n        #[getter]\n        pub fn get_yc(&self) -> f32 {\n            self.0.yc\n        }\n\n        #[setter]\n        pub fn set_yc(&mut self, yc: f32) {\n            self.0.yc = yc;\n        }\n\n        #[getter]\n        pub fn get_angle(&self) -> Option<f32> {\n            self.0.angle\n        }\n\n        #[setter]\n        pub fn set_angle(&mut self, angle: Option<f32>) {\n            self.0.angle = angle;\n        }\n\n        #[getter]\n        pub fn get_aspect(&self) -> f32 {\n            self.0.aspect\n        }\n\n        #[setter]\n        pub fn set_aspect(&mut self, aspect: f32) {\n            self.0.aspect = aspect;\n        }\n\n        #[getter]\n        pub fn get_height(&self) -> f32 {\n            self.0.height\n        }\n\n        #[setter]\n        pub fn set_height(&mut self, height: f32) {\n            self.0.height = height;\n        }\n\n        /// Constructor. Creates new generic bbox and doesn't generate vertex cache\n        ///\n        #[new]\n        #[pyo3(signature = (xc, yc, angle, aspect, height))]\n        pub fn new(xc: f32, yc: f32, angle: Option<f32>, aspect: f32, height: f32) -> Self {\n            Self(Universal2DBox::new(xc, yc, angle, aspect, height))\n        }\n\n        /// Constructor. Creates new generic bbox and doesn't generate vertex cache\n        ///\n        #[staticmethod]\n        #[pyo3(signature = (xc, yc, angle, aspect, height, confidence))]\n        pub fn new_with_confidence(\n            xc: f32,\n            yc: f32,\n            angle: Option<f32>,\n            aspect: f32,\n            height: f32,\n            confidence: f32,\n        ) -> Self {\n            assert!(\n                (0.0..=1.0).contains(&confidence),\n                \"Confidence must lay between 0.0 and 1.0\"\n            );\n\n            Self(Universal2DBox::new_with_confidence(\n                xc, yc, angle, aspect, height, confidence,\n            ))\n        }\n\n        /// Constructor. Creates new generic bbox and doesn't generate vertex cache\n        ///\n        #[staticmethod]\n        pub fn ltwh(left: f32, top: f32, width: f32, height: f32) -> Self {\n            Self(Universal2DBox::ltwh_with_confidence(\n                left, top, width, height, 1.0,\n            ))\n        }\n\n        /// Constructor. Creates new generic bbox and doesn't generate vertex cache\n        ///\n        #[staticmethod]\n        pub fn ltwh_with_confidence(\n            left: f32,\n            top: f32,\n            width: f32,\n            height: f32,\n            confidence: f32,\n        ) -> Self {\n            Self(Universal2DBox::ltwh_with_confidence(\n                left, top, width, height, confidence,\n            ))\n        }\n\n        pub fn area(&self) -> f32 {\n            self.0.area()\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::prelude::Universal2DBox;\n    use crate::track::ObservationAttributes;\n    use crate::utils::bbox::BoundingBox;\n    use crate::EPS;\n\n    #[test]\n    fn test_radius() {\n        let bb1 = BoundingBox::new(0.0, 0.0, 6.0, 8.0).as_xyaah();\n        let r = bb1.get_radius();\n        assert!((r - 5.0).abs() < EPS);\n    }\n\n    #[test]\n    fn test_not_too_far() {\n        let bb1 = BoundingBox::new(0.0, 0.0, 6.0, 8.0).as_xyaah();\n        let bb2 = BoundingBox::new(6.0, 0.0, 6.0, 8.0).as_xyaah();\n        assert!(!Universal2DBox::too_far(&bb1, &bb2));\n    }\n\n    #[test]\n    fn test_same() {\n        let bb1 = BoundingBox::new(0.0, 0.0, 6.0, 8.0).as_xyaah();\n        assert!(!Universal2DBox::too_far(&bb1, &bb1));\n    }\n\n    #[test]\n    fn test_too_far() {\n        let bb1 = BoundingBox::new(0.0, 0.0, 6.0, 8.0).as_xyaah();\n        let bb2 = BoundingBox::new(10.1, 0.0, 6.0, 8.0).as_xyaah();\n        assert!(Universal2DBox::too_far(&bb1, &bb2));\n    }\n\n    #[test]\n    fn dist_same() {\n        let bb1 = BoundingBox::new(0.0, 0.0, 6.0, 8.0).as_xyaah();\n        assert!(Universal2DBox::dist_in_2r(&bb1, &bb1) < EPS);\n    }\n\n    #[test]\n    fn dist_less_1() {\n        let bb1 = BoundingBox::new(0.0, 0.0, 6.0, 8.0).as_xyaah();\n        let bb2 = BoundingBox::new(6.0, 0.0, 6.0, 8.0).as_xyaah();\n        let d = Universal2DBox::dist_in_2r(&bb1, &bb2);\n        assert!((d - 0.6).abs() < EPS);\n    }\n\n    #[test]\n    fn dist_is_1() {\n        let bb1 = BoundingBox::new(0.0, 0.0, 6.0, 8.0).as_xyaah();\n        let bb2 = BoundingBox::new(10.0, 0.0, 6.0, 8.0).as_xyaah();\n        let d = Universal2DBox::dist_in_2r(&bb1, &bb2);\n        assert!((d - 1.0).abs() < EPS);\n    }\n\n    #[test]\n    fn test_iou() {\n        let bb1 = BoundingBox::new(-1.0, -1.0, 2.0, 2.0);\n\n        let bb2 = BoundingBox::new(-0.9, -0.9, 2.0, 2.0);\n        let bb3 = BoundingBox::new(1.0, 1.0, 3.0, 3.0);\n\n        assert!(BoundingBox::calculate_metric_object(&Some(&bb1), &Some(&bb1)).unwrap() > 0.999);\n        assert!(BoundingBox::calculate_metric_object(&Some(&bb2), &Some(&bb2)).unwrap() > 0.999);\n        assert!(BoundingBox::calculate_metric_object(&Some(&bb1), &Some(&bb2)).unwrap() > 0.8);\n        assert!(BoundingBox::calculate_metric_object(&Some(&bb1), &Some(&bb3)).unwrap() < 0.001);\n        assert!(BoundingBox::calculate_metric_object(&Some(&bb2), &Some(&bb3)).unwrap() < 0.001);\n    }\n}\n"
  },
  {
    "path": "src/utils/clipping/bbox_own_areas.rs",
    "content": "use crate::prelude::Universal2DBox;\nuse crate::EPS;\nuse geo::{Area, BooleanOps, MultiPolygon, Polygon};\nuse rayon::prelude::*;\nuse std::collections::HashSet;\nuse std::sync::Arc;\n\npub fn exclusively_owned_areas(boxes: &[&Universal2DBox]) -> Vec<MultiPolygon> {\n    let mut distances = HashSet::new();\n    for (i, b1) in boxes.iter().enumerate() {\n        for (j, b2) in boxes[i + 1..].iter().enumerate() {\n            let j = j + i + 1;\n            if !Universal2DBox::too_far(b1, b2) {\n                distances.insert((i, j));\n            }\n        }\n    }\n\n    let distances = Arc::new(distances);\n    boxes\n        .par_iter()\n        .enumerate()\n        .map(|(i, own)| {\n            let mut own_poly = MultiPolygon::from(Polygon::from(*own));\n            for (j, other) in boxes.iter().enumerate() {\n                if distances.contains(&(i, j)) || distances.contains(&(j, i)) {\n                    let clipping = MultiPolygon::from(Polygon::from(*other));\n                    own_poly = own_poly.difference(&clipping);\n                }\n            }\n            own_poly\n        })\n        .collect()\n}\n\npub fn exclusively_owned_areas_normalized_shares(\n    boxes: &[&Universal2DBox],\n    own_polygons: &[MultiPolygon],\n) -> Vec<f32> {\n    boxes\n        .iter()\n        .zip(own_polygons.iter())\n        .map(|(b, poly)| (poly.unsigned_area() / (b.area() + EPS) as f64) as f32)\n        .map(|e| if e >= 1.0 { 1.0 } else { e })\n        .collect()\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::prelude::BoundingBox;\n    use crate::utils::clipping::bbox_own_areas::{\n        exclusively_owned_areas, exclusively_owned_areas_normalized_shares,\n    };\n    use crate::EPS;\n    use geo::Area;\n\n    #[test]\n    fn test() {\n        let bb1 = BoundingBox::new(0.0, 0.0, 10.0, 10.0);\n        let bb2 = BoundingBox::new(5.0, 5.0, 10.0, 10.0);\n        let bb3 = BoundingBox::new(10.0, 10.0, 10.0, 10.0);\n        let boxes = &[&bb1.into(), &bb2.into(), &bb3.into()];\n\n        let own_polygons = exclusively_owned_areas(boxes);\n\n        let bb1_own = own_polygons[0].unsigned_area();\n        let bb2_own = own_polygons[1].unsigned_area();\n        let bb3_own = own_polygons[2].unsigned_area();\n\n        assert!((bb1_own - 75.0).abs() < EPS as f64);\n        assert!((bb2_own - 50.0).abs() < EPS as f64);\n        assert!((bb3_own - 75.0).abs() < EPS as f64);\n\n        let own_areas = exclusively_owned_areas_normalized_shares(boxes, own_polygons.as_slice());\n\n        assert!((own_areas[0] - 0.75).abs() < EPS);\n        assert!((own_areas[1] - 0.50).abs() < EPS);\n        assert!((own_areas[2] - 0.75).abs() < EPS);\n    }\n}\n"
  },
  {
    "path": "src/utils/clipping/clipping_py.rs",
    "content": "use crate::utils::bbox::python::PyUniversal2DBox;\nuse geo::{Area, CoordsIter, Polygon};\nuse pyo3::prelude::*;\n\n#[derive(Debug)]\n#[pyclass]\n#[pyo3(name = \"Polygon\")]\npub struct PyPolygon(pub(crate) Polygon<f64>);\n\n#[pymethods]\nimpl PyPolygon {\n    #[pyo3(text_signature = \"($self)\")]\n    pub fn get_points(&self) -> Vec<(f64, f64)> {\n        self.0.coords_iter().map(|c| (c.x, c.y)).collect()\n    }\n\n    #[classattr]\n    const __hash__: Option<Py<PyAny>> = None;\n\n    fn __repr__(&self) -> String {\n        format!(\"{self:?}\")\n    }\n\n    fn __str__(&self) -> String {\n        format!(\"{self:#?}\")\n    }\n}\n\n#[pyfunction]\n#[pyo3(\n    name = \"sutherland_hodgman_clip\",\n    text_signature = \"(subject, clipping)\"\n)]\npub fn sutherland_hodgman_clip_py(\n    subject: PyUniversal2DBox,\n    clipping: PyUniversal2DBox,\n) -> PyPolygon {\n    PyPolygon(subject.0.sutherland_hodgman_clip(clipping.0))\n}\n\n#[pyfunction]\n#[pyo3(name = \"intersection_area\", text_signature = \"(subject, clipping)\")]\npub fn intersection_area_py(subject: PyUniversal2DBox, clipping: PyUniversal2DBox) -> f64 {\n    let poly = sutherland_hodgman_clip_py(subject, clipping);\n    poly.0.unsigned_area()\n}\n"
  },
  {
    "path": "src/utils/clipping.rs",
    "content": "/// Python interface for `sutherland_hodgman_clip`\n///\n#[cfg(feature = \"python\")]\npub mod clipping_py;\n\n/// The function to calculate polygons solely owned by a bounding box\n///\npub mod bbox_own_areas;\n\nuse geo::{Coord, CoordsIter, LineString, Polygon};\n\nfn is_inside(q: &Coord<f64>, p1: &Coord<f64>, p2: &Coord<f64>) -> bool {\n    let r = (p2.x - p1.x) * (q.y - p1.y) - (p2.y - p1.y) * (q.x - p1.x);\n    r <= 0.0\n}\n\nfn compute_intersection(\n    cp1: &Coord<f64>,\n    cp2: &Coord<f64>,\n    s: &Coord<f64>,\n    e: &Coord<f64>,\n) -> Coord<f64> {\n    let dc = Coord {\n        x: cp1.x - cp2.x,\n        y: cp1.y - cp2.y,\n    };\n    let dp = Coord {\n        x: s.x - e.x,\n        y: s.y - e.y,\n    };\n    let n1 = cp1.x * cp2.y - cp1.y * cp2.x;\n    let n2 = s.x * e.y - s.y * e.x;\n    let n3 = 1.0 / (dc.x * dp.y - dc.y * dp.x);\n    Coord {\n        x: (n1 * dp.x - n2 * dc.x) * n3,\n        y: (n1 * dp.y - n2 * dc.y) * n3,\n    }\n}\n\npub fn sutherland_hodgman_clip(\n    subject_polygon: &Polygon<f64>,\n    clipping_polygon: &Polygon<f64>,\n) -> Polygon<f64> {\n    let mut final_polygon = subject_polygon.coords_iter().collect::<Vec<_>>();\n    final_polygon.pop();\n\n    let mut clipping_polygon = clipping_polygon.coords_iter().collect::<Vec<_>>();\n    clipping_polygon.pop();\n\n    for i in 0..clipping_polygon.len() {\n        let next_polygon = final_polygon;\n        final_polygon = Vec::default();\n\n        let i_i = if i == 0 {\n            clipping_polygon.len() - 1\n        } else {\n            i - 1\n        };\n\n        let c_edge_start = clipping_polygon[i_i];\n\n        let c_edge_end = clipping_polygon[i];\n        for j in 0..next_polygon.len() {\n            let j_i = if j == 0 {\n                next_polygon.len() - 1\n            } else {\n                j - 1\n            };\n\n            let s_edge_start = next_polygon[j_i];\n            let s_edge_end = next_polygon[j];\n            if is_inside(&s_edge_end, &c_edge_start, &c_edge_end) {\n                if !is_inside(&s_edge_start, &c_edge_start, &c_edge_end) {\n                    let int = compute_intersection(\n                        &s_edge_start,\n                        &s_edge_end,\n                        &c_edge_start,\n                        &c_edge_end,\n                    );\n                    final_polygon.push(int);\n                }\n                final_polygon.push(s_edge_end);\n            } else if is_inside(&s_edge_start, &c_edge_start, &c_edge_end) {\n                let int =\n                    compute_intersection(&s_edge_start, &s_edge_end, &c_edge_start, &c_edge_end);\n                final_polygon.push(int);\n            }\n        }\n    }\n    Polygon::new(LineString::new(final_polygon), vec![])\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::utils::clipping::sutherland_hodgman_clip;\n    use geo::{polygon, Polygon};\n\n    #[test]\n    fn clip() {\n        let subject_polygon: Polygon<f64> = polygon![\n            (x: 8055.658, y: 7977.5537),\n            (x: 8010.734, y: 7999.9697),\n            (x: 8032.9717, y: 8044.537),\n            (x: 8077.896, y: 8022.121),\n        ];\n\n        let clip_polygon: Polygon<f64> = polygon![\n            (x: 8055.805, y: 7977.847),\n            (x: 8010.871, y: 8000.2676),\n            (x: 8033.105, y: 8044.8286),\n            (x: 8078.039, y: 8022.408),\n        ];\n\n        let _result = sutherland_hodgman_clip(&subject_polygon, &clip_polygon);\n    }\n}\n"
  },
  {
    "path": "src/utils/kalman/kalman_2d_box.rs",
    "content": "use std::ops::SubAssign;\n// Original source code idea from\n// https://github.com/nwojke/deep_sort/blob/master/deep_sort/kalman_filter.py\n//\nuse crate::utils::bbox::Universal2DBox;\nuse crate::utils::kalman::{KalmanState, CHI2INV95, CHI2_UPPER_BOUND, DT};\nuse nalgebra::{SMatrix, SVector};\n\npub const DIM_2D_BOX: usize = 5;\npub const DIM_2D_BOX_X2: usize = DIM_2D_BOX * 2;\n\n/// Kalman filter\n///\n#[derive(Debug)]\npub struct Universal2DBoxKalmanFilter {\n    motion_matrix: SMatrix<f32, DIM_2D_BOX_X2, DIM_2D_BOX_X2>,\n    update_matrix: SMatrix<f32, DIM_2D_BOX, DIM_2D_BOX_X2>,\n    std_position_weight: f32,\n    std_velocity_weight: f32,\n}\n\n/// Default initializer\nimpl Default for Universal2DBoxKalmanFilter {\n    fn default() -> Self {\n        Universal2DBoxKalmanFilter::new(1.0 / 20.0, 1.0 / 160.0)\n    }\n}\n\nimpl Universal2DBoxKalmanFilter {\n    /// Constructor with custom weights (shouldn't be used without the need)\n    pub fn new(position_weight: f32, velocity_weight: f32) -> Self {\n        let mut motion_matrix: SMatrix<f32, DIM_2D_BOX_X2, DIM_2D_BOX_X2> = SMatrix::identity();\n\n        for i in 0..DIM_2D_BOX {\n            motion_matrix[(i, DIM_2D_BOX + i)] = DT as f32;\n        }\n\n        Universal2DBoxKalmanFilter {\n            motion_matrix,\n            update_matrix: SMatrix::identity(),\n            std_position_weight: position_weight,\n            std_velocity_weight: velocity_weight,\n        }\n    }\n\n    fn std_position(&self, k: f32, cnst: f32, p: f32) -> [f32; DIM_2D_BOX] {\n        let pos_weight = k * self.std_position_weight * p;\n        [pos_weight, pos_weight, pos_weight, cnst, pos_weight]\n    }\n\n    fn std_velocity(&self, k: f32, cnst: f32, p: f32) -> [f32; DIM_2D_BOX] {\n        let vel_weight = k * self.std_velocity_weight * p;\n        [vel_weight, vel_weight, vel_weight, cnst, vel_weight]\n    }\n\n    /// Initialize the filter with the first observation\n    ///\n    pub fn initiate(&self, bbox: &Universal2DBox) -> KalmanState<DIM_2D_BOX_X2> {\n        let mean: SVector<f32, DIM_2D_BOX_X2> = SVector::from_iterator([\n            bbox.xc,\n            bbox.yc,\n            bbox.angle.unwrap_or(0.0),\n            bbox.aspect,\n            bbox.height,\n            0.0,\n            0.0,\n            0.0,\n            0.0,\n            0.0,\n        ]);\n\n        let mut std: SVector<f32, DIM_2D_BOX_X2> = SVector::from_iterator(\n            self.std_position(2.0, 1e-2, bbox.height)\n                .into_iter()\n                .chain(self.std_velocity(10.0, 1e-5, bbox.height)),\n        );\n\n        std = std.component_mul(&std);\n\n        let covariance: SMatrix<f32, DIM_2D_BOX_X2, DIM_2D_BOX_X2> = SMatrix::from_diagonal(&std);\n        KalmanState { mean, covariance }\n    }\n\n    /// Predicts the state from the last state\n    ///\n    pub fn predict(&self, state: &KalmanState<DIM_2D_BOX_X2>) -> KalmanState<DIM_2D_BOX_X2> {\n        let (mean, covariance) = (state.mean, state.covariance);\n        let std_pos = self.std_position(1.0, 1e-2, mean[4]);\n        let std_vel = self.std_velocity(1.0, 1e-5, mean[4]);\n\n        let mut std: SVector<f32, DIM_2D_BOX_X2> =\n            SVector::from_iterator(std_pos.into_iter().chain(std_vel));\n\n        std = std.component_mul(&std);\n\n        let motion_cov: SMatrix<f32, DIM_2D_BOX_X2, DIM_2D_BOX_X2> = SMatrix::from_diagonal(&std);\n\n        let mean = self.motion_matrix * mean;\n        let covariance =\n            self.motion_matrix * covariance * self.motion_matrix.transpose() + motion_cov;\n        KalmanState { mean, covariance }\n    }\n\n    fn project(\n        &self,\n        mean: SVector<f32, DIM_2D_BOX_X2>,\n        covariance: SMatrix<f32, DIM_2D_BOX_X2, DIM_2D_BOX_X2>,\n    ) -> KalmanState<DIM_2D_BOX> {\n        let mut std: SVector<f32, DIM_2D_BOX> =\n            SVector::from_iterator(self.std_position(1.0, 1e-1, mean[4]));\n\n        std = std.component_mul(&std);\n\n        let innovation_cov: SMatrix<f32, DIM_2D_BOX, DIM_2D_BOX> = SMatrix::from_diagonal(&std);\n\n        let mean = self.update_matrix * mean;\n        let covariance =\n            self.update_matrix * covariance * self.update_matrix.transpose() + innovation_cov;\n        KalmanState { mean, covariance }\n    }\n\n    /// Updates the state with the current observation\n    ///\n    pub fn update(\n        &self,\n        state: &KalmanState<DIM_2D_BOX_X2>,\n        measurement: &Universal2DBox,\n    ) -> KalmanState<DIM_2D_BOX_X2> {\n        let (mean, covariance) = (state.mean, state.covariance);\n        let projected_state = self.project(mean, covariance);\n        let (projected_mean, projected_cov) = (projected_state.mean, projected_state.covariance);\n        let b = (covariance * self.update_matrix.transpose()).transpose();\n        let kalman_gain = projected_cov.solve_lower_triangular(&b).unwrap();\n\n        let innovation = SVector::from_iterator([\n            measurement.xc,\n            measurement.yc,\n            measurement.angle.unwrap_or(0.0),\n            measurement.aspect,\n            measurement.height,\n        ]) - projected_mean;\n\n        let innovation: SMatrix<f32, 1, DIM_2D_BOX> = innovation.transpose();\n\n        let mean = mean + (innovation * kalman_gain).transpose();\n        let covariance = covariance - kalman_gain.transpose() * projected_cov * kalman_gain;\n        KalmanState { mean, covariance }\n    }\n\n    pub fn distance(&self, state: KalmanState<DIM_2D_BOX_X2>, measurement: &Universal2DBox) -> f32 {\n        let (mean, covariance) = (state.mean, state.covariance);\n        let projected_state = self.project(mean, covariance);\n        let (mean, covariance) = (projected_state.mean, projected_state.covariance);\n\n        let measurements = {\n            let mut r: SVector<f32, DIM_2D_BOX> = SVector::from_vec(vec![\n                measurement.xc,\n                measurement.yc,\n                measurement.angle.unwrap_or(0.0),\n                measurement.aspect,\n                measurement.height,\n            ]);\n            r.sub_assign(&mean);\n            r\n        };\n\n        let choletsky = covariance.cholesky().unwrap().l();\n        let res = choletsky.solve_lower_triangular(&measurements).unwrap();\n        res.component_mul(&res).sum()\n    }\n\n    pub fn calculate_cost(distance: f32, inverted: bool) -> f32 {\n        if !inverted {\n            if distance > CHI2INV95[4] {\n                CHI2_UPPER_BOUND\n            } else {\n                distance\n            }\n        } else if distance > CHI2INV95[4] {\n            0.0\n        } else {\n            CHI2_UPPER_BOUND - distance\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::utils::bbox::{BoundingBox, Universal2DBox};\n    use crate::utils::kalman::kalman_2d_box::Universal2DBoxKalmanFilter;\n    use crate::utils::kalman::CHI2INV95;\n\n    #[test]\n    fn constructor() {\n        let f = Universal2DBoxKalmanFilter::default();\n        let bbox = BoundingBox::new(1.0, 2.0, 5.0, 5.0);\n\n        let state = f.initiate(&bbox.into());\n        let new_bb = BoundingBox::try_from(state);\n        assert_eq!(new_bb.unwrap(), bbox);\n    }\n\n    #[test]\n    fn step() {\n        let f = Universal2DBoxKalmanFilter::default();\n        let bbox = BoundingBox::new(-10.0, 2.0, 2.0, 5.0);\n\n        let state = f.initiate(&bbox.into());\n        let state = f.predict(&state);\n        let p = Universal2DBox::try_from(state).unwrap();\n\n        let est_p = Universal2DBox::new(-9.0, 4.5, None, 0.4, 5.0);\n        assert_eq!(p, est_p);\n\n        let bbox = Universal2DBox::new(8.75, 52.35, None, 0.150_849_15, 100.1);\n        let state = f.update(&state, &bbox);\n        let est_p = Universal2DBox::new(10.070248, 55.90909, None, 0.3951147, 107.173546);\n\n        let state = f.predict(&state);\n        let p = Universal2DBox::try_from(state).unwrap();\n        assert_eq!(p, est_p);\n    }\n\n    #[test]\n    fn gating_distance() {\n        let f = Universal2DBoxKalmanFilter::default();\n        let bbox = BoundingBox::new(-10.0, 2.0, 2.0, 5.0);\n\n        let upd_bbox = BoundingBox::new(-9.5, 2.1, 2.0, 5.0);\n\n        let new_bbox_1 = BoundingBox::new(-9.0, 2.2, 2.0, 5.0);\n\n        let new_bbox_2 = BoundingBox::new(-5.0, 1.5, 2.2, 5.0);\n\n        let state = f.initiate(&bbox.into());\n        let state = f.predict(&state);\n        let state = f.update(&state, &upd_bbox.into());\n        let state = f.predict(&state);\n\n        let dist = f.distance(state, &new_bbox_1.into());\n        let dist = Universal2DBoxKalmanFilter::calculate_cost(dist, false);\n        dbg!(&dist);\n        assert!((0.0..CHI2INV95[4]).contains(&dist));\n\n        let dist = f.distance(state, &new_bbox_2.into());\n        let dist = Universal2DBoxKalmanFilter::calculate_cost(dist, false);\n        dbg!(&dist);\n        assert!(dist > CHI2INV95[4]);\n    }\n}\n\n#[cfg(feature = \"python\")]\npub mod python {\n    use crate::prelude::Universal2DBox;\n    use crate::utils::bbox::python::{PyBoundingBox, PyUniversal2DBox};\n    use crate::utils::kalman::kalman_2d_box::{Universal2DBoxKalmanFilter, DIM_2D_BOX_X2};\n    use crate::utils::kalman::KalmanState;\n    use pyo3::prelude::*;\n\n    #[pyclass]\n    #[pyo3(name = \"Universal2DBoxKalmanFilter\")]\n    pub struct PyUniversal2DBoxKalmanFilter {\n        filter: Universal2DBoxKalmanFilter,\n    }\n\n    #[derive(Clone)]\n    #[pyclass]\n    #[pyo3(name = \"Universal2DBoxKalmanFilterState\")]\n    pub struct PyUniversal2DBoxKalmanFilterState {\n        state: KalmanState<{ DIM_2D_BOX_X2 }>,\n    }\n\n    #[pymethods]\n    impl PyUniversal2DBoxKalmanFilterState {\n        #[pyo3(signature = ())]\n        pub fn universal_bbox(&self) -> PyUniversal2DBox {\n            PyUniversal2DBox(Universal2DBox::try_from(self.state).unwrap())\n        }\n\n        #[pyo3(signature = ())]\n        pub fn bbox(&self) -> PyResult<PyBoundingBox> {\n            self.universal_bbox().as_ltwh()\n        }\n    }\n\n    #[pymethods]\n    impl PyUniversal2DBoxKalmanFilter {\n        #[new]\n        #[pyo3(signature = (position_weight = 0.05, velocity_weight = 0.00625))]\n        pub fn new(position_weight: f32, velocity_weight: f32) -> Self {\n            Self {\n                filter: Universal2DBoxKalmanFilter::new(position_weight, velocity_weight),\n            }\n        }\n\n        #[pyo3(signature = (bbox))]\n        pub fn initiate(&self, bbox: PyUniversal2DBox) -> PyUniversal2DBoxKalmanFilterState {\n            PyUniversal2DBoxKalmanFilterState {\n                state: self.filter.initiate(&bbox.0),\n            }\n        }\n\n        #[pyo3(signature = (state))]\n        pub fn predict(\n            &self,\n            state: PyUniversal2DBoxKalmanFilterState,\n        ) -> PyUniversal2DBoxKalmanFilterState {\n            PyUniversal2DBoxKalmanFilterState {\n                state: self.filter.predict(&state.state),\n            }\n        }\n\n        #[pyo3(signature = (state, bbox))]\n        pub fn update(\n            &self,\n            state: PyUniversal2DBoxKalmanFilterState,\n            bbox: PyUniversal2DBox,\n        ) -> PyUniversal2DBoxKalmanFilterState {\n            PyUniversal2DBoxKalmanFilterState {\n                state: self.filter.update(&state.state, &bbox.0),\n            }\n        }\n\n        #[pyo3(signature = (state, bbox))]\n        pub fn distance(\n            &self,\n            state: PyUniversal2DBoxKalmanFilterState,\n            bbox: PyUniversal2DBox,\n        ) -> f32 {\n            self.filter.distance(state.state, &bbox.0)\n        }\n\n        #[staticmethod]\n        #[pyo3(signature = (distance, inverted))]\n        pub fn calculate_cost(distance: f32, inverted: bool) -> f32 {\n            Universal2DBoxKalmanFilter::calculate_cost(distance, inverted)\n        }\n    }\n}\n"
  },
  {
    "path": "src/utils/kalman/kalman_2d_point.rs",
    "content": "use crate::utils::kalman::{KalmanState, CHI2INV95, CHI2_UPPER_BOUND, DT};\nuse nalgebra::{Point2, SMatrix, SVector};\nuse std::ops::SubAssign;\n\npub const DIM_2D_POINT: usize = 2;\npub const DIM_2D_POINT_X2: usize = DIM_2D_POINT * 2;\n\n/// Kalman filter\n///\n#[derive(Debug)]\npub struct Point2DKalmanFilter {\n    motion_matrix: SMatrix<f32, DIM_2D_POINT_X2, DIM_2D_POINT_X2>,\n    update_matrix: SMatrix<f32, DIM_2D_POINT, DIM_2D_POINT_X2>,\n    std_position_weight: f32,\n    std_velocity_weight: f32,\n}\n\n/// Default initializer\nimpl Default for Point2DKalmanFilter {\n    fn default() -> Self {\n        Point2DKalmanFilter::new(1.0 / 20.0, 1.0 / 160.0)\n    }\n}\n\nimpl Point2DKalmanFilter {\n    pub fn new(position_weight: f32, velocity_weight: f32) -> Self {\n        let mut motion_matrix: SMatrix<f32, DIM_2D_POINT_X2, DIM_2D_POINT_X2> = SMatrix::identity();\n\n        for i in 0..DIM_2D_POINT {\n            motion_matrix[(i, DIM_2D_POINT + i)] = DT as f32;\n        }\n\n        Point2DKalmanFilter {\n            motion_matrix,\n            update_matrix: SMatrix::identity(),\n            std_position_weight: position_weight,\n            std_velocity_weight: velocity_weight,\n        }\n    }\n\n    fn std_position(&self, k: f32) -> [f32; DIM_2D_POINT] {\n        let pos_weight = k * self.std_position_weight;\n        [pos_weight, pos_weight]\n    }\n\n    fn std_velocity(&self, k: f32) -> [f32; DIM_2D_POINT] {\n        let vel_weight = k * self.std_velocity_weight;\n        [vel_weight, vel_weight]\n    }\n\n    pub fn initiate(&self, p: &Point2<f32>) -> KalmanState<DIM_2D_POINT_X2> {\n        let mean: SVector<f32, DIM_2D_POINT_X2> = SVector::from_iterator([p.x, p.y, 0.0, 0.0]);\n\n        let mut std: SVector<f32, DIM_2D_POINT_X2> = SVector::from_iterator(\n            self.std_position(2.0)\n                .into_iter()\n                .chain(self.std_velocity(10.0)),\n        );\n\n        std = std.component_mul(&std);\n\n        let covariance: SMatrix<f32, DIM_2D_POINT_X2, DIM_2D_POINT_X2> =\n            SMatrix::from_diagonal(&std);\n        KalmanState { mean, covariance }\n    }\n\n    pub fn predict(&self, state: &KalmanState<DIM_2D_POINT_X2>) -> KalmanState<DIM_2D_POINT_X2> {\n        let (mean, covariance) = (state.mean, state.covariance);\n        let std_pos = self.std_position(1.0);\n        let std_vel = self.std_velocity(1.0);\n\n        let mut std: SVector<f32, DIM_2D_POINT_X2> =\n            SVector::from_iterator(std_pos.into_iter().chain(std_vel));\n\n        std = std.component_mul(&std);\n\n        let motion_cov: SMatrix<f32, DIM_2D_POINT_X2, DIM_2D_POINT_X2> =\n            SMatrix::from_diagonal(&std);\n\n        let mean = self.motion_matrix * mean;\n        let covariance =\n            self.motion_matrix * covariance * self.motion_matrix.transpose() + motion_cov;\n        KalmanState { mean, covariance }\n    }\n\n    fn project(\n        &self,\n        mean: SVector<f32, DIM_2D_POINT_X2>,\n        covariance: SMatrix<f32, DIM_2D_POINT_X2, DIM_2D_POINT_X2>,\n    ) -> KalmanState<DIM_2D_POINT> {\n        let mut std: SVector<f32, DIM_2D_POINT> = SVector::from_iterator(self.std_position(1.0));\n\n        std = std.component_mul(&std);\n\n        let innovation_cov: SMatrix<f32, DIM_2D_POINT, DIM_2D_POINT> = SMatrix::from_diagonal(&std);\n\n        let mean = self.update_matrix * mean;\n        let covariance =\n            self.update_matrix * covariance * self.update_matrix.transpose() + innovation_cov;\n        KalmanState { mean, covariance }\n    }\n\n    pub fn update(\n        &self,\n        state: &KalmanState<DIM_2D_POINT_X2>,\n        p: &Point2<f32>,\n    ) -> KalmanState<DIM_2D_POINT_X2> {\n        let (mean, covariance) = (state.mean, state.covariance);\n        let projected_state = self.project(mean, covariance);\n        let (projected_mean, projected_cov) = (projected_state.mean, projected_state.covariance);\n        let b = (covariance * self.update_matrix.transpose()).transpose();\n        let kalman_gain = projected_cov.solve_lower_triangular(&b).unwrap();\n\n        let innovation = SVector::from_iterator([p.x, p.y]) - projected_mean;\n\n        let innovation: SMatrix<f32, 1, DIM_2D_POINT> = innovation.transpose();\n\n        let mean = mean + (innovation * kalman_gain).transpose();\n        let covariance = covariance - kalman_gain.transpose() * projected_cov * kalman_gain;\n        KalmanState { mean, covariance }\n    }\n\n    pub fn distance(&self, state: &KalmanState<DIM_2D_POINT_X2>, p: &Point2<f32>) -> f32 {\n        let (mean, covariance) = (state.mean, state.covariance);\n        let projected_state = self.project(mean, covariance);\n        let (mean, covariance) = (projected_state.mean, projected_state.covariance);\n\n        let measurements = {\n            let mut r: SVector<f32, DIM_2D_POINT> = SVector::from_vec(vec![p.x, p.y]);\n            r.sub_assign(&mean);\n            r\n        };\n\n        let choletsky = covariance.cholesky().unwrap().l();\n        let res = choletsky.solve_lower_triangular(&measurements).unwrap();\n        res.component_mul(&res).sum()\n    }\n\n    pub fn calculate_cost(distance: f32, inverted: bool) -> f32 {\n        if !inverted {\n            if distance > CHI2INV95[1] {\n                CHI2_UPPER_BOUND\n            } else {\n                distance\n            }\n        } else if distance > CHI2INV95[4] {\n            0.0\n        } else {\n            CHI2_UPPER_BOUND - distance\n        }\n    }\n}\n\nimpl From<KalmanState<{ DIM_2D_POINT_X2 }>> for Point2<f32> {\n    fn from(s: KalmanState<{ DIM_2D_POINT_X2 }>) -> Self {\n        Point2::from([s.mean.x, s.mean.y])\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::utils::kalman::kalman_2d_point::Point2DKalmanFilter;\n    use nalgebra::Point2;\n\n    #[test]\n    fn test() {\n        let p = Point2::from([1.0, 0.0]);\n        let f = Point2DKalmanFilter::default();\n        let state = f.initiate(&p);\n        let state = f.predict(&state);\n        dbg!(Point2::from(state));\n\n        let p = Point2::from([1.1, 0.1]);\n        let state = f.update(&state, &p);\n        let state = f.predict(&state);\n        dbg!(Point2::from(state));\n\n        let p = Point2::from([1.2, 0.2]);\n        let state = f.update(&state, &p);\n        let state = f.predict(&state);\n        dbg!(Point2::from(state));\n\n        let p = Point2::from([1.3, 0.3]);\n        let state = f.update(&state, &p);\n        let state = f.predict(&state);\n        dbg!(Point2::from(state));\n\n        let p = Point2::from([1.4, 0.4]);\n        let state = f.update(&state, &p);\n        let state = f.predict(&state);\n        dbg!(Point2::from(state));\n\n        let p = Point2::from([1.5, 0.5]);\n        let state = f.update(&state, &p);\n        let state = f.predict(&state);\n        dbg!(Point2::from(state));\n\n        let p = Point2::from([1.6, 0.6]);\n        let state = f.update(&state, &p);\n        let state = f.predict(&state);\n        dbg!(Point2::from(state));\n\n        let p = Point2::from([1.7, 0.7]);\n        let state = f.update(&state, &p);\n        let state = f.predict(&state);\n        dbg!(Point2::from(state));\n\n        let p = Point2::from([1.8, 0.67]);\n        let state = f.update(&state, &p);\n        let state = f.predict(&state);\n        dbg!(Point2::from(state));\n\n        let p = Point2::from([1.9, 0.60]);\n        let state = f.update(&state, &p);\n        let state = f.predict(&state);\n        dbg!(Point2::from(state));\n\n        let dist = f.distance(&state, &Point2::from([2.0, 0.57]));\n        dbg!(&dist);\n    }\n}\n\n#[cfg(feature = \"python\")]\npub mod python {\n    use crate::utils::kalman::kalman_2d_point::{Point2DKalmanFilter, DIM_2D_POINT_X2};\n    use crate::utils::kalman::KalmanState;\n    use nalgebra::Point2;\n    use pyo3::prelude::*;\n\n    #[pyclass]\n    #[pyo3(name = \"Point2DKalmanFilter\")]\n    pub struct PyPoint2DKalmanFilter {\n        filter: Point2DKalmanFilter,\n    }\n\n    #[derive(Clone)]\n    #[pyclass]\n    #[pyo3(name = \"Point2DKalmanFilterState\")]\n    pub struct PyPoint2DKalmanFilterState {\n        state: KalmanState<{ DIM_2D_POINT_X2 }>,\n    }\n\n    impl PyPoint2DKalmanFilterState {\n        pub fn new(state: KalmanState<{ DIM_2D_POINT_X2 }>) -> Self {\n            Self { state }\n        }\n\n        pub fn inner(&self) -> &KalmanState<{ DIM_2D_POINT_X2 }> {\n            &self.state\n        }\n    }\n\n    #[pymethods]\n    impl PyPoint2DKalmanFilterState {\n        #[pyo3(signature = ())]\n        pub fn x(&self) -> f32 {\n            self.state.mean[0]\n        }\n\n        #[pyo3(signature = ())]\n        pub fn y(&self) -> f32 {\n            self.state.mean[1]\n        }\n    }\n\n    #[pymethods]\n    impl PyPoint2DKalmanFilter {\n        #[new]\n        #[pyo3(signature = (position_weight = 0.05, velocity_weight = 0.00625))]\n        pub fn new(position_weight: f32, velocity_weight: f32) -> Self {\n            Self {\n                filter: Point2DKalmanFilter::new(position_weight, velocity_weight),\n            }\n        }\n\n        #[pyo3(signature = (x, y))]\n        pub fn initiate(&self, x: f32, y: f32) -> PyPoint2DKalmanFilterState {\n            PyPoint2DKalmanFilterState {\n                state: self.filter.initiate(&Point2::from([x, y])),\n            }\n        }\n\n        #[pyo3(signature = (state))]\n        pub fn predict(&self, state: PyPoint2DKalmanFilterState) -> PyPoint2DKalmanFilterState {\n            PyPoint2DKalmanFilterState {\n                state: self.filter.predict(&state.state),\n            }\n        }\n\n        #[pyo3(signature = (state, x, y))]\n        pub fn update(\n            &self,\n            state: PyPoint2DKalmanFilterState,\n            x: f32,\n            y: f32,\n        ) -> PyPoint2DKalmanFilterState {\n            PyPoint2DKalmanFilterState {\n                state: self.filter.update(&state.state, &Point2::from([x, y])),\n            }\n        }\n\n        #[pyo3(signature = (state, x, y))]\n        pub fn distance(&self, state: PyPoint2DKalmanFilterState, x: f32, y: f32) -> f32 {\n            self.filter.distance(&state.state, &Point2::from([x, y]))\n        }\n\n        #[staticmethod]\n        #[pyo3(signature = (distance, inverted))]\n        pub fn calculate_cost(distance: f32, inverted: bool) -> f32 {\n            Point2DKalmanFilter::calculate_cost(distance, inverted)\n        }\n    }\n}\n"
  },
  {
    "path": "src/utils/kalman/kalman_2d_point_vec.rs",
    "content": "use crate::utils::kalman::kalman_2d_point::{Point2DKalmanFilter, DIM_2D_POINT_X2};\nuse crate::utils::kalman::KalmanState;\nuse nalgebra::Point2;\n\n#[derive(Debug)]\npub struct Vec2DKalmanFilter {\n    f: Point2DKalmanFilter,\n}\n\n/// Default initializer\nimpl Default for Vec2DKalmanFilter {\n    fn default() -> Self {\n        Self {\n            f: Point2DKalmanFilter::new(1.0 / 20.0, 1.0 / 160.0),\n        }\n    }\n}\n\nimpl Vec2DKalmanFilter {\n    pub fn new(position_weight: f32, velocity_weight: f32) -> Self {\n        Self {\n            f: Point2DKalmanFilter::new(position_weight, velocity_weight),\n        }\n    }\n\n    pub fn initiate(&self, points: &[Point2<f32>]) -> Vec<KalmanState<DIM_2D_POINT_X2>> {\n        points.iter().map(|p| self.f.initiate(p)).collect()\n    }\n\n    pub fn predict(\n        &self,\n        state: &[KalmanState<DIM_2D_POINT_X2>],\n    ) -> Vec<KalmanState<DIM_2D_POINT_X2>> {\n        state.iter().map(|s| self.f.predict(s)).collect()\n    }\n\n    pub fn update(\n        &self,\n        state: &[KalmanState<DIM_2D_POINT_X2>],\n        points: &[Point2<f32>],\n    ) -> Vec<KalmanState<DIM_2D_POINT_X2>> {\n        assert_eq!(\n            state.len(),\n            points.len(),\n            \"Lengths of state and points must match\"\n        );\n        state\n            .iter()\n            .zip(points.iter())\n            .map(|(s, p)| self.f.update(s, p))\n            .collect()\n    }\n\n    pub fn distance(\n        &self,\n        state: &[KalmanState<DIM_2D_POINT_X2>],\n        points: &[Point2<f32>],\n    ) -> Vec<f32> {\n        assert_eq!(\n            state.len(),\n            points.len(),\n            \"Lengths of state and points must match\"\n        );\n        state\n            .iter()\n            .zip(points.iter())\n            .map(|(s, p)| self.f.distance(s, p))\n            .collect()\n    }\n\n    pub fn calculate_cost(distances: &[f32], inverted: bool) -> Vec<f32> {\n        distances\n            .iter()\n            .map(|d| Point2DKalmanFilter::calculate_cost(*d, inverted))\n            .collect()\n    }\n}\n\n#[cfg(feature = \"python\")]\npub mod python {\n    use crate::utils::kalman::kalman_2d_point::python::PyPoint2DKalmanFilterState;\n    use crate::utils::kalman::kalman_2d_point_vec::Vec2DKalmanFilter;\n    use nalgebra::Point2;\n    use pyo3::prelude::*;\n\n    #[pyclass]\n    #[pyo3(name = \"Vec2DKalmanFilter\")]\n    pub struct PyVec2DKalmanFilter {\n        filter: Vec2DKalmanFilter,\n    }\n\n    #[pymethods]\n    impl PyVec2DKalmanFilter {\n        #[new]\n        #[pyo3(signature = (position_weight = 0.05, velocity_weight = 0.00625))]\n        pub fn new(position_weight: f32, velocity_weight: f32) -> Self {\n            Self {\n                filter: Vec2DKalmanFilter::new(position_weight, velocity_weight),\n            }\n        }\n\n        #[pyo3(signature = (points))]\n        pub fn initiate(&self, points: Vec<(f32, f32)>) -> Vec<PyPoint2DKalmanFilterState> {\n            let args = points\n                .iter()\n                .map(|(x, y)| Point2::from([*x, *y]))\n                .collect::<Vec<_>>();\n\n            self.filter\n                .initiate(&args)\n                .into_iter()\n                .map(PyPoint2DKalmanFilterState::new)\n                .collect()\n        }\n\n        #[pyo3(signature = (state))]\n        pub fn predict(\n            &self,\n            state: Vec<PyPoint2DKalmanFilterState>,\n        ) -> Vec<PyPoint2DKalmanFilterState> {\n            let args = state.into_iter().map(|s| *s.inner()).collect::<Vec<_>>();\n            self.filter\n                .predict(&args)\n                .into_iter()\n                .map(PyPoint2DKalmanFilterState::new)\n                .collect()\n        }\n\n        #[pyo3(signature = (state, points))]\n        pub fn update(\n            &self,\n            state: Vec<PyPoint2DKalmanFilterState>,\n            points: Vec<(f32, f32)>,\n        ) -> Vec<PyPoint2DKalmanFilterState> {\n            let point_args = points\n                .iter()\n                .map(|(x, y)| Point2::from([*x, *y]))\n                .collect::<Vec<_>>();\n            let state_args = state.iter().map(|s| *s.inner()).collect::<Vec<_>>();\n            self.filter\n                .update(&state_args, &point_args)\n                .into_iter()\n                .map(PyPoint2DKalmanFilterState::new)\n                .collect()\n        }\n\n        #[pyo3(signature = (state, points))]\n        pub fn distance(\n            &self,\n            state: Vec<PyPoint2DKalmanFilterState>,\n            points: Vec<(f32, f32)>,\n        ) -> Vec<f32> {\n            let point_args = points\n                .iter()\n                .map(|(x, y)| Point2::from([*x, *y]))\n                .collect::<Vec<_>>();\n            let state_args = state.iter().map(|s| *s.inner()).collect::<Vec<_>>();\n            self.filter.distance(&state_args, &point_args)\n        }\n\n        #[staticmethod]\n        #[pyo3(signature = (distances, inverted))]\n        pub fn calculate_cost(distances: Vec<f32>, inverted: bool) -> Vec<f32> {\n            Vec2DKalmanFilter::calculate_cost(&distances, inverted)\n        }\n    }\n}\n"
  },
  {
    "path": "src/utils/kalman.rs",
    "content": "use crate::prelude::{BoundingBox, Universal2DBox};\nuse crate::Errors;\nuse kalman_2d_box::DIM_2D_BOX;\nuse nalgebra::{SMatrix, SVector};\n\n/// Kalman filter for the prediction of axis-aligned and oriented bounding boxes\n///\npub mod kalman_2d_box;\n/// Kalman filter for 2d point\n///\npub mod kalman_2d_point;\n/// Kalman filter for Vector of 2d points\n///\npub mod kalman_2d_point_vec;\n\npub const CHI2_UPPER_BOUND: f32 = 100.0;\n\npub const CHI2INV95: [f32; 9] = [\n    3.8415, 5.9915, 7.8147, 9.4877, 11.070, 12.592, 14.067, 15.507, 16.919,\n];\n\nmacro_rules! pretty_print {\n    ($arr:expr) => {{\n        let indent = 4;\n        let prefix = String::from_utf8(vec![b' '; indent]).unwrap();\n        let mut result_els = vec![\"\".to_string()];\n        for i in 0..$arr.nrows() {\n            let mut row_els = vec![];\n            for j in 0..$arr.ncols() {\n                row_els.push(format!(\"{:12.3}\", $arr[(i, j)]));\n            }\n            let row_str = row_els.into_iter().collect::<Vec<_>>().join(\" \");\n            let row_str = format!(\"{}{}\", prefix, row_str);\n            result_els.push(row_str);\n        }\n        result_els.into_iter().collect::<Vec<_>>().join(\"\\n\")\n    }};\n}\n\n/// Kalman filter current state\n///\n#[derive(Copy, Clone, Debug)]\npub struct KalmanState<const X: usize> {\n    mean: SVector<f32, X>,\n    covariance: SMatrix<f32, X, X>,\n}\n\nimpl KalmanState<{ kalman_2d_box::DIM_2D_BOX_X2 }> {\n    pub fn mean_pos_xc(&self) -> f32 {\n        self.mean[0]\n    }\n    pub fn mean_pos_yc(&self) -> f32 {\n        self.mean[1]\n    }\n    pub fn mean_vel_xc(&self) -> f32 {\n        self.mean[DIM_2D_BOX]\n    }\n    pub fn mean_vel_yc(&self) -> f32 {\n        self.mean[DIM_2D_BOX + 1]\n    }\n}\n\nimpl<const X: usize> KalmanState<X> {\n    /// dump the state\n    ///\n    pub fn dump(&self) {\n        eprintln!(\"Mean={}\", pretty_print!(self.mean.transpose()));\n        eprintln!(\"Covariance={}\", pretty_print!(self.covariance));\n    }\n}\n\nimpl<const X: usize> TryFrom<KalmanState<X>> for Universal2DBox {\n    type Error = Errors;\n\n    fn try_from(value: KalmanState<X>) -> Result<Self, Self::Error> {\n        if value.mean.len() < 5 {\n            Err(Self::Error::OutOfRange)\n        } else {\n            Ok(Universal2DBox::new(\n                value.mean[0],\n                value.mean[1],\n                if value.mean[2] == 0.0 {\n                    None\n                } else {\n                    Some(value.mean[2])\n                },\n                value.mean[3],\n                value.mean[4],\n            ))\n        }\n    }\n}\n\nimpl<const X: usize> TryFrom<KalmanState<X>> for BoundingBox {\n    type Error = Errors;\n\n    fn try_from(value: KalmanState<X>) -> Result<Self, Self::Error> {\n        let bb = Universal2DBox::try_from(value)?;\n        BoundingBox::try_from(&bb)\n    }\n}\n\npub const DT: u64 = 1;\n"
  },
  {
    "path": "src/utils/nms/nms_py.rs",
    "content": "use crate::utils::bbox::python::PyUniversal2DBox;\nuse crate::utils::bbox::Universal2DBox;\nuse crate::utils::nms::nms;\nuse pyo3::prelude::*;\n\n/// # NMS Python interface\n///\n/// The python function name is `nms`. When the function is called, the GIL is released until the end of its execution.\n///\n/// The signature is:\n/// ```python\n/// def nms(detections: List[(Universal2DBox, Optional(float))], nms_threshold: float, score_threshold: Optional(float) -> List[Universal2DBox]\n/// ```\n/// # Parameters\n/// * `detections` receives the list of tuples `(Universal2DBox, Optional(float))` where the first argument is bbox `Universal2DBox`, the second argument\n///   is confidence or another ranking parameter. It is Optional value - can be `None` or `float`. If the confidence is None, the confidence is calculated\n///   as height of the box.\n/// * `nms_threshold` - the threshold that is used to remove excessive boxes out of the list.\n/// * `score_threshold` - the threshold that filters boxes by confidence value. If it's not used, then None can be set.\n/**\n# Example\n\n```python\nfrom similari import nms, BoundingBox\n\n if __name__ == '__main__':\n\n     print(\"With score\")\n     bbox1 = (BoundingBox(10.0, 11.0, 3.0, 3.8).as_xyaah(), 1.0)\n     bbox2 = (BoundingBox(10.3, 11.1, 2.9, 3.9).as_xyaah(), 0.9)\n     res = nms([bbox2, bbox1], nms_threshold = 0.7, score_threshold = 0.0)\n     print(res[0].as_xywh())\n\n     print(\"No score\")\n     bbox1 = (BoundingBox(10.0, 11.0, 3.0, 4.0).as_xyaah(), None)\n     bbox2 = (BoundingBox(10.3, 11.1, 2.9, 3.9).as_xyaah(), None)\n     res = nms([bbox2, bbox1], nms_threshold = 0.7, score_threshold = 0.0)\n     print(res[0].as_xywh())\n ```\n*/\n#[pyfunction]\n#[pyo3(\n    name = \"nms\",\n    signature = (detections, nms_threshold, score_threshold)\n)]\npub fn nms_py(\n    detections: Vec<(PyUniversal2DBox, Option<f32>)>,\n    nms_threshold: f32,\n    score_threshold: Option<f32>,\n) -> Vec<PyUniversal2DBox> {\n    Python::with_gil(|py| {\n        py.allow_threads(|| {\n            let detections: Vec<(Universal2DBox, Option<f32>)> =\n                unsafe { std::mem::transmute(detections) };\n\n            nms(&detections, nms_threshold, score_threshold)\n                .into_iter()\n                .cloned()\n                .map(PyUniversal2DBox)\n                .collect()\n        })\n    })\n}\n"
  },
  {
    "path": "src/utils/nms.rs",
    "content": "#[cfg(feature = \"python\")]\npub mod nms_py;\n\nuse crate::utils::bbox::Universal2DBox;\nuse itertools::Itertools;\nuse std::collections::HashSet;\n\n#[derive(Clone, Debug)]\nstruct Candidate<'a> {\n    bbox: &'a Universal2DBox,\n    rank: f32,\n    index: usize,\n}\n\nimpl<'a> Candidate<'a> {\n    pub fn new(bbox: &'a Universal2DBox, rank: &Option<f32>, index: usize) -> Self {\n        Self {\n            bbox,\n            rank: rank.unwrap_or(bbox.height),\n            index,\n        }\n    }\n}\n\n/// NMS algorithm implementation\n///\n/// # Parameters\n/// * `detections` - boxes with optional scores to filter out with NMS; if `detection.1` is `None`, that the score is set as `detection.0.height`;\n/// * `nms_threshold` - when to exclude the box from set by NMS;\n/// * `score_threshold` - when to exclude the from set by initial score. if `score_threshold` is None, then `f32::MAX` is used.\n///\npub fn nms(\n    detections: &[(Universal2DBox, Option<f32>)],\n    nms_threshold: f32,\n    score_threshold: Option<f32>,\n) -> Vec<&Universal2DBox> {\n    let score_threshold = score_threshold.unwrap_or(f32::MIN);\n    let nms_boxes = detections\n        .iter()\n        .filter(|(e, score)| {\n            score.unwrap_or(f32::MAX) > score_threshold && e.height > 0.0 && e.aspect > 0.0\n        })\n        .enumerate()\n        .map(|(index, (b, score))| Candidate::new(b, score, index))\n        .sorted_by(|a, b| b.rank.partial_cmp(&a.rank).unwrap())\n        .collect::<Vec<_>>();\n\n    let mut excluded = HashSet::new();\n\n    for (index, cb) in nms_boxes.iter().enumerate() {\n        if excluded.contains(&cb.index) {\n            continue;\n        }\n\n        for ob in &nms_boxes[index + 1..] {\n            if excluded.contains(&ob.index) {\n                continue;\n            }\n\n            let metric = Universal2DBox::intersection(cb.bbox, ob.bbox) as f32 / ob.bbox.area();\n            if metric > nms_threshold {\n                excluded.insert(ob.index);\n            }\n        }\n    }\n\n    nms_boxes\n        .into_iter()\n        .filter(|e| !excluded.contains(&e.index))\n        .map(|e| e.bbox)\n        .collect()\n}\n\n// /// NMS algorithm implementation\n// ///\n// /// # Parameters\n// /// * `detections` - boxes with optional scores to filter out with NMS; if `detection.1` is `None`, that the score is set as `detection.0.height`;\n// /// * `nms_threshold` - when to exclude the box from set by NMS;\n// /// * `score_threshold` - when to exclude the from set by initial score. if `score_threshold` is None, then `f32::MAX` is used.\n// ///\n// pub fn parallel_nms(\n//     detections: &[(Universal2DBox, Option<f32>)],\n//     nms_threshold: f32,\n//     score_threshold: Option<f32>,\n// ) -> Vec<&Universal2DBox> {\n//     let score_threshold = score_threshold.unwrap_or(f32::MIN);\n//     let nms_boxes = detections\n//         .iter()\n//         .filter(|(e, score)| {\n//             score.unwrap_or(f32::MAX) > score_threshold && e.height() > 0.0 && e.aspect() > 0.0\n//         })\n//         .enumerate()\n//         .map(|(index, (b, score))| Candidate::new(b, score, index))\n//         .sorted_by(|a, b| b.rank.partial_cmp(&a.rank).unwrap())\n//         .collect::<Vec<_>>();\n//\n//     let weight_matrix = nms_boxes\n//         .par_iter()\n//         .enumerate()\n//         .flat_map(|(index, cb)| {\n//             nms_boxes[index + 1..]\n//                 .iter()\n//                 .enumerate()\n//                 .map(|(inner_index, ob)| {\n//                     (\n//                         (index, inner_index),\n//                         Universal2DBox::intersection(cb.bbox, ob.bbox) as f32 / ob.bbox.area(),\n//                     )\n//                 })\n//                 .collect::<Vec<_>>()\n//         })\n//         .collect::<HashMap<_, _>>();\n//\n//     let mut excluded = HashSet::new();\n//\n//     for (index, cb) in nms_boxes.iter().enumerate() {\n//         if excluded.contains(&cb.index) {\n//             continue;\n//         }\n//\n//         for (internal_index, ob) in nms_boxes[index + 1..].iter().enumerate() {\n//             if excluded.contains(&ob.index) {\n//                 continue;\n//             }\n//\n//             let metric = weight_matrix.get(&(index, internal_index)).unwrap();\n//             if *metric > nms_threshold {\n//                 excluded.insert(ob.index);\n//             }\n//         }\n//     }\n//\n//     nms_boxes\n//         .into_iter()\n//         .filter(|e| !excluded.contains(&e.index))\n//         .map(|e| e.bbox)\n//         .collect()\n// }\n//\n// #[cfg(test)]\n// mod tests {\n//     use crate::utils::bbox::Universal2DBox;\n//     use crate::utils::nms::{nms, parallel_nms};\n//\n//     #[test]\n//     fn nms_test() {\n//         let bboxes = [\n//             (Universal2DBox::new(0.0, 0.0, None, 1.0, 5.0), None),\n//             (Universal2DBox::new(0.0, 0.0, None, 1.05, 5.1), None),\n//             (Universal2DBox::new(0.0, 0.0, None, 1.0, 4.9), None),\n//             (Universal2DBox::new(3.0, 4.0, None, 1.0, 4.5), None),\n//         ];\n//         let res_serial = nms(&bboxes, 0.8, None);\n//         let res_parallel = parallel_nms(&bboxes, 0.8, None);\n//         assert_eq!(res_serial, res_parallel);\n//     }\n// }\n"
  },
  {
    "path": "src/utils/point.rs",
    "content": "pub struct Point2D(pub f32, pub f32);\n"
  },
  {
    "path": "src/utils/primitive.rs",
    "content": "use crate::track::ObservationAttributes;\n\nimpl ObservationAttributes for f32 {\n    type MetricObject = f32;\n\n    fn calculate_metric_object(\n        left: &Option<&Self>,\n        right: &Option<&Self>,\n    ) -> Option<Self::MetricObject> {\n        if let (Some(left), Some(right)) = (left, right) {\n            Some((*left - *right).abs())\n        } else {\n            None\n        }\n    }\n}\n\nimpl ObservationAttributes for () {\n    type MetricObject = ();\n\n    fn calculate_metric_object(\n        _left: &Option<&Self>,\n        _right: &Option<&Self>,\n    ) -> Option<Self::MetricObject> {\n        None\n    }\n}\n"
  },
  {
    "path": "src/utils.rs",
    "content": "/// Various bounding boxes implementations for axis-aligned and oriented (rotated)\n///\npub mod bbox;\n\n/// Bounding box intersection calculation for oriented bounding boxes\n///\npub mod clipping;\n\n/// Auxiliary traits implementations for primitive types\n///\npub mod primitive;\n\n/// Non maximum suppression implementation for axis-aligned and oriented bounding boxes\n///\npub mod nms;\n\n/// Kalman filter related stuff\npub mod kalman;\n\n/// 2D Points stuff\npub mod point;\n"
  }
]