pixeltable 0.2.20__py3-none-any.whl → 0.2.22__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.
- pixeltable/__init__.py +7 -19
- pixeltable/__version__.py +2 -2
- pixeltable/catalog/__init__.py +7 -7
- pixeltable/catalog/column.py +37 -11
- pixeltable/catalog/globals.py +21 -0
- pixeltable/catalog/insertable_table.py +6 -4
- pixeltable/catalog/table.py +227 -148
- pixeltable/catalog/table_version.py +66 -28
- pixeltable/catalog/table_version_path.py +0 -8
- pixeltable/catalog/view.py +18 -19
- pixeltable/dataframe.py +16 -32
- pixeltable/env.py +6 -1
- pixeltable/exec/__init__.py +1 -2
- pixeltable/exec/aggregation_node.py +27 -17
- pixeltable/exec/cache_prefetch_node.py +1 -1
- pixeltable/exec/data_row_batch.py +9 -26
- pixeltable/exec/exec_node.py +36 -7
- pixeltable/exec/expr_eval_node.py +19 -11
- pixeltable/exec/in_memory_data_node.py +14 -11
- pixeltable/exec/sql_node.py +266 -138
- pixeltable/exprs/__init__.py +1 -0
- pixeltable/exprs/arithmetic_expr.py +3 -1
- pixeltable/exprs/array_slice.py +7 -7
- pixeltable/exprs/column_property_ref.py +37 -10
- pixeltable/exprs/column_ref.py +93 -14
- pixeltable/exprs/comparison.py +5 -5
- pixeltable/exprs/compound_predicate.py +8 -7
- pixeltable/exprs/data_row.py +56 -36
- pixeltable/exprs/expr.py +65 -63
- pixeltable/exprs/expr_dict.py +55 -0
- pixeltable/exprs/expr_set.py +26 -15
- pixeltable/exprs/function_call.py +53 -24
- pixeltable/exprs/globals.py +4 -1
- pixeltable/exprs/in_predicate.py +8 -7
- pixeltable/exprs/inline_expr.py +4 -4
- pixeltable/exprs/is_null.py +4 -4
- pixeltable/exprs/json_mapper.py +11 -12
- pixeltable/exprs/json_path.py +5 -10
- pixeltable/exprs/literal.py +5 -5
- pixeltable/exprs/method_ref.py +5 -4
- pixeltable/exprs/object_ref.py +2 -1
- pixeltable/exprs/row_builder.py +88 -36
- pixeltable/exprs/rowid_ref.py +14 -13
- pixeltable/exprs/similarity_expr.py +12 -7
- pixeltable/exprs/sql_element_cache.py +12 -6
- pixeltable/exprs/type_cast.py +8 -6
- pixeltable/exprs/variable.py +5 -4
- pixeltable/ext/functions/whisperx.py +7 -2
- pixeltable/func/aggregate_function.py +1 -1
- pixeltable/func/callable_function.py +2 -2
- pixeltable/func/function.py +11 -10
- pixeltable/func/function_registry.py +6 -7
- pixeltable/func/query_template_function.py +11 -12
- pixeltable/func/signature.py +17 -15
- pixeltable/func/udf.py +0 -4
- pixeltable/functions/__init__.py +2 -2
- pixeltable/functions/audio.py +4 -6
- pixeltable/functions/globals.py +84 -42
- pixeltable/functions/huggingface.py +31 -34
- pixeltable/functions/image.py +59 -45
- pixeltable/functions/json.py +0 -1
- pixeltable/functions/llama_cpp.py +106 -0
- pixeltable/functions/mistralai.py +2 -2
- pixeltable/functions/ollama.py +147 -0
- pixeltable/functions/openai.py +22 -25
- pixeltable/functions/replicate.py +72 -0
- pixeltable/functions/string.py +59 -50
- pixeltable/functions/timestamp.py +20 -20
- pixeltable/functions/together.py +2 -2
- pixeltable/functions/video.py +11 -20
- pixeltable/functions/whisper.py +2 -20
- pixeltable/globals.py +65 -74
- pixeltable/index/base.py +2 -2
- pixeltable/index/btree.py +20 -7
- pixeltable/index/embedding_index.py +12 -14
- pixeltable/io/__init__.py +1 -2
- pixeltable/io/external_store.py +11 -5
- pixeltable/io/fiftyone.py +178 -0
- pixeltable/io/globals.py +98 -2
- pixeltable/io/hf_datasets.py +1 -1
- pixeltable/io/label_studio.py +6 -6
- pixeltable/io/parquet.py +14 -13
- pixeltable/iterators/base.py +3 -2
- pixeltable/iterators/document.py +10 -8
- pixeltable/iterators/video.py +126 -60
- pixeltable/metadata/__init__.py +4 -3
- pixeltable/metadata/converters/convert_14.py +4 -2
- pixeltable/metadata/converters/convert_15.py +1 -1
- pixeltable/metadata/converters/convert_19.py +1 -0
- pixeltable/metadata/converters/convert_20.py +1 -1
- pixeltable/metadata/converters/convert_21.py +34 -0
- pixeltable/metadata/converters/util.py +54 -12
- pixeltable/metadata/notes.py +1 -0
- pixeltable/metadata/schema.py +40 -21
- pixeltable/plan.py +149 -165
- pixeltable/py.typed +0 -0
- pixeltable/store.py +57 -37
- pixeltable/tool/create_test_db_dump.py +6 -6
- pixeltable/tool/create_test_video.py +1 -1
- pixeltable/tool/doc_plugins/griffe.py +3 -34
- pixeltable/tool/embed_udf.py +1 -1
- pixeltable/tool/mypy_plugin.py +55 -0
- pixeltable/type_system.py +260 -61
- pixeltable/utils/arrow.py +10 -9
- pixeltable/utils/coco.py +4 -4
- pixeltable/utils/documents.py +16 -2
- pixeltable/utils/filecache.py +9 -9
- pixeltable/utils/formatter.py +10 -11
- pixeltable/utils/http_server.py +2 -5
- pixeltable/utils/media_store.py +6 -6
- pixeltable/utils/pytorch.py +10 -11
- pixeltable/utils/sql.py +2 -1
- {pixeltable-0.2.20.dist-info → pixeltable-0.2.22.dist-info}/METADATA +50 -13
- pixeltable-0.2.22.dist-info/RECORD +153 -0
- pixeltable/exec/media_validation_node.py +0 -43
- pixeltable/utils/help.py +0 -11
- pixeltable-0.2.20.dist-info/RECORD +0 -147
- {pixeltable-0.2.20.dist-info → pixeltable-0.2.22.dist-info}/LICENSE +0 -0
- {pixeltable-0.2.20.dist-info → pixeltable-0.2.22.dist-info}/WHEEL +0 -0
- {pixeltable-0.2.20.dist-info → pixeltable-0.2.22.dist-info}/entry_points.txt +0 -0
pixeltable/functions/globals.py
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
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,44 @@ 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(
|
|
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:
|
|
20
|
-
|
|
21
|
-
def update(self, val:
|
|
22
|
-
if val is
|
|
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
|
+
@sum.to_sql
|
|
40
|
+
def _(val: sql.ColumnElement) -> Optional[sql.ColumnElement]:
|
|
41
|
+
# This can produce a Decimal. We are deliberately avoiding an explicit cast to a Bigint here, because that can
|
|
42
|
+
# cause overflows in Postgres. We're instead doing the conversion to the target type in SqlNode.__iter__().
|
|
43
|
+
return sql.sql.func.sum(val)
|
|
33
44
|
|
|
34
|
-
|
|
45
|
+
|
|
46
|
+
@func.uda(update_types=[ts.IntType(nullable=True)], value_type=ts.IntType(), allows_window=True, requires_order_by=False)
|
|
35
47
|
class count(func.Aggregator):
|
|
36
48
|
def __init__(self):
|
|
37
49
|
self.count = 0
|
|
38
50
|
|
|
39
|
-
def update(self, val: int) -> None:
|
|
51
|
+
def update(self, val: Optional[int]) -> None:
|
|
40
52
|
if val is not None:
|
|
41
53
|
self.count += 1
|
|
42
54
|
|
|
@@ -44,57 +56,87 @@ class count(func.Aggregator):
|
|
|
44
56
|
return self.count
|
|
45
57
|
|
|
46
58
|
|
|
47
|
-
@
|
|
48
|
-
|
|
59
|
+
@count.to_sql
|
|
60
|
+
def _(val: sql.ColumnElement) -> Optional[sql.ColumnElement]:
|
|
61
|
+
return sql.sql.func.count(val)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@func.uda(
|
|
65
|
+
update_types=[ts.IntType(nullable=True)], value_type=ts.IntType(nullable=True), allows_window=True,
|
|
66
|
+
requires_order_by=False)
|
|
67
|
+
class min(func.Aggregator):
|
|
49
68
|
def __init__(self):
|
|
50
|
-
self.val = None
|
|
69
|
+
self.val: Optional[int] = None
|
|
51
70
|
|
|
52
|
-
def update(self, val: Optional[
|
|
53
|
-
if val is
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
def update(self, val: Optional[int]) -> None:
|
|
72
|
+
if val is None:
|
|
73
|
+
return
|
|
74
|
+
if self.val is None:
|
|
75
|
+
self.val = val
|
|
76
|
+
else:
|
|
77
|
+
self.val = builtins.min(self.val, val)
|
|
59
78
|
|
|
60
|
-
def value(self) -> Optional[
|
|
79
|
+
def value(self) -> Optional[int]:
|
|
61
80
|
return self.val
|
|
62
81
|
|
|
63
82
|
|
|
64
|
-
@
|
|
65
|
-
|
|
83
|
+
@min.to_sql
|
|
84
|
+
def _(val: sql.ColumnElement) -> Optional[sql.ColumnElement]:
|
|
85
|
+
return sql.sql.func.min(val)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@func.uda(
|
|
89
|
+
update_types=[ts.IntType(nullable=True)], value_type=ts.IntType(nullable=True), allows_window=True,
|
|
90
|
+
requires_order_by=False)
|
|
91
|
+
class max(func.Aggregator):
|
|
66
92
|
def __init__(self):
|
|
67
|
-
self.val = None
|
|
93
|
+
self.val: Optional[int] = None
|
|
68
94
|
|
|
69
|
-
def update(self, val: Optional[
|
|
70
|
-
if val is
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
95
|
+
def update(self, val: Optional[int]) -> None:
|
|
96
|
+
if val is None:
|
|
97
|
+
return
|
|
98
|
+
if self.val is None:
|
|
99
|
+
self.val = val
|
|
100
|
+
else:
|
|
101
|
+
self.val = builtins.max(self.val, val)
|
|
76
102
|
|
|
77
|
-
def value(self) -> Optional[
|
|
103
|
+
def value(self) -> Optional[int]:
|
|
78
104
|
return self.val
|
|
79
105
|
|
|
80
106
|
|
|
81
|
-
@
|
|
107
|
+
@max.to_sql
|
|
108
|
+
def _(val: sql.ColumnElement) -> Optional[sql.ColumnElement]:
|
|
109
|
+
return sql.sql.func.max(val)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@func.uda(
|
|
113
|
+
update_types=[ts.IntType(nullable=True)], value_type=ts.FloatType(nullable=True), allows_window=False,
|
|
114
|
+
requires_order_by=False)
|
|
82
115
|
class mean(func.Aggregator):
|
|
83
116
|
def __init__(self):
|
|
84
|
-
self.sum =
|
|
117
|
+
self.sum: Optional[int] = None
|
|
85
118
|
self.count = 0
|
|
86
119
|
|
|
87
|
-
def update(self, val: int) -> None:
|
|
88
|
-
if val is
|
|
120
|
+
def update(self, val: Optional[int]) -> None:
|
|
121
|
+
if val is None:
|
|
122
|
+
return
|
|
123
|
+
if self.sum is None:
|
|
124
|
+
self.sum = val
|
|
125
|
+
else:
|
|
89
126
|
self.sum += val
|
|
90
|
-
|
|
127
|
+
self.count += 1
|
|
91
128
|
|
|
92
|
-
def value(self) -> float:
|
|
129
|
+
def value(self) -> Optional[float]:
|
|
93
130
|
if self.count == 0:
|
|
94
131
|
return None
|
|
95
132
|
return self.sum / self.count
|
|
96
133
|
|
|
97
134
|
|
|
135
|
+
@mean.to_sql
|
|
136
|
+
def _(val: sql.ColumnElement) -> Optional[sql.ColumnElement]:
|
|
137
|
+
return sql.sql.func.avg(val)
|
|
138
|
+
|
|
139
|
+
|
|
98
140
|
__all__ = local_public_names(__name__)
|
|
99
141
|
|
|
100
142
|
|
|
@@ -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
|
|
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[
|
|
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) ->
|
|
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
|
|
65
|
+
return pxt.ArrayType((model.get_sentence_embedding_dimension(),), dtype=pxt.FloatType(), nullable=False)
|
|
68
66
|
except ImportError:
|
|
69
|
-
return
|
|
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
|
|
132
|
-
def clip_text(text: Batch[str], *, model_id: str) -> Batch[
|
|
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
|
|
170
|
-
def clip_image(image: Batch[PIL.Image.Image], *, model_id: str) -> Batch[
|
|
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) ->
|
|
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
|
|
212
|
+
return pxt.ArrayType((model.config.projection_dim,), dtype=pxt.FloatType(), nullable=False)
|
|
215
213
|
except ImportError:
|
|
216
|
-
return
|
|
214
|
+
return pxt.ArrayType((None,), dtype=pxt.FloatType(), nullable=False)
|
|
217
215
|
|
|
218
216
|
|
|
219
217
|
@pxt.udf(batch_size=4)
|
|
@@ -288,7 +286,7 @@ def vit_for_image_classification(
|
|
|
288
286
|
*,
|
|
289
287
|
model_id: str,
|
|
290
288
|
top_k: int = 5
|
|
291
|
-
) -> Batch[
|
|
289
|
+
) -> Batch[dict[str, Any]]:
|
|
292
290
|
"""
|
|
293
291
|
Computes image classifications for the specified image using a Vision Transformer (ViT) model.
|
|
294
292
|
`model_id` should be a reference to a pretrained [ViT Model](https://huggingface.co/docs/transformers/en/model_doc/vit).
|
|
@@ -309,24 +307,24 @@ def vit_for_image_classification(
|
|
|
309
307
|
top_k: The number of classes to return.
|
|
310
308
|
|
|
311
309
|
Returns:
|
|
312
|
-
A
|
|
313
|
-
in the following format:
|
|
310
|
+
A dictionary containing the output of the image classification model, in the following format:
|
|
314
311
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
312
|
+
```python
|
|
313
|
+
{
|
|
314
|
+
'scores': [0.325, 0.198, 0.105], # list of probabilities of the top-k most likely classes
|
|
315
|
+
'labels': [340, 353, 386], # list of class IDs for the top-k most likely classes
|
|
316
|
+
'label_text': ['zebra', 'gazelle', 'African elephant, Loxodonta africana'],
|
|
317
|
+
# corresponding text names of the top-k most likely classes
|
|
318
|
+
```
|
|
322
319
|
|
|
323
320
|
Examples:
|
|
324
321
|
Add a computed column that applies the model `google/vit-base-patch16-224` to an existing
|
|
325
|
-
Pixeltable column `image` of the table `tbl
|
|
322
|
+
Pixeltable column `image` of the table `tbl`, returning the 10 most likely classes for each image:
|
|
326
323
|
|
|
327
324
|
>>> tbl['image_class'] = vit_for_image_classification(
|
|
328
325
|
... tbl.image,
|
|
329
|
-
... model_id='google/vit-base-patch16-224'
|
|
326
|
+
... model_id='google/vit-base-patch16-224',
|
|
327
|
+
... top_k=10
|
|
330
328
|
... )
|
|
331
329
|
"""
|
|
332
330
|
env.Env.get().require_package('transformers')
|
|
@@ -346,15 +344,14 @@ def vit_for_image_classification(
|
|
|
346
344
|
probs = torch.softmax(logits, dim=-1)
|
|
347
345
|
top_k_probs, top_k_indices = torch.topk(probs, top_k, dim=-1)
|
|
348
346
|
|
|
347
|
+
# There is no official post_process method for ViT models; for consistency, we structure the output
|
|
348
|
+
# the same way as the output of the DETR model given by `post_process_object_detection`.
|
|
349
349
|
return [
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
}
|
|
356
|
-
for k in range(top_k_probs.shape[1])
|
|
357
|
-
]
|
|
350
|
+
{
|
|
351
|
+
'scores': [top_k_probs[n, k].item() for k in range(top_k_probs.shape[1])],
|
|
352
|
+
'labels': [top_k_indices[n, k].item() for k in range(top_k_probs.shape[1])],
|
|
353
|
+
'label_text': [model.config.id2label[top_k_indices[n, k].item()] for k in range(top_k_probs.shape[1])],
|
|
354
|
+
}
|
|
358
355
|
for n in range(top_k_probs.shape[0])
|
|
359
356
|
]
|
|
360
357
|
|
pixeltable/functions/image.py
CHANGED
|
@@ -15,13 +15,12 @@ from typing import Optional
|
|
|
15
15
|
|
|
16
16
|
import PIL.Image
|
|
17
17
|
|
|
18
|
-
import pixeltable
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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) ->
|
|
87
|
+
def _(self: Expr, mode: str) -> pxt.ColumnType:
|
|
89
88
|
input_type = self.col_type
|
|
90
|
-
assert isinstance(input_type,
|
|
91
|
-
return
|
|
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
|
-
@
|
|
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]) ->
|
|
107
|
+
def _(self: Expr, box: tuple[int, int, int, int]) -> pxt.ColumnType:
|
|
109
108
|
input_type = self.col_type
|
|
110
|
-
assert isinstance(input_type,
|
|
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
|
|
113
|
-
return
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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) ->
|
|
146
|
+
def _(self: Expr) -> pxt.ColumnType:
|
|
148
147
|
input_type = self.col_type
|
|
149
|
-
assert isinstance(input_type,
|
|
150
|
-
return
|
|
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
|
-
@
|
|
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]) ->
|
|
180
|
+
def _(self: Expr, size: tuple[int, int]) -> pxt.ColumnType:
|
|
167
181
|
input_type = self.col_type
|
|
168
|
-
assert isinstance(input_type,
|
|
169
|
-
return
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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) ->
|
|
232
|
+
def _(self: Expr) -> pxt.ColumnType:
|
|
219
233
|
return self.col_type
|
|
220
234
|
|
|
221
235
|
|
|
222
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
301
|
-
def getpixel(self: PIL.Image.Image, xy:
|
|
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))
|
|
326
|
+
return self.getpixel(tuple(xy))
|
|
313
327
|
|
|
314
328
|
|
|
315
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
407
|
+
@pxt.udf(is_property=True)
|
|
394
408
|
def mode(self: PIL.Image.Image) -> str:
|
|
395
409
|
return self.mode
|
|
396
410
|
|