pixeltable 0.2.14__tar.gz → 0.2.15__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pixeltable might be problematic. Click here for more details.
- {pixeltable-0.2.14 → pixeltable-0.2.15}/PKG-INFO +2 -2
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/__version__.py +2 -2
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/column.py +3 -3
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/table.py +3 -5
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/env.py +4 -4
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/arithmetic_expr.py +41 -16
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/expr.py +37 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/function_call.py +5 -1
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/globals.py +3 -1
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/inline_array.py +2 -2
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/__init__.py +1 -1
- pixeltable-0.2.15/pixeltable/functions/json.py +46 -0
- pixeltable-0.2.14/pixeltable/functions/eval.py → pixeltable-0.2.15/pixeltable/functions/vision.py +170 -27
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/io/external_store.py +2 -2
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/io/globals.py +1 -1
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/io/pandas.py +34 -8
- pixeltable-0.2.15/pixeltable/iterators/video.py +128 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/store.py +65 -28
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/tool/create_test_db_dump.py +5 -5
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/type_system.py +73 -53
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pyproject.toml +2 -2
- pixeltable-0.2.14/pixeltable/iterators/video.py +0 -96
- {pixeltable-0.2.14 → pixeltable-0.2.15}/LICENSE +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/README.md +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/__init__.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/__init__.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/catalog.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/dir.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/globals.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/insertable_table.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/named_function.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/path.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/path_dict.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/schema_object.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/table_version.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/table_version_path.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/view.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/dataframe.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exceptions.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/__init__.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/aggregation_node.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/cache_prefetch_node.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/component_iteration_node.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/data_row_batch.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/exec_context.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/exec_node.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/expr_eval_node.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/in_memory_data_node.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/media_validation_node.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/row_update_node.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/sql_node.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/__init__.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/array_slice.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/column_property_ref.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/column_ref.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/comparison.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/compound_predicate.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/data_row.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/expr_set.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/in_predicate.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/inline_dict.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/is_null.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/json_mapper.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/json_path.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/literal.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/method_ref.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/object_ref.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/row_builder.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/rowid_ref.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/similarity_expr.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/type_cast.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/variable.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/ext/__init__.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/ext/functions/__init__.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/ext/functions/whisperx.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/ext/functions/yolox.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/func/__init__.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/func/aggregate_function.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/func/callable_function.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/func/expr_template_function.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/func/function.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/func/function_registry.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/func/globals.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/func/query_template_function.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/func/signature.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/func/udf.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/fireworks.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/globals.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/huggingface.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/image.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/openai.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/string.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/timestamp.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/together.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/util.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/video.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/whisper.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/globals.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/index/__init__.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/index/base.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/index/btree.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/index/embedding_index.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/io/__init__.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/io/hf_datasets.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/io/label_studio.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/io/parquet.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/iterators/__init__.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/iterators/base.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/iterators/document.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/iterators/string.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/__init__.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/converters/convert_10.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/converters/convert_12.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/converters/convert_13.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/converters/convert_14.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/converters/convert_15.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/converters/convert_16.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/converters/convert_17.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/converters/convert_18.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/converters/util.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/notes.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/schema.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/plan.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/tool/create_test_video.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/tool/doc_plugins/griffe.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/tool/doc_plugins/mkdocstrings.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/tool/doc_plugins/templates/material/udf.html.jinja +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/tool/embed_udf.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/__init__.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/arrow.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/coco.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/code.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/documents.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/filecache.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/formatter.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/help.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/http_server.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/media_store.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/pytorch.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/s3.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/sql.py +0 -0
- {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/transactional_directory.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pixeltable
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.15
|
|
4
4
|
Summary: Pixeltable: The Multimodal AI Data Plane
|
|
5
5
|
Author: Pixeltable, Inc.
|
|
6
6
|
Author-email: contact@pixeltable.com
|
|
@@ -21,9 +21,9 @@ Requires-Dist: more-itertools (>=10.2,<11.0)
|
|
|
21
21
|
Requires-Dist: numpy (>=1.25)
|
|
22
22
|
Requires-Dist: opencv-python-headless (>=4.7.0.68,<5.0.0.0)
|
|
23
23
|
Requires-Dist: pandas (>=2.0,<3.0)
|
|
24
|
-
Requires-Dist: pgserver (==0.1.4)
|
|
25
24
|
Requires-Dist: pgvector (>=0.2.1,<0.3.0)
|
|
26
25
|
Requires-Dist: pillow (>=9.3.0)
|
|
26
|
+
Requires-Dist: pixeltable-pgserver (==0.2.4)
|
|
27
27
|
Requires-Dist: psutil (>=5.9.5,<6.0.0)
|
|
28
28
|
Requires-Dist: psycopg2-binary (>=2.9.5,<3.0.0)
|
|
29
29
|
Requires-Dist: pymupdf (>=1.24.1,<2.0.0)
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
# These version placeholders will be replaced during build.
|
|
2
|
-
__version__ = "0.2.
|
|
3
|
-
__version_tuple__ = (0, 2,
|
|
2
|
+
__version__ = "0.2.15"
|
|
3
|
+
__version_tuple__ = (0, 2, 15)
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from typing import
|
|
5
|
-
from uuid import UUID
|
|
4
|
+
from typing import Any, Callable, Optional, Union
|
|
6
5
|
|
|
7
6
|
import sqlalchemy as sql
|
|
8
7
|
|
|
9
8
|
import pixeltable.exceptions as excs
|
|
10
9
|
import pixeltable.type_system as ts
|
|
10
|
+
|
|
11
11
|
from .globals import is_valid_identifier
|
|
12
12
|
|
|
13
13
|
_logger = logging.getLogger('pixeltable')
|
|
@@ -21,7 +21,7 @@ class Column:
|
|
|
21
21
|
def __init__(
|
|
22
22
|
self, name: Optional[str], col_type: Optional[ts.ColumnType] = None,
|
|
23
23
|
computed_with: Optional[Union['Expr', Callable]] = None,
|
|
24
|
-
is_pk: bool = False, stored:
|
|
24
|
+
is_pk: bool = False, stored: bool = True,
|
|
25
25
|
col_id: Optional[int] = None, schema_version_add: Optional[int] = None,
|
|
26
26
|
schema_version_drop: Optional[int] = None, sa_col_type: Optional[sql.sqltypes.TypeEngine] = None,
|
|
27
27
|
records_errors: Optional[bool] = None, value_expr_dict: Optional[dict[str, Any]] = None,
|
|
@@ -434,8 +434,8 @@ class Table(SchemaObject):
|
|
|
434
434
|
for name, spec in schema.items():
|
|
435
435
|
col_type: Optional[ts.ColumnType] = None
|
|
436
436
|
value_expr: Optional[exprs.Expr] = None
|
|
437
|
-
stored: Optional[bool] = None
|
|
438
437
|
primary_key: Optional[bool] = None
|
|
438
|
+
stored = True
|
|
439
439
|
|
|
440
440
|
if isinstance(spec, ts.ColumnType):
|
|
441
441
|
# TODO: create copy
|
|
@@ -455,7 +455,7 @@ class Table(SchemaObject):
|
|
|
455
455
|
if value_expr is not None and isinstance(value_expr, exprs.Expr):
|
|
456
456
|
# create copy so we can modify it
|
|
457
457
|
value_expr = value_expr.copy()
|
|
458
|
-
stored = spec.get('stored')
|
|
458
|
+
stored = spec.get('stored', True)
|
|
459
459
|
primary_key = spec.get('primary_key')
|
|
460
460
|
|
|
461
461
|
column = Column(
|
|
@@ -478,12 +478,10 @@ class Table(SchemaObject):
|
|
|
478
478
|
raise excs.Error(f'Column name conflicts with a registered query: {col.name!r}')
|
|
479
479
|
if col.stored is False and not (col.is_computed and col.col_type.is_image_type()):
|
|
480
480
|
raise excs.Error(f'Column {col.name!r}: stored={col.stored} only applies to computed image columns')
|
|
481
|
-
if col.stored is False and
|
|
481
|
+
if col.stored is False and col.has_window_fn_call():
|
|
482
482
|
raise excs.Error((
|
|
483
483
|
f'Column {col.name!r}: stored={col.stored} is not valid for image columns computed with a streaming '
|
|
484
484
|
f'function'))
|
|
485
|
-
if col.stored is None:
|
|
486
|
-
col.stored = not (col.is_computed and col.col_type.is_image_type() and not col.has_window_fn_call())
|
|
487
485
|
|
|
488
486
|
@classmethod
|
|
489
487
|
def _verify_schema(cls, schema: list[Column]) -> None:
|
|
@@ -16,7 +16,7 @@ from dataclasses import dataclass
|
|
|
16
16
|
from pathlib import Path
|
|
17
17
|
from typing import Callable, Optional, Dict, Any, List, TYPE_CHECKING
|
|
18
18
|
|
|
19
|
-
import
|
|
19
|
+
import pixeltable_pgserver
|
|
20
20
|
import sqlalchemy as sql
|
|
21
21
|
import yaml
|
|
22
22
|
from tqdm import TqdmWarning
|
|
@@ -60,7 +60,7 @@ class Env:
|
|
|
60
60
|
self._sa_engine: Optional[sql.engine.base.Engine] = None
|
|
61
61
|
self._pgdata_dir: Optional[Path] = None
|
|
62
62
|
self._db_name: Optional[str] = None
|
|
63
|
-
self._db_server: Optional[
|
|
63
|
+
self._db_server: Optional[pixeltable_pgserver.PostgresServer] = None
|
|
64
64
|
self._db_url: Optional[str] = None
|
|
65
65
|
|
|
66
66
|
# info about installed packages that are utilized by some parts of the code;
|
|
@@ -266,8 +266,8 @@ class Env:
|
|
|
266
266
|
self._db_name = os.environ.get('PIXELTABLE_DB', 'pixeltable')
|
|
267
267
|
self._pgdata_dir = Path(os.environ.get('PIXELTABLE_PGDATA', str(self._home / 'pgdata')))
|
|
268
268
|
|
|
269
|
-
# in
|
|
270
|
-
self._db_server =
|
|
269
|
+
# in pixeltable_pgserver.get_server(): cleanup_mode=None will leave db on for debugging purposes
|
|
270
|
+
self._db_server = pixeltable_pgserver.get_server(self._pgdata_dir, cleanup_mode=None)
|
|
271
271
|
self._db_url = self._db_server.get_uri(database=self._db_name)
|
|
272
272
|
|
|
273
273
|
if reinit_db:
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
3
4
|
|
|
4
5
|
import sqlalchemy as sql
|
|
5
6
|
|
|
6
|
-
from .globals import ArithmeticOperator
|
|
7
|
-
from .expr import Expr
|
|
8
|
-
from .data_row import DataRow
|
|
9
|
-
from .row_builder import RowBuilder
|
|
10
7
|
import pixeltable.exceptions as excs
|
|
11
|
-
import pixeltable.catalog as catalog
|
|
12
8
|
import pixeltable.type_system as ts
|
|
13
9
|
|
|
10
|
+
from .data_row import DataRow
|
|
11
|
+
from .expr import Expr
|
|
12
|
+
from .globals import ArithmeticOperator
|
|
13
|
+
from .row_builder import RowBuilder
|
|
14
|
+
|
|
14
15
|
|
|
15
16
|
class ArithmeticExpr(Expr):
|
|
16
17
|
"""
|
|
17
18
|
Allows arithmetic exprs on json paths
|
|
18
19
|
"""
|
|
19
20
|
def __init__(self, operator: ArithmeticOperator, op1: Expr, op2: Expr):
|
|
20
|
-
|
|
21
|
-
if op1.col_type.is_json_type() or op2.col_type.is_json_type():
|
|
21
|
+
if op1.col_type.is_json_type() or op2.col_type.is_json_type() or operator == ArithmeticOperator.DIV:
|
|
22
22
|
# we assume it's a float
|
|
23
|
-
super().__init__(ts.FloatType())
|
|
23
|
+
super().__init__(ts.FloatType(nullable=(op1.col_type.nullable or op2.col_type.nullable)))
|
|
24
24
|
else:
|
|
25
|
-
super().__init__(
|
|
25
|
+
super().__init__(op1.col_type.supertype(op2.col_type))
|
|
26
26
|
self.operator = operator
|
|
27
27
|
self.components = [op1, op2]
|
|
28
28
|
|
|
@@ -43,7 +43,7 @@ class ArithmeticExpr(Expr):
|
|
|
43
43
|
def _equals(self, other: ArithmeticExpr) -> bool:
|
|
44
44
|
return self.operator == other.operator
|
|
45
45
|
|
|
46
|
-
def _id_attrs(self) ->
|
|
46
|
+
def _id_attrs(self) -> list[tuple[str, Any]]:
|
|
47
47
|
return super()._id_attrs() + [('operator', self.operator.value)]
|
|
48
48
|
|
|
49
49
|
@property
|
|
@@ -55,6 +55,7 @@ class ArithmeticExpr(Expr):
|
|
|
55
55
|
return self.components[1]
|
|
56
56
|
|
|
57
57
|
def sql_expr(self) -> Optional[sql.ClauseElement]:
|
|
58
|
+
assert self.col_type.is_int_type() or self.col_type.is_float_type() or self.col_type.is_json_type()
|
|
58
59
|
left = self._op1.sql_expr()
|
|
59
60
|
right = self._op2.sql_expr()
|
|
60
61
|
if left is None or right is None:
|
|
@@ -66,14 +67,31 @@ class ArithmeticExpr(Expr):
|
|
|
66
67
|
if self.operator == ArithmeticOperator.MUL:
|
|
67
68
|
return left * right
|
|
68
69
|
if self.operator == ArithmeticOperator.DIV:
|
|
69
|
-
|
|
70
|
+
assert self.col_type.is_float_type()
|
|
71
|
+
# We have to cast to a `float`, or else we'll get a `Decimal`
|
|
72
|
+
return sql.sql.expression.cast(left / right, sql.Float)
|
|
70
73
|
if self.operator == ArithmeticOperator.MOD:
|
|
71
|
-
|
|
74
|
+
if self.col_type.is_int_type():
|
|
75
|
+
return left % right
|
|
76
|
+
if self.col_type.is_float_type():
|
|
77
|
+
# Postgres does not support modulus for floats
|
|
78
|
+
return None
|
|
79
|
+
if self.operator == ArithmeticOperator.FLOORDIV:
|
|
80
|
+
# Postgres has a DIV operator, but it behaves differently from Python's // operator
|
|
81
|
+
# (Postgres rounds toward 0, Python rounds toward negative infinity)
|
|
82
|
+
# We need the behavior to be consistent, so that expressions will evaluate the same way
|
|
83
|
+
# whether or not their operands can be translated to SQL. These SQL clauses should
|
|
84
|
+
# mimic the behavior of Python's // operator.
|
|
85
|
+
if self.col_type.is_int_type():
|
|
86
|
+
return sql.sql.expression.cast(sql.func.floor(left / right), sql.Integer)
|
|
87
|
+
if self.col_type.is_float_type():
|
|
88
|
+
return sql.sql.expression.cast(sql.func.floor(left / right), sql.Float)
|
|
72
89
|
|
|
73
90
|
def eval(self, data_row: DataRow, row_builder: RowBuilder) -> None:
|
|
74
91
|
op1_val = data_row[self._op1.slot_idx]
|
|
75
92
|
op2_val = data_row[self._op2.slot_idx]
|
|
76
|
-
|
|
93
|
+
|
|
94
|
+
# if one or both columns is JsonTyped, we need a dynamic check that they are numeric
|
|
77
95
|
if self._op1.col_type.is_json_type() and not isinstance(op1_val, int) and not isinstance(op1_val, float):
|
|
78
96
|
raise excs.Error(
|
|
79
97
|
f'{self.operator} requires numeric type, but {self._op1} has type {type(op1_val).__name__}')
|
|
@@ -81,6 +99,11 @@ class ArithmeticExpr(Expr):
|
|
|
81
99
|
raise excs.Error(
|
|
82
100
|
f'{self.operator} requires numeric type, but {self._op2} has type {type(op2_val).__name__}')
|
|
83
101
|
|
|
102
|
+
# if either operand is None, always return None
|
|
103
|
+
if op1_val is None or op2_val is None:
|
|
104
|
+
data_row[self.slot_idx] = None
|
|
105
|
+
return
|
|
106
|
+
|
|
84
107
|
if self.operator == ArithmeticOperator.ADD:
|
|
85
108
|
data_row[self.slot_idx] = op1_val + op2_val
|
|
86
109
|
elif self.operator == ArithmeticOperator.SUB:
|
|
@@ -91,12 +114,14 @@ class ArithmeticExpr(Expr):
|
|
|
91
114
|
data_row[self.slot_idx] = op1_val / op2_val
|
|
92
115
|
elif self.operator == ArithmeticOperator.MOD:
|
|
93
116
|
data_row[self.slot_idx] = op1_val % op2_val
|
|
117
|
+
elif self.operator == ArithmeticOperator.FLOORDIV:
|
|
118
|
+
data_row[self.slot_idx] = op1_val // op2_val
|
|
94
119
|
|
|
95
|
-
def _as_dict(self) ->
|
|
120
|
+
def _as_dict(self) -> dict:
|
|
96
121
|
return {'operator': self.operator.value, **super()._as_dict()}
|
|
97
122
|
|
|
98
123
|
@classmethod
|
|
99
|
-
def _from_dict(cls, d:
|
|
124
|
+
def _from_dict(cls, d: dict, components: list[Expr]) -> Expr:
|
|
100
125
|
assert 'operator' in d
|
|
101
126
|
assert len(components) == 2
|
|
102
127
|
return cls(ArithmeticOperator(d['operator']), components[0], components[1])
|
|
@@ -503,6 +503,9 @@ class Expr(abc.ABC):
|
|
|
503
503
|
return Comparison(op, self, Literal(other)) # type: ignore[arg-type]
|
|
504
504
|
raise TypeError(f'Other must be Expr or literal: {type(other)}')
|
|
505
505
|
|
|
506
|
+
def __neg__(self) -> 'pixeltable.exprs.ArithmeticExpr':
|
|
507
|
+
return self._make_arithmetic_expr(ArithmeticOperator.MUL, -1)
|
|
508
|
+
|
|
506
509
|
def __add__(self, other: object) -> 'pixeltable.exprs.ArithmeticExpr':
|
|
507
510
|
return self._make_arithmetic_expr(ArithmeticOperator.ADD, other)
|
|
508
511
|
|
|
@@ -518,6 +521,27 @@ class Expr(abc.ABC):
|
|
|
518
521
|
def __mod__(self, other: object) -> 'pixeltable.exprs.ArithmeticExpr':
|
|
519
522
|
return self._make_arithmetic_expr(ArithmeticOperator.MOD, other)
|
|
520
523
|
|
|
524
|
+
def __floordiv__(self, other: object) -> 'pixeltable.exprs.ArithmeticExpr':
|
|
525
|
+
return self._make_arithmetic_expr(ArithmeticOperator.FLOORDIV, other)
|
|
526
|
+
|
|
527
|
+
def __radd__(self, other: object) -> 'pixeltable.exprs.ArithmeticExpr':
|
|
528
|
+
return self._rmake_arithmetic_expr(ArithmeticOperator.ADD, other)
|
|
529
|
+
|
|
530
|
+
def __rsub__(self, other: object) -> 'pixeltable.exprs.ArithmeticExpr':
|
|
531
|
+
return self._rmake_arithmetic_expr(ArithmeticOperator.SUB, other)
|
|
532
|
+
|
|
533
|
+
def __rmul__(self, other: object) -> 'pixeltable.exprs.ArithmeticExpr':
|
|
534
|
+
return self._rmake_arithmetic_expr(ArithmeticOperator.MUL, other)
|
|
535
|
+
|
|
536
|
+
def __rtruediv__(self, other: object) -> 'pixeltable.exprs.ArithmeticExpr':
|
|
537
|
+
return self._rmake_arithmetic_expr(ArithmeticOperator.DIV, other)
|
|
538
|
+
|
|
539
|
+
def __rmod__(self, other: object) -> 'pixeltable.exprs.ArithmeticExpr':
|
|
540
|
+
return self._rmake_arithmetic_expr(ArithmeticOperator.MOD, other)
|
|
541
|
+
|
|
542
|
+
def __rfloordiv__(self, other: object) -> 'pixeltable.exprs.ArithmeticExpr':
|
|
543
|
+
return self._rmake_arithmetic_expr(ArithmeticOperator.FLOORDIV, other)
|
|
544
|
+
|
|
521
545
|
def _make_arithmetic_expr(self, op: ArithmeticOperator, other: object) -> 'pixeltable.exprs.ArithmeticExpr':
|
|
522
546
|
"""
|
|
523
547
|
other: Union[Expr, LiteralPythonTypes]
|
|
@@ -531,6 +555,19 @@ class Expr(abc.ABC):
|
|
|
531
555
|
return ArithmeticExpr(op, self, Literal(other)) # type: ignore[arg-type]
|
|
532
556
|
raise TypeError(f'Other must be Expr or literal: {type(other)}')
|
|
533
557
|
|
|
558
|
+
def _rmake_arithmetic_expr(self, op: ArithmeticOperator, other: object) -> 'pixeltable.exprs.ArithmeticExpr':
|
|
559
|
+
"""
|
|
560
|
+
Right-handed version of _make_arithmetic_expr. other must be a literal; if it were an Expr,
|
|
561
|
+
the operation would have already been evaluated in its left-handed form.
|
|
562
|
+
"""
|
|
563
|
+
# TODO: check for compatibility
|
|
564
|
+
from .arithmetic_expr import ArithmeticExpr
|
|
565
|
+
from .literal import Literal
|
|
566
|
+
assert not isinstance(other, Expr) # Else the left-handed form would have evaluated first
|
|
567
|
+
if isinstance(other, typing.get_args(LiteralPythonTypes)):
|
|
568
|
+
return ArithmeticExpr(op, Literal(other), self) # type: ignore[arg-type]
|
|
569
|
+
raise TypeError(f'Other must be Expr or literal: {type(other)}')
|
|
570
|
+
|
|
534
571
|
def __and__(self, other: object) -> Expr:
|
|
535
572
|
if not isinstance(other, Expr):
|
|
536
573
|
raise TypeError(f'Other needs to be an expression: {type(other)}')
|
|
@@ -200,7 +200,11 @@ class FunctionCall(Expr):
|
|
|
200
200
|
if is_var_param:
|
|
201
201
|
pass
|
|
202
202
|
else:
|
|
203
|
-
|
|
203
|
+
assert param.col_type is not None
|
|
204
|
+
# Check that the argument is consistent with the expected parameter type, with the allowance that
|
|
205
|
+
# non-nullable parameters can still accept nullable arguments (since function calls with Nones
|
|
206
|
+
# assigned to non-nullable parameters will always return None)
|
|
207
|
+
if not param.col_type.is_supertype_of(arg.col_type, ignore_nullable=True):
|
|
204
208
|
raise excs.Error(
|
|
205
209
|
f'Parameter {param_name}: argument type {arg.col_type} does not match parameter type '
|
|
206
210
|
f'{param.col_type}')
|
|
@@ -68,6 +68,7 @@ class ArithmeticOperator(enum.Enum):
|
|
|
68
68
|
MUL = 2
|
|
69
69
|
DIV = 3
|
|
70
70
|
MOD = 4
|
|
71
|
+
FLOORDIV = 5
|
|
71
72
|
|
|
72
73
|
def __str__(self) -> str:
|
|
73
74
|
if self == self.ADD:
|
|
@@ -80,4 +81,5 @@ class ArithmeticOperator(enum.Enum):
|
|
|
80
81
|
return '/'
|
|
81
82
|
if self == self.MOD:
|
|
82
83
|
return '%'
|
|
83
|
-
|
|
84
|
+
if self == self.FLOORDIV:
|
|
85
|
+
return '//'
|
|
@@ -51,9 +51,9 @@ class InlineArray(Expr):
|
|
|
51
51
|
# try to infer the element type
|
|
52
52
|
for idx, val in self.elements:
|
|
53
53
|
if idx is not None:
|
|
54
|
-
inferred_element_type =
|
|
54
|
+
inferred_element_type = inferred_element_type.supertype(self.components[idx].col_type)
|
|
55
55
|
else:
|
|
56
|
-
inferred_element_type =
|
|
56
|
+
inferred_element_type = inferred_element_type.supertype(ts.ColumnType.infer_literal_type(val))
|
|
57
57
|
if inferred_element_type is None:
|
|
58
58
|
break
|
|
59
59
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from . import fireworks, huggingface, image, openai, string, together, video, timestamp
|
|
1
|
+
from . import fireworks, huggingface, image, openai, string, together, video, timestamp, json, vision
|
|
2
2
|
from .globals import *
|
|
3
3
|
from pixeltable.utils.code import local_public_names
|
|
4
4
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pixeltable [UDFs](https://pixeltable.readme.io/docs/user-defined-functions-udfs) for `JsonType`.
|
|
3
|
+
|
|
4
|
+
Example:
|
|
5
|
+
```python
|
|
6
|
+
import pixeltable as pxt
|
|
7
|
+
|
|
8
|
+
t = pxt.get_table(...)
|
|
9
|
+
t.select(pxt.functions.json.make_list()).collect()
|
|
10
|
+
```
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
import pixeltable.func as func
|
|
16
|
+
import pixeltable.type_system as ts
|
|
17
|
+
from pixeltable.utils.code import local_public_names
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@func.uda(
|
|
21
|
+
update_types=[ts.JsonType(nullable=True)],
|
|
22
|
+
value_type=ts.JsonType(),
|
|
23
|
+
requires_order_by=False,
|
|
24
|
+
allows_window=False,
|
|
25
|
+
)
|
|
26
|
+
class make_list(func.Aggregator):
|
|
27
|
+
"""
|
|
28
|
+
Collects arguments into a list.
|
|
29
|
+
"""
|
|
30
|
+
def __init__(self):
|
|
31
|
+
self.output: list[Any] = []
|
|
32
|
+
|
|
33
|
+
def update(self, obj: Any) -> None:
|
|
34
|
+
if obj is None:
|
|
35
|
+
return
|
|
36
|
+
self.output.append(obj)
|
|
37
|
+
|
|
38
|
+
def value(self) -> list[Any]:
|
|
39
|
+
return self.output
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
__all__ = local_public_names(__name__)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def __dir__():
|
|
46
|
+
return __all__
|
pixeltable-0.2.14/pixeltable/functions/eval.py → pixeltable-0.2.15/pixeltable/functions/vision.py
RENAMED
|
@@ -1,11 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
"""
|
|
2
|
+
Pixeltable [UDFs](https://pixeltable.readme.io/docs/user-defined-functions-udfs) for Computer Vision.
|
|
3
|
+
|
|
4
|
+
Example:
|
|
5
|
+
```python
|
|
6
|
+
import pixeltable as pxt
|
|
7
|
+
from pixeltable.functions import vision as pxtv
|
|
8
|
+
|
|
9
|
+
t = pxt.get_table(...)
|
|
10
|
+
t.select(pxtv.draw_bounding_boxes(t.img, boxes=t.boxes, label=t.labels)).collect()
|
|
11
|
+
```
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import colorsys
|
|
15
|
+
import hashlib
|
|
16
|
+
import random
|
|
2
17
|
from collections import defaultdict
|
|
3
|
-
import
|
|
18
|
+
from typing import Optional, Union, Any
|
|
4
19
|
|
|
20
|
+
import PIL.Image
|
|
21
|
+
import PIL.Image
|
|
5
22
|
import numpy as np
|
|
6
23
|
|
|
7
|
-
import pixeltable.type_system as ts
|
|
8
24
|
import pixeltable.func as func
|
|
25
|
+
import pixeltable.type_system as ts
|
|
26
|
+
from pixeltable.utils.code import local_public_names
|
|
9
27
|
|
|
10
28
|
|
|
11
29
|
# TODO: figure out a better submodule structure
|
|
@@ -14,7 +32,7 @@ import pixeltable.func as func
|
|
|
14
32
|
# the following function has been adapted from MMEval
|
|
15
33
|
# (sources at https://github.com/open-mmlab/mmeval)
|
|
16
34
|
# Copyright (c) OpenMMLab. All rights reserved.
|
|
17
|
-
def
|
|
35
|
+
def __calculate_bboxes_area(bboxes: np.ndarray) -> np.ndarray:
|
|
18
36
|
"""Calculate area of bounding boxes.
|
|
19
37
|
|
|
20
38
|
Args:
|
|
@@ -31,7 +49,7 @@ def calculate_bboxes_area(bboxes: np.ndarray) -> np.ndarray:
|
|
|
31
49
|
# the following function has been adapted from MMEval
|
|
32
50
|
# (sources at https://github.com/open-mmlab/mmeval)
|
|
33
51
|
# Copyright (c) OpenMMLab. All rights reserved.
|
|
34
|
-
def
|
|
52
|
+
def __calculate_overlaps(bboxes1: np.ndarray, bboxes2: np.ndarray) -> np.ndarray:
|
|
35
53
|
"""Calculate the overlap between each bbox of bboxes1 and bboxes2.
|
|
36
54
|
|
|
37
55
|
Args:
|
|
@@ -58,8 +76,8 @@ def calculate_overlaps(bboxes1: np.ndarray, bboxes2: np.ndarray) -> np.ndarray:
|
|
|
58
76
|
exchange = False
|
|
59
77
|
|
|
60
78
|
# Calculate the bboxes area.
|
|
61
|
-
area1 =
|
|
62
|
-
area2 =
|
|
79
|
+
area1 = __calculate_bboxes_area(bboxes1)
|
|
80
|
+
area2 = __calculate_bboxes_area(bboxes2)
|
|
63
81
|
eps = np.finfo(np.float32).eps
|
|
64
82
|
|
|
65
83
|
for i in range(bboxes1.shape[0]):
|
|
@@ -80,9 +98,9 @@ def calculate_overlaps(bboxes1: np.ndarray, bboxes2: np.ndarray) -> np.ndarray:
|
|
|
80
98
|
# the following function has been adapted from MMEval
|
|
81
99
|
# (sources at https://github.com/open-mmlab/mmeval)
|
|
82
100
|
# Copyright (c) OpenMMLab. All rights reserved.
|
|
83
|
-
def
|
|
101
|
+
def __calculate_image_tpfp(
|
|
84
102
|
pred_bboxes: np.ndarray, pred_scores: np.ndarray, gt_bboxes: np.ndarray, min_iou: float
|
|
85
|
-
) ->
|
|
103
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
86
104
|
"""Calculate the true positive and false positive on an image.
|
|
87
105
|
|
|
88
106
|
Args:
|
|
@@ -95,11 +113,8 @@ def calculate_image_tpfp(
|
|
|
95
113
|
|
|
96
114
|
Returns:
|
|
97
115
|
tuple (tp, fp):
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
the true positive flag of each predicted bbox on this image.
|
|
101
|
-
- fp (numpy.ndarray): Shape (N,),
|
|
102
|
-
the false positive flag of each predicted bbox on this image.
|
|
116
|
+
tp: Shape (N,), the true positive flag of each predicted bbox on this image.
|
|
117
|
+
fp: Shape (N,), the false positive flag of each predicted bbox on this image.
|
|
103
118
|
"""
|
|
104
119
|
# Step 1. Concatenate `gt_bboxes` and `ignore_gt_bboxes`, then set
|
|
105
120
|
# the `ignore_gt_flags`.
|
|
@@ -121,7 +136,7 @@ def calculate_image_tpfp(
|
|
|
121
136
|
|
|
122
137
|
# Step 4. Calculate the IoUs between the predicted bboxes and the
|
|
123
138
|
# ground truth bboxes.
|
|
124
|
-
ious =
|
|
139
|
+
ious = __calculate_overlaps(pred_bboxes, gt_bboxes)
|
|
125
140
|
# For each pred bbox, the max iou with all gts.
|
|
126
141
|
ious_max = ious.max(axis=1)
|
|
127
142
|
# For each pred bbox, which gt overlaps most with it.
|
|
@@ -160,14 +175,17 @@ def calculate_image_tpfp(
|
|
|
160
175
|
],
|
|
161
176
|
)
|
|
162
177
|
def eval_detections(
|
|
163
|
-
pred_bboxes:
|
|
164
|
-
pred_labels:
|
|
165
|
-
pred_scores:
|
|
166
|
-
gt_bboxes:
|
|
167
|
-
gt_labels:
|
|
168
|
-
) ->
|
|
178
|
+
pred_bboxes: list[list[int]],
|
|
179
|
+
pred_labels: list[int],
|
|
180
|
+
pred_scores: list[float],
|
|
181
|
+
gt_bboxes: list[list[int]],
|
|
182
|
+
gt_labels: list[int],
|
|
183
|
+
) -> dict:
|
|
184
|
+
"""
|
|
185
|
+
Evaluates the performance of a set of predicted bounding boxes against a set of ground truth bounding boxes.
|
|
186
|
+
"""
|
|
169
187
|
class_idxs = list(set(pred_labels + gt_labels))
|
|
170
|
-
result:
|
|
188
|
+
result: list[dict] = []
|
|
171
189
|
pred_bboxes_arr = np.asarray(pred_bboxes)
|
|
172
190
|
pred_classes_arr = np.asarray(pred_labels)
|
|
173
191
|
pred_scores_arr = np.asarray(pred_scores)
|
|
@@ -177,7 +195,7 @@ def eval_detections(
|
|
|
177
195
|
pred_filter = pred_classes_arr == class_idx
|
|
178
196
|
gt_filter = gt_classes_arr == class_idx
|
|
179
197
|
class_pred_scores = pred_scores_arr[pred_filter]
|
|
180
|
-
tp, fp =
|
|
198
|
+
tp, fp = __calculate_image_tpfp(pred_bboxes_arr[pred_filter], class_pred_scores, gt_bboxes_arr[gt_filter], [0.5])
|
|
181
199
|
ordered_class_pred_scores = -np.sort(-class_pred_scores)
|
|
182
200
|
result.append(
|
|
183
201
|
{
|
|
@@ -194,17 +212,21 @@ def eval_detections(
|
|
|
194
212
|
|
|
195
213
|
@func.uda(update_types=[ts.JsonType()], value_type=ts.JsonType(), allows_std_agg=True, allows_window=False)
|
|
196
214
|
class mean_ap(func.Aggregator):
|
|
215
|
+
"""
|
|
216
|
+
Calculates the mean average precision (mAP) over
|
|
217
|
+
[`eval_detections()`][pixeltable.functions.vision.eval_detections] results.
|
|
218
|
+
"""
|
|
197
219
|
def __init__(self):
|
|
198
|
-
self.class_tpfp:
|
|
220
|
+
self.class_tpfp: dict[int, list[dict]] = defaultdict(list)
|
|
199
221
|
|
|
200
|
-
def update(self, eval_dicts:
|
|
222
|
+
def update(self, eval_dicts: list[dict]) -> None:
|
|
201
223
|
for eval_dict in eval_dicts:
|
|
202
224
|
class_idx = eval_dict['class']
|
|
203
225
|
self.class_tpfp[class_idx].append(eval_dict)
|
|
204
226
|
|
|
205
|
-
def value(self) ->
|
|
227
|
+
def value(self) -> dict:
|
|
206
228
|
eps = np.finfo(np.float32).eps
|
|
207
|
-
result:
|
|
229
|
+
result: dict[int, float] = {}
|
|
208
230
|
for class_idx, tpfp in self.class_tpfp.items():
|
|
209
231
|
a1 = [x['tp'] for x in tpfp]
|
|
210
232
|
tp = np.concatenate([x['tp'] for x in tpfp], axis=0)
|
|
@@ -225,3 +247,124 @@ class mean_ap(func.Aggregator):
|
|
|
225
247
|
ap = np.sum((mrec[ind + 1] - mrec[ind]) * mpre[ind + 1])
|
|
226
248
|
result[class_idx] = ap.item()
|
|
227
249
|
return result
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _create_label_colors(labels: list[Any]) -> dict[Any, str]:
|
|
253
|
+
"""
|
|
254
|
+
Create random colors for labels such that a particular label always gets the same color.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
dict mapping labels to colors
|
|
258
|
+
"""
|
|
259
|
+
distinct_labels = set(labels)
|
|
260
|
+
result: dict[Any, str] = {}
|
|
261
|
+
for label in distinct_labels:
|
|
262
|
+
# consistent hash for the label
|
|
263
|
+
label_hash = int(hashlib.md5(str(label).encode()).hexdigest(), 16)
|
|
264
|
+
hue = (label_hash % 360) / 360.0
|
|
265
|
+
rgb = colorsys.hsv_to_rgb(hue, 0.7, 0.95)
|
|
266
|
+
hex_color = '#{:02x}{:02x}{:02x}'.format(int(rgb[0]*255), int(rgb[1]*255), int(rgb[2]*255))
|
|
267
|
+
result[label] = hex_color
|
|
268
|
+
return result
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
@func.udf
|
|
272
|
+
def draw_bounding_boxes(
|
|
273
|
+
img: PIL.Image.Image,
|
|
274
|
+
boxes: list[list[int]],
|
|
275
|
+
labels: Optional[list[Any]] = None,
|
|
276
|
+
color: Optional[str] = None,
|
|
277
|
+
label_colors: Optional[dict[Union[str, int], str]] = None,
|
|
278
|
+
box_colors: Optional[list[str]] = None,
|
|
279
|
+
fill: bool = False,
|
|
280
|
+
width: int = 1,
|
|
281
|
+
font: Optional[str] = None,
|
|
282
|
+
font_size: Optional[int] = None,
|
|
283
|
+
) -> PIL.Image.Image:
|
|
284
|
+
"""
|
|
285
|
+
Draws bounding boxes on the given image.
|
|
286
|
+
|
|
287
|
+
Labels can be any type that supports `str()` and is hashable (e.g., strings, ints, etc.).
|
|
288
|
+
|
|
289
|
+
Colors can be specified as common HTML color names (e.g., 'red') supported by PIL's
|
|
290
|
+
[`ImageColor`](https://pillow.readthedocs.io/en/stable/reference/ImageColor.html#imagecolor-module) module or as
|
|
291
|
+
RGB hex codes (e.g., '#FF0000').
|
|
292
|
+
|
|
293
|
+
If no colors are specified, this function randomly assigns each label a specific color based on a hash of the label.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
img: The image on which to draw the bounding boxes.
|
|
297
|
+
boxes: List of bounding boxes, each represented as [xmin, ymin, xmax, ymax].
|
|
298
|
+
labels: List of labels for each bounding box.
|
|
299
|
+
color: Single color to be used for all bounding boxes and labels.
|
|
300
|
+
label_colors: Dictionary mapping labels to colors.
|
|
301
|
+
box_colors: List of colors, one per bounding box.
|
|
302
|
+
fill: Whether to fill the bounding boxes with color.
|
|
303
|
+
width: Width of the bounding box borders.
|
|
304
|
+
font: Name of a system font or path to a TrueType font file, as required by
|
|
305
|
+
[`PIL.ImageFont.truetype()`](https://pillow.readthedocs.io/en/stable/reference/ImageFont.html#PIL.ImageFont.truetype).
|
|
306
|
+
If `None`, uses the default provided by
|
|
307
|
+
[`PIL.ImageFont.load_default()`](https://pillow.readthedocs.io/en/stable/reference/ImageFont.html#PIL.ImageFont.load_default).
|
|
308
|
+
font_size: Size of the font used for labels in points. Only used in conjunction with non-`None` `font` argument.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
The image with bounding boxes drawn on it.
|
|
312
|
+
"""
|
|
313
|
+
color_params = sum([color is not None, label_colors is not None, box_colors is not None])
|
|
314
|
+
if color_params > 1:
|
|
315
|
+
raise ValueError("Only one of 'color', 'label_colors', or 'box_colors' can be set")
|
|
316
|
+
|
|
317
|
+
# ensure the number of labels matches the number of boxes
|
|
318
|
+
num_boxes = len(boxes)
|
|
319
|
+
if labels is None:
|
|
320
|
+
labels = [None] * num_boxes
|
|
321
|
+
elif len(labels) != num_boxes:
|
|
322
|
+
raise ValueError('Number of boxes and labels must match')
|
|
323
|
+
|
|
324
|
+
DEFAULT_COLOR = 'white'
|
|
325
|
+
if box_colors is not None:
|
|
326
|
+
if len(box_colors) != num_boxes:
|
|
327
|
+
raise ValueError('Number of boxes and box colors must match')
|
|
328
|
+
else:
|
|
329
|
+
if color is not None:
|
|
330
|
+
box_colors = [color] * num_boxes
|
|
331
|
+
elif label_colors is not None:
|
|
332
|
+
box_colors = [label_colors.get(label, DEFAULT_COLOR) for label in labels]
|
|
333
|
+
else:
|
|
334
|
+
label_colors = _create_label_colors(labels)
|
|
335
|
+
box_colors = [label_colors[label] for label in labels]
|
|
336
|
+
|
|
337
|
+
from PIL import ImageDraw, ImageFont, ImageColor
|
|
338
|
+
# set default font if not provided
|
|
339
|
+
if font is None:
|
|
340
|
+
txt_font = ImageFont.load_default()
|
|
341
|
+
else:
|
|
342
|
+
txt_font = ImageFont.truetype(font=font, size=font_size or 10)
|
|
343
|
+
|
|
344
|
+
img_to_draw = img.copy()
|
|
345
|
+
draw = ImageDraw.Draw(img_to_draw, 'RGBA' if fill else 'RGB')
|
|
346
|
+
|
|
347
|
+
for i, (bbox, label) in enumerate(zip(boxes, labels)):
|
|
348
|
+
# determine color for the current box and label
|
|
349
|
+
color = box_colors[i % len(box_colors)]
|
|
350
|
+
|
|
351
|
+
if fill:
|
|
352
|
+
rgb_color = ImageColor.getrgb(color)
|
|
353
|
+
fill_color = rgb_color + (100,) # semi-transparent
|
|
354
|
+
draw.rectangle(bbox, outline=color, width=width, fill=fill_color)
|
|
355
|
+
else:
|
|
356
|
+
draw.rectangle(bbox, outline=color, width=width)
|
|
357
|
+
|
|
358
|
+
if label is not None:
|
|
359
|
+
label_str = str(label)
|
|
360
|
+
margin = width + 1
|
|
361
|
+
draw.text((bbox[0] + margin, bbox[1] + margin), label_str, fill=color, font=txt_font)
|
|
362
|
+
|
|
363
|
+
return img_to_draw
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
__all__ = local_public_names(__name__)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def __dir__():
|
|
370
|
+
return __all__
|