pixeltable 0.2.20__py3-none-any.whl → 0.2.21__py3-none-any.whl

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 (84) hide show
  1. pixeltable/__init__.py +7 -19
  2. pixeltable/__version__.py +2 -2
  3. pixeltable/catalog/__init__.py +7 -7
  4. pixeltable/catalog/globals.py +3 -0
  5. pixeltable/catalog/table.py +208 -145
  6. pixeltable/catalog/table_version.py +36 -18
  7. pixeltable/catalog/table_version_path.py +0 -8
  8. pixeltable/catalog/view.py +3 -3
  9. pixeltable/dataframe.py +9 -24
  10. pixeltable/env.py +1 -1
  11. pixeltable/exec/__init__.py +1 -1
  12. pixeltable/exec/aggregation_node.py +22 -15
  13. pixeltable/exec/data_row_batch.py +7 -7
  14. pixeltable/exec/exec_node.py +35 -7
  15. pixeltable/exec/expr_eval_node.py +2 -1
  16. pixeltable/exec/in_memory_data_node.py +9 -9
  17. pixeltable/exec/sql_node.py +265 -136
  18. pixeltable/exprs/__init__.py +1 -0
  19. pixeltable/exprs/data_row.py +30 -19
  20. pixeltable/exprs/expr.py +15 -14
  21. pixeltable/exprs/expr_dict.py +55 -0
  22. pixeltable/exprs/expr_set.py +21 -15
  23. pixeltable/exprs/function_call.py +21 -8
  24. pixeltable/exprs/rowid_ref.py +2 -2
  25. pixeltable/exprs/sql_element_cache.py +5 -1
  26. pixeltable/ext/functions/whisperx.py +7 -2
  27. pixeltable/func/callable_function.py +2 -2
  28. pixeltable/func/function_registry.py +6 -7
  29. pixeltable/func/query_template_function.py +11 -12
  30. pixeltable/func/signature.py +17 -15
  31. pixeltable/func/udf.py +0 -4
  32. pixeltable/functions/__init__.py +1 -1
  33. pixeltable/functions/audio.py +4 -6
  34. pixeltable/functions/globals.py +86 -42
  35. pixeltable/functions/huggingface.py +12 -14
  36. pixeltable/functions/image.py +59 -45
  37. pixeltable/functions/json.py +0 -1
  38. pixeltable/functions/mistralai.py +2 -2
  39. pixeltable/functions/openai.py +22 -25
  40. pixeltable/functions/string.py +50 -50
  41. pixeltable/functions/timestamp.py +20 -20
  42. pixeltable/functions/together.py +2 -2
  43. pixeltable/functions/video.py +11 -20
  44. pixeltable/functions/whisper.py +2 -20
  45. pixeltable/globals.py +55 -56
  46. pixeltable/index/base.py +2 -2
  47. pixeltable/index/btree.py +7 -7
  48. pixeltable/index/embedding_index.py +8 -10
  49. pixeltable/io/external_store.py +11 -5
  50. pixeltable/io/globals.py +2 -0
  51. pixeltable/io/hf_datasets.py +1 -1
  52. pixeltable/io/label_studio.py +6 -6
  53. pixeltable/io/parquet.py +14 -13
  54. pixeltable/iterators/document.py +9 -7
  55. pixeltable/iterators/video.py +10 -1
  56. pixeltable/metadata/__init__.py +3 -2
  57. pixeltable/metadata/converters/convert_14.py +4 -2
  58. pixeltable/metadata/converters/convert_15.py +1 -1
  59. pixeltable/metadata/converters/convert_19.py +1 -0
  60. pixeltable/metadata/converters/convert_20.py +1 -1
  61. pixeltable/metadata/converters/util.py +9 -8
  62. pixeltable/metadata/schema.py +32 -21
  63. pixeltable/plan.py +136 -154
  64. pixeltable/store.py +51 -36
  65. pixeltable/tool/create_test_db_dump.py +6 -6
  66. pixeltable/tool/doc_plugins/griffe.py +3 -34
  67. pixeltable/tool/mypy_plugin.py +32 -0
  68. pixeltable/type_system.py +243 -60
  69. pixeltable/utils/arrow.py +10 -9
  70. pixeltable/utils/coco.py +4 -4
  71. pixeltable/utils/documents.py +1 -1
  72. pixeltable/utils/filecache.py +9 -9
  73. pixeltable/utils/formatter.py +1 -1
  74. pixeltable/utils/http_server.py +2 -5
  75. pixeltable/utils/media_store.py +6 -6
  76. pixeltable/utils/pytorch.py +10 -11
  77. pixeltable/utils/sql.py +2 -1
  78. {pixeltable-0.2.20.dist-info → pixeltable-0.2.21.dist-info}/METADATA +6 -5
  79. pixeltable-0.2.21.dist-info/RECORD +148 -0
  80. pixeltable/utils/help.py +0 -11
  81. pixeltable-0.2.20.dist-info/RECORD +0 -147
  82. {pixeltable-0.2.20.dist-info → pixeltable-0.2.21.dist-info}/LICENSE +0 -0
  83. {pixeltable-0.2.20.dist-info → pixeltable-0.2.21.dist-info}/WHEEL +0 -0
  84. {pixeltable-0.2.20.dist-info → pixeltable-0.2.21.dist-info}/entry_points.txt +0 -0
@@ -1,4 +1,8 @@
1
- from typing import Optional, Union, Any
1
+ import builtins
2
+ from typing import _GenericAlias # type: ignore[attr-defined]
3
+ from typing import Optional, Union
4
+
5
+ import sqlalchemy as sql
2
6
 
3
7
  import pixeltable.func as func
4
8
  import pixeltable.type_system as ts
@@ -7,36 +11,46 @@ from pixeltable.utils.code import local_public_names
7
11
 
8
12
 
9
13
  # TODO: remove and replace calls with astype()
10
- def cast(expr: exprs.Expr, target_type: ts.ColumnType) -> exprs.Expr:
11
- expr.col_type = target_type
14
+ def cast(expr: exprs.Expr, target_type: Union[ts.ColumnType, type, _GenericAlias]) -> exprs.Expr:
15
+ expr.col_type = ts.ColumnType.normalize_type(target_type)
12
16
  return expr
13
17
 
14
18
 
15
- @func.uda(update_types=[ts.IntType()], value_type=ts.IntType(), allows_window=True, requires_order_by=False)
19
+ @func.uda(
20
+ update_types=[ts.IntType(nullable=True)], value_type=ts.IntType(nullable=False),
21
+ allows_window=True, requires_order_by=False)
16
22
  class sum(func.Aggregator):
17
23
  """Sums the selected integers or floats."""
18
24
  def __init__(self):
19
- self.sum: Union[int, float] = 0
20
-
21
- def update(self, val: Union[int, float]) -> None:
22
- if val is not None:
25
+ self.sum: Optional[int] = None
26
+
27
+ def update(self, val: Optional[int]) -> None:
28
+ if val is None:
29
+ return
30
+ if self.sum is None:
31
+ self.sum = val
32
+ else:
23
33
  self.sum += val
24
34
 
25
35
  def value(self) -> Union[int, float]:
26
36
  return self.sum
27
37
 
28
- # @sum.to_sql
29
- # def _(val: 'sqlalchemy.ColumnElements') -> Optional['sqlalchemy.ColumnElements']:
30
- # import sqlalchemy as sql
31
- # return sql.sql.functions.sum(val)
32
38
 
39
+ # disable type checking: mypy doesn't seem to understand that 'sum' is an instance of Function
40
+ # TODO: find a way to have this type-checked
41
+ @sum.to_sql # type: ignore
42
+ def _(val: sql.ColumnElement) -> Optional[sql.ColumnElement]:
43
+ # This can produce a Decimal. We are deliberately avoiding an explicit cast to a Bigint here, because that can
44
+ # cause overflows in Postgres. We're instead doing the conversion to the target type in SqlNode.__iter__().
45
+ return sql.sql.func.sum(val)
33
46
 
34
- @func.uda(update_types=[ts.IntType()], value_type=ts.IntType(), allows_window=True, requires_order_by=False)
47
+
48
+ @func.uda(update_types=[ts.IntType(nullable=True)], value_type=ts.IntType(), allows_window=True, requires_order_by=False)
35
49
  class count(func.Aggregator):
36
50
  def __init__(self):
37
51
  self.count = 0
38
52
 
39
- def update(self, val: int) -> None:
53
+ def update(self, val: Optional[int]) -> None:
40
54
  if val is not None:
41
55
  self.count += 1
42
56
 
@@ -44,57 +58,87 @@ class count(func.Aggregator):
44
58
  return self.count
45
59
 
46
60
 
47
- @func.uda(update_types=[ts.FloatType()], value_type=ts.FloatType(nullable=True), allows_window=True, requires_order_by=False)
48
- class max(func.Aggregator):
61
+ @count.to_sql # type: ignore
62
+ def _(val: sql.ColumnElement) -> Optional[sql.ColumnElement]:
63
+ return sql.sql.func.count(val)
64
+
65
+
66
+ @func.uda(
67
+ update_types=[ts.IntType(nullable=True)], value_type=ts.IntType(nullable=True), allows_window=True,
68
+ requires_order_by=False)
69
+ class min(func.Aggregator):
49
70
  def __init__(self):
50
- self.val = None
71
+ self.val: Optional[int] = None
51
72
 
52
- def update(self, val: Optional[float]) -> None:
53
- if val is not None:
54
- if self.val is None:
55
- self.val = val
56
- else:
57
- import builtins
58
- self.val = builtins.max(self.val, val)
73
+ def update(self, val: Optional[int]) -> None:
74
+ if val is None:
75
+ return
76
+ if self.val is None:
77
+ self.val = val
78
+ else:
79
+ self.val = builtins.min(self.val, val)
59
80
 
60
- def value(self) -> Optional[float]:
81
+ def value(self) -> Optional[int]:
61
82
  return self.val
62
83
 
63
84
 
64
- @func.uda(update_types=[ts.FloatType()], value_type=ts.FloatType(nullable=True), allows_window=True, requires_order_by=False)
65
- class min(func.Aggregator):
85
+ @min.to_sql # type: ignore
86
+ def _(val: sql.ColumnElement) -> Optional[sql.ColumnElement]:
87
+ return sql.sql.func.min(val)
88
+
89
+
90
+ @func.uda(
91
+ update_types=[ts.IntType(nullable=True)], value_type=ts.IntType(nullable=True), allows_window=True,
92
+ requires_order_by=False)
93
+ class max(func.Aggregator):
66
94
  def __init__(self):
67
- self.val = None
95
+ self.val: Optional[int] = None
68
96
 
69
- def update(self, val: Optional[float]) -> None:
70
- if val is not None:
71
- if self.val is None:
72
- self.val = val
73
- else:
74
- import builtins
75
- self.val = builtins.min(self.val, val)
97
+ def update(self, val: Optional[int]) -> None:
98
+ if val is None:
99
+ return
100
+ if self.val is None:
101
+ self.val = val
102
+ else:
103
+ self.val = builtins.max(self.val, val)
76
104
 
77
- def value(self) -> Optional[float]:
105
+ def value(self) -> Optional[int]:
78
106
  return self.val
79
107
 
80
108
 
81
- @func.uda(update_types=[ts.IntType()], value_type=ts.FloatType(), allows_window=False, requires_order_by=False)
109
+ @max.to_sql # type: ignore
110
+ def _(val: sql.ColumnElement) -> Optional[sql.ColumnElement]:
111
+ return sql.sql.func.max(val)
112
+
113
+
114
+ @func.uda(
115
+ update_types=[ts.IntType(nullable=True)], value_type=ts.FloatType(nullable=True), allows_window=False,
116
+ requires_order_by=False)
82
117
  class mean(func.Aggregator):
83
118
  def __init__(self):
84
- self.sum = 0
119
+ self.sum: Optional[int] = None
85
120
  self.count = 0
86
121
 
87
- def update(self, val: int) -> None:
88
- if val is not None:
122
+ def update(self, val: Optional[int]) -> None:
123
+ if val is None:
124
+ return
125
+ if self.sum is None:
126
+ self.sum = val
127
+ else:
89
128
  self.sum += val
90
- self.count += 1
129
+ self.count += 1
91
130
 
92
- def value(self) -> float:
131
+ def value(self) -> Optional[float]:
93
132
  if self.count == 0:
94
133
  return None
95
134
  return self.sum / self.count
96
135
 
97
136
 
137
+ @mean.to_sql # type: ignore
138
+ def _(val: sql.ColumnElement) -> Optional[sql.ColumnElement]:
139
+ return sql.sql.func.avg(val)
140
+
141
+
98
142
  __all__ = local_public_names(__name__)
99
143
 
100
144
 
@@ -10,20 +10,18 @@ UDFs).
10
10
  from typing import Callable, TypeVar, Optional, Any
11
11
 
12
12
  import PIL.Image
13
- import numpy as np
14
13
 
15
14
  import pixeltable as pxt
16
15
  import pixeltable.env as env
17
- import pixeltable.type_system as ts
18
16
  from pixeltable.func import Batch
19
17
  from pixeltable.functions.util import resolve_torch_device, normalize_image_mode
20
18
  from pixeltable.utils.code import local_public_names
21
19
 
22
20
 
23
- @pxt.udf(batch_size=32, return_type=ts.ArrayType((None,), dtype=ts.FloatType()))
21
+ @pxt.udf(batch_size=32)
24
22
  def sentence_transformer(
25
23
  sentence: Batch[str], *, model_id: str, normalize_embeddings: bool = False
26
- ) -> Batch[np.ndarray]:
24
+ ) -> Batch[pxt.Array[(None,), float]]:
27
25
  """
28
26
  Computes sentence embeddings. `model_id` should be a pretrained Sentence Transformers model, as described
29
27
  in the [Sentence Transformers Pretrained Models](https://sbert.net/docs/sentence_transformer/pretrained_models.html)
@@ -59,14 +57,14 @@ def sentence_transformer(
59
57
 
60
58
 
61
59
  @sentence_transformer.conditional_return_type
62
- def _(model_id: str) -> ts.ArrayType:
60
+ def _(model_id: str) -> pxt.ArrayType:
63
61
  try:
64
62
  from sentence_transformers import SentenceTransformer
65
63
 
66
64
  model = _lookup_model(model_id, SentenceTransformer)
67
- return ts.ArrayType((model.get_sentence_embedding_dimension(),), dtype=ts.FloatType(), nullable=False)
65
+ return pxt.ArrayType((model.get_sentence_embedding_dimension(),), dtype=pxt.FloatType(), nullable=False)
68
66
  except ImportError:
69
- return ts.ArrayType((None,), dtype=ts.FloatType(), nullable=False)
67
+ return pxt.ArrayType((None,), dtype=pxt.FloatType(), nullable=False)
70
68
 
71
69
 
72
70
  @pxt.udf
@@ -128,8 +126,8 @@ def cross_encoder_list(sentence1: str, sentences2: list, *, model_id: str) -> li
128
126
  return array.tolist()
129
127
 
130
128
 
131
- @pxt.udf(batch_size=32, return_type=ts.ArrayType((None,), dtype=ts.FloatType(), nullable=False))
132
- def clip_text(text: Batch[str], *, model_id: str) -> Batch[np.ndarray]:
129
+ @pxt.udf(batch_size=32)
130
+ def clip_text(text: Batch[str], *, model_id: str) -> Batch[pxt.Array[(None,), float]]:
133
131
  """
134
132
  Computes a CLIP embedding for the specified text. `model_id` should be a reference to a pretrained
135
133
  [CLIP Model](https://huggingface.co/docs/transformers/model_doc/clip).
@@ -166,8 +164,8 @@ def clip_text(text: Batch[str], *, model_id: str) -> Batch[np.ndarray]:
166
164
  return [embeddings[i] for i in range(embeddings.shape[0])]
167
165
 
168
166
 
169
- @pxt.udf(batch_size=32, return_type=ts.ArrayType((None,), dtype=ts.FloatType(), nullable=False))
170
- def clip_image(image: Batch[PIL.Image.Image], *, model_id: str) -> Batch[np.ndarray]:
167
+ @pxt.udf(batch_size=32)
168
+ def clip_image(image: Batch[PIL.Image.Image], *, model_id: str) -> Batch[pxt.Array[(None,), float]]:
171
169
  """
172
170
  Computes a CLIP embedding for the specified image. `model_id` should be a reference to a pretrained
173
171
  [CLIP Model](https://huggingface.co/docs/transformers/model_doc/clip).
@@ -206,14 +204,14 @@ def clip_image(image: Batch[PIL.Image.Image], *, model_id: str) -> Batch[np.ndar
206
204
 
207
205
  @clip_text.conditional_return_type
208
206
  @clip_image.conditional_return_type
209
- def _(model_id: str) -> ts.ArrayType:
207
+ def _(model_id: str) -> pxt.ArrayType:
210
208
  try:
211
209
  from transformers import CLIPModel
212
210
 
213
211
  model = _lookup_model(model_id, CLIPModel.from_pretrained)
214
- return ts.ArrayType((model.config.projection_dim,), dtype=ts.FloatType(), nullable=False)
212
+ return pxt.ArrayType((model.config.projection_dim,), dtype=pxt.FloatType(), nullable=False)
215
213
  except ImportError:
216
- return ts.ArrayType((None,), dtype=ts.FloatType(), nullable=False)
214
+ return pxt.ArrayType((None,), dtype=pxt.FloatType(), nullable=False)
217
215
 
218
216
 
219
217
  @pxt.udf(batch_size=4)
@@ -15,13 +15,12 @@ from typing import Optional
15
15
 
16
16
  import PIL.Image
17
17
 
18
- import pixeltable.func as func
19
- import pixeltable.type_system as ts
20
- from pixeltable.utils.code import local_public_names
18
+ import pixeltable as pxt
21
19
  from pixeltable.exprs import Expr
20
+ from pixeltable.utils.code import local_public_names
22
21
 
23
22
 
24
- @func.udf(is_method=True)
23
+ @pxt.udf(is_method=True)
25
24
  def b64_encode(img: PIL.Image.Image, image_format: str = 'png') -> str:
26
25
  """
27
26
  Convert image to a base64-encoded string.
@@ -38,7 +37,7 @@ def b64_encode(img: PIL.Image.Image, image_format: str = 'png') -> str:
38
37
  return b64_bytes.decode('utf-8')
39
38
 
40
39
 
41
- @func.udf(substitute_fn=PIL.Image.alpha_composite, is_method=True)
40
+ @pxt.udf(substitute_fn=PIL.Image.alpha_composite, is_method=True)
42
41
  def alpha_composite(im1: PIL.Image.Image, im2: PIL.Image.Image) -> PIL.Image.Image:
43
42
  """
44
43
  Alpha composite `im2` over `im1`.
@@ -48,7 +47,7 @@ def alpha_composite(im1: PIL.Image.Image, im2: PIL.Image.Image) -> PIL.Image.Ima
48
47
  pass
49
48
 
50
49
 
51
- @func.udf(substitute_fn=PIL.Image.blend, is_method=True)
50
+ @pxt.udf(substitute_fn=PIL.Image.blend, is_method=True)
52
51
  def blend(im1: PIL.Image.Image, im2: PIL.Image.Image, alpha: float) -> PIL.Image.Image:
53
52
  """
54
53
  Return a new image by interpolating between two input images, using a constant alpha.
@@ -57,7 +56,7 @@ def blend(im1: PIL.Image.Image, im2: PIL.Image.Image, alpha: float) -> PIL.Image
57
56
  """
58
57
  pass
59
58
 
60
- @func.udf(substitute_fn=PIL.Image.composite, is_method=True)
59
+ @pxt.udf(substitute_fn=PIL.Image.composite, is_method=True)
61
60
  def composite(image1: PIL.Image.Image, image2: PIL.Image.Image, mask: PIL.Image.Image) -> PIL.Image.Image:
62
61
  """
63
62
  Return a composite image by blending two images using a mask.
@@ -70,7 +69,7 @@ def composite(image1: PIL.Image.Image, image2: PIL.Image.Image, mask: PIL.Image.
70
69
  # PIL.Image.Image methods
71
70
 
72
71
  # Image.convert()
73
- @func.udf(is_method=True)
72
+ @pxt.udf(is_method=True)
74
73
  def convert(self: PIL.Image.Image, mode: str) -> PIL.Image.Image:
75
74
  """
76
75
  Convert the image to a different mode.
@@ -85,14 +84,14 @@ def convert(self: PIL.Image.Image, mode: str) -> PIL.Image.Image:
85
84
 
86
85
 
87
86
  @convert.conditional_return_type
88
- def _(self: Expr, mode: str) -> ts.ColumnType:
87
+ def _(self: Expr, mode: str) -> pxt.ColumnType:
89
88
  input_type = self.col_type
90
- assert isinstance(input_type, ts.ImageType)
91
- return ts.ImageType(size=input_type.size, mode=mode, nullable=input_type.nullable)
89
+ assert isinstance(input_type, pxt.ImageType)
90
+ return pxt.ImageType(size=input_type.size, mode=mode, nullable=input_type.nullable)
92
91
 
93
92
 
94
93
  # Image.crop()
95
- @func.udf(substitute_fn=PIL.Image.Image.crop, is_method=True)
94
+ @pxt.udf(substitute_fn=PIL.Image.Image.crop, is_method=True)
96
95
  def crop(self: PIL.Image.Image, box: tuple[int, int, int, int]) -> PIL.Image.Image:
97
96
  """
98
97
  Return a rectangular region from the image. The box is a 4-tuple defining the left, upper, right, and lower pixel
@@ -105,16 +104,16 @@ def crop(self: PIL.Image.Image, box: tuple[int, int, int, int]) -> PIL.Image.Ima
105
104
 
106
105
 
107
106
  @crop.conditional_return_type
108
- def _(self: Expr, box: tuple[int, int, int, int]) -> ts.ColumnType:
107
+ def _(self: Expr, box: tuple[int, int, int, int]) -> pxt.ColumnType:
109
108
  input_type = self.col_type
110
- assert isinstance(input_type, ts.ImageType)
109
+ assert isinstance(input_type, pxt.ImageType)
111
110
  if (isinstance(box, list) or isinstance(box, tuple)) and len(box) == 4 and all(isinstance(x, int) for x in box):
112
- return ts.ImageType(size=(box[2] - box[0], box[3] - box[1]), mode=input_type.mode, nullable=input_type.nullable)
113
- return ts.ImageType(mode=input_type.mode, nullable=input_type.nullable) # we can't compute the size statically
111
+ return pxt.ImageType(size=(box[2] - box[0], box[3] - box[1]), mode=input_type.mode, nullable=input_type.nullable)
112
+ return pxt.ImageType(mode=input_type.mode, nullable=input_type.nullable) # we can't compute the size statically
114
113
 
115
114
 
116
115
  # Image.getchannel()
117
- @func.udf(substitute_fn=PIL.Image.Image.getchannel, is_method=True)
116
+ @pxt.udf(substitute_fn=PIL.Image.Image.getchannel, is_method=True)
118
117
  def getchannel(self: PIL.Image.Image, channel: int) -> PIL.Image.Image:
119
118
  """
120
119
  Return an L-mode image containing a single channel of the original image.
@@ -128,7 +127,7 @@ def getchannel(self: PIL.Image.Image, channel: int) -> PIL.Image.Image:
128
127
  pass
129
128
 
130
129
 
131
- @func.udf(is_method=True)
130
+ @pxt.udf(is_method=True)
132
131
  def get_metadata(self: PIL.Image.Image) -> dict:
133
132
  """
134
133
  Return metadata for the image.
@@ -144,14 +143,29 @@ def get_metadata(self: PIL.Image.Image) -> dict:
144
143
 
145
144
 
146
145
  @getchannel.conditional_return_type
147
- def _(self: Expr) -> ts.ColumnType:
146
+ def _(self: Expr) -> pxt.ColumnType:
148
147
  input_type = self.col_type
149
- assert isinstance(input_type, ts.ImageType)
150
- return ts.ImageType(size=input_type.size, mode='L', nullable=input_type.nullable)
148
+ assert isinstance(input_type, pxt.ImageType)
149
+ return pxt.ImageType(size=input_type.size, mode='L', nullable=input_type.nullable)
150
+
151
+
152
+ # Image.point()
153
+ @pxt.udf(is_method=True)
154
+ def point(self: PIL.Image.Image, lut: list[int], mode: Optional[str] = None) -> PIL.Image.Image:
155
+ """
156
+ Map image pixels through a lookup table.
157
+
158
+ Equivalent to
159
+ [`PIL.Image.Image.point()`](https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.point)
160
+
161
+ Args:
162
+ lut: A lookup table.
163
+ """
164
+ return self.point(lut, mode=mode)
151
165
 
152
166
 
153
167
  # Image.resize()
154
- @func.udf(is_method=True)
168
+ @pxt.udf(is_method=True)
155
169
  def resize(self: PIL.Image.Image, size: tuple[int, int]) -> PIL.Image.Image:
156
170
  """
157
171
  Return a resized copy of the image. The size parameter is a tuple containing the width and height of the new image.
@@ -163,14 +177,14 @@ def resize(self: PIL.Image.Image, size: tuple[int, int]) -> PIL.Image.Image:
163
177
 
164
178
 
165
179
  @resize.conditional_return_type
166
- def _(self: Expr, size: tuple[int, int]) -> ts.ColumnType:
180
+ def _(self: Expr, size: tuple[int, int]) -> pxt.ColumnType:
167
181
  input_type = self.col_type
168
- assert isinstance(input_type, ts.ImageType)
169
- return ts.ImageType(size=size, mode=input_type.mode, nullable=input_type.nullable)
182
+ assert isinstance(input_type, pxt.ImageType)
183
+ return pxt.ImageType(size=size, mode=input_type.mode, nullable=input_type.nullable)
170
184
 
171
185
 
172
186
  # Image.rotate()
173
- @func.udf(is_method=True)
187
+ @pxt.udf(is_method=True)
174
188
  def rotate(self: PIL.Image.Image, angle: int) -> PIL.Image.Image:
175
189
  """
176
190
  Return a copy of the image rotated by the given angle.
@@ -184,7 +198,7 @@ def rotate(self: PIL.Image.Image, angle: int) -> PIL.Image.Image:
184
198
  return self.rotate(angle)
185
199
 
186
200
 
187
- @func.udf(substitute_fn=PIL.Image.Image.effect_spread, is_method=True)
201
+ @pxt.udf(substitute_fn=PIL.Image.Image.effect_spread, is_method=True)
188
202
  def effect_spread(self: PIL.Image.Image, distance: int) -> PIL.Image.Image:
189
203
  """
190
204
  Randomly spread pixels in an image.
@@ -198,7 +212,7 @@ def effect_spread(self: PIL.Image.Image, distance: int) -> PIL.Image.Image:
198
212
  pass
199
213
 
200
214
 
201
- @func.udf(substitute_fn=PIL.Image.Image.transpose, is_method=True)
215
+ @pxt.udf(substitute_fn=PIL.Image.Image.transpose, is_method=True)
202
216
  def transpose(self: PIL.Image.Image, method: int) -> PIL.Image.Image:
203
217
  """
204
218
  Transpose the image.
@@ -215,11 +229,11 @@ def transpose(self: PIL.Image.Image, method: int) -> PIL.Image.Image:
215
229
  @rotate.conditional_return_type
216
230
  @effect_spread.conditional_return_type
217
231
  @transpose.conditional_return_type
218
- def _(self: Expr) -> ts.ColumnType:
232
+ def _(self: Expr) -> pxt.ColumnType:
219
233
  return self.col_type
220
234
 
221
235
 
222
- @func.udf(substitute_fn=PIL.Image.Image.entropy, is_method=True)
236
+ @pxt.udf(substitute_fn=PIL.Image.Image.entropy, is_method=True)
223
237
  def entropy(self: PIL.Image.Image, mask: Optional[PIL.Image.Image] = None, extrema: Optional[list] = None) -> float:
224
238
  """
225
239
  Returns the entropy of the image, optionally using a mask and extrema.
@@ -234,7 +248,7 @@ def entropy(self: PIL.Image.Image, mask: Optional[PIL.Image.Image] = None, extre
234
248
  pass
235
249
 
236
250
 
237
- @func.udf(substitute_fn=PIL.Image.Image.getbands, is_method=True)
251
+ @pxt.udf(substitute_fn=PIL.Image.Image.getbands, is_method=True)
238
252
  def getbands(self: PIL.Image.Image) -> tuple[str]:
239
253
  """
240
254
  Return a tuple containing the names of the image bands.
@@ -245,7 +259,7 @@ def getbands(self: PIL.Image.Image) -> tuple[str]:
245
259
  pass
246
260
 
247
261
 
248
- @func.udf(substitute_fn=PIL.Image.Image.getbbox, is_method=True)
262
+ @pxt.udf(substitute_fn=PIL.Image.Image.getbbox, is_method=True)
249
263
  def getbbox(self: PIL.Image.Image, *, alpha_only: bool = True) -> tuple[int, int, int, int]:
250
264
  """
251
265
  Return a bounding box for the non-zero regions of the image.
@@ -258,7 +272,7 @@ def getbbox(self: PIL.Image.Image, *, alpha_only: bool = True) -> tuple[int, int
258
272
  pass
259
273
 
260
274
 
261
- @func.udf(substitute_fn=PIL.Image.Image.getcolors, is_method=True)
275
+ @pxt.udf(substitute_fn=PIL.Image.Image.getcolors, is_method=True)
262
276
  def getcolors(self: PIL.Image.Image, maxcolors: int = 256) -> tuple[tuple[int, int, int], int]:
263
277
  """
264
278
  Return a list of colors used in the image, up to a maximum of `maxcolors`.
@@ -272,7 +286,7 @@ def getcolors(self: PIL.Image.Image, maxcolors: int = 256) -> tuple[tuple[int, i
272
286
  pass
273
287
 
274
288
 
275
- @func.udf(substitute_fn=PIL.Image.Image.getextrema, is_method=True)
289
+ @pxt.udf(substitute_fn=PIL.Image.Image.getextrema, is_method=True)
276
290
  def getextrema(self: PIL.Image.Image) -> tuple[int, int]:
277
291
  """
278
292
  Return a 2-tuple containing the minimum and maximum pixel values of the image.
@@ -283,7 +297,7 @@ def getextrema(self: PIL.Image.Image) -> tuple[int, int]:
283
297
  pass
284
298
 
285
299
 
286
- @func.udf(substitute_fn=PIL.Image.Image.getpalette, is_method=True)
300
+ @pxt.udf(substitute_fn=PIL.Image.Image.getpalette, is_method=True)
287
301
  def getpalette(self: PIL.Image.Image, mode: Optional[str] = None) -> tuple[int]:
288
302
  """
289
303
  Return the palette of the image, optionally converting it to a different mode.
@@ -297,8 +311,8 @@ def getpalette(self: PIL.Image.Image, mode: Optional[str] = None) -> tuple[int]:
297
311
  pass
298
312
 
299
313
 
300
- @func.udf(param_types=[ts.ImageType(), ts.ArrayType((2,), dtype=ts.IntType())], is_method=True)
301
- def getpixel(self: PIL.Image.Image, xy: tuple[int, int]) -> tuple[int]:
314
+ @pxt.udf(is_method=True)
315
+ def getpixel(self: PIL.Image.Image, xy: list) -> tuple[int]:
302
316
  """
303
317
  Return the pixel value at the given position. The position `xy` is a tuple containing the x and y coordinates.
304
318
 
@@ -309,10 +323,10 @@ def getpixel(self: PIL.Image.Image, xy: tuple[int, int]) -> tuple[int]:
309
323
  xy: The coordinates, given as (x, y).
310
324
  """
311
325
  # `xy` will be a list; `tuple(xy)` is necessary for pillow 9 compatibility
312
- return self.getpixel(tuple(xy)) # type: ignore[arg-type]
326
+ return self.getpixel(tuple(xy))
313
327
 
314
328
 
315
- @func.udf(substitute_fn=PIL.Image.Image.getprojection, is_method=True)
329
+ @pxt.udf(substitute_fn=PIL.Image.Image.getprojection, is_method=True)
316
330
  def getprojection(self: PIL.Image.Image) -> tuple[int]:
317
331
  """
318
332
  Return two sequences representing the horizontal and vertical projection of the image.
@@ -323,7 +337,7 @@ def getprojection(self: PIL.Image.Image) -> tuple[int]:
323
337
  pass
324
338
 
325
339
 
326
- @func.udf(substitute_fn=PIL.Image.Image.histogram, is_method=True)
340
+ @pxt.udf(substitute_fn=PIL.Image.Image.histogram, is_method=True)
327
341
  def histogram(
328
342
  self: PIL.Image.Image, mask: Optional[PIL.Image.Image] = None, extrema: Optional[list] = None
329
343
  ) -> list[int]:
@@ -340,7 +354,7 @@ def histogram(
340
354
  pass
341
355
 
342
356
 
343
- @func.udf(substitute_fn=PIL.Image.Image.quantize, is_method=True)
357
+ @pxt.udf(substitute_fn=PIL.Image.Image.quantize, is_method=True)
344
358
  def quantize(
345
359
  self: PIL.Image.Image,
346
360
  colors: int = 256,
@@ -365,7 +379,7 @@ def quantize(
365
379
  pass
366
380
 
367
381
 
368
- @func.udf(substitute_fn=PIL.Image.Image.reduce, is_method=True)
382
+ @pxt.udf(substitute_fn=PIL.Image.Image.reduce, is_method=True)
369
383
  def reduce(self: PIL.Image.Image, factor: int, box: Optional[tuple[int, int, int, int]] = None) -> PIL.Image.Image:
370
384
  """
371
385
  Reduce the image by the given factor.
@@ -380,17 +394,17 @@ def reduce(self: PIL.Image.Image, factor: int, box: Optional[tuple[int, int, int
380
394
  pass
381
395
 
382
396
 
383
- @func.udf(is_property=True)
397
+ @pxt.udf(is_property=True)
384
398
  def width(self: PIL.Image.Image) -> int:
385
399
  return self.width
386
400
 
387
401
 
388
- @func.udf(is_property=True)
402
+ @pxt.udf(is_property=True)
389
403
  def height(self: PIL.Image.Image) -> int:
390
404
  return self.height
391
405
 
392
406
 
393
- @func.udf(is_property=True)
407
+ @pxt.udf(is_property=True)
394
408
  def mode(self: PIL.Image.Image) -> str:
395
409
  return self.mode
396
410
 
@@ -13,7 +13,6 @@ t.select(pxt.functions.json.make_list()).collect()
13
13
  from typing import Any
14
14
 
15
15
  import pixeltable as pxt
16
- import pixeltable.type_system as ts
17
16
  from pixeltable.utils.code import local_public_names
18
17
 
19
18
 
@@ -140,8 +140,8 @@ _embedding_dimensions_cache: dict[str, int] = {
140
140
  }
141
141
 
142
142
 
143
- @pxt.udf(batch_size=16, return_type=pxt.ArrayType((None,), dtype=pxt.FloatType()))
144
- def embeddings(input: Batch[str], *, model: str) -> Batch[np.ndarray]:
143
+ @pxt.udf(batch_size=16)
144
+ def embeddings(input: Batch[str], *, model: str) -> Batch[pxt.Array[(None,), float]]:
145
145
  """
146
146
  Embeddings API.
147
147