pixeltable 0.2.5__py3-none-any.whl → 0.2.7__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 (110) hide show
  1. pixeltable/__init__.py +20 -9
  2. pixeltable/__version__.py +3 -0
  3. pixeltable/catalog/column.py +23 -7
  4. pixeltable/catalog/insertable_table.py +32 -19
  5. pixeltable/catalog/table.py +210 -20
  6. pixeltable/catalog/table_version.py +272 -111
  7. pixeltable/catalog/table_version_path.py +6 -1
  8. pixeltable/dataframe.py +184 -110
  9. pixeltable/datatransfer/__init__.py +1 -0
  10. pixeltable/datatransfer/label_studio.py +526 -0
  11. pixeltable/datatransfer/remote.py +113 -0
  12. pixeltable/env.py +213 -79
  13. pixeltable/exec/__init__.py +2 -1
  14. pixeltable/exec/data_row_batch.py +6 -7
  15. pixeltable/exec/expr_eval_node.py +28 -28
  16. pixeltable/exec/sql_scan_node.py +7 -6
  17. pixeltable/exprs/__init__.py +4 -3
  18. pixeltable/exprs/column_ref.py +11 -2
  19. pixeltable/exprs/comparison.py +39 -1
  20. pixeltable/exprs/data_row.py +7 -0
  21. pixeltable/exprs/expr.py +26 -19
  22. pixeltable/exprs/function_call.py +17 -18
  23. pixeltable/exprs/globals.py +14 -2
  24. pixeltable/exprs/image_member_access.py +9 -28
  25. pixeltable/exprs/in_predicate.py +96 -0
  26. pixeltable/exprs/inline_array.py +13 -11
  27. pixeltable/exprs/inline_dict.py +15 -13
  28. pixeltable/exprs/row_builder.py +7 -1
  29. pixeltable/exprs/similarity_expr.py +67 -0
  30. pixeltable/ext/functions/whisperx.py +30 -0
  31. pixeltable/ext/functions/yolox.py +16 -0
  32. pixeltable/func/__init__.py +0 -2
  33. pixeltable/func/aggregate_function.py +5 -2
  34. pixeltable/func/callable_function.py +57 -13
  35. pixeltable/func/expr_template_function.py +14 -3
  36. pixeltable/func/function.py +35 -4
  37. pixeltable/func/signature.py +5 -15
  38. pixeltable/func/udf.py +8 -12
  39. pixeltable/functions/fireworks.py +9 -4
  40. pixeltable/functions/huggingface.py +48 -5
  41. pixeltable/functions/openai.py +49 -11
  42. pixeltable/functions/pil/image.py +61 -64
  43. pixeltable/functions/together.py +32 -6
  44. pixeltable/functions/util.py +0 -43
  45. pixeltable/functions/video.py +46 -8
  46. pixeltable/globals.py +443 -0
  47. pixeltable/index/__init__.py +1 -0
  48. pixeltable/index/base.py +9 -2
  49. pixeltable/index/btree.py +54 -0
  50. pixeltable/index/embedding_index.py +91 -15
  51. pixeltable/io/__init__.py +4 -0
  52. pixeltable/io/globals.py +59 -0
  53. pixeltable/{utils → io}/hf_datasets.py +48 -17
  54. pixeltable/io/pandas.py +148 -0
  55. pixeltable/{utils → io}/parquet.py +58 -33
  56. pixeltable/iterators/__init__.py +1 -1
  57. pixeltable/iterators/base.py +8 -4
  58. pixeltable/iterators/document.py +225 -93
  59. pixeltable/iterators/video.py +16 -9
  60. pixeltable/metadata/__init__.py +8 -4
  61. pixeltable/metadata/converters/convert_12.py +3 -0
  62. pixeltable/metadata/converters/convert_13.py +41 -0
  63. pixeltable/metadata/converters/convert_14.py +13 -0
  64. pixeltable/metadata/converters/convert_15.py +29 -0
  65. pixeltable/metadata/converters/util.py +63 -0
  66. pixeltable/metadata/schema.py +12 -6
  67. pixeltable/plan.py +11 -24
  68. pixeltable/store.py +16 -23
  69. pixeltable/tool/create_test_db_dump.py +49 -14
  70. pixeltable/type_system.py +27 -58
  71. pixeltable/utils/coco.py +94 -0
  72. pixeltable/utils/documents.py +42 -12
  73. pixeltable/utils/http_server.py +70 -0
  74. pixeltable-0.2.7.dist-info/METADATA +137 -0
  75. pixeltable-0.2.7.dist-info/RECORD +126 -0
  76. {pixeltable-0.2.5.dist-info → pixeltable-0.2.7.dist-info}/WHEEL +1 -1
  77. pixeltable/client.py +0 -600
  78. pixeltable/exprs/image_similarity_predicate.py +0 -58
  79. pixeltable/func/batched_function.py +0 -53
  80. pixeltable/func/nos_function.py +0 -202
  81. pixeltable/tests/conftest.py +0 -171
  82. pixeltable/tests/ext/test_yolox.py +0 -21
  83. pixeltable/tests/functions/test_fireworks.py +0 -43
  84. pixeltable/tests/functions/test_functions.py +0 -60
  85. pixeltable/tests/functions/test_huggingface.py +0 -158
  86. pixeltable/tests/functions/test_openai.py +0 -162
  87. pixeltable/tests/functions/test_together.py +0 -112
  88. pixeltable/tests/test_audio.py +0 -65
  89. pixeltable/tests/test_catalog.py +0 -27
  90. pixeltable/tests/test_client.py +0 -21
  91. pixeltable/tests/test_component_view.py +0 -379
  92. pixeltable/tests/test_dataframe.py +0 -440
  93. pixeltable/tests/test_dirs.py +0 -107
  94. pixeltable/tests/test_document.py +0 -120
  95. pixeltable/tests/test_exprs.py +0 -802
  96. pixeltable/tests/test_function.py +0 -332
  97. pixeltable/tests/test_index.py +0 -138
  98. pixeltable/tests/test_migration.py +0 -44
  99. pixeltable/tests/test_nos.py +0 -54
  100. pixeltable/tests/test_snapshot.py +0 -231
  101. pixeltable/tests/test_table.py +0 -1343
  102. pixeltable/tests/test_transactional_directory.py +0 -42
  103. pixeltable/tests/test_types.py +0 -52
  104. pixeltable/tests/test_video.py +0 -159
  105. pixeltable/tests/test_view.py +0 -535
  106. pixeltable/tests/utils.py +0 -442
  107. pixeltable/utils/clip.py +0 -18
  108. pixeltable-0.2.5.dist-info/METADATA +0 -128
  109. pixeltable-0.2.5.dist-info/RECORD +0 -139
  110. {pixeltable-0.2.5.dist-info → pixeltable-0.2.7.dist-info}/LICENSE +0 -0
@@ -1,4 +1,4 @@
1
- from typing import Callable, TypeVar, Optional
1
+ from typing import Callable, TypeVar, Optional, Any
2
2
 
3
3
  import PIL.Image
4
4
  import numpy as np
@@ -14,6 +14,7 @@ from pixeltable.functions.util import resolve_torch_device
14
14
  def sentence_transformer(
15
15
  sentences: Batch[str], *, model_id: str, normalize_embeddings: bool = False
16
16
  ) -> Batch[np.ndarray]:
17
+ """Runs the specified sentence transformer model."""
17
18
  env.Env.get().require_package('sentence_transformers')
18
19
  from sentence_transformers import SentenceTransformer
19
20
 
@@ -23,6 +24,16 @@ def sentence_transformer(
23
24
  return [array[i] for i in range(array.shape[0])]
24
25
 
25
26
 
27
+ @sentence_transformer.conditional_return_type
28
+ def _(model_id: str) -> ts.ArrayType:
29
+ try:
30
+ from sentence_transformers import SentenceTransformer
31
+ model = _lookup_model(model_id, SentenceTransformer)
32
+ return ts.ArrayType((model.get_sentence_embedding_dimension(),), dtype=ts.FloatType(), nullable=False)
33
+ except ImportError:
34
+ return ts.ArrayType((None,), dtype=ts.FloatType(), nullable=False)
35
+
36
+
26
37
  @pxt.udf
27
38
  def sentence_transformer_list(sentences: list, *, model_id: str, normalize_embeddings: bool = False) -> list:
28
39
  env.Env.get().require_package('sentence_transformers')
@@ -36,6 +47,7 @@ def sentence_transformer_list(sentences: list, *, model_id: str, normalize_embed
36
47
 
37
48
  @pxt.udf(batch_size=32)
38
49
  def cross_encoder(sentences1: Batch[str], sentences2: Batch[str], *, model_id: str) -> Batch[float]:
50
+ """Runs the specified cross-encoder model."""
39
51
  env.Env.get().require_package('sentence_transformers')
40
52
  from sentence_transformers import CrossEncoder
41
53
 
@@ -56,15 +68,15 @@ def cross_encoder_list(sentence1: str, sentences2: list, *, model_id: str) -> li
56
68
  return array.tolist()
57
69
 
58
70
 
59
- @pxt.udf(batch_size=32, return_type=ts.ArrayType((512,), dtype=ts.FloatType(), nullable=False))
71
+ @pxt.udf(batch_size=32, return_type=ts.ArrayType((None,), dtype=ts.FloatType(), nullable=False))
60
72
  def clip_text(text: Batch[str], *, model_id: str) -> Batch[np.ndarray]:
73
+ """Runs the specified CLIP model on text."""
61
74
  env.Env.get().require_package('transformers')
62
75
  device = resolve_torch_device('auto')
63
76
  import torch
64
77
  from transformers import CLIPModel, CLIPProcessor
65
78
 
66
79
  model = _lookup_model(model_id, CLIPModel.from_pretrained, device=device)
67
- assert model.config.projection_dim == 512
68
80
  processor = _lookup_processor(model_id, CLIPProcessor.from_pretrained)
69
81
 
70
82
  with torch.no_grad():
@@ -74,15 +86,15 @@ def clip_text(text: Batch[str], *, model_id: str) -> Batch[np.ndarray]:
74
86
  return [embeddings[i] for i in range(embeddings.shape[0])]
75
87
 
76
88
 
77
- @pxt.udf(batch_size=32, return_type=ts.ArrayType((512,), dtype=ts.FloatType(), nullable=False))
89
+ @pxt.udf(batch_size=32, return_type=ts.ArrayType((None,), dtype=ts.FloatType(), nullable=False))
78
90
  def clip_image(image: Batch[PIL.Image.Image], *, model_id: str) -> Batch[np.ndarray]:
91
+ """Runs the specified CLIP model on images."""
79
92
  env.Env.get().require_package('transformers')
80
93
  device = resolve_torch_device('auto')
81
94
  import torch
82
95
  from transformers import CLIPModel, CLIPProcessor
83
96
 
84
97
  model = _lookup_model(model_id, CLIPModel.from_pretrained, device=device)
85
- assert model.config.projection_dim == 512
86
98
  processor = _lookup_processor(model_id, CLIPProcessor.from_pretrained)
87
99
 
88
100
  with torch.no_grad():
@@ -92,8 +104,20 @@ def clip_image(image: Batch[PIL.Image.Image], *, model_id: str) -> Batch[np.ndar
92
104
  return [embeddings[i] for i in range(embeddings.shape[0])]
93
105
 
94
106
 
107
+ @clip_text.conditional_return_type
108
+ @clip_image.conditional_return_type
109
+ def _(model_id: str) -> ts.ArrayType:
110
+ try:
111
+ from transformers import CLIPModel
112
+ model = _lookup_model(model_id, CLIPModel.from_pretrained)
113
+ return ts.ArrayType((model.config.projection_dim,), dtype=ts.FloatType(), nullable=False)
114
+ except ImportError:
115
+ return ts.ArrayType((None,), dtype=ts.FloatType(), nullable=False)
116
+
117
+
95
118
  @pxt.udf(batch_size=4)
96
119
  def detr_for_object_detection(image: Batch[PIL.Image.Image], *, model_id: str, threshold: float = 0.5) -> Batch[dict]:
120
+ """Runs the specified DETR model."""
97
121
  env.Env.get().require_package('transformers')
98
122
  device = resolve_torch_device('auto')
99
123
  import torch
@@ -121,6 +145,25 @@ def detr_for_object_detection(image: Batch[PIL.Image.Image], *, model_id: str, t
121
145
  ]
122
146
 
123
147
 
148
+ @pxt.udf
149
+ def detr_to_coco(image: PIL.Image.Image, detr_info: dict[str, Any]) -> dict[str, Any]:
150
+ bboxes, labels = detr_info['boxes'], detr_info['labels']
151
+ annotations = [
152
+ {
153
+ 'bbox': [bbox[0], bbox[1], bbox[2] - bbox[0], bbox[3] - bbox[1]],
154
+ 'category': label
155
+ }
156
+ for bbox, label in zip(bboxes, labels)
157
+ ]
158
+ return {
159
+ 'image': {
160
+ 'width': image.width,
161
+ 'height': image.height
162
+ },
163
+ 'annotations': annotations
164
+ }
165
+
166
+
124
167
  T = TypeVar('T')
125
168
 
126
169
 
@@ -16,8 +16,13 @@ from pixeltable import env
16
16
  from pixeltable.func import Batch
17
17
 
18
18
 
19
- def openai_client() -> openai.OpenAI:
20
- return env.Env.get().get_client('openai', lambda api_key: openai.OpenAI(api_key=api_key))
19
+ @env.register_client('openai')
20
+ def _(api_key: str) -> openai.OpenAI:
21
+ return openai.OpenAI(api_key=api_key)
22
+
23
+
24
+ def _openai_client() -> openai.OpenAI:
25
+ return env.Env.get().get_client('openai')
21
26
 
22
27
 
23
28
  # Exponential backoff decorator using tenacity.
@@ -44,7 +49,7 @@ def speech(
44
49
  response_format: Optional[str] = None,
45
50
  speed: Optional[float] = None
46
51
  ) -> str:
47
- content = openai_client().audio.speech.create(
52
+ content = _openai_client().audio.speech.create(
48
53
  input=input,
49
54
  model=model,
50
55
  voice=voice,
@@ -53,7 +58,7 @@ def speech(
53
58
  )
54
59
  ext = response_format or 'mp3'
55
60
  output_filename = str(env.Env.get().tmp_dir / f"{uuid.uuid4()}.{ext}")
56
- content.stream_to_file(output_filename, chunk_size=1 << 20)
61
+ content.write_to_file(output_filename)
57
62
  return output_filename
58
63
 
59
64
 
@@ -71,7 +76,7 @@ def transcriptions(
71
76
  temperature: Optional[float] = None
72
77
  ) -> dict:
73
78
  file = pathlib.Path(audio)
74
- transcription = openai_client().audio.transcriptions.create(
79
+ transcription = _openai_client().audio.transcriptions.create(
75
80
  file=file,
76
81
  model=model,
77
82
  language=_opt(language),
@@ -93,7 +98,7 @@ def translations(
93
98
  temperature: Optional[float] = None
94
99
  ) -> dict:
95
100
  file = pathlib.Path(audio)
96
- translation = openai_client().audio.translations.create(
101
+ translation = _openai_client().audio.translations.create(
97
102
  file=file,
98
103
  model=model,
99
104
  prompt=_opt(prompt),
@@ -127,7 +132,7 @@ def chat_completions(
127
132
  tool_choice: Optional[dict] = None,
128
133
  user: Optional[str] = None
129
134
  ) -> dict:
130
- result = openai_client().chat.completions.create(
135
+ result = _openai_client().chat.completions.create(
131
136
  messages=messages,
132
137
  model=model,
133
138
  frequency_penalty=_opt(frequency_penalty),
@@ -171,7 +176,7 @@ def vision(
171
176
  }}
172
177
  ]}
173
178
  ]
174
- result = openai_client().chat.completions.create(
179
+ result = _openai_client().chat.completions.create(
175
180
  messages=messages,
176
181
  model=model
177
182
  )
@@ -181,17 +186,26 @@ def vision(
181
186
  #####################################
182
187
  # Embeddings Endpoints
183
188
 
189
+ _embedding_dimensions_cache: dict[str, int] = {
190
+ 'text-embedding-ada-002': 1536,
191
+ 'text-embedding-3-small': 1536,
192
+ 'text-embedding-3-large': 3072,
193
+ }
194
+
195
+
184
196
  @pxt.udf(batch_size=32, return_type=ts.ArrayType((None,), dtype=ts.FloatType()))
185
197
  @_retry
186
198
  def embeddings(
187
199
  input: Batch[str],
188
200
  *,
189
201
  model: str,
202
+ dimensions: Optional[int] = None,
190
203
  user: Optional[str] = None
191
204
  ) -> Batch[np.ndarray]:
192
- result = openai_client().embeddings.create(
205
+ result = _openai_client().embeddings.create(
193
206
  input=input,
194
207
  model=model,
208
+ dimensions=_opt(dimensions),
195
209
  user=_opt(user),
196
210
  encoding_format='float'
197
211
  )
@@ -201,6 +215,16 @@ def embeddings(
201
215
  ]
202
216
 
203
217
 
218
+ @embeddings.conditional_return_type
219
+ def _(model: str, dimensions: Optional[int] = None) -> ts.ArrayType:
220
+ if dimensions is None:
221
+ if model not in _embedding_dimensions_cache:
222
+ # TODO: find some other way to retrieve a sample
223
+ return ts.ArrayType((None,), dtype=ts.FloatType(), nullable=False)
224
+ dimensions = _embedding_dimensions_cache.get(model, None)
225
+ return ts.ArrayType((dimensions,), dtype=ts.FloatType(), nullable=False)
226
+
227
+
204
228
  #####################################
205
229
  # Images Endpoints
206
230
 
@@ -216,7 +240,7 @@ def image_generations(
216
240
  user: Optional[str] = None
217
241
  ) -> PIL.Image.Image:
218
242
  # TODO(aaron-siegel): Decompose CPU/GPU ops into separate functions
219
- result = openai_client().images.generate(
243
+ result = _openai_client().images.generate(
220
244
  prompt=prompt,
221
245
  model=_opt(model),
222
246
  quality=_opt(quality),
@@ -232,6 +256,20 @@ def image_generations(
232
256
  return img
233
257
 
234
258
 
259
+ @image_generations.conditional_return_type
260
+ def _(size: Optional[str] = None) -> ts.ImageType:
261
+ if size is None:
262
+ return ts.ImageType(size=(1024, 1024))
263
+ x_pos = size.find('x')
264
+ if x_pos == -1:
265
+ return ts.ImageType()
266
+ try:
267
+ width, height = int(size[:x_pos]), int(size[x_pos + 1:])
268
+ except ValueError:
269
+ return ts.ImageType()
270
+ return ts.ImageType(size=(width, height))
271
+
272
+
235
273
  #####################################
236
274
  # Moderations Endpoints
237
275
 
@@ -242,7 +280,7 @@ def moderations(
242
280
  *,
243
281
  model: Optional[str] = None
244
282
  ) -> dict:
245
- result = openai_client().moderations.create(
283
+ result = _openai_client().moderations.create(
246
284
  input=input,
247
285
  model=_opt(model)
248
286
  )
@@ -1,16 +1,12 @@
1
- from typing import Dict, Any, Tuple, Optional
1
+ from typing import Tuple, Optional
2
2
 
3
3
  import PIL.Image
4
+ from PIL.Image import Dither
4
5
 
5
- from pixeltable.type_system import FloatType, ImageType, IntType, ArrayType, ColumnType, StringType, JsonType, BoolType
6
6
  import pixeltable.func as func
7
+ from pixeltable.type_system import FloatType, ImageType, IntType, ArrayType, ColumnType, StringType, JsonType
7
8
 
8
9
 
9
- def _caller_return_type(bound_args: Optional[Dict[str, Any]]) -> ColumnType:
10
- if bound_args is None:
11
- return ImageType()
12
- return bound_args['self'].col_type
13
-
14
10
  @func.udf(
15
11
  py_fn=PIL.Image.alpha_composite, return_type=ImageType(), param_types=[ImageType(), ImageType()])
16
12
  def alpha_composite(im1: PIL.Image.Image, im2: PIL.Image.Image) -> PIL.Image.Image:
@@ -28,71 +24,78 @@ def composite(image1: PIL.Image.Image, image2: PIL.Image.Image, mask: PIL.Image.
28
24
  # PIL.Image.Image methods
29
25
 
30
26
  # Image.convert()
31
- def _convert_return_type(bound_args: Dict[str, Any]) -> ColumnType:
32
- if bound_args is None:
33
- return ImageType()
34
- assert 'self' in bound_args
35
- assert 'mode' in bound_args
36
- img_type = bound_args['self'].col_type
37
- return ImageType(size=img_type.size, mode=bound_args['mode'])
38
- @func.udf(return_type=_convert_return_type, param_types=[ImageType(), StringType()])
27
+ @func.udf(param_types=[ImageType(), StringType()])
39
28
  def convert(self: PIL.Image.Image, mode: str) -> PIL.Image.Image:
40
29
  return self.convert(mode)
41
30
 
31
+
32
+ @convert.conditional_return_type
33
+ def _(self: PIL.Image.Image, mode: str) -> ColumnType:
34
+ input_type = self.col_type
35
+ assert input_type.is_image_type()
36
+ return ImageType(size=input_type.size, mode=mode, nullable=input_type.nullable)
37
+
38
+
42
39
  # Image.crop()
43
- def _crop_return_type(bound_args: Dict[str, Any]) -> ColumnType:
44
- if bound_args is None:
45
- return ImageType()
46
- img_type = bound_args['self'].col_type
47
- box = bound_args['box']
48
- if isinstance(box, list) and all(isinstance(x, int) for x in box):
49
- return ImageType(size=(box[2] - box[0], box[3] - box[1]), mode=img_type.mode)
50
- return ImageType() # we can't compute the size statically
51
40
  @func.udf(
52
- py_fn=PIL.Image.Image.crop, return_type=_crop_return_type,
41
+ py_fn=PIL.Image.Image.crop,
53
42
  param_types=[ImageType(), ArrayType((4,), dtype=IntType())])
54
43
  def crop(self: PIL.Image.Image, box: Tuple[int, int, int, int]) -> PIL.Image.Image:
55
44
  pass
56
45
 
46
+ @crop.conditional_return_type
47
+ def _(self: PIL.Image.Image, box: Tuple[int, int, int, int]) -> ColumnType:
48
+ input_type = self.col_type
49
+ assert input_type.is_image_type()
50
+ if isinstance(box, list) and all(isinstance(x, int) for x in box):
51
+ return ImageType(size=(box[2] - box[0], box[3] - box[1]), mode=input_type.mode, nullable=input_type.nullable)
52
+ return ImageType(mode=input_type.mode, nullable=input_type.nullable) # we can't compute the size statically
53
+
57
54
  # Image.getchannel()
58
- def _getchannel_return_type(bound_args: Dict[str, Any]) -> ColumnType:
59
- if bound_args is None:
60
- return ImageType()
61
- img_type = bound_args['self'].col_type
62
- return ImageType(size=img_type.size, mode='L')
63
- @func.udf(
64
- py_fn=PIL.Image.Image.getchannel, return_type=_getchannel_return_type, param_types=[ImageType(), IntType()])
55
+ @func.udf(py_fn=PIL.Image.Image.getchannel, param_types=[ImageType(), IntType()])
65
56
  def getchannel(self: PIL.Image.Image, channel: int) -> PIL.Image.Image:
66
57
  pass
67
58
 
59
+ @getchannel.conditional_return_type
60
+ def _(self: PIL.Image.Image) -> ColumnType:
61
+ input_type = self.col_type
62
+ assert input_type.is_image_type()
63
+ return ImageType(size=input_type.size, mode='L', nullable=input_type.nullable)
64
+
65
+
68
66
  # Image.resize()
69
- def resize_return_type(bound_args: Dict[str, Any]) -> ColumnType:
70
- if bound_args is None:
71
- return ImageType()
72
- assert 'size' in bound_args
73
- return ImageType(size=bound_args['size'])
74
- @func.udf(return_type=resize_return_type, param_types=[ImageType(), ArrayType((2, ), dtype=IntType())])
67
+ @func.udf(param_types=[ImageType(), ArrayType((2, ), dtype=IntType())])
75
68
  def resize(self: PIL.Image.Image, size: Tuple[int, int]) -> PIL.Image.Image:
76
69
  return self.resize(size)
77
70
 
71
+ @resize.conditional_return_type
72
+ def _(self: PIL.Image.Image, size: Tuple[int, int]) -> ColumnType:
73
+ input_type = self.col_type
74
+ assert input_type.is_image_type()
75
+ return ImageType(size=size, mode=input_type.mode, nullable=input_type.nullable)
76
+
78
77
  # Image.rotate()
79
- @func.udf(return_type=ImageType(), param_types=[ImageType(), IntType()])
78
+ @func.udf(param_types=[ImageType(), IntType()])
80
79
  def rotate(self: PIL.Image.Image, angle: int) -> PIL.Image.Image:
81
80
  return self.rotate(angle)
82
81
 
83
- # Image.transform()
84
- @func.udf(return_type= _caller_return_type, param_types=[ImageType(), ArrayType((2,), dtype=IntType()), IntType()])
85
- def transform(self: PIL.Image.Image, size: Tuple[int, int], method: int) -> PIL.Image.Image:
86
- return self.transform(size, method)
82
+ @func.udf(py_fn=PIL.Image.Image.effect_spread, param_types=[ImageType(), IntType()])
83
+ def effect_spread(self: PIL.Image.Image, distance: int) -> PIL.Image.Image:
84
+ pass
87
85
 
88
- @func.udf(
89
- py_fn=PIL.Image.Image.effect_spread, return_type=_caller_return_type, param_types=[ImageType(), FloatType()])
90
- def effect_spread(self: PIL.Image.Image, distance: float) -> PIL.Image.Image:
86
+ @func.udf(py_fn=PIL.Image.Image.transpose, param_types=[ImageType(), IntType()])
87
+ def transpose(self: PIL.Image.Image, method: int) -> PIL.Image.Image:
91
88
  pass
92
89
 
90
+ @rotate.conditional_return_type
91
+ @effect_spread.conditional_return_type
92
+ @transpose.conditional_return_type
93
+ def _(self: PIL.Image.Image) -> ColumnType:
94
+ return self.col_type
95
+
93
96
  @func.udf(
94
97
  py_fn=PIL.Image.Image.entropy, return_type=FloatType(), param_types=[ImageType(), ImageType(), JsonType()])
95
- def entropy(self: PIL.Image.Image, mask: PIL.Image.Image, histogram: Dict) -> float:
98
+ def entropy(self: PIL.Image.Image, mask: PIL.Image.Image, extrema: Optional[list] = None) -> float:
96
99
  pass
97
100
 
98
101
  @func.udf(py_fn=PIL.Image.Image.getbands, return_type=JsonType(), param_types=[ImageType()])
@@ -103,8 +106,7 @@ def getbands(self: PIL.Image.Image) -> Tuple[str]:
103
106
  def getbbox(self: PIL.Image.Image) -> Tuple[int, int, int, int]:
104
107
  pass
105
108
 
106
- @func.udf(
107
- py_fn=PIL.Image.Image.getcolors, return_type=JsonType(), param_types=[ImageType(), IntType()])
109
+ @func.udf(py_fn=PIL.Image.Image.getcolors, return_type=JsonType(), param_types=[ImageType(), IntType()])
108
110
  def getcolors(self: PIL.Image.Image, maxcolors: int) -> Tuple[Tuple[int, int, int], int]:
109
111
  pass
110
112
 
@@ -114,37 +116,32 @@ def getextrema(self: PIL.Image.Image) -> Tuple[int, int]:
114
116
 
115
117
  @func.udf(
116
118
  py_fn=PIL.Image.Image.getpalette, return_type=JsonType(), param_types=[ImageType(), StringType()])
117
- def getpalette(self: PIL.Image.Image, mode: str) -> Tuple[int]:
119
+ def getpalette(self: PIL.Image.Image, mode: Optional[str] = None) -> Tuple[int]:
118
120
  pass
119
121
 
120
122
  @func.udf(
121
- py_fn=PIL.Image.Image.getpixel, return_type=JsonType(), param_types=[ImageType(), ArrayType((2,), dtype=IntType())])
122
- def getpixel(self: PIL.Image.Image, xy: Tuple[int, int]) -> Tuple[int]:
123
- pass
123
+ return_type=JsonType(), param_types=[ImageType(), ArrayType((2,), dtype=IntType())])
124
+ def getpixel(self: PIL.Image.Image, xy: tuple[int, int]) -> Tuple[int]:
125
+ # `xy` will be a list; `tuple(xy)` is necessary for pillow 9 compatibility
126
+ return self.getpixel(tuple(xy))
124
127
 
125
- @func.udf(
126
- py_fn=PIL.Image.Image.getprojection, return_type=JsonType(), param_types=[ImageType()])
128
+ @func.udf(py_fn=PIL.Image.Image.getprojection, return_type=JsonType(), param_types=[ImageType()])
127
129
  def getprojection(self: PIL.Image.Image) -> Tuple[int]:
128
130
  pass
129
131
 
130
- @func.udf(
131
- py_fn=PIL.Image.Image.histogram, return_type=JsonType(), param_types=[ImageType(), ImageType(), JsonType()])
132
- def histogram(self: PIL.Image.Image, mask: PIL.Image.Image, histogram: Dict) -> Tuple[int]:
132
+ @func.udf(py_fn=PIL.Image.Image.histogram, return_type=JsonType(), param_types=[ImageType(), ImageType(), JsonType()])
133
+ def histogram(self: PIL.Image.Image, mask: PIL.Image.Image, extrema: Optional[list] = None) -> Tuple[int]:
133
134
  pass
134
135
 
135
136
  @func.udf(
136
137
  py_fn=PIL.Image.Image.quantize, return_type=ImageType(),
137
138
  param_types=[ImageType(), IntType(), IntType(nullable=True), IntType(), IntType(nullable=True), IntType()])
138
139
  def quantize(
139
- self: PIL.Image.Image, colors: int, method: int, kmeans: int, palette: int, dither: int) -> PIL.Image.Image:
140
+ self: PIL.Image.Image, colors: int = 256, method: Optional[int] = None, kmeans: int = 0,
141
+ palette: Optional[int] = None, dither: int = Dither.FLOYDSTEINBERG) -> PIL.Image.Image:
140
142
  pass
141
143
 
142
144
  @func.udf(
143
145
  py_fn=PIL.Image.Image.reduce, return_type=ImageType(), param_types=[ImageType(), IntType(), JsonType()])
144
- def reduce(self: PIL.Image.Image, factor: int, filter: Tuple[int]) -> PIL.Image.Image:
145
- pass
146
-
147
- @func.udf(
148
- py_fn=PIL.Image.Image.transpose, return_type=_caller_return_type, param_types=[ImageType(), IntType()])
149
- def transpose(self: PIL.Image.Image, method: int) -> PIL.Image.Image:
146
+ def reduce(self: PIL.Image.Image, factor: int, box: Optional[Tuple[int]]) -> PIL.Image.Image:
150
147
  pass
@@ -11,8 +11,13 @@ from pixeltable import env
11
11
  from pixeltable.func import Batch
12
12
 
13
13
 
14
- def together_client() -> together.Together:
15
- return env.Env.get().get_client('together', lambda api_key: together.Together(api_key=api_key))
14
+ @env.register_client('together')
15
+ def _(api_key: str) -> together.Together:
16
+ return together.Together(api_key=api_key)
17
+
18
+
19
+ def _together_client() -> together.Together:
20
+ return env.Env.get().get_client('together')
16
21
 
17
22
 
18
23
  @pxt.udf
@@ -31,7 +36,7 @@ def completions(
31
36
  n: Optional[int] = None,
32
37
  safety_model: Optional[str] = None
33
38
  ) -> dict:
34
- return together_client().completions.create(
39
+ return _together_client().completions.create(
35
40
  prompt=prompt,
36
41
  model=model,
37
42
  max_tokens=max_tokens,
@@ -66,7 +71,7 @@ def chat_completions(
66
71
  tools: Optional[dict] = None,
67
72
  tool_choice: Optional[dict] = None
68
73
  ) -> dict:
69
- return together_client().chat.completions.create(
74
+ return _together_client().chat.completions.create(
70
75
  messages=messages,
71
76
  model=model,
72
77
  max_tokens=max_tokens,
@@ -85,15 +90,36 @@ def chat_completions(
85
90
  ).dict()
86
91
 
87
92
 
93
+ _embedding_dimensions_cache = {
94
+ 'togethercomputer/m2-bert-80M-2k-retrieval': 768,
95
+ 'togethercomputer/m2-bert-80M-8k-retrieval': 768,
96
+ 'togethercomputer/m2-bert-80M-32k-retrieval': 768,
97
+ 'WhereIsAI/UAE-Large-V1': 1024,
98
+ 'BAAI/bge-large-en-v1.5': 1024,
99
+ 'BAAI/bge-base-en-v1.5': 768,
100
+ 'sentence-transformers/msmarco-bert-base-dot-v5': 768,
101
+ 'bert-base-uncased': 768,
102
+ }
103
+
104
+
88
105
  @pxt.udf(batch_size=32, return_type=pxt.ArrayType((None,), dtype=pxt.FloatType()))
89
106
  def embeddings(input: Batch[str], *, model: str) -> Batch[np.ndarray]:
90
- result = together_client().embeddings.create(input=input, model=model)
107
+ result = _together_client().embeddings.create(input=input, model=model)
91
108
  return [
92
109
  np.array(data.embedding, dtype=np.float64)
93
110
  for data in result.data
94
111
  ]
95
112
 
96
113
 
114
+ @embeddings.conditional_return_type
115
+ def _(model: str) -> pxt.ArrayType:
116
+ if model not in _embedding_dimensions_cache:
117
+ # TODO: find some other way to retrieve a sample
118
+ return pxt.ArrayType((None,), dtype=pxt.FloatType())
119
+ dimensions = _embedding_dimensions_cache[model]
120
+ return pxt.ArrayType((dimensions,), dtype=pxt.FloatType())
121
+
122
+
97
123
  @pxt.udf
98
124
  def image_generations(
99
125
  prompt: str,
@@ -106,7 +132,7 @@ def image_generations(
106
132
  negative_prompt: Optional[str] = None,
107
133
  ) -> PIL.Image.Image:
108
134
  # TODO(aaron-siegel): Decompose CPU/GPU ops into separate functions
109
- result = together_client().images.generate(
135
+ result = _together_client().images.generate(
110
136
  prompt=prompt,
111
137
  model=model,
112
138
  steps=steps,
@@ -1,46 +1,3 @@
1
- from typing import Tuple, List, Optional
2
- import types
3
- import sys
4
-
5
- import pixeltable.func as func
6
- import pixeltable.type_system as ts
7
- import pixeltable.env as env
8
-
9
-
10
- def create_nos_modules() -> List[types.ModuleType]:
11
- """Create module pixeltable.functions.nos with one submodule per task and return the submodules"""
12
- models = env.Env.get().nos_client.ListModels()
13
- model_info = [env.Env.get().nos_client.GetModelInfo(model) for model in models]
14
- model_info.sort(key=lambda info: info.task.value)
15
-
16
- module_name = 'pixeltable.functions.nos'
17
- nos_module = types.ModuleType(module_name)
18
- nos_module.__package__ = 'pixeltable.functions'
19
- sys.modules[module_name] = nos_module
20
-
21
- prev_task = ''
22
- new_modules: List[types.ModuleType] = []
23
- sub_module: Optional[types.ModuleType] = None
24
- for info in model_info:
25
- if info.task.value != prev_task:
26
- # we construct one submodule per task
27
- namespace = info.task.name.lower()
28
- submodule_name = f'{module_name}.{namespace}'
29
- sub_module = types.ModuleType(submodule_name)
30
- sub_module.__package__ = module_name
31
- setattr(nos_module, namespace, sub_module)
32
- new_modules.append(sub_module)
33
- sys.modules[submodule_name] = sub_module
34
- prev_task = info.task.value
35
-
36
- # add a Function for this model to the module
37
- model_id = info.name.replace("/", "_").replace("-", "_")
38
- pt_func = func.NOSFunction(info, f'{submodule_name}.{model_id}')
39
- setattr(sub_module, model_id, pt_func)
40
-
41
- return new_modules
42
-
43
-
44
1
  def resolve_torch_device(device: str) -> str:
45
2
  import torch
46
3
  if device == 'auto':
@@ -1,14 +1,13 @@
1
- from typing import Optional
2
1
  import uuid
2
+ from typing import Optional
3
+
3
4
  import av
4
- import sys
5
5
 
6
6
  import pixeltable.env as env
7
7
  import pixeltable.func as func
8
8
  import pixeltable.type_system as ts
9
9
 
10
-
11
- _format_defaults = { # format -> (codec, ext)
10
+ _format_defaults = { # format -> (codec, ext)
12
11
  'wav': ('pcm_s16le', 'wav'),
13
12
  'mp3': ('libmp3lame', 'mp3'),
14
13
  'flac': ('flac', 'flac'),
@@ -35,11 +34,13 @@ _extract_audio_param_types = [
35
34
  ts.VideoType(nullable=False),
36
35
  ts.IntType(nullable=False),
37
36
  ts.StringType(nullable=False),
38
- ts.StringType(nullable=False)
37
+ ts.StringType(nullable=True),
39
38
  ]
39
+
40
+
40
41
  @func.udf(return_type=ts.AudioType(nullable=True), param_types=_extract_audio_param_types)
41
42
  def extract_audio(
42
- video_path: str, stream_idx: int = 0, format: str = 'wav', codec: Optional[str] = None
43
+ video_path: str, stream_idx: int = 0, format: str = 'wav', codec: Optional[str] = None
43
44
  ) -> Optional[str]:
44
45
  """Extract an audio stream from a video file, save it as a media file and return its path"""
45
46
  if format not in _format_defaults:
@@ -51,12 +52,49 @@ def extract_audio(
51
52
  return None
52
53
  audio_stream = container.streams.audio[stream_idx]
53
54
  # create this in our tmp directory, so it'll get cleaned up if it's being generated as part of a query
54
- output_filename = str(env.Env.get().tmp_dir / f"{uuid.uuid4()}.{ext}")
55
+ output_filename = str(env.Env.get().tmp_dir / f'{uuid.uuid4()}.{ext}')
55
56
 
56
- with av.open(output_filename, "w", format=format) as output_container:
57
+ with av.open(output_filename, 'w', format=format) as output_container:
57
58
  output_stream = output_container.add_stream(codec or default_codec)
58
59
  for packet in container.demux(audio_stream):
59
60
  for frame in packet.decode():
60
61
  output_container.mux(output_stream.encode(frame))
61
62
 
62
63
  return output_filename
64
+
65
+
66
+ @func.udf(return_type=ts.JsonType(nullable=False), param_types=[ts.VideoType(nullable=False)])
67
+ def get_metadata(video: str) -> dict:
68
+ """Gets various metadata associated with a video file.
69
+
70
+ Args:
71
+ video (str): Path to the video file.
72
+
73
+ Returns:
74
+ A dictionary containing the associated metadata.
75
+ """
76
+ with av.open(video) as container:
77
+ assert isinstance(container, av.container.InputContainer)
78
+ video_streams_info = [
79
+ {
80
+ 'duration': stream.duration,
81
+ 'frames': stream.frames,
82
+ 'language': stream.language,
83
+ 'average_rate': float(stream.average_rate) if stream.average_rate is not None else None,
84
+ 'base_rate': float(stream.base_rate) if stream.base_rate is not None else None,
85
+ 'guessed_rate': float(stream.guessed_rate) if stream.guessed_rate is not None else None,
86
+ 'pix_fmt': getattr(stream.codec_context, 'pix_fmt', None),
87
+ 'width': stream.width,
88
+ 'height': stream.height,
89
+ }
90
+ for stream in container.streams
91
+ if isinstance(stream, av.video.stream.VideoStream)
92
+ ]
93
+ result = {
94
+ 'bit_exact': container.bit_exact,
95
+ 'bit_rate': container.bit_rate,
96
+ 'size': container.size,
97
+ 'metadata': container.metadata,
98
+ 'streams': video_streams_info, # TODO: Audio streams?
99
+ }
100
+ return result