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.

Files changed (142) hide show
  1. {pixeltable-0.2.14 → pixeltable-0.2.15}/PKG-INFO +2 -2
  2. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/__version__.py +2 -2
  3. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/column.py +3 -3
  4. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/table.py +3 -5
  5. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/env.py +4 -4
  6. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/arithmetic_expr.py +41 -16
  7. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/expr.py +37 -0
  8. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/function_call.py +5 -1
  9. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/globals.py +3 -1
  10. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/inline_array.py +2 -2
  11. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/__init__.py +1 -1
  12. pixeltable-0.2.15/pixeltable/functions/json.py +46 -0
  13. pixeltable-0.2.14/pixeltable/functions/eval.py → pixeltable-0.2.15/pixeltable/functions/vision.py +170 -27
  14. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/io/external_store.py +2 -2
  15. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/io/globals.py +1 -1
  16. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/io/pandas.py +34 -8
  17. pixeltable-0.2.15/pixeltable/iterators/video.py +128 -0
  18. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/store.py +65 -28
  19. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/tool/create_test_db_dump.py +5 -5
  20. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/type_system.py +73 -53
  21. {pixeltable-0.2.14 → pixeltable-0.2.15}/pyproject.toml +2 -2
  22. pixeltable-0.2.14/pixeltable/iterators/video.py +0 -96
  23. {pixeltable-0.2.14 → pixeltable-0.2.15}/LICENSE +0 -0
  24. {pixeltable-0.2.14 → pixeltable-0.2.15}/README.md +0 -0
  25. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/__init__.py +0 -0
  26. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/__init__.py +0 -0
  27. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/catalog.py +0 -0
  28. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/dir.py +0 -0
  29. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/globals.py +0 -0
  30. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/insertable_table.py +0 -0
  31. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/named_function.py +0 -0
  32. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/path.py +0 -0
  33. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/path_dict.py +0 -0
  34. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/schema_object.py +0 -0
  35. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/table_version.py +0 -0
  36. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/table_version_path.py +0 -0
  37. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/catalog/view.py +0 -0
  38. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/dataframe.py +0 -0
  39. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exceptions.py +0 -0
  40. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/__init__.py +0 -0
  41. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/aggregation_node.py +0 -0
  42. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/cache_prefetch_node.py +0 -0
  43. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/component_iteration_node.py +0 -0
  44. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/data_row_batch.py +0 -0
  45. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/exec_context.py +0 -0
  46. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/exec_node.py +0 -0
  47. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/expr_eval_node.py +0 -0
  48. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/in_memory_data_node.py +0 -0
  49. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/media_validation_node.py +0 -0
  50. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/row_update_node.py +0 -0
  51. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exec/sql_node.py +0 -0
  52. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/__init__.py +0 -0
  53. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/array_slice.py +0 -0
  54. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/column_property_ref.py +0 -0
  55. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/column_ref.py +0 -0
  56. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/comparison.py +0 -0
  57. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/compound_predicate.py +0 -0
  58. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/data_row.py +0 -0
  59. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/expr_set.py +0 -0
  60. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/in_predicate.py +0 -0
  61. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/inline_dict.py +0 -0
  62. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/is_null.py +0 -0
  63. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/json_mapper.py +0 -0
  64. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/json_path.py +0 -0
  65. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/literal.py +0 -0
  66. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/method_ref.py +0 -0
  67. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/object_ref.py +0 -0
  68. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/row_builder.py +0 -0
  69. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/rowid_ref.py +0 -0
  70. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/similarity_expr.py +0 -0
  71. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/type_cast.py +0 -0
  72. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/exprs/variable.py +0 -0
  73. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/ext/__init__.py +0 -0
  74. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/ext/functions/__init__.py +0 -0
  75. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/ext/functions/whisperx.py +0 -0
  76. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/ext/functions/yolox.py +0 -0
  77. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/func/__init__.py +0 -0
  78. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/func/aggregate_function.py +0 -0
  79. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/func/callable_function.py +0 -0
  80. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/func/expr_template_function.py +0 -0
  81. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/func/function.py +0 -0
  82. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/func/function_registry.py +0 -0
  83. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/func/globals.py +0 -0
  84. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/func/query_template_function.py +0 -0
  85. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/func/signature.py +0 -0
  86. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/func/udf.py +0 -0
  87. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/fireworks.py +0 -0
  88. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/globals.py +0 -0
  89. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/huggingface.py +0 -0
  90. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/image.py +0 -0
  91. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/openai.py +0 -0
  92. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/string.py +0 -0
  93. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/timestamp.py +0 -0
  94. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/together.py +0 -0
  95. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/util.py +0 -0
  96. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/video.py +0 -0
  97. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/functions/whisper.py +0 -0
  98. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/globals.py +0 -0
  99. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/index/__init__.py +0 -0
  100. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/index/base.py +0 -0
  101. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/index/btree.py +0 -0
  102. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/index/embedding_index.py +0 -0
  103. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/io/__init__.py +0 -0
  104. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/io/hf_datasets.py +0 -0
  105. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/io/label_studio.py +0 -0
  106. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/io/parquet.py +0 -0
  107. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/iterators/__init__.py +0 -0
  108. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/iterators/base.py +0 -0
  109. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/iterators/document.py +0 -0
  110. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/iterators/string.py +0 -0
  111. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/__init__.py +0 -0
  112. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/converters/convert_10.py +0 -0
  113. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/converters/convert_12.py +0 -0
  114. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/converters/convert_13.py +0 -0
  115. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/converters/convert_14.py +0 -0
  116. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/converters/convert_15.py +0 -0
  117. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/converters/convert_16.py +0 -0
  118. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/converters/convert_17.py +0 -0
  119. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/converters/convert_18.py +0 -0
  120. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/converters/util.py +0 -0
  121. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/notes.py +0 -0
  122. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/metadata/schema.py +0 -0
  123. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/plan.py +0 -0
  124. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/tool/create_test_video.py +0 -0
  125. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/tool/doc_plugins/griffe.py +0 -0
  126. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/tool/doc_plugins/mkdocstrings.py +0 -0
  127. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/tool/doc_plugins/templates/material/udf.html.jinja +0 -0
  128. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/tool/embed_udf.py +0 -0
  129. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/__init__.py +0 -0
  130. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/arrow.py +0 -0
  131. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/coco.py +0 -0
  132. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/code.py +0 -0
  133. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/documents.py +0 -0
  134. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/filecache.py +0 -0
  135. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/formatter.py +0 -0
  136. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/help.py +0 -0
  137. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/http_server.py +0 -0
  138. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/media_store.py +0 -0
  139. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/pytorch.py +0 -0
  140. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/s3.py +0 -0
  141. {pixeltable-0.2.14 → pixeltable-0.2.15}/pixeltable/utils/sql.py +0 -0
  142. {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.14
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.14"
3
- __version_tuple__ = (0, 2, 14)
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 Optional, Union, Callable, Any
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: Optional[bool] = None,
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 not (col.col_type.is_image_type() and not col.has_window_fn_call()):
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 pgserver
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[pgserver.PostgresServer] = None
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 pgserver.get_server(): cleanup_mode=None will leave db on for debugging purposes
270
- self._db_server = pgserver.get_server(self._pgdata_dir, cleanup_mode=None)
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
- from typing import Optional, List, Any, Dict, Tuple
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
- # TODO: determine most specific common supertype
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__(ts.ColumnType.supertype(op1.col_type, op2.col_type))
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) -> List[Tuple[str, Any]]:
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
- return left / right
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
- return left % right
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
- # check types if we couldn't do that prior to execution
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) -> Dict:
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: Dict, components: List[Expr]) -> Expr:
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
- if not param.col_type.is_supertype_of(arg.col_type):
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 = ts.ColumnType.supertype(inferred_element_type, self.components[idx].col_type)
54
+ inferred_element_type = inferred_element_type.supertype(self.components[idx].col_type)
55
55
  else:
56
- inferred_element_type = ts.ColumnType.supertype(inferred_element_type, ts.ColumnType.infer_literal_type(val))
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__
@@ -1,11 +1,29 @@
1
- from typing import List, Tuple, Dict
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 sys
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 calculate_bboxes_area(bboxes: np.ndarray) -> np.ndarray:
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 calculate_overlaps(bboxes1: np.ndarray, bboxes2: np.ndarray) -> np.ndarray:
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 = calculate_bboxes_area(bboxes1)
62
- area2 = calculate_bboxes_area(bboxes2)
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 calculate_image_tpfp(
101
+ def __calculate_image_tpfp(
84
102
  pred_bboxes: np.ndarray, pred_scores: np.ndarray, gt_bboxes: np.ndarray, min_iou: float
85
- ) -> Tuple[np.ndarray, np.ndarray]:
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
- - tp (numpy.ndarray): Shape (N,),
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 = calculate_overlaps(pred_bboxes, gt_bboxes)
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: List[List[int]],
164
- pred_labels: List[int],
165
- pred_scores: List[float],
166
- gt_bboxes: List[List[int]],
167
- gt_labels: List[int],
168
- ) -> Dict:
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: List[Dict] = []
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 = calculate_image_tpfp(pred_bboxes_arr[pred_filter], class_pred_scores, gt_bboxes_arr[gt_filter], [0.5])
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: Dict[int, List[Dict]] = defaultdict(list)
220
+ self.class_tpfp: dict[int, list[dict]] = defaultdict(list)
199
221
 
200
- def update(self, eval_dicts: List[Dict]) -> None:
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) -> Dict:
227
+ def value(self) -> dict:
206
228
  eps = np.finfo(np.float32).eps
207
- result: Dict[int, float] = {}
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__