pixeltable 0.2.13__py3-none-any.whl → 0.2.15__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 (58) hide show
  1. pixeltable/__init__.py +1 -1
  2. pixeltable/__version__.py +2 -2
  3. pixeltable/catalog/column.py +8 -3
  4. pixeltable/catalog/globals.py +8 -0
  5. pixeltable/catalog/table.py +25 -9
  6. pixeltable/catalog/table_version.py +30 -55
  7. pixeltable/catalog/view.py +1 -1
  8. pixeltable/env.py +4 -4
  9. pixeltable/exec/__init__.py +2 -1
  10. pixeltable/exec/row_update_node.py +61 -0
  11. pixeltable/exec/{sql_scan_node.py → sql_node.py} +120 -56
  12. pixeltable/exprs/__init__.py +1 -1
  13. pixeltable/exprs/arithmetic_expr.py +41 -16
  14. pixeltable/exprs/expr.py +72 -22
  15. pixeltable/exprs/function_call.py +64 -29
  16. pixeltable/exprs/globals.py +5 -1
  17. pixeltable/exprs/inline_array.py +18 -11
  18. pixeltable/exprs/method_ref.py +63 -0
  19. pixeltable/ext/__init__.py +9 -0
  20. pixeltable/ext/functions/__init__.py +8 -0
  21. pixeltable/ext/functions/whisperx.py +45 -5
  22. pixeltable/ext/functions/yolox.py +60 -14
  23. pixeltable/func/callable_function.py +12 -4
  24. pixeltable/func/expr_template_function.py +1 -1
  25. pixeltable/func/function.py +12 -2
  26. pixeltable/func/function_registry.py +24 -9
  27. pixeltable/func/udf.py +32 -4
  28. pixeltable/functions/__init__.py +1 -1
  29. pixeltable/functions/fireworks.py +33 -0
  30. pixeltable/functions/huggingface.py +96 -6
  31. pixeltable/functions/image.py +226 -41
  32. pixeltable/functions/json.py +46 -0
  33. pixeltable/functions/openai.py +214 -0
  34. pixeltable/functions/string.py +195 -218
  35. pixeltable/functions/timestamp.py +210 -0
  36. pixeltable/functions/together.py +106 -0
  37. pixeltable/functions/video.py +2 -2
  38. pixeltable/functions/{eval.py → vision.py} +170 -27
  39. pixeltable/functions/whisper.py +32 -0
  40. pixeltable/io/__init__.py +1 -1
  41. pixeltable/io/external_store.py +2 -2
  42. pixeltable/io/globals.py +133 -1
  43. pixeltable/io/pandas.py +82 -31
  44. pixeltable/iterators/video.py +55 -23
  45. pixeltable/metadata/__init__.py +1 -1
  46. pixeltable/metadata/converters/convert_18.py +39 -0
  47. pixeltable/metadata/notes.py +10 -0
  48. pixeltable/plan.py +76 -1
  49. pixeltable/store.py +65 -28
  50. pixeltable/tool/create_test_db_dump.py +8 -9
  51. pixeltable/tool/doc_plugins/griffe.py +4 -0
  52. pixeltable/type_system.py +84 -63
  53. {pixeltable-0.2.13.dist-info → pixeltable-0.2.15.dist-info}/METADATA +2 -2
  54. {pixeltable-0.2.13.dist-info → pixeltable-0.2.15.dist-info}/RECORD +57 -51
  55. pixeltable/exprs/image_member_access.py +0 -96
  56. {pixeltable-0.2.13.dist-info → pixeltable-0.2.15.dist-info}/LICENSE +0 -0
  57. {pixeltable-0.2.13.dist-info → pixeltable-0.2.15.dist-info}/WHEEL +0 -0
  58. {pixeltable-0.2.13.dist-info → pixeltable-0.2.15.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,210 @@
1
+ """
2
+ Pixeltable [UDFs](https://pixeltable.readme.io/docs/user-defined-functions-udfs) for `TimestampType`.
3
+
4
+ Usage example:
5
+ ```python
6
+ import pixeltable as pxt
7
+
8
+ t = pxt.get_table(...)
9
+ t.select(t.timestamp_col.year, t.timestamp_col.weekday()).collect()
10
+ ```
11
+ """
12
+
13
+ from datetime import datetime
14
+ from typing import Optional
15
+
16
+ import pixeltable.func as func
17
+ from pixeltable.utils.code import local_public_names
18
+
19
+
20
+ @func.udf(is_method=True)
21
+ def year(self: datetime) -> int:
22
+ """
23
+ Between [`MINYEAR`](https://docs.python.org/3/library/datetime.html#datetime.MINYEAR) and
24
+ [`MAXYEAR`](https://docs.python.org/3/library/datetime.html#datetime.MAXYEAR) inclusive.
25
+
26
+ Equivalent to [`datetime.year`](https://docs.python.org/3/library/datetime.html#datetime.datetime.year).
27
+ """
28
+ return self.year
29
+
30
+
31
+ @func.udf(is_method=True)
32
+ def month(self: datetime) -> int:
33
+ """
34
+ Between 1 and 12 inclusive.
35
+
36
+ Equivalent to [`datetime.month`](https://docs.python.org/3/library/datetime.html#datetime.datetime.month).
37
+ """
38
+ return self.month
39
+
40
+
41
+ @func.udf(is_method=True)
42
+ def day(self: datetime) -> int:
43
+ """
44
+ Between 1 and the number of days in the given month of the given year.
45
+
46
+ Equivalent to [`datetime.day`](https://docs.python.org/3/library/datetime.html#datetime.datetime.day).
47
+ """
48
+ return self.day
49
+
50
+
51
+ @func.udf(is_method=True)
52
+ def hour(self: datetime) -> int:
53
+ """
54
+ Between 0 and 23 inclusive.
55
+
56
+ Equivalent to [`datetime.hour`](https://docs.python.org/3/library/datetime.html#datetime.datetime.hour).
57
+ """
58
+ return self.hour
59
+
60
+
61
+ @func.udf(is_method=True)
62
+ def minute(self: datetime) -> int:
63
+ """
64
+ Between 0 and 59 inclusive.
65
+
66
+ Equivalent to [`datetime.minute`](https://docs.python.org/3/library/datetime.html#datetime.datetime.minute).
67
+ """
68
+ return self.minute
69
+
70
+
71
+ @func.udf(is_method=True)
72
+ def second(self: datetime) -> int:
73
+ """
74
+ Between 0 and 59 inclusive.
75
+
76
+ Equivalent to [`datetime.second`](https://docs.python.org/3/library/datetime.html#datetime.datetime.second).
77
+ """
78
+ return self.second
79
+
80
+
81
+ @func.udf(is_method=True)
82
+ def microsecond(self: datetime) -> int:
83
+ """
84
+ Between 0 and 999999 inclusive.
85
+
86
+ Equivalent to [`datetime.microsecond`](https://docs.python.org/3/library/datetime.html#datetime.datetime.microsecond).
87
+ """
88
+ return self.microsecond
89
+
90
+
91
+ @func.udf(is_method=True)
92
+ def weekday(self: datetime) -> int:
93
+ """
94
+ Between 0 (Monday) and 6 (Sunday) inclusive.
95
+
96
+ Equivalent to [`datetime.weekday()`](https://docs.python.org/3/library/datetime.html#datetime.datetime.weekday).
97
+ """
98
+ return self.weekday()
99
+
100
+ @func.udf(is_method=True)
101
+ def isoweekday(self: datetime) -> int:
102
+ """
103
+ Return the day of the week as an integer, where Monday is 1 and Sunday is 7.
104
+
105
+ Equivalent to [`datetime.isoweekday()`](https://docs.python.org/3/library/datetime.html#datetime.datetime.isoweekday).
106
+ """
107
+ return self.isoweekday()
108
+
109
+
110
+ @func.udf(is_method=True)
111
+ def isocalendar(self: datetime) -> dict:
112
+ """
113
+ Return a dictionary with three entries: `'year'`, `'week'`, and `'weekday'`.
114
+
115
+ Equivalent to
116
+ [`datetime.isocalendar()`](https://docs.python.org/3/library/datetime.html#datetime.datetime.isocalendar).
117
+ """
118
+ iso_year, iso_week, iso_weekday = self.isocalendar()
119
+ return {'year': iso_year, 'week': iso_week, 'weekday': iso_weekday}
120
+
121
+
122
+ @func.udf(is_method=True)
123
+ def isoformat(self: datetime, sep: str = 'T', timespec: str = 'auto') -> str:
124
+ """
125
+ Return a string representing the date and time in ISO 8601 format.
126
+
127
+ Equivalent to [`datetime.isoformat()`](https://docs.python.org/3/library/datetime.html#datetime.datetime.isoformat).
128
+
129
+ Args:
130
+ sep: Separator between date and time.
131
+ timespec: The number of additional terms in the output. See the [`datetime.isoformat()`](https://docs.python.org/3/library/datetime.html#datetime.datetime.isoformat) documentation for more details.
132
+ """
133
+ return self.isoformat(sep=sep, timespec=timespec)
134
+
135
+
136
+ @func.udf(is_method=True)
137
+ def strftime(self: datetime, format: str) -> str:
138
+ """
139
+ Return a string representing the date and time, controlled by an explicit format string.
140
+
141
+ Equivalent to [`datetime.strftime()`](https://docs.python.org/3/library/datetime.html#datetime.datetime.strftime).
142
+
143
+ Args:
144
+ format: The format string to control the output. For a complete list of formatting directives, see [`strftime()` and `strptime()` Behavior](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior).
145
+ """
146
+ return self.strftime(format)
147
+
148
+
149
+ # @func.udf
150
+ # def date(self: datetime) -> datetime:
151
+ # """
152
+ # Return the date part of the datetime.
153
+ #
154
+ # Equivalent to [`datetime.date()`](https://docs.python.org/3/library/datetime.html#datetime.datetime.date).
155
+ # """
156
+ # d = self.date()
157
+ # return datetime(d.year, d.month, d.day)
158
+ #
159
+ #
160
+ # @func.udf
161
+ # def time(self: datetime) -> datetime:
162
+ # """
163
+ # Return the time part of the datetime, with microseconds set to 0.
164
+ #
165
+ # Equivalent to [`datetime.time()`](https://docs.python.org/3/library/datetime.html#datetime.datetime.time).
166
+ # """
167
+ # t = self.time()
168
+ # return datetime(1, 1, 1, t.hour, t.minute, t.second, t.microsecond)
169
+
170
+
171
+ @func.udf(is_method=True)
172
+ def replace(
173
+ self: datetime, year: Optional[int] = None, month: Optional[int] = None, day: Optional[int] = None,
174
+ hour: Optional[int] = None, minute: Optional[int] = None, second: Optional[int] = None,
175
+ microsecond: Optional[int] = None) -> datetime:
176
+ """
177
+ Return a datetime with the same attributes, except for those attributes given new values by whichever keyword
178
+ arguments are specified.
179
+
180
+ Equivalent to [`datetime.replace()`](https://docs.python.org/3/library/datetime.html#datetime.datetime.replace).
181
+ """
182
+ kwargs = {k: v for k, v in locals().items() if k != 'self' and v is not None}
183
+ return self.replace(**kwargs)
184
+
185
+
186
+ @func.udf(is_method=True)
187
+ def toordinal(self: datetime) -> int:
188
+ """
189
+ Return the proleptic Gregorian ordinal of the date, where January 1 of year 1 has ordinal 1.
190
+
191
+ Equivalent to [`datetime.toordinal()`](https://docs.python.org/3/library/datetime.html#datetime.datetime.toordinal).
192
+ """
193
+ return self.toordinal()
194
+
195
+
196
+ @func.udf(is_method=True)
197
+ def posix_timestamp(self: datetime) -> float:
198
+ """
199
+ Return POSIX timestamp corresponding to the datetime instance.
200
+
201
+ Equivalent to [`datetime.timestamp()`](https://docs.python.org/3/library/datetime.html#datetime.datetime.timestamp).
202
+ """
203
+ return self.timestamp()
204
+
205
+
206
+ __all__ = local_public_names(__name__)
207
+
208
+
209
+ def __dir__():
210
+ return __all__
@@ -1,3 +1,10 @@
1
+ """
2
+ Pixeltable [UDFs](https://pixeltable.readme.io/docs/user-defined-functions-udfs)
3
+ that wrap various endpoints from the Together AI API. In order to use them, you must
4
+ first `pip install together` and configure your Together AI credentials, as described in
5
+ the [Working with Together AI](https://pixeltable.readme.io/docs/together-ai) tutorial.
6
+ """
7
+
1
8
  import base64
2
9
  from typing import Optional, TYPE_CHECKING
3
10
 
@@ -41,6 +48,31 @@ def completions(
41
48
  n: Optional[int] = None,
42
49
  safety_model: Optional[str] = None,
43
50
  ) -> dict:
51
+ """
52
+ Generate completions based on a given prompt using a specified model.
53
+
54
+ Equivalent to the Together AI `completions` API endpoint.
55
+ For additional details, see: [https://docs.together.ai/reference/completions-1](https://docs.together.ai/reference/completions-1)
56
+
57
+ __Requirements:__
58
+
59
+ - `pip install together`
60
+
61
+ Args:
62
+ prompt: A string providing context for the model to complete.
63
+ model: The name of the model to query.
64
+
65
+ For details on the other parameters, see: [https://docs.together.ai/reference/completions-1](https://docs.together.ai/reference/completions-1)
66
+
67
+ Returns:
68
+ A dictionary containing the response and other metadata.
69
+
70
+ Examples:
71
+ Add a computed column that applies the model `mistralai/Mixtral-8x7B-v0.1` to an existing Pixeltable column `tbl.prompt`
72
+ of the table `tbl`:
73
+
74
+ >>> tbl['response'] = completions(tbl.prompt, model='mistralai/Mixtral-8x7B-v0.1')
75
+ """
44
76
  return (
45
77
  _together_client()
46
78
  .completions.create(
@@ -80,6 +112,32 @@ def chat_completions(
80
112
  tools: Optional[dict] = None,
81
113
  tool_choice: Optional[dict] = None,
82
114
  ) -> dict:
115
+ """
116
+ Generate chat completions based on a given prompt using a specified model.
117
+
118
+ Equivalent to the Together AI `chat/completions` API endpoint.
119
+ For additional details, see: [https://docs.together.ai/reference/chat-completions-1](https://docs.together.ai/reference/chat-completions-1)
120
+
121
+ __Requirements:__
122
+
123
+ - `pip install together`
124
+
125
+ Args:
126
+ messages: A list of messages comprising the conversation so far.
127
+ model: The name of the model to query.
128
+
129
+ For details on the other parameters, see: [https://docs.together.ai/reference/chat-completions-1](https://docs.together.ai/reference/chat-completions-1)
130
+
131
+ Returns:
132
+ A dictionary containing the response and other metadata.
133
+
134
+ Examples:
135
+ Add a computed column that applies the model `mistralai/Mixtral-8x7B-v0.1` to an existing Pixeltable column `tbl.prompt`
136
+ of the table `tbl`:
137
+
138
+ >>> messages = [{'role': 'user', 'content': tbl.prompt}]
139
+ ... tbl['response'] = chat_completions(tbl.prompt, model='mistralai/Mixtral-8x7B-v0.1')
140
+ """
83
141
  return (
84
142
  _together_client()
85
143
  .chat.completions.create(
@@ -117,6 +175,29 @@ _embedding_dimensions_cache = {
117
175
 
118
176
  @pxt.udf(batch_size=32, return_type=pxt.ArrayType((None,), dtype=pxt.FloatType()))
119
177
  def embeddings(input: Batch[str], *, model: str) -> Batch[np.ndarray]:
178
+ """
179
+ Query an embedding model for a given string of text.
180
+
181
+ Equivalent to the Together AI `embeddings` API endpoint.
182
+ For additional details, see: [https://docs.together.ai/reference/embeddings-2](https://docs.together.ai/reference/embeddings-2)
183
+
184
+ __Requirements:__
185
+
186
+ - `pip install together`
187
+
188
+ Args:
189
+ input: A string providing the text for the model to embed.
190
+ model: The name of the embedding model to use.
191
+
192
+ Returns:
193
+ An array representing the application of the given embedding to `input`.
194
+
195
+ Examples:
196
+ Add a computed column that applies the model `togethercomputer/m2-bert-80M-8k-retrieval`
197
+ to an existing Pixeltable column `tbl.text` of the table `tbl`:
198
+
199
+ >>> tbl['response'] = embeddings(tbl.text, model='togethercomputer/m2-bert-80M-8k-retrieval')
200
+ """
120
201
  result = _together_client().embeddings.create(input=input, model=model)
121
202
  return [np.array(data.embedding, dtype=np.float64) for data in result.data]
122
203
 
@@ -141,6 +222,31 @@ def image_generations(
141
222
  width: Optional[int] = None,
142
223
  negative_prompt: Optional[str] = None,
143
224
  ) -> PIL.Image.Image:
225
+ """
226
+ Generate images based on a given prompt using a specified model.
227
+
228
+ Equivalent to the Together AI `images/generations` API endpoint.
229
+ For additional details, see: [https://docs.together.ai/reference/post_images-generations](https://docs.together.ai/reference/post_images-generations)
230
+
231
+ __Requirements:__
232
+
233
+ - `pip install together`
234
+
235
+ Args:
236
+ prompt: A description of the desired images.
237
+ model: The model to use for image generation.
238
+
239
+ For details on the other parameters, see: [https://docs.together.ai/reference/post_images-generations](https://docs.together.ai/reference/post_images-generations)
240
+
241
+ Returns:
242
+ The generated image.
243
+
244
+ Examples:
245
+ Add a computed column that applies the model `runwayml/stable-diffusion-v1-5`
246
+ to an existing Pixeltable column `tbl.prompt` of the table `tbl`:
247
+
248
+ >>> tbl['response'] = image_generations(tbl.prompt, model='runwayml/stable-diffusion-v1-5')
249
+ """
144
250
  # TODO(aaron-siegel): Decompose CPU/GPU ops into separate functions
145
251
  result = _together_client().images.generate(
146
252
  prompt=prompt, model=model, steps=steps, seed=seed, height=height, width=width, negative_prompt=negative_prompt
@@ -96,7 +96,7 @@ _extract_audio_param_types = [
96
96
  ]
97
97
 
98
98
 
99
- @func.udf(return_type=ts.AudioType(nullable=True), param_types=_extract_audio_param_types)
99
+ @func.udf(return_type=ts.AudioType(nullable=True), param_types=_extract_audio_param_types, is_method=True)
100
100
  def extract_audio(
101
101
  video_path: str, stream_idx: int = 0, format: str = 'wav', codec: Optional[str] = None
102
102
  ) -> Optional[str]:
@@ -128,7 +128,7 @@ def extract_audio(
128
128
  return output_filename
129
129
 
130
130
 
131
- @func.udf(return_type=ts.JsonType(nullable=False), param_types=[ts.VideoType(nullable=False)])
131
+ @func.udf(return_type=ts.JsonType(nullable=False), param_types=[ts.VideoType(nullable=False)], is_method=True)
132
132
  def get_metadata(video: str) -> dict:
133
133
  """
134
134
  Gets various metadata associated with a video file and returns it as a dictionary.
@@ -1,11 +1,29 @@
1
- from typing import List, Tuple, Dict
1
+ """
2
+ Pixeltable [UDFs](https://pixeltable.readme.io/docs/user-defined-functions-udfs) for Computer Vision.
3
+
4
+ Example:
5
+ ```python
6
+ import pixeltable as pxt
7
+ from pixeltable.functions import vision as pxtv
8
+
9
+ t = pxt.get_table(...)
10
+ t.select(pxtv.draw_bounding_boxes(t.img, boxes=t.boxes, label=t.labels)).collect()
11
+ ```
12
+ """
13
+
14
+ import colorsys
15
+ import hashlib
16
+ import random
2
17
  from collections import defaultdict
3
- import sys
18
+ from typing import Optional, Union, Any
4
19
 
20
+ import PIL.Image
21
+ import PIL.Image
5
22
  import numpy as np
6
23
 
7
- import pixeltable.type_system as ts
8
24
  import pixeltable.func as func
25
+ import pixeltable.type_system as ts
26
+ from pixeltable.utils.code import local_public_names
9
27
 
10
28
 
11
29
  # TODO: figure out a better submodule structure
@@ -14,7 +32,7 @@ import pixeltable.func as func
14
32
  # the following function has been adapted from MMEval
15
33
  # (sources at https://github.com/open-mmlab/mmeval)
16
34
  # Copyright (c) OpenMMLab. All rights reserved.
17
- def calculate_bboxes_area(bboxes: np.ndarray) -> np.ndarray:
35
+ def __calculate_bboxes_area(bboxes: np.ndarray) -> np.ndarray:
18
36
  """Calculate area of bounding boxes.
19
37
 
20
38
  Args:
@@ -31,7 +49,7 @@ def calculate_bboxes_area(bboxes: np.ndarray) -> np.ndarray:
31
49
  # the following function has been adapted from MMEval
32
50
  # (sources at https://github.com/open-mmlab/mmeval)
33
51
  # Copyright (c) OpenMMLab. All rights reserved.
34
- def calculate_overlaps(bboxes1: np.ndarray, bboxes2: np.ndarray) -> np.ndarray:
52
+ def __calculate_overlaps(bboxes1: np.ndarray, bboxes2: np.ndarray) -> np.ndarray:
35
53
  """Calculate the overlap between each bbox of bboxes1 and bboxes2.
36
54
 
37
55
  Args:
@@ -58,8 +76,8 @@ def calculate_overlaps(bboxes1: np.ndarray, bboxes2: np.ndarray) -> np.ndarray:
58
76
  exchange = False
59
77
 
60
78
  # Calculate the bboxes area.
61
- area1 = calculate_bboxes_area(bboxes1)
62
- area2 = calculate_bboxes_area(bboxes2)
79
+ area1 = __calculate_bboxes_area(bboxes1)
80
+ area2 = __calculate_bboxes_area(bboxes2)
63
81
  eps = np.finfo(np.float32).eps
64
82
 
65
83
  for i in range(bboxes1.shape[0]):
@@ -80,9 +98,9 @@ def calculate_overlaps(bboxes1: np.ndarray, bboxes2: np.ndarray) -> np.ndarray:
80
98
  # the following function has been adapted from MMEval
81
99
  # (sources at https://github.com/open-mmlab/mmeval)
82
100
  # Copyright (c) OpenMMLab. All rights reserved.
83
- def calculate_image_tpfp(
101
+ def __calculate_image_tpfp(
84
102
  pred_bboxes: np.ndarray, pred_scores: np.ndarray, gt_bboxes: np.ndarray, min_iou: float
85
- ) -> Tuple[np.ndarray, np.ndarray]:
103
+ ) -> tuple[np.ndarray, np.ndarray]:
86
104
  """Calculate the true positive and false positive on an image.
87
105
 
88
106
  Args:
@@ -95,11 +113,8 @@ def calculate_image_tpfp(
95
113
 
96
114
  Returns:
97
115
  tuple (tp, fp):
98
-
99
- - tp (numpy.ndarray): Shape (N,),
100
- the true positive flag of each predicted bbox on this image.
101
- - fp (numpy.ndarray): Shape (N,),
102
- the false positive flag of each predicted bbox on this image.
116
+ tp: Shape (N,), the true positive flag of each predicted bbox on this image.
117
+ fp: Shape (N,), the false positive flag of each predicted bbox on this image.
103
118
  """
104
119
  # Step 1. Concatenate `gt_bboxes` and `ignore_gt_bboxes`, then set
105
120
  # the `ignore_gt_flags`.
@@ -121,7 +136,7 @@ def calculate_image_tpfp(
121
136
 
122
137
  # Step 4. Calculate the IoUs between the predicted bboxes and the
123
138
  # ground truth bboxes.
124
- ious = calculate_overlaps(pred_bboxes, gt_bboxes)
139
+ ious = __calculate_overlaps(pred_bboxes, gt_bboxes)
125
140
  # For each pred bbox, the max iou with all gts.
126
141
  ious_max = ious.max(axis=1)
127
142
  # For each pred bbox, which gt overlaps most with it.
@@ -160,14 +175,17 @@ def calculate_image_tpfp(
160
175
  ],
161
176
  )
162
177
  def eval_detections(
163
- pred_bboxes: List[List[int]],
164
- pred_labels: List[int],
165
- pred_scores: List[float],
166
- gt_bboxes: List[List[int]],
167
- gt_labels: List[int],
168
- ) -> Dict:
178
+ pred_bboxes: list[list[int]],
179
+ pred_labels: list[int],
180
+ pred_scores: list[float],
181
+ gt_bboxes: list[list[int]],
182
+ gt_labels: list[int],
183
+ ) -> dict:
184
+ """
185
+ Evaluates the performance of a set of predicted bounding boxes against a set of ground truth bounding boxes.
186
+ """
169
187
  class_idxs = list(set(pred_labels + gt_labels))
170
- result: List[Dict] = []
188
+ result: list[dict] = []
171
189
  pred_bboxes_arr = np.asarray(pred_bboxes)
172
190
  pred_classes_arr = np.asarray(pred_labels)
173
191
  pred_scores_arr = np.asarray(pred_scores)
@@ -177,7 +195,7 @@ def eval_detections(
177
195
  pred_filter = pred_classes_arr == class_idx
178
196
  gt_filter = gt_classes_arr == class_idx
179
197
  class_pred_scores = pred_scores_arr[pred_filter]
180
- tp, fp = calculate_image_tpfp(pred_bboxes_arr[pred_filter], class_pred_scores, gt_bboxes_arr[gt_filter], [0.5])
198
+ tp, fp = __calculate_image_tpfp(pred_bboxes_arr[pred_filter], class_pred_scores, gt_bboxes_arr[gt_filter], [0.5])
181
199
  ordered_class_pred_scores = -np.sort(-class_pred_scores)
182
200
  result.append(
183
201
  {
@@ -194,17 +212,21 @@ def eval_detections(
194
212
 
195
213
  @func.uda(update_types=[ts.JsonType()], value_type=ts.JsonType(), allows_std_agg=True, allows_window=False)
196
214
  class mean_ap(func.Aggregator):
215
+ """
216
+ Calculates the mean average precision (mAP) over
217
+ [`eval_detections()`][pixeltable.functions.vision.eval_detections] results.
218
+ """
197
219
  def __init__(self):
198
- self.class_tpfp: Dict[int, List[Dict]] = defaultdict(list)
220
+ self.class_tpfp: dict[int, list[dict]] = defaultdict(list)
199
221
 
200
- def update(self, eval_dicts: List[Dict]) -> None:
222
+ def update(self, eval_dicts: list[dict]) -> None:
201
223
  for eval_dict in eval_dicts:
202
224
  class_idx = eval_dict['class']
203
225
  self.class_tpfp[class_idx].append(eval_dict)
204
226
 
205
- def value(self) -> Dict:
227
+ def value(self) -> dict:
206
228
  eps = np.finfo(np.float32).eps
207
- result: Dict[int, float] = {}
229
+ result: dict[int, float] = {}
208
230
  for class_idx, tpfp in self.class_tpfp.items():
209
231
  a1 = [x['tp'] for x in tpfp]
210
232
  tp = np.concatenate([x['tp'] for x in tpfp], axis=0)
@@ -225,3 +247,124 @@ class mean_ap(func.Aggregator):
225
247
  ap = np.sum((mrec[ind + 1] - mrec[ind]) * mpre[ind + 1])
226
248
  result[class_idx] = ap.item()
227
249
  return result
250
+
251
+
252
+ def _create_label_colors(labels: list[Any]) -> dict[Any, str]:
253
+ """
254
+ Create random colors for labels such that a particular label always gets the same color.
255
+
256
+ Returns:
257
+ dict mapping labels to colors
258
+ """
259
+ distinct_labels = set(labels)
260
+ result: dict[Any, str] = {}
261
+ for label in distinct_labels:
262
+ # consistent hash for the label
263
+ label_hash = int(hashlib.md5(str(label).encode()).hexdigest(), 16)
264
+ hue = (label_hash % 360) / 360.0
265
+ rgb = colorsys.hsv_to_rgb(hue, 0.7, 0.95)
266
+ hex_color = '#{:02x}{:02x}{:02x}'.format(int(rgb[0]*255), int(rgb[1]*255), int(rgb[2]*255))
267
+ result[label] = hex_color
268
+ return result
269
+
270
+
271
+ @func.udf
272
+ def draw_bounding_boxes(
273
+ img: PIL.Image.Image,
274
+ boxes: list[list[int]],
275
+ labels: Optional[list[Any]] = None,
276
+ color: Optional[str] = None,
277
+ label_colors: Optional[dict[Union[str, int], str]] = None,
278
+ box_colors: Optional[list[str]] = None,
279
+ fill: bool = False,
280
+ width: int = 1,
281
+ font: Optional[str] = None,
282
+ font_size: Optional[int] = None,
283
+ ) -> PIL.Image.Image:
284
+ """
285
+ Draws bounding boxes on the given image.
286
+
287
+ Labels can be any type that supports `str()` and is hashable (e.g., strings, ints, etc.).
288
+
289
+ Colors can be specified as common HTML color names (e.g., 'red') supported by PIL's
290
+ [`ImageColor`](https://pillow.readthedocs.io/en/stable/reference/ImageColor.html#imagecolor-module) module or as
291
+ RGB hex codes (e.g., '#FF0000').
292
+
293
+ If no colors are specified, this function randomly assigns each label a specific color based on a hash of the label.
294
+
295
+ Args:
296
+ img: The image on which to draw the bounding boxes.
297
+ boxes: List of bounding boxes, each represented as [xmin, ymin, xmax, ymax].
298
+ labels: List of labels for each bounding box.
299
+ color: Single color to be used for all bounding boxes and labels.
300
+ label_colors: Dictionary mapping labels to colors.
301
+ box_colors: List of colors, one per bounding box.
302
+ fill: Whether to fill the bounding boxes with color.
303
+ width: Width of the bounding box borders.
304
+ font: Name of a system font or path to a TrueType font file, as required by
305
+ [`PIL.ImageFont.truetype()`](https://pillow.readthedocs.io/en/stable/reference/ImageFont.html#PIL.ImageFont.truetype).
306
+ If `None`, uses the default provided by
307
+ [`PIL.ImageFont.load_default()`](https://pillow.readthedocs.io/en/stable/reference/ImageFont.html#PIL.ImageFont.load_default).
308
+ font_size: Size of the font used for labels in points. Only used in conjunction with non-`None` `font` argument.
309
+
310
+ Returns:
311
+ The image with bounding boxes drawn on it.
312
+ """
313
+ color_params = sum([color is not None, label_colors is not None, box_colors is not None])
314
+ if color_params > 1:
315
+ raise ValueError("Only one of 'color', 'label_colors', or 'box_colors' can be set")
316
+
317
+ # ensure the number of labels matches the number of boxes
318
+ num_boxes = len(boxes)
319
+ if labels is None:
320
+ labels = [None] * num_boxes
321
+ elif len(labels) != num_boxes:
322
+ raise ValueError('Number of boxes and labels must match')
323
+
324
+ DEFAULT_COLOR = 'white'
325
+ if box_colors is not None:
326
+ if len(box_colors) != num_boxes:
327
+ raise ValueError('Number of boxes and box colors must match')
328
+ else:
329
+ if color is not None:
330
+ box_colors = [color] * num_boxes
331
+ elif label_colors is not None:
332
+ box_colors = [label_colors.get(label, DEFAULT_COLOR) for label in labels]
333
+ else:
334
+ label_colors = _create_label_colors(labels)
335
+ box_colors = [label_colors[label] for label in labels]
336
+
337
+ from PIL import ImageDraw, ImageFont, ImageColor
338
+ # set default font if not provided
339
+ if font is None:
340
+ txt_font = ImageFont.load_default()
341
+ else:
342
+ txt_font = ImageFont.truetype(font=font, size=font_size or 10)
343
+
344
+ img_to_draw = img.copy()
345
+ draw = ImageDraw.Draw(img_to_draw, 'RGBA' if fill else 'RGB')
346
+
347
+ for i, (bbox, label) in enumerate(zip(boxes, labels)):
348
+ # determine color for the current box and label
349
+ color = box_colors[i % len(box_colors)]
350
+
351
+ if fill:
352
+ rgb_color = ImageColor.getrgb(color)
353
+ fill_color = rgb_color + (100,) # semi-transparent
354
+ draw.rectangle(bbox, outline=color, width=width, fill=fill_color)
355
+ else:
356
+ draw.rectangle(bbox, outline=color, width=width)
357
+
358
+ if label is not None:
359
+ label_str = str(label)
360
+ margin = width + 1
361
+ draw.text((bbox[0] + margin, bbox[1] + margin), label_str, fill=color, font=txt_font)
362
+
363
+ return img_to_draw
364
+
365
+
366
+ __all__ = local_public_names(__name__)
367
+
368
+
369
+ def __dir__():
370
+ return __all__
@@ -1,3 +1,11 @@
1
+ """
2
+ Pixeltable [UDF](https://pixeltable.readme.io/docs/user-defined-functions-udfs)
3
+ that wraps the OpenAI Whisper library.
4
+
5
+ This UDF will cause Pixeltable to invoke the relevant model locally. In order to use it, you must
6
+ first `pip install openai-whisper`.
7
+ """
8
+
1
9
  from typing import TYPE_CHECKING, Optional
2
10
 
3
11
  import pixeltable as pxt
@@ -39,6 +47,30 @@ def transcribe(
39
47
  append_punctuations: str = '"\'.。,,!!??::”)]}、',
40
48
  decode_options: Optional[dict] = None,
41
49
  ) -> dict:
50
+ """
51
+ Transcribe an audio file using Whisper.
52
+
53
+ This UDF runs a transcription model _locally_ using the Whisper library,
54
+ equivalent to the Whisper `transcribe` function, as described in the
55
+ [Whisper library documentation](https://github.com/openai/whisper).
56
+
57
+ __Requirements:__
58
+
59
+ - `pip install openai-whisper`
60
+
61
+ Args:
62
+ audio: The audio file to transcribe.
63
+ model: The name of the model to use for transcription.
64
+
65
+ Returns:
66
+ A dictionary containing the transcription and various other metadata.
67
+
68
+ Examples:
69
+ Add a computed column that applies the model `base.en` to an existing Pixeltable column `tbl.audio`
70
+ of the table `tbl`:
71
+
72
+ >>> tbl['result'] = transcribe(tbl.audio, model='base.en')
73
+ """
42
74
  import torch
43
75
 
44
76
  if decode_options is None:
pixeltable/io/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from .external_store import ExternalStore, SyncStatus
2
- from .globals import create_label_studio_project
2
+ from .globals import create_label_studio_project, import_rows, import_json
3
3
  from .hf_datasets import import_huggingface_dataset
4
4
  from .pandas import import_csv, import_excel, import_pandas
5
5
  from .parquet import import_parquet