diff --git a/dodgeballtracker/__main__.py b/dodgeballtracker/__main__.py index 02c86bf..42bae72 100644 --- a/dodgeballtracker/__main__.py +++ b/dodgeballtracker/__main__.py @@ -1,25 +1,25 @@ +import json5 as json from contextlib import closing, contextmanager from pathlib import Path import sqlite3 +from dodgeballtracker.metadata import VideoMetadata + from dodgeballtracker.helper import time_tracker from dodgeballtracker.logic import (DodgeballTracker, get_court_perspective_transform) -from dodgeballtracker.data_dumper import SQLiteDataDumper +from dodgeballtracker.data_dumper import SQLiteDataDumper, DataDumper import numpy as np import logging import click import hashlib -from typing import ContextManager +from typing import ContextManager, Iterator from dodgeballtracker.video_writer import (MP4VVideoWriter, VideoWriter, DetectedObjectBox, MP4VVideoWriterWithFrame) -here = Path(__file__).parent -input_dir = here / '..' / 'data' -output_dir = here / '..' / 'outputs' @click.group() @@ -53,15 +53,29 @@ def get_file_sha256(file_: Path) -> str: @contextmanager -def create_data_dumper(input_file: Path, outout_file: Path): - with closing(sqlite3.connect(outout_file)) as conn: +def create_data_dumper(input_file: Path, + output_file: Path, + metadata: VideoMetadata) -> Iterator[DataDumper]: + with closing(sqlite3.connect(output_file)) as conn: conn.execute('PRAGMA foreign_keys = 1') dumper = SQLiteDataDumper(conn) - dumper.start(input_file, get_file_sha256(input_file)) + dumper.start(input_file, get_file_sha256(input_file), metadata) yield dumper +def deserialized_video_metadata(positional_data: Path | None) -> VideoMetadata | None: + if not positional_data: + return None + with open(positional_data, 'rt') as file_: + data = json.load(file_) + return VideoMetadata(**data) + + +def process_logic(input_: Path, + output_dir: Path | None, + metadata_file: Path | None, + display_video: bool): + metadata = deserialized_video_metadata(metadata_file) -def process_logic(input_: Path, output_dir: Path | None, display_video: bool): output_dir = get_output_dir(input_, output_dir) detected_box_objects = [ DetectedObjectBox.from_viridis(threshold) for threshold in @@ -71,7 +85,7 @@ def process_logic(input_: Path, output_dir: Path | None, display_video: bool): transformation_matrix = get_court_perspective_transform() with get_video_writer(display_video, input_, output_dir / 'video.mov') as video_writer: - with create_data_dumper(input_, output_dir / 'data.sqlite') as data_dumper: + with create_data_dumper(input_, output_dir / 'data.sqlite', metadata) as data_dumper: tracker = DodgeballTracker(input_, video_writer, data_dumper, @@ -86,15 +100,25 @@ def process_logic(input_: Path, output_dir: Path | None, display_video: bool): def train(): pass + @cli.command() @click.option('--input', '-i', 'input_', type=click.Path(readable=True, path_type=Path)) @click.option('--output-dir', '-o', type=click.Path(path_type=Path)) +@click.option('--metadata-file', '-f', type=click.Path(path_type=Path, file_okay=True, exists=True)) @click.option('--display-video', '-d', type=bool, is_flag=True, show_default=True, default=False) -def process(input_: Path, output_dir: Path | None, display_video: bool): - process_logic(input_, output_dir, display_video) +def process(input_: Path, + output_dir: Path | None, + metadata_file: Path | None, + display_video: bool): + process_logic(input_, + output_dir, + metadata_file, + display_video) if __name__ == "__main__": cli() - # process_logic(Path('/home/didier/all-dressed-programming/dodgeball-tracker/data/experiments/rue-honore-beaugrand-ouest-001.MOV'), None, False) + # process_logic(Path('/home/didier/all-dressed-programming/dodgeball-tracker/data/experiments/rue-honore-beaugrand-ouest-001.MOV'), + # Path('/home/didier/all-dressed-programming/dodgeball-tracker/outputs/benchmark-001'), + # False) diff --git a/dodgeballtracker/data_dumper.py b/dodgeballtracker/data_dumper.py index 8e90b95..090be17 100644 --- a/dodgeballtracker/data_dumper.py +++ b/dodgeballtracker/data_dumper.py @@ -2,6 +2,8 @@ import abc import pandas as pd from pathlib import Path + +from dodgeballtracker.metadata import VideoMetadata from dodgeballtracker.typing import TrackedDetectedObject, VideoTimestamp from sqlite3 import Connection @@ -61,15 +63,20 @@ class SQLiteDataDumper(DataDumper): ymax DOUBLE PRECISION NOT NULL, score DOUBLE PRECISION NOT NULL, category INTEGER NOT NULL, + object_id INTEGER NOT NULL, video_frame_id INTEGER NOT NULL, + FOREIGN KEY(video_frame_id) REFERENCES video_frame(id) ); ''') self._conn.commit() - def start(self, video_path: Path, hexa_hash: str) -> None: + def start(self, + video_path: Path, + hexa_hash: str, + metadata: VideoMetadata|None=None) -> None: self._create_tables() cur = self._conn.cursor() @@ -101,9 +108,16 @@ class SQLiteDataDumper(DataDumper): RETURNING id ''', (ts, frame_index, self._video_id)) frame_id = res.fetchone()[0] - cols = ['xmin', 'ymin', 'xmax', 'ymax', 'score', 'category'] + cols = ['xmin', 'ymin', 'xmax', 'ymax', 'score', 'category', 'object_id'] cur.executemany(''' - INSERT INTO tracked_detected_object(xmin, ymin, xmax, ymax, score, category, video_frame_id) - VALUES (?, ?, ?, ?, ?, ?, ?) + INSERT INTO tracked_detected_object(xmin, + ymin, + xmax, + ymax, + score, + category, + object_id, + video_frame_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) ''', (list(x[cols]) + [frame_id] for x in df.iloc)) self._conn.commit() diff --git a/dodgeballtracker/metadata.py b/dodgeballtracker/metadata.py new file mode 100644 index 0000000..7638805 --- /dev/null +++ b/dodgeballtracker/metadata.py @@ -0,0 +1,20 @@ +from pydantic import BaseModel + +class KnownImagePoint(BaseModel): + height: float + width: float + + +class BirdViewPoint(BaseModel): + x: float + y: float + + +class KnownPoint(BaseModel): + image: KnownImagePoint + bird_view: BirdViewPoint + + +class VideoMetadata(BaseModel): + knownPoints: list[KnownPoint] + diff --git a/dodgeballtracker/test_data_dumper.py b/dodgeballtracker/test_data_dumper.py index e46222b..a7eb457 100644 --- a/dodgeballtracker/test_data_dumper.py +++ b/dodgeballtracker/test_data_dumper.py @@ -3,6 +3,8 @@ import tempfile from pathlib import Path from sqlite3 import Connection + +from dodgeballtracker.metadata import VideoMetadata from dodgeballtracker.typing import TrackedDetectedObject import pandas as pd @@ -27,6 +29,12 @@ def list_tables(conn: Connection) -> set[str]: ''') return {row[0] for row in res} +def video_metadata() -> VideoMetadata: + data = { + + } + return VideoMetadata() + @pytest.fixture def tmp_dir(): with tempfile.TemporaryDirectory() as dir_: diff --git a/dodgeballtracker/typing.py b/dodgeballtracker/typing.py index 0ab2fc7..78a8e3c 100644 --- a/dodgeballtracker/typing.py +++ b/dodgeballtracker/typing.py @@ -10,8 +10,7 @@ def tensor_of(data_type: pa.DataType, cols: list[str]) -> pa.DataFrameSchema: return pa.DataFrameSchema({col: pa.Column(data_type) for col in cols}) -DetectedObject = tensor_of(Float32, ["xmin", "ymin", "xmax", "ymax", "score", - "category"]) +DetectedObject = tensor_of(Float32, ["xmin", "ymin", "xmax", "ymax", "score", "category"]) TrackedDetectedObject = tensor_of(Float64, list(DetectedObject.columns) + ["object_id", "index"]) diff --git a/outputs/.gitignore b/outputs/.gitignore deleted file mode 100644 index 2b01a9b..0000000 --- a/outputs/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.json -*.mp4 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 0b7994e..4e15e33 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,165 +1,206 @@ absl-py==2.1.0 -anyio==4.2.0 -appnope==0.1.3 +aiodns==3.2.0 +aiohappyeyeballs==2.3.6 +aiohttp==3.10.5 +aiosignal==1.3.1 +annotated-types==0.7.0 +anyio==4.4.0 argon2-cffi==23.1.0 argon2-cffi-bindings==21.2.0 arrow==1.3.0 asttokens==2.4.1 +astunparse==1.6.3 async-lru==2.0.4 +async-timeout==4.0.3 +async_generator==1.10 attrs==23.2.0 -Babel==2.14.0 -beautifulsoup4==4.12.2 +Babel==2.15.0 +bcrypt==4.1.3 +beautifulsoup4==4.12.3 bleach==6.1.0 -boxmot==10.0.51 -build==1.0.3 +boxmot==10.0.81 +Brotli==1.1.0 +brotlicffi==1.1.0.0 cachetools==5.3.2 -certifi==2023.11.17 -cffi==1.16.0 -cfgv==3.4.0 +certifi==2024.7.4 +cffi==1.17.1 +chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 -comm==0.2.1 -contourpy==1.2.0 +comm==0.2.2 +contourpy==1.2.1 +cryptography==43.0.0 +curio==1.6 +curl_cffi==0.7.1 cycler==0.12.1 -Cython==3.0.8 -debugpy==1.8.0 +ddt==1.7.2 decorator==5.1.1 -deep-sort-realtime==1.3.2 -defusedxml==0.7.1 -distlib==0.3.8 -exceptiongroup==1.2.0 -executing==2.0.1 +defusedxml==0.8.0rc2 +executing==2.1.0 fastjsonschema==2.19.1 -filelock==3.13.1 +filelock==3.15.1 filterpy==1.4.5 -fonttools==4.47.2 +fonttools==4.53.1 fqdn==1.5.1 -fsspec==2023.12.2 -ftfy==6.1.3 -gdown==4.6.3 +frozenlist==1.4.1 +fsspec==2024.3.0 +ftfy==6.2.3 +future==1.0.0 +gdown==5.2.0 gitdb==4.0.11 -GitPython==3.1.41 -google-auth==2.26.2 -google-auth-oauthlib==1.2.0 -grpcio==1.60.0 -identify==2.5.33 -idna==3.6 -ipykernel==6.28.0 -ipython==8.20.0 -ipywidgets==8.1.1 +GitPython==3.1.43 +google-auth==2.30.0 +google-auth-oauthlib==1.2.1 +grpcio==1.64.1 +gviz-api==1.10.0 +h11==0.14.0 +html5lib==1.1 +httpcore==1.0.5 +httpx==0.27.0 +idna==3.7 +iniconfig==2.0.0 +ipykernel==6.29.5 +ipython==8.27.0 +ipywidgets==8.1.5 isoduration==20.11.0 jedi==0.19.1 -Jinja2==3.1.3 -joblib==1.3.2 +jeepney==0.8.0 +Jinja2==3.1.4 +joblib==1.4.2 json5==0.9.14 -jsonpointer==2.4 -jsonschema==4.20.0 +jsonpointer==3.0.0 +jsonschema==4.22.0 jsonschema-specifications==2023.12.1 -jupyter==1.0.0 +jupyter==1.1.1 jupyter-console==6.6.3 -jupyter-events==0.9.0 -jupyter-lsp==2.2.1 -jupyter_client==8.6.0 -jupyter_core==5.7.1 -jupyter_server==2.12.4 -jupyter_server_terminals==0.5.1 -jupyterlab==4.0.10 -jupyterlab-widgets==3.0.9 +jupyter-events==0.10.0 +jupyter-lsp==2.2.5 +jupyter_client==8.6.2 +jupyter_core==5.7.2 +jupyter_server==2.14.2 +jupyter_server_terminals==0.5.3 +jupyterlab==4.2.5 jupyterlab_pygments==0.3.0 -jupyterlab_server==2.25.2 +jupyterlab_server==2.27.3 +jupyterlab_widgets==3.0.13 kiwisolver==1.4.5 -lapx==0.5.5 +lapx==0.5.11 loguru==0.7.2 -Markdown==3.5.2 -MarkupSafe==2.1.3 -matplotlib==3.8.2 -matplotlib-inline==0.1.6 +lz4==4.3.3 +Markdown==3.7 +MarkupSafe==2.1.5 +matplotlib==3.9.1 +matplotlib-inline==0.1.7 mistune==3.0.2 mpmath==1.3.0 -nbclient==0.9.0 -nbconvert==7.14.1 -nbformat==5.9.2 -nest-asyncio==1.5.8 -networkx==3.2.1 -nodeenv==1.8.0 -notebook==7.0.6 -notebook_shim==0.2.3 -numpy==1.24.4 +multidict==6.1.0 +multimethod==1.9.1 +mutagen==1.47.0 +mypy_extensions==1.0.0 +nbclient==0.10.0 +nbconvert==7.16.4 +nbformat==5.10.4 +nest_asyncio==1.6.0 +networkx==3.3 +notebook==7.2.2 +notebook_shim==0.2.4 +numpy==1.26.4 oauthlib==3.2.2 -opencv-python==4.9.0.80 -overrides==7.4.0 -packaging==23.2 -pandas==2.1.4 -pandocfilters==1.5.0 -parso==0.8.3 +olefile==0.47 +opencv @ file:///build/source/modules/python/package/dist/opencv-4.9.0-py3-none-any.whl#sha256=0b1c6093b66ec745df186877df1e6e72241dfe8dda392f023bc361fe1d5411ca +outcome==1.3.0.post0 +overrides==7.7.0 +packaging==24.1 +pandas==2.2.2 +pandera==0.20.4 +pandocfilters==1.5.1 +paramiko==3.5.0 +parso==0.8.4 pexpect==4.9.0 -pillow==10.2.0 -pip-tools==7.3.0 -platformdirs==4.1.0 -pre-commit==3.6.0 -prometheus-client==0.19.0 -prompt-toolkit==3.0.43 -protobuf==4.23.4 -psutil==5.9.7 +pillow==10.4.0 +platformdirs==4.2.2 +pluggy==1.5.0 +prometheus_client==0.21.0 +prompt_toolkit==3.0.47 +protobuf==5.28.2 +psutil==6.0.0 ptyprocess==0.7.0 -pure-eval==0.2.2 +pure_eval==0.2.2 py-cpuinfo==9.0.0 -pyasn1==0.5.1 -pyasn1-modules==0.3.0 -pycparser==2.21 -Pygments==2.17.2 -pyparsing==3.1.1 -pyproject_hooks==1.0.0 +pyasn1==0.6.0 +pyasn1_modules==0.4.0 +pybind11==2.13.5 +pycares==4.4.0 +pycparser==2.22 +pycryptodomex==3.20.0 +pydantic==2.8.2 +pydantic_core==2.20.1 +Pygments==2.18.0 +pyparsing==3.1.2 PySocks==1.7.1 -python-dateutil==2.8.2 +pyspnego==0.10.2 +pytest==8.3.2 +python-dateutil==2.9.0.post0 python-json-logger==2.0.7 -pytz==2023.3.post1 -PyYAML==6.0.1 -pyzmq==25.1.2 -qtconsole==5.5.1 -QtPy==2.4.1 -referencing==0.32.1 -regex==2023.12.25 -requests==2.31.0 -requests-oauthlib==1.3.1 -rfc3339-validator==0.1.4 -rfc3986-validator==0.1.1 -rpds-py==0.17.1 +python-socks==2.4.4 +pytz==2024.2 +PyYAML==6.0.2 +pyzmq==26.0.3 +referencing==0.34.0 +regex==2024.5.15 +requests==2.32.3 +requests-oauthlib==2.0.0 +rfc3339_validator==0.1.4 +rfc3986_validator==0.1.1 +rpds-py==0.18.1 rsa==4.9 -scikit-learn==1.4.0 -scipy==1.11.4 -seaborn==0.13.1 +scikit-learn==1.5.0 +scipy==1.14.0 +seaborn==0.13.2 +SecretStorage==3.3.3 Send2Trash==1.8.2 +setuptools==72.1.0.post0 six==1.16.0 -smmap==5.0.1 -sniffio==1.3.0 +smbprotocol==1.12.0 +smmap==6.0.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 soupsieve==2.5 -stack-data==0.6.3 -sympy==1.12 -tensorboard==2.15.1 +stack_data==0.6.3 +sympy==1.12.1 +tensorboard==2.17.0 tensorboard-data-server==0.7.2 -terminado==0.18.0 -thop==0.1.1.post2209072238 -threadpoolctl==3.2.0 -tinycss2==1.2.1 -tomli==2.0.1 -torch==2.1.2 -torchvision==0.16.2 -tornado==6.4 -tqdm==4.66.1 -traitlets==5.14.1 -types-python-dateutil==2.8.19.20240106 -typing_extensions==4.9.0 -tzdata==2023.4 -ultralytics==8.1.3 +tensorboard-plugin-profile==2.11.1 +tensorboard-plugin-wit==1.7.0 +terminado==0.18.1 +threadpoolctl==3.4.0 +tinycss2==1.3.0 +toml==0.10.2 +torch==2.5.0 +torchvision==0.20.0a0 +tornado==6.4.1 +tqdm==4.66.4 +traitlets==5.14.3 +trio==0.25.0 +triton==3.1.0 +typeguard==4.3.0 +types-python-dateutil==2.9.0.20240316 +typing_extensions==4.12.2 +typing_inspect==0.9.0 +tzdata==2024.1 +ultralytics==8.3.13 +ultralytics-thop==2.0.9 uri-template==1.3.0 -urllib3==2.1.0 -virtualenv==20.25.0 +urllib3==2.2.2 wcwidth==0.2.13 webcolors==1.13 webencodings==0.5.1 -websocket-client==1.7.0 -Werkzeug==3.0.1 -widgetsnbextension==4.0.9 +websocket-client==1.8.0 +websockets==12.0 +Werkzeug==3.0.4 +wheel==0.43.0 +widgetsnbextension==4.0.13 +wrapt==1.16.0 yacs==0.1.8 -youtube-dl==2021.12.17 +yarl==1.9.4 +yt-dlp==2024.10.22