Spaces:
Runtime error
Runtime error
Upload folder using huggingface_hub
Browse files- README.md +18 -21
- app.py +2 -2
- space.py +3 -3
- src/README.md +18 -5
- src/backend/gradio_image_annotation/image_annotator.py +11 -4
- src/backend/gradio_image_annotation/templates/component/index.js +0 -0
- src/backend/gradio_image_annotation/templates/component/style.css +1 -1
- src/demo/app.py +2 -2
- src/demo/space.py +3 -3
- src/frontend/Index.svelte +8 -2
- src/frontend/package-lock.json +19 -0
- src/frontend/package.json +1 -0
- src/frontend/shared/Box.ts +9 -0
- src/frontend/shared/Canvas.svelte +37 -1
- src/frontend/shared/ImageAnnotator.svelte +36 -6
- src/frontend/shared/ImageCanvas.svelte +2 -0
- src/pyproject.toml +1 -1
README.md
CHANGED
@@ -1,19 +1,3 @@
|
|
1 |
-
---
|
2 |
-
tags:
|
3 |
-
- gradio-custom-component
|
4 |
-
- gradio-template-Image
|
5 |
-
- bounding box
|
6 |
-
- annotator
|
7 |
-
- annotate
|
8 |
-
- boxes
|
9 |
-
title: gradio_image_annotation V0.2.2
|
10 |
-
colorFrom: yellow
|
11 |
-
colorTo: green
|
12 |
-
sdk: docker
|
13 |
-
pinned: false
|
14 |
-
license: apache-2.0
|
15 |
-
short_description: A Gradio component for image annotation
|
16 |
-
---
|
17 |
|
18 |
# `gradio_image_annotation`
|
19 |
<a href="https://pypi.org/project/gradio_image_annotation/" target="_blank"><img alt="PyPI - Version" src="https://img.shields.io/pypi/v/gradio_image_annotation"></a>
|
@@ -98,7 +82,7 @@ def get_boxes_json(annotations):
|
|
98 |
|
99 |
|
100 |
with gr.Blocks() as demo:
|
101 |
-
with gr.Tab("Object annotation"):
|
102 |
annotator = image_annotator(
|
103 |
example_annotation,
|
104 |
label_list=["Person", "Vehicle"],
|
@@ -108,7 +92,7 @@ with gr.Blocks() as demo:
|
|
108 |
json_boxes = gr.JSON()
|
109 |
button_get.click(get_boxes_json, annotator, json_boxes)
|
110 |
|
111 |
-
with gr.Tab("Crop"):
|
112 |
with gr.Row():
|
113 |
annotator_crop = image_annotator(
|
114 |
examples_crop[0],
|
@@ -325,12 +309,12 @@ int | str | None
|
|
325 |
<td align="left" style="width: 25%;">
|
326 |
|
327 |
```python
|
328 |
-
list["upload" | "clipboard"] | None
|
329 |
```
|
330 |
|
331 |
</td>
|
332 |
-
<td align="left"><code>["upload", "clipboard"]</code></td>
|
333 |
-
<td align="left">List of sources for the image. "upload" creates a box where user can drop an image file, "clipboard" allows users to paste an image from the clipboard. If None, defaults to ["upload", "clipboard"].</td>
|
334 |
</tr>
|
335 |
|
336 |
<tr>
|
@@ -527,6 +511,19 @@ bool | None
|
|
527 |
<td align="left"><code>None</code></td>
|
528 |
<td align="left">If True, will show a button to remove the selected bounding box.</td>
|
529 |
</tr>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
530 |
</tbody></table>
|
531 |
|
532 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
|
2 |
# `gradio_image_annotation`
|
3 |
<a href="https://pypi.org/project/gradio_image_annotation/" target="_blank"><img alt="PyPI - Version" src="https://img.shields.io/pypi/v/gradio_image_annotation"></a>
|
|
|
82 |
|
83 |
|
84 |
with gr.Blocks() as demo:
|
85 |
+
with gr.Tab("Object annotation", id="tab_object_annotation"):
|
86 |
annotator = image_annotator(
|
87 |
example_annotation,
|
88 |
label_list=["Person", "Vehicle"],
|
|
|
92 |
json_boxes = gr.JSON()
|
93 |
button_get.click(get_boxes_json, annotator, json_boxes)
|
94 |
|
95 |
+
with gr.Tab("Crop", id="tab_crop"):
|
96 |
with gr.Row():
|
97 |
annotator_crop = image_annotator(
|
98 |
examples_crop[0],
|
|
|
309 |
<td align="left" style="width: 25%;">
|
310 |
|
311 |
```python
|
312 |
+
list["upload" | "webcam" | "clipboard"] | None
|
313 |
```
|
314 |
|
315 |
</td>
|
316 |
+
<td align="left"><code>["upload", "webcam", "clipboard"]</code></td>
|
317 |
+
<td align="left">List of sources for the image. "upload" creates a box where user can drop an image file, "webcam" allows user to take snapshot from their webcam, "clipboard" allows users to paste an image from the clipboard. If None, defaults to ["upload", "webcam", "clipboard"].</td>
|
318 |
</tr>
|
319 |
|
320 |
<tr>
|
|
|
511 |
<td align="left"><code>None</code></td>
|
512 |
<td align="left">If True, will show a button to remove the selected bounding box.</td>
|
513 |
</tr>
|
514 |
+
|
515 |
+
<tr>
|
516 |
+
<td align="left"><code>handles_cursor</code></td>
|
517 |
+
<td align="left" style="width: 25%;">
|
518 |
+
|
519 |
+
```python
|
520 |
+
bool | None
|
521 |
+
```
|
522 |
+
|
523 |
+
</td>
|
524 |
+
<td align="left"><code>True</code></td>
|
525 |
+
<td align="left">If True, the cursor will change when hovering over box handles in drag mode. Can be CPU-intensive.</td>
|
526 |
+
</tr>
|
527 |
</tbody></table>
|
528 |
|
529 |
|
app.py
CHANGED
@@ -67,7 +67,7 @@ def get_boxes_json(annotations):
|
|
67 |
|
68 |
|
69 |
with gr.Blocks() as demo:
|
70 |
-
with gr.Tab("Object annotation"):
|
71 |
annotator = image_annotator(
|
72 |
example_annotation,
|
73 |
label_list=["Person", "Vehicle"],
|
@@ -77,7 +77,7 @@ with gr.Blocks() as demo:
|
|
77 |
json_boxes = gr.JSON()
|
78 |
button_get.click(get_boxes_json, annotator, json_boxes)
|
79 |
|
80 |
-
with gr.Tab("Crop"):
|
81 |
with gr.Row():
|
82 |
annotator_crop = image_annotator(
|
83 |
examples_crop[0],
|
|
|
67 |
|
68 |
|
69 |
with gr.Blocks() as demo:
|
70 |
+
with gr.Tab("Object annotation", id="tab_object_annotation"):
|
71 |
annotator = image_annotator(
|
72 |
example_annotation,
|
73 |
label_list=["Person", "Vehicle"],
|
|
|
77 |
json_boxes = gr.JSON()
|
78 |
button_get.click(get_boxes_json, annotator, json_boxes)
|
79 |
|
80 |
+
with gr.Tab("Crop", id="tab_crop"):
|
81 |
with gr.Row():
|
82 |
annotator_crop = image_annotator(
|
83 |
examples_crop[0],
|
space.py
CHANGED
@@ -3,7 +3,7 @@ import gradio as gr
|
|
3 |
from app import demo as app
|
4 |
import os
|
5 |
|
6 |
-
_docs = {'image_annotator': {'description': 'Creates a component to annotate images with bounding boxes. The bounding boxes can be created and edited by the user or be passed by code.\nIt is also possible to predefine a set of valid classes and colors.', 'members': {'__init__': {'value': {'type': 'dict | None', 'default': 'None', 'description': "A dict or None. The dictionary must contain a key 'image' with either an URL to an image, a numpy image or a PIL image. Optionally it may contain a key 'boxes' with a list of boxes. Each box must be a dict wit the keys: 'xmin', 'ymin', 'xmax' and 'ymax' with the absolute image coordinates of the box. Optionally can also include the keys 'label' and 'color' describing the label and color of the box. Color must be a tuple of RGB values (e.g. `(255,255,255)`)."}, 'boxes_alpha': {'type': 'float | None', 'default': 'None', 'description': 'Opacity of the bounding boxes 0 and 1.'}, 'label_list': {'type': 'list[str] | None', 'default': 'None', 'description': 'List of valid labels.'}, 'label_colors': {'type': 'list[str] | None', 'default': 'None', 'description': 'Optional list of colors for each label when `label_list` is used. Colors must be a tuple of RGB values (e.g. `(255,255,255)`).'}, 'box_min_size': {'type': 'int | None', 'default': 'None', 'description': 'Minimum valid bounding box size.'}, 'handle_size': {'type': 'int | None', 'default': 'None', 'description': 'Size of the bounding box resize handles.'}, 'box_thickness': {'type': 'int | None', 'default': 'None', 'description': 'Thickness of the bounding box outline.'}, 'box_selected_thickness': {'type': 'int | None', 'default': 'None', 'description': 'Thickness of the bounding box outline when it is selected.'}, 'disable_edit_boxes': {'type': 'bool | None', 'default': 'None', 'description': 'Disables the ability to set and edit the label and color of the boxes.'}, 'single_box': {'type': 'bool', 'default': 'False', 'description': 'If True, at most one box can be drawn.'}, 'height': {'type': 'int | str | None', 'default': 'None', 'description': 'The height of the displayed image, specified in pixels if a number is passed, or in CSS units if a string is passed.'}, 'width': {'type': 'int | str | None', 'default': 'None', 'description': 'The width of the displayed image, specified in pixels if a number is passed, or in CSS units if a string is passed.'}, 'image_mode': {'type': '"1"\n | "L"\n | "P"\n | "RGB"\n | "RGBA"\n | "CMYK"\n | "YCbCr"\n | "LAB"\n | "HSV"\n | "I"\n | "F"', 'default': '"RGB"', 'description': '"RGB" if color, or "L" if black and white. See https://pillow.readthedocs.io/en/stable/handbook/concepts.html for other supported image modes and their meaning.'}, 'sources': {'type': 'list["upload" | "clipboard"] | None', 'default': '["upload", "clipboard"]', 'description': 'List of sources for the image. "upload" creates a box where user can drop an image file, "clipboard" allows users to paste an image from the clipboard. If None, defaults to ["upload", "clipboard"].'}, 'image_type': {'type': '"numpy" | "pil" | "filepath"', 'default': '"numpy"', 'description': 'The format the image is converted before being passed into the prediction function. "numpy" converts the image to a numpy array with shape (height, width, 3) and values from 0 to 255, "pil" converts the image to a PIL image object, "filepath" passes a str path to a temporary file containing the image. If the image is SVG, the `type` is ignored and the filepath of the SVG is returned.'}, 'label': {'type': 'str | None', 'default': 'None', 'description': 'The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to.'}, 'container': {'type': 'bool', 'default': 'True', 'description': 'If True, will place the component in a container - providing some extra padding around the border.'}, 'scale': {'type': 'int | None', 'default': 'None', 'description': 'relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.'}, 'min_width': {'type': 'int', 'default': '160', 'description': 'minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.'}, 'interactive': {'type': 'bool | None', 'default': 'True', 'description': 'if True, will allow users to upload and annotate an image; if False, can only be used to display annotated images.'}, 'visible': {'type': 'bool', 'default': 'True', 'description': 'If False, component will be hidden.'}, 'elem_id': {'type': 'str | None', 'default': 'None', 'description': 'An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'elem_classes': {'type': 'list[str] | str | None', 'default': 'None', 'description': 'An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'render': {'type': 'bool', 'default': 'True', 'description': 'If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.'}, 'show_label': {'type': 'bool | None', 'default': 'None', 'description': 'if True, will display label.'}, 'show_download_button': {'type': 'bool', 'default': 'True', 'description': 'If True, will show a button to download the image.'}, 'show_share_button': {'type': 'bool | None', 'default': 'None', 'description': 'If True, will show a share icon in the corner of the component that allows user to share outputs to Hugging Face Spaces Discussions. If False, icon does not appear. If set to None (default behavior), then the icon appears if this Gradio app is launched on Spaces, but not otherwise.'}, 'show_clear_button': {'type': 'bool | None', 'default': 'True', 'description': 'If True, will show a button to clear the current image.'}, 'show_remove_button': {'type': 'bool | None', 'default': 'None', 'description': 'If True, will show a button to remove the selected bounding box.'}}, 'postprocess': {'value': {'type': 'dict | None', 'description': 'A dict with an image and an optional list of boxes or None.'}}, 'preprocess': {'return': {'type': 'dict | None', 'description': 'A dict with the image and boxes or None.'}, 'value': None}}, 'events': {'clear': {'type': None, 'default': None, 'description': 'This listener is triggered when the user clears the image_annotator using the X button for the component.'}, 'change': {'type': None, 'default': None, 'description': 'Triggered when the value of the image_annotator changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input.'}, 'upload': {'type': None, 'default': None, 'description': 'This listener is triggered when the user uploads a file into the image_annotator.'}}}, '__meta__': {'additional_interfaces': {}, 'user_fn_refs': {'image_annotator': []}}}
|
7 |
|
8 |
abs_path = os.path.join(os.path.dirname(__file__), "css.css")
|
9 |
|
@@ -107,7 +107,7 @@ def get_boxes_json(annotations):
|
|
107 |
|
108 |
|
109 |
with gr.Blocks() as demo:
|
110 |
-
with gr.Tab("Object annotation"):
|
111 |
annotator = image_annotator(
|
112 |
example_annotation,
|
113 |
label_list=["Person", "Vehicle"],
|
@@ -117,7 +117,7 @@ with gr.Blocks() as demo:
|
|
117 |
json_boxes = gr.JSON()
|
118 |
button_get.click(get_boxes_json, annotator, json_boxes)
|
119 |
|
120 |
-
with gr.Tab("Crop"):
|
121 |
with gr.Row():
|
122 |
annotator_crop = image_annotator(
|
123 |
examples_crop[0],
|
|
|
3 |
from app import demo as app
|
4 |
import os
|
5 |
|
6 |
+
_docs = {'image_annotator': {'description': 'Creates a component to annotate images with bounding boxes. The bounding boxes can be created and edited by the user or be passed by code.\nIt is also possible to predefine a set of valid classes and colors.', 'members': {'__init__': {'value': {'type': 'dict | None', 'default': 'None', 'description': "A dict or None. The dictionary must contain a key 'image' with either an URL to an image, a numpy image or a PIL image. Optionally it may contain a key 'boxes' with a list of boxes. Each box must be a dict wit the keys: 'xmin', 'ymin', 'xmax' and 'ymax' with the absolute image coordinates of the box. Optionally can also include the keys 'label' and 'color' describing the label and color of the box. Color must be a tuple of RGB values (e.g. `(255,255,255)`)."}, 'boxes_alpha': {'type': 'float | None', 'default': 'None', 'description': 'Opacity of the bounding boxes 0 and 1.'}, 'label_list': {'type': 'list[str] | None', 'default': 'None', 'description': 'List of valid labels.'}, 'label_colors': {'type': 'list[str] | None', 'default': 'None', 'description': 'Optional list of colors for each label when `label_list` is used. Colors must be a tuple of RGB values (e.g. `(255,255,255)`).'}, 'box_min_size': {'type': 'int | None', 'default': 'None', 'description': 'Minimum valid bounding box size.'}, 'handle_size': {'type': 'int | None', 'default': 'None', 'description': 'Size of the bounding box resize handles.'}, 'box_thickness': {'type': 'int | None', 'default': 'None', 'description': 'Thickness of the bounding box outline.'}, 'box_selected_thickness': {'type': 'int | None', 'default': 'None', 'description': 'Thickness of the bounding box outline when it is selected.'}, 'disable_edit_boxes': {'type': 'bool | None', 'default': 'None', 'description': 'Disables the ability to set and edit the label and color of the boxes.'}, 'single_box': {'type': 'bool', 'default': 'False', 'description': 'If True, at most one box can be drawn.'}, 'height': {'type': 'int | str | None', 'default': 'None', 'description': 'The height of the displayed image, specified in pixels if a number is passed, or in CSS units if a string is passed.'}, 'width': {'type': 'int | str | None', 'default': 'None', 'description': 'The width of the displayed image, specified in pixels if a number is passed, or in CSS units if a string is passed.'}, 'image_mode': {'type': '"1"\n | "L"\n | "P"\n | "RGB"\n | "RGBA"\n | "CMYK"\n | "YCbCr"\n | "LAB"\n | "HSV"\n | "I"\n | "F"', 'default': '"RGB"', 'description': '"RGB" if color, or "L" if black and white. See https://pillow.readthedocs.io/en/stable/handbook/concepts.html for other supported image modes and their meaning.'}, 'sources': {'type': 'list["upload" | "webcam" | "clipboard"] | None', 'default': '["upload", "webcam", "clipboard"]', 'description': 'List of sources for the image. "upload" creates a box where user can drop an image file, "webcam" allows user to take snapshot from their webcam, "clipboard" allows users to paste an image from the clipboard. If None, defaults to ["upload", "webcam", "clipboard"].'}, 'image_type': {'type': '"numpy" | "pil" | "filepath"', 'default': '"numpy"', 'description': 'The format the image is converted before being passed into the prediction function. "numpy" converts the image to a numpy array with shape (height, width, 3) and values from 0 to 255, "pil" converts the image to a PIL image object, "filepath" passes a str path to a temporary file containing the image. If the image is SVG, the `type` is ignored and the filepath of the SVG is returned.'}, 'label': {'type': 'str | None', 'default': 'None', 'description': 'The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to.'}, 'container': {'type': 'bool', 'default': 'True', 'description': 'If True, will place the component in a container - providing some extra padding around the border.'}, 'scale': {'type': 'int | None', 'default': 'None', 'description': 'relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.'}, 'min_width': {'type': 'int', 'default': '160', 'description': 'minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.'}, 'interactive': {'type': 'bool | None', 'default': 'True', 'description': 'if True, will allow users to upload and annotate an image; if False, can only be used to display annotated images.'}, 'visible': {'type': 'bool', 'default': 'True', 'description': 'If False, component will be hidden.'}, 'elem_id': {'type': 'str | None', 'default': 'None', 'description': 'An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'elem_classes': {'type': 'list[str] | str | None', 'default': 'None', 'description': 'An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'render': {'type': 'bool', 'default': 'True', 'description': 'If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.'}, 'show_label': {'type': 'bool | None', 'default': 'None', 'description': 'if True, will display label.'}, 'show_download_button': {'type': 'bool', 'default': 'True', 'description': 'If True, will show a button to download the image.'}, 'show_share_button': {'type': 'bool | None', 'default': 'None', 'description': 'If True, will show a share icon in the corner of the component that allows user to share outputs to Hugging Face Spaces Discussions. If False, icon does not appear. If set to None (default behavior), then the icon appears if this Gradio app is launched on Spaces, but not otherwise.'}, 'show_clear_button': {'type': 'bool | None', 'default': 'True', 'description': 'If True, will show a button to clear the current image.'}, 'show_remove_button': {'type': 'bool | None', 'default': 'None', 'description': 'If True, will show a button to remove the selected bounding box.'}, 'handles_cursor': {'type': 'bool | None', 'default': 'True', 'description': 'If True, the cursor will change when hovering over box handles in drag mode. Can be CPU-intensive.'}}, 'postprocess': {'value': {'type': 'dict | None', 'description': 'A dict with an image and an optional list of boxes or None.'}}, 'preprocess': {'return': {'type': 'dict | None', 'description': 'A dict with the image and boxes or None.'}, 'value': None}}, 'events': {'clear': {'type': None, 'default': None, 'description': 'This listener is triggered when the user clears the image_annotator using the X button for the component.'}, 'change': {'type': None, 'default': None, 'description': 'Triggered when the value of the image_annotator changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input.'}, 'upload': {'type': None, 'default': None, 'description': 'This listener is triggered when the user uploads a file into the image_annotator.'}}}, '__meta__': {'additional_interfaces': {}, 'user_fn_refs': {'image_annotator': []}}}
|
7 |
|
8 |
abs_path = os.path.join(os.path.dirname(__file__), "css.css")
|
9 |
|
|
|
107 |
|
108 |
|
109 |
with gr.Blocks() as demo:
|
110 |
+
with gr.Tab("Object annotation", id="tab_object_annotation"):
|
111 |
annotator = image_annotator(
|
112 |
example_annotation,
|
113 |
label_list=["Person", "Vehicle"],
|
|
|
117 |
json_boxes = gr.JSON()
|
118 |
button_get.click(get_boxes_json, annotator, json_boxes)
|
119 |
|
120 |
+
with gr.Tab("Crop", id="tab_crop"):
|
121 |
with gr.Row():
|
122 |
annotator_crop = image_annotator(
|
123 |
examples_crop[0],
|
src/README.md
CHANGED
@@ -82,7 +82,7 @@ def get_boxes_json(annotations):
|
|
82 |
|
83 |
|
84 |
with gr.Blocks() as demo:
|
85 |
-
with gr.Tab("Object annotation"):
|
86 |
annotator = image_annotator(
|
87 |
example_annotation,
|
88 |
label_list=["Person", "Vehicle"],
|
@@ -92,7 +92,7 @@ with gr.Blocks() as demo:
|
|
92 |
json_boxes = gr.JSON()
|
93 |
button_get.click(get_boxes_json, annotator, json_boxes)
|
94 |
|
95 |
-
with gr.Tab("Crop"):
|
96 |
with gr.Row():
|
97 |
annotator_crop = image_annotator(
|
98 |
examples_crop[0],
|
@@ -309,12 +309,12 @@ int | str | None
|
|
309 |
<td align="left" style="width: 25%;">
|
310 |
|
311 |
```python
|
312 |
-
list["upload" | "clipboard"] | None
|
313 |
```
|
314 |
|
315 |
</td>
|
316 |
-
<td align="left"><code>["upload", "clipboard"]</code></td>
|
317 |
-
<td align="left">List of sources for the image. "upload" creates a box where user can drop an image file, "clipboard" allows users to paste an image from the clipboard. If None, defaults to ["upload", "clipboard"].</td>
|
318 |
</tr>
|
319 |
|
320 |
<tr>
|
@@ -511,6 +511,19 @@ bool | None
|
|
511 |
<td align="left"><code>None</code></td>
|
512 |
<td align="left">If True, will show a button to remove the selected bounding box.</td>
|
513 |
</tr>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
514 |
</tbody></table>
|
515 |
|
516 |
|
|
|
82 |
|
83 |
|
84 |
with gr.Blocks() as demo:
|
85 |
+
with gr.Tab("Object annotation", id="tab_object_annotation"):
|
86 |
annotator = image_annotator(
|
87 |
example_annotation,
|
88 |
label_list=["Person", "Vehicle"],
|
|
|
92 |
json_boxes = gr.JSON()
|
93 |
button_get.click(get_boxes_json, annotator, json_boxes)
|
94 |
|
95 |
+
with gr.Tab("Crop", id="tab_crop"):
|
96 |
with gr.Row():
|
97 |
annotator_crop = image_annotator(
|
98 |
examples_crop[0],
|
|
|
309 |
<td align="left" style="width: 25%;">
|
310 |
|
311 |
```python
|
312 |
+
list["upload" | "webcam" | "clipboard"] | None
|
313 |
```
|
314 |
|
315 |
</td>
|
316 |
+
<td align="left"><code>["upload", "webcam", "clipboard"]</code></td>
|
317 |
+
<td align="left">List of sources for the image. "upload" creates a box where user can drop an image file, "webcam" allows user to take snapshot from their webcam, "clipboard" allows users to paste an image from the clipboard. If None, defaults to ["upload", "webcam", "clipboard"].</td>
|
318 |
</tr>
|
319 |
|
320 |
<tr>
|
|
|
511 |
<td align="left"><code>None</code></td>
|
512 |
<td align="left">If True, will show a button to remove the selected bounding box.</td>
|
513 |
</tr>
|
514 |
+
|
515 |
+
<tr>
|
516 |
+
<td align="left"><code>handles_cursor</code></td>
|
517 |
+
<td align="left" style="width: 25%;">
|
518 |
+
|
519 |
+
```python
|
520 |
+
bool | None
|
521 |
+
```
|
522 |
+
|
523 |
+
</td>
|
524 |
+
<td align="left"><code>True</code></td>
|
525 |
+
<td align="left">If True, the cursor will change when hovering over box handles in drag mode. Can be CPU-intensive.</td>
|
526 |
+
</tr>
|
527 |
</tbody></table>
|
528 |
|
529 |
|
src/backend/gradio_image_annotation/image_annotator.py
CHANGED
@@ -60,7 +60,11 @@ class image_annotator(Component):
|
|
60 |
image_mode: Literal[
|
61 |
"1", "L", "P", "RGB", "RGBA", "CMYK", "YCbCr", "LAB", "HSV", "I", "F"
|
62 |
] = "RGB",
|
63 |
-
sources: list[Literal["upload", "clipboard"]] | None = [
|
|
|
|
|
|
|
|
|
64 |
image_type: Literal["numpy", "pil", "filepath"] = "numpy",
|
65 |
label: str | None = None,
|
66 |
container: bool = True,
|
@@ -76,6 +80,7 @@ class image_annotator(Component):
|
|
76 |
show_share_button: bool | None = None,
|
77 |
show_clear_button: bool | None = True,
|
78 |
show_remove_button: bool | None = None,
|
|
|
79 |
):
|
80 |
"""
|
81 |
Parameters:
|
@@ -92,7 +97,7 @@ class image_annotator(Component):
|
|
92 |
height: The height of the displayed image, specified in pixels if a number is passed, or in CSS units if a string is passed.
|
93 |
width: The width of the displayed image, specified in pixels if a number is passed, or in CSS units if a string is passed.
|
94 |
image_mode: "RGB" if color, or "L" if black and white. See https://pillow.readthedocs.io/en/stable/handbook/concepts.html for other supported image modes and their meaning.
|
95 |
-
sources: List of sources for the image. "upload" creates a box where user can drop an image file, "clipboard" allows users to paste an image from the clipboard. If None, defaults to ["upload", "clipboard"].
|
96 |
image_type: The format the image is converted before being passed into the prediction function. "numpy" converts the image to a numpy array with shape (height, width, 3) and values from 0 to 255, "pil" converts the image to a PIL image object, "filepath" passes a str path to a temporary file containing the image. If the image is SVG, the `type` is ignored and the filepath of the SVG is returned.
|
97 |
label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to.
|
98 |
container: If True, will place the component in a container - providing some extra padding around the border.
|
@@ -108,8 +113,9 @@ class image_annotator(Component):
|
|
108 |
show_share_button: If True, will show a share icon in the corner of the component that allows user to share outputs to Hugging Face Spaces Discussions. If False, icon does not appear. If set to None (default behavior), then the icon appears if this Gradio app is launched on Spaces, but not otherwise.
|
109 |
show_clear_button: If True, will show a button to clear the current image.
|
110 |
show_remove_button: If True, will show a button to remove the selected bounding box.
|
|
|
111 |
"""
|
112 |
-
|
113 |
valid_types = ["numpy", "pil", "filepath"]
|
114 |
if image_type not in valid_types:
|
115 |
raise ValueError(
|
@@ -121,7 +127,7 @@ class image_annotator(Component):
|
|
121 |
self.image_mode = image_mode
|
122 |
|
123 |
self.sources = sources
|
124 |
-
valid_sources = ["upload", "clipboard", None]
|
125 |
if isinstance(sources, str):
|
126 |
self.sources = [sources]
|
127 |
if self.sources is None:
|
@@ -141,6 +147,7 @@ class image_annotator(Component):
|
|
141 |
)
|
142 |
self.show_clear_button = show_clear_button
|
143 |
self.show_remove_button = show_remove_button
|
|
|
144 |
|
145 |
self.boxes_alpha = boxes_alpha
|
146 |
self.box_min_size = box_min_size
|
|
|
60 |
image_mode: Literal[
|
61 |
"1", "L", "P", "RGB", "RGBA", "CMYK", "YCbCr", "LAB", "HSV", "I", "F"
|
62 |
] = "RGB",
|
63 |
+
sources: list[Literal["upload", "webcam", "clipboard"]] | None = [
|
64 |
+
"upload",
|
65 |
+
"webcam",
|
66 |
+
"clipboard",
|
67 |
+
],
|
68 |
image_type: Literal["numpy", "pil", "filepath"] = "numpy",
|
69 |
label: str | None = None,
|
70 |
container: bool = True,
|
|
|
80 |
show_share_button: bool | None = None,
|
81 |
show_clear_button: bool | None = True,
|
82 |
show_remove_button: bool | None = None,
|
83 |
+
handles_cursor: bool | None = True,
|
84 |
):
|
85 |
"""
|
86 |
Parameters:
|
|
|
97 |
height: The height of the displayed image, specified in pixels if a number is passed, or in CSS units if a string is passed.
|
98 |
width: The width of the displayed image, specified in pixels if a number is passed, or in CSS units if a string is passed.
|
99 |
image_mode: "RGB" if color, or "L" if black and white. See https://pillow.readthedocs.io/en/stable/handbook/concepts.html for other supported image modes and their meaning.
|
100 |
+
sources: List of sources for the image. "upload" creates a box where user can drop an image file, "webcam" allows user to take snapshot from their webcam, "clipboard" allows users to paste an image from the clipboard. If None, defaults to ["upload", "webcam", "clipboard"].
|
101 |
image_type: The format the image is converted before being passed into the prediction function. "numpy" converts the image to a numpy array with shape (height, width, 3) and values from 0 to 255, "pil" converts the image to a PIL image object, "filepath" passes a str path to a temporary file containing the image. If the image is SVG, the `type` is ignored and the filepath of the SVG is returned.
|
102 |
label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to.
|
103 |
container: If True, will place the component in a container - providing some extra padding around the border.
|
|
|
113 |
show_share_button: If True, will show a share icon in the corner of the component that allows user to share outputs to Hugging Face Spaces Discussions. If False, icon does not appear. If set to None (default behavior), then the icon appears if this Gradio app is launched on Spaces, but not otherwise.
|
114 |
show_clear_button: If True, will show a button to clear the current image.
|
115 |
show_remove_button: If True, will show a button to remove the selected bounding box.
|
116 |
+
handles_cursor: If True, the cursor will change when hovering over box handles in drag mode. Can be CPU-intensive.
|
117 |
"""
|
118 |
+
|
119 |
valid_types = ["numpy", "pil", "filepath"]
|
120 |
if image_type not in valid_types:
|
121 |
raise ValueError(
|
|
|
127 |
self.image_mode = image_mode
|
128 |
|
129 |
self.sources = sources
|
130 |
+
valid_sources = ["upload", "clipboard", "webcam", None]
|
131 |
if isinstance(sources, str):
|
132 |
self.sources = [sources]
|
133 |
if self.sources is None:
|
|
|
147 |
)
|
148 |
self.show_clear_button = show_clear_button
|
149 |
self.show_remove_button = show_remove_button
|
150 |
+
self.handles_cursor = handles_cursor
|
151 |
|
152 |
self.boxes_alpha = boxes_alpha
|
153 |
self.box_min_size = box_min_size
|
src/backend/gradio_image_annotation/templates/component/index.js
CHANGED
The diff for this file is too large to render.
See raw diff
|
|
src/backend/gradio_image_annotation/templates/component/style.css
CHANGED
@@ -1 +1 @@
|
|
1 |
-
.block.svelte-nl1om8{position:relative;margin:0;box-shadow:var(--block-shadow);border-width:var(--block-border-width);border-color:var(--block-border-color);border-radius:var(--block-radius);background:var(--block-background-fill);width:100%;line-height:var(--line-sm)}.block.border_focus.svelte-nl1om8{border-color:var(--color-accent)}.block.border_contrast.svelte-nl1om8{border-color:var(--body-text-color)}.padded.svelte-nl1om8{padding:var(--block-padding)}.hidden.svelte-nl1om8{display:none}.hide-container.svelte-nl1om8{margin:0;box-shadow:none;--block-border-width:0;background:transparent;padding:0;overflow:visible}div.svelte-1hnfib2{margin-bottom:var(--spacing-lg);color:var(--block-info-text-color);font-weight:var(--block-info-text-weight);font-size:var(--block-info-text-size);line-height:var(--line-sm)}span.has-info.svelte-22c38v{margin-bottom:var(--spacing-xs)}span.svelte-22c38v:not(.has-info){margin-bottom:var(--spacing-lg)}span.svelte-22c38v{display:inline-block;position:relative;z-index:var(--layer-4);border:solid var(--block-title-border-width) var(--block-title-border-color);border-radius:var(--block-title-radius);background:var(--block-title-background-fill);padding:var(--block-title-padding);color:var(--block-title-text-color);font-weight:var(--block-title-text-weight);font-size:var(--block-title-text-size);line-height:var(--line-sm)}.hide.svelte-22c38v{margin:0;height:0}label.svelte-9gxdi0{display:inline-flex;align-items:center;z-index:var(--layer-2);box-shadow:var(--block-label-shadow);border:var(--block-label-border-width) solid var(--border-color-primary);border-top:none;border-left:none;border-radius:var(--block-label-radius);background:var(--block-label-background-fill);padding:var(--block-label-padding);pointer-events:none;color:var(--block-label-text-color);font-weight:var(--block-label-text-weight);font-size:var(--block-label-text-size);line-height:var(--line-sm)}.gr-group label.svelte-9gxdi0{border-top-left-radius:0}label.float.svelte-9gxdi0{position:absolute;top:var(--block-label-margin);left:var(--block-label-margin)}label.svelte-9gxdi0:not(.float){position:static;margin-top:var(--block-label-margin);margin-left:var(--block-label-margin)}.hide.svelte-9gxdi0{height:0}span.svelte-9gxdi0{opacity:.8;margin-right:var(--size-2);width:calc(var(--block-label-text-size) - 1px);height:calc(var(--block-label-text-size) - 1px)}.hide-label.svelte-9gxdi0{box-shadow:none;border-width:0;background:transparent;overflow:visible}button.svelte-1lrphxw{display:flex;justify-content:center;align-items:center;gap:1px;z-index:var(--layer-2);border-radius:var(--radius-sm);color:var(--block-label-text-color);border:1px solid transparent}button[disabled].svelte-1lrphxw{opacity:.5;box-shadow:none}button[disabled].svelte-1lrphxw:hover{cursor:not-allowed}.padded.svelte-1lrphxw{padding:2px;background:var(--bg-color);box-shadow:var(--shadow-drop);border:1px solid var(--button-secondary-border-color)}button.svelte-1lrphxw:hover,button.highlight.svelte-1lrphxw{cursor:pointer;color:var(--color-accent)}.padded.svelte-1lrphxw:hover{border:2px solid var(--button-secondary-border-color-hover);padding:1px;color:var(--block-label-text-color)}span.svelte-1lrphxw{padding:0 1px;font-size:10px}div.svelte-1lrphxw{padding:2px;display:flex;align-items:flex-end}.small.svelte-1lrphxw{width:14px;height:14px}.medium.svelte-1lrphxw{width:20px;height:20px}.large.svelte-1lrphxw{width:22px;height:22px}.pending.svelte-1lrphxw{animation:svelte-1lrphxw-flash .5s infinite}@keyframes svelte-1lrphxw-flash{0%{opacity:.5}50%{opacity:1}to{opacity:.5}}.transparent.svelte-1lrphxw{background:transparent;border:none;box-shadow:none}.empty.svelte-3w3rth{display:flex;justify-content:center;align-items:center;margin-top:calc(0px - var(--size-6));height:var(--size-full)}.icon.svelte-3w3rth{opacity:.5;height:var(--size-5);color:var(--body-text-color)}.small.svelte-3w3rth{min-height:calc(var(--size-32) - 20px)}.large.svelte-3w3rth{min-height:calc(var(--size-64) - 20px)}.unpadded_box.svelte-3w3rth{margin-top:0}.small_parent.svelte-3w3rth{min-height:100%!important}.dropdown-arrow.svelte-145leq6{fill:currentColor}.wrap.svelte-kzcjhc{display:flex;flex-direction:column;justify-content:center;align-items:center;min-height:var(--size-60);color:var(--block-label-text-color);line-height:var(--line-md);height:100%;padding-top:var(--size-3)}.or.svelte-kzcjhc{color:var(--body-text-color-subdued);display:flex}.icon-wrap.svelte-kzcjhc{width:30px;margin-bottom:var(--spacing-lg)}@media (--screen-md){.wrap.svelte-kzcjhc{font-size:var(--text-lg)}}.hovered.svelte-kzcjhc{color:var(--color-accent)}div.svelte-q32hvf{border-top:1px solid transparent;display:flex;max-height:100%;justify-content:center;align-items:center;gap:var(--spacing-sm);height:auto;align-items:flex-end;color:var(--block-label-text-color);flex-shrink:0}.show_border.svelte-q32hvf{border-top:1px solid var(--block-border-color);margin-top:var(--spacing-xxl);box-shadow:var(--shadow-drop)}.source-selection.svelte-1jp3vgd{display:flex;align-items:center;justify-content:center;border-top:1px solid var(--border-color-primary);width:95%;bottom:0;left:0;right:0;margin-left:auto;margin-right:auto}.icon.svelte-1jp3vgd{width:22px;height:22px;margin:var(--spacing-lg) var(--spacing-xs);padding:var(--spacing-xs);color:var(--neutral-400);border-radius:var(--radius-md)}.selected.svelte-1jp3vgd{color:var(--color-accent)}.icon.svelte-1jp3vgd:hover,.icon.svelte-1jp3vgd:focus{color:var(--color-accent)}.wrap.svelte-16nch4a.svelte-16nch4a{display:flex;flex-direction:column;justify-content:center;align-items:center;z-index:var(--layer-2);transition:opacity .1s ease-in-out;border-radius:var(--block-radius);background:var(--block-background-fill);padding:0 var(--size-6);max-height:var(--size-screen-h);overflow:hidden}.wrap.center.svelte-16nch4a.svelte-16nch4a{top:0;right:0;left:0}.wrap.default.svelte-16nch4a.svelte-16nch4a{top:0;right:0;bottom:0;left:0}.hide.svelte-16nch4a.svelte-16nch4a{opacity:0;pointer-events:none}.generating.svelte-16nch4a.svelte-16nch4a{animation:svelte-16nch4a-pulseStart 1s cubic-bezier(.4,0,.6,1),svelte-16nch4a-pulse 2s cubic-bezier(.4,0,.6,1) 1s infinite;border:2px solid var(--color-accent);background:transparent;z-index:var(--layer-1);pointer-events:none}.translucent.svelte-16nch4a.svelte-16nch4a{background:none}@keyframes svelte-16nch4a-pulseStart{0%{opacity:0}to{opacity:1}}@keyframes svelte-16nch4a-pulse{0%,to{opacity:1}50%{opacity:.5}}.loading.svelte-16nch4a.svelte-16nch4a{z-index:var(--layer-2);color:var(--body-text-color)}.eta-bar.svelte-16nch4a.svelte-16nch4a{position:absolute;top:0;right:0;bottom:0;left:0;transform-origin:left;opacity:.8;z-index:var(--layer-1);transition:10ms;background:var(--background-fill-secondary)}.progress-bar-wrap.svelte-16nch4a.svelte-16nch4a{border:1px solid var(--border-color-primary);background:var(--background-fill-primary);width:55.5%;height:var(--size-4)}.progress-bar.svelte-16nch4a.svelte-16nch4a{transform-origin:left;background-color:var(--loader-color);width:var(--size-full);height:var(--size-full)}.progress-level.svelte-16nch4a.svelte-16nch4a{display:flex;flex-direction:column;align-items:center;gap:1;z-index:var(--layer-2);width:var(--size-full)}.progress-level-inner.svelte-16nch4a.svelte-16nch4a{margin:var(--size-2) auto;color:var(--body-text-color);font-size:var(--text-sm);font-family:var(--font-mono)}.meta-text.svelte-16nch4a.svelte-16nch4a{position:absolute;top:0;right:0;z-index:var(--layer-2);padding:var(--size-1) var(--size-2);font-size:var(--text-sm);font-family:var(--font-mono)}.meta-text-center.svelte-16nch4a.svelte-16nch4a{display:flex;position:absolute;top:0;right:0;justify-content:center;align-items:center;transform:translateY(var(--size-6));z-index:var(--layer-2);padding:var(--size-1) var(--size-2);font-size:var(--text-sm);font-family:var(--font-mono);text-align:center}.error.svelte-16nch4a.svelte-16nch4a{box-shadow:var(--shadow-drop);border:solid 1px var(--error-border-color);border-radius:var(--radius-full);background:var(--error-background-fill);padding-right:var(--size-4);padding-left:var(--size-4);color:var(--error-text-color);font-weight:var(--weight-semibold);font-size:var(--text-lg);line-height:var(--line-lg);font-family:var(--font)}.minimal.svelte-16nch4a .progress-text.svelte-16nch4a{background:var(--block-background-fill)}.border.svelte-16nch4a.svelte-16nch4a{border:1px solid var(--border-color-primary)}.clear-status.svelte-16nch4a.svelte-16nch4a{position:absolute;display:flex;top:var(--size-2);right:var(--size-2);justify-content:flex-end;gap:var(--spacing-sm);z-index:var(--layer-1)}.wrap.svelte-cr2edf.svelte-cr2edf{overflow-y:auto;transition:opacity .5s ease-in-out;background:var(--block-background-fill);position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:var(--size-40);width:var(--size-full)}.wrap.svelte-cr2edf.svelte-cr2edf:after{content:"";position:absolute;top:0;left:0;width:var(--upload-progress-width);height:100%;transition:all .5s ease-in-out;z-index:1}.uploading.svelte-cr2edf.svelte-cr2edf{font-size:var(--text-lg);font-family:var(--font);z-index:2}.file-name.svelte-cr2edf.svelte-cr2edf{margin:var(--spacing-md);font-size:var(--text-lg);color:var(--body-text-color-subdued)}.file.svelte-cr2edf.svelte-cr2edf{font-size:var(--text-md);z-index:2;display:flex;align-items:center}.file.svelte-cr2edf progress.svelte-cr2edf{display:inline;height:var(--size-1);width:100%;transition:all .5s ease-in-out;color:var(--color-accent);border:none}.file.svelte-cr2edf progress[value].svelte-cr2edf::-webkit-progress-value{background-color:var(--color-accent);border-radius:20px}.file.svelte-cr2edf progress[value].svelte-cr2edf::-webkit-progress-bar{background-color:var(--border-color-accent);border-radius:20px}.progress-bar.svelte-cr2edf.svelte-cr2edf{width:14px;height:14px;border-radius:50%;background:radial-gradient(closest-side,var(--block-background-fill) 64%,transparent 53% 100%),conic-gradient(var(--color-accent) var(--upload-progress-width),var(--border-color-accent) 0);transition:all .5s ease-in-out}button.svelte-1s26xmt{cursor:pointer;width:var(--size-full)}.hidden.svelte-1s26xmt{display:none;height:0!important;position:absolute;width:0;flex-grow:0}.center.svelte-1s26xmt{display:flex;justify-content:center}.flex.svelte-1s26xmt{display:flex;flex-direction:column;justify-content:center;align-items:center}.disable_click.svelte-1s26xmt{cursor:default}input.svelte-1s26xmt{display:none}div.svelte-1wj0ocy{display:flex;top:var(--size-2);right:var(--size-2);justify-content:flex-end;gap:var(--spacing-sm);z-index:var(--layer-1)}.not-absolute.svelte-1wj0ocy{margin:var(--size-1)}input.svelte-16l8u73{display:block;position:relative;background:var(--background-fill-primary);line-height:var(--line-sm)}svg.svelte-43sxxs.svelte-43sxxs{width:var(--size-20);height:var(--size-20)}svg.svelte-43sxxs path.svelte-43sxxs{fill:var(--loader-color)}div.svelte-43sxxs.svelte-43sxxs{z-index:var(--layer-2)}.margin.svelte-43sxxs.svelte-43sxxs{margin:var(--size-4)}.wrap.svelte-1yserjw.svelte-1yserjw{display:flex;flex-direction:column;justify-content:center;align-items:center;z-index:var(--layer-top);transition:opacity .1s ease-in-out;border-radius:var(--block-radius);background:var(--block-background-fill);padding:0 var(--size-6);max-height:var(--size-screen-h);overflow:hidden;pointer-events:none}.wrap.center.svelte-1yserjw.svelte-1yserjw{top:0;right:0;left:0}.wrap.default.svelte-1yserjw.svelte-1yserjw{top:0;right:0;bottom:0;left:0}.hide.svelte-1yserjw.svelte-1yserjw{opacity:0;pointer-events:none}.generating.svelte-1yserjw.svelte-1yserjw{animation:svelte-1yserjw-pulse 2s cubic-bezier(.4,0,.6,1) infinite;border:2px solid var(--color-accent);background:transparent;z-index:var(--layer-1)}.translucent.svelte-1yserjw.svelte-1yserjw{background:none}@keyframes svelte-1yserjw-pulse{0%,to{opacity:1}50%{opacity:.5}}.loading.svelte-1yserjw.svelte-1yserjw{z-index:var(--layer-2);color:var(--body-text-color)}.eta-bar.svelte-1yserjw.svelte-1yserjw{position:absolute;top:0;right:0;bottom:0;left:0;transform-origin:left;opacity:.8;z-index:var(--layer-1);transition:10ms;background:var(--background-fill-secondary)}.progress-bar-wrap.svelte-1yserjw.svelte-1yserjw{border:1px solid var(--border-color-primary);background:var(--background-fill-primary);width:55.5%;height:var(--size-4)}.progress-bar.svelte-1yserjw.svelte-1yserjw{transform-origin:left;background-color:var(--loader-color);width:var(--size-full);height:var(--size-full)}.progress-level.svelte-1yserjw.svelte-1yserjw{display:flex;flex-direction:column;align-items:center;gap:1;z-index:var(--layer-2);width:var(--size-full)}.progress-level-inner.svelte-1yserjw.svelte-1yserjw{margin:var(--size-2) auto;color:var(--body-text-color);font-size:var(--text-sm);font-family:var(--font-mono)}.meta-text.svelte-1yserjw.svelte-1yserjw{position:absolute;top:0;right:0;z-index:var(--layer-2);padding:var(--size-1) var(--size-2);font-size:var(--text-sm);font-family:var(--font-mono)}.meta-text-center.svelte-1yserjw.svelte-1yserjw{display:flex;position:absolute;top:0;right:0;justify-content:center;align-items:center;transform:translateY(var(--size-6));z-index:var(--layer-2);padding:var(--size-1) var(--size-2);font-size:var(--text-sm);font-family:var(--font-mono);text-align:center}.error.svelte-1yserjw.svelte-1yserjw{box-shadow:var(--shadow-drop);border:solid 1px var(--error-border-color);border-radius:var(--radius-full);background:var(--error-background-fill);padding-right:var(--size-4);padding-left:var(--size-4);color:var(--error-text-color);font-weight:var(--weight-semibold);font-size:var(--text-lg);line-height:var(--line-lg);font-family:var(--font)}.minimal.svelte-1yserjw .progress-text.svelte-1yserjw{background:var(--block-background-fill)}.border.svelte-1yserjw.svelte-1yserjw{border:1px solid var(--border-color-primary)}.toast-body.svelte-solcu7{display:flex;position:relative;right:0;left:0;align-items:center;margin:var(--size-6) var(--size-4);margin:auto;border-radius:var(--container-radius);overflow:hidden;pointer-events:auto}.toast-body.error.svelte-solcu7{border:1px solid var(--color-red-700);background:var(--color-red-50)}.dark .toast-body.error.svelte-solcu7{border:1px solid var(--color-red-500);background-color:var(--color-grey-950)}.toast-body.warning.svelte-solcu7{border:1px solid var(--color-yellow-700);background:var(--color-yellow-50)}.dark .toast-body.warning.svelte-solcu7{border:1px solid var(--color-yellow-500);background-color:var(--color-grey-950)}.toast-body.info.svelte-solcu7{border:1px solid var(--color-grey-700);background:var(--color-grey-50)}.dark .toast-body.info.svelte-solcu7{border:1px solid var(--color-grey-500);background-color:var(--color-grey-950)}.toast-title.svelte-solcu7{display:flex;align-items:center;font-weight:var(--weight-bold);font-size:var(--text-lg);line-height:var(--line-sm);text-transform:capitalize}.toast-title.error.svelte-solcu7{color:var(--color-red-700)}.dark .toast-title.error.svelte-solcu7{color:var(--color-red-50)}.toast-title.warning.svelte-solcu7{color:var(--color-yellow-700)}.dark .toast-title.warning.svelte-solcu7{color:var(--color-yellow-50)}.toast-title.info.svelte-solcu7{color:var(--color-grey-700)}.dark .toast-title.info.svelte-solcu7{color:var(--color-grey-50)}.toast-close.svelte-solcu7{margin:0 var(--size-3);border-radius:var(--size-3);padding:0px var(--size-1-5);font-size:var(--size-5);line-height:var(--size-5)}.toast-close.error.svelte-solcu7{color:var(--color-red-700)}.dark .toast-close.error.svelte-solcu7{color:var(--color-red-500)}.toast-close.warning.svelte-solcu7{color:var(--color-yellow-700)}.dark .toast-close.warning.svelte-solcu7{color:var(--color-yellow-500)}.toast-close.info.svelte-solcu7{color:var(--color-grey-700)}.dark .toast-close.info.svelte-solcu7{color:var(--color-grey-500)}.toast-text.svelte-solcu7{font-size:var(--text-lg)}.toast-text.error.svelte-solcu7{color:var(--color-red-700)}.dark .toast-text.error.svelte-solcu7{color:var(--color-red-50)}.toast-text.warning.svelte-solcu7{color:var(--color-yellow-700)}.dark .toast-text.warning.svelte-solcu7{color:var(--color-yellow-50)}.toast-text.info.svelte-solcu7{color:var(--color-grey-700)}.dark .toast-text.info.svelte-solcu7{color:var(--color-grey-50)}.toast-details.svelte-solcu7{margin:var(--size-3) var(--size-3) var(--size-3) 0;width:100%}.toast-icon.svelte-solcu7{display:flex;position:absolute;position:relative;flex-shrink:0;justify-content:center;align-items:center;margin:var(--size-2);border-radius:var(--radius-full);padding:var(--size-1);padding-left:calc(var(--size-1) - 1px);width:35px;height:35px}.toast-icon.error.svelte-solcu7{color:var(--color-red-700)}.dark .toast-icon.error.svelte-solcu7{color:var(--color-red-500)}.toast-icon.warning.svelte-solcu7{color:var(--color-yellow-700)}.dark .toast-icon.warning.svelte-solcu7{color:var(--color-yellow-500)}.toast-icon.info.svelte-solcu7{color:var(--color-grey-700)}.dark .toast-icon.info.svelte-solcu7{color:var(--color-grey-500)}@keyframes svelte-solcu7-countdown{0%{transform:scaleX(1)}to{transform:scaleX(0)}}.timer.svelte-solcu7{position:absolute;bottom:0;left:0;transform-origin:0 0;animation:svelte-solcu7-countdown 10s linear forwards;width:100%;height:var(--size-1)}.timer.error.svelte-solcu7{background:var(--color-red-700)}.dark .timer.error.svelte-solcu7{background:var(--color-red-500)}.timer.warning.svelte-solcu7{background:var(--color-yellow-700)}.dark .timer.warning.svelte-solcu7{background:var(--color-yellow-500)}.timer.info.svelte-solcu7{background:var(--color-grey-700)}.dark .timer.info.svelte-solcu7{background:var(--color-grey-500)}.toast-wrap.svelte-gatr8h{display:flex;position:fixed;top:var(--size-4);right:var(--size-4);flex-direction:column;align-items:end;gap:var(--size-2);z-index:var(--layer-top);width:calc(100% - var(--size-8))}@media (--screen-sm){.toast-wrap.svelte-gatr8h{width:calc(var(--size-96) + var(--size-10))}}div.svelte-1vvnm05{width:var(--size-10);height:var(--size-10)}.table.svelte-1vvnm05{margin:0 auto}button.svelte-8huxfn,a.svelte-8huxfn{display:inline-flex;justify-content:center;align-items:center;transition:var(--button-transition);box-shadow:var(--button-shadow);padding:var(--size-0-5) var(--size-2);text-align:center}button.svelte-8huxfn:hover,button[disabled].svelte-8huxfn,a.svelte-8huxfn:hover,a.disabled.svelte-8huxfn{box-shadow:var(--button-shadow-hover)}button.svelte-8huxfn:active,a.svelte-8huxfn:active{box-shadow:var(--button-shadow-active)}button[disabled].svelte-8huxfn,a.disabled.svelte-8huxfn{opacity:.5;filter:grayscale(30%);cursor:not-allowed}.hidden.svelte-8huxfn{display:none}.primary.svelte-8huxfn{border:var(--button-border-width) solid var(--button-primary-border-color);background:var(--button-primary-background-fill);color:var(--button-primary-text-color)}.primary.svelte-8huxfn:hover,.primary[disabled].svelte-8huxfn{border-color:var(--button-primary-border-color-hover);background:var(--button-primary-background-fill-hover);color:var(--button-primary-text-color-hover)}.secondary.svelte-8huxfn{border:var(--button-border-width) solid var(--button-secondary-border-color);background:var(--button-secondary-background-fill);color:var(--button-secondary-text-color)}.secondary.svelte-8huxfn:hover,.secondary[disabled].svelte-8huxfn{border-color:var(--button-secondary-border-color-hover);background:var(--button-secondary-background-fill-hover);color:var(--button-secondary-text-color-hover)}.stop.svelte-8huxfn{border:var(--button-border-width) solid var(--button-cancel-border-color);background:var(--button-cancel-background-fill);color:var(--button-cancel-text-color)}.stop.svelte-8huxfn:hover,.stop[disabled].svelte-8huxfn{border-color:var(--button-cancel-border-color-hover);background:var(--button-cancel-background-fill-hover);color:var(--button-cancel-text-color-hover)}.sm.svelte-8huxfn{border-radius:var(--button-small-radius);padding:var(--button-small-padding);font-weight:var(--button-small-text-weight);font-size:var(--button-small-text-size)}.lg.svelte-8huxfn{border-radius:var(--button-large-radius);padding:var(--button-large-padding);font-weight:var(--button-large-text-weight);font-size:var(--button-large-text-size)}.button-icon.svelte-8huxfn{width:var(--text-xl);height:var(--text-xl);margin-right:var(--spacing-xl)}.options.svelte-yuohum{--window-padding:var(--size-8);position:fixed;z-index:var(--layer-top);margin-left:0;box-shadow:var(--shadow-drop-lg);border-radius:var(--container-radius);background:var(--background-fill-primary);min-width:fit-content;max-width:inherit;overflow:auto;color:var(--body-text-color);list-style:none}.item.svelte-yuohum{display:flex;cursor:pointer;padding:var(--size-2)}.item.svelte-yuohum:hover,.active.svelte-yuohum{background:var(--background-fill-secondary)}.inner-item.svelte-yuohum{padding-right:var(--size-1)}.hide.svelte-yuohum{visibility:hidden}.icon-wrap.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg{color:var(--body-text-color);margin-right:var(--size-2);width:var(--size-5)}label.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg:not(.container),label.svelte-xtjjyg:not(.container) .wrap.svelte-xtjjyg.svelte-xtjjyg,label.svelte-xtjjyg:not(.container) .wrap-inner.svelte-xtjjyg.svelte-xtjjyg,label.svelte-xtjjyg:not(.container) .secondary-wrap.svelte-xtjjyg.svelte-xtjjyg,label.svelte-xtjjyg:not(.container) .token.svelte-xtjjyg.svelte-xtjjyg,label.svelte-xtjjyg:not(.container) input.svelte-xtjjyg.svelte-xtjjyg{height:100%}.container.svelte-xtjjyg .wrap.svelte-xtjjyg.svelte-xtjjyg{box-shadow:var(--input-shadow);border:var(--input-border-width) solid var(--border-color-primary)}.wrap.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg{position:relative;border-radius:var(--input-radius);background:var(--input-background-fill)}.wrap.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg:focus-within{box-shadow:var(--input-shadow-focus);border-color:var(--input-border-color-focus)}.wrap-inner.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg{display:flex;position:relative;flex-wrap:wrap;align-items:center;gap:var(--checkbox-label-gap);padding:var(--checkbox-label-padding)}.token.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg{display:flex;align-items:center;transition:var(--button-transition);cursor:pointer;box-shadow:var(--checkbox-label-shadow);border:var(--checkbox-label-border-width) solid var(--checkbox-label-border-color);border-radius:var(--button-small-radius);background:var(--checkbox-label-background-fill);padding:var(--checkbox-label-padding);color:var(--checkbox-label-text-color);font-weight:var(--checkbox-label-text-weight);font-size:var(--checkbox-label-text-size);line-height:var(--line-md)}.token.svelte-xtjjyg>.svelte-xtjjyg+.svelte-xtjjyg{margin-left:var(--size-2)}.token-remove.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg{fill:var(--body-text-color);display:flex;justify-content:center;align-items:center;cursor:pointer;border:var(--checkbox-border-width) solid var(--border-color-primary);border-radius:var(--radius-full);background:var(--background-fill-primary);padding:var(--size-0-5);width:16px;height:16px}.secondary-wrap.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg{display:flex;flex:1 1 0%;align-items:center;border:none;min-width:min-content}input.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg{margin:var(--spacing-sm);outline:none;border:none;background:inherit;width:var(--size-full);color:var(--body-text-color);font-size:var(--input-text-size)}input.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg:disabled{-webkit-text-fill-color:var(--body-text-color);-webkit-opacity:1;opacity:1;cursor:not-allowed}.remove-all.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg{margin-left:var(--size-1);width:20px;height:20px}.subdued.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg{color:var(--body-text-color-subdued)}input[readonly].svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg{cursor:pointer}.icon-wrap.svelte-1m1zvyj.svelte-1m1zvyj{color:var(--body-text-color);margin-right:var(--size-2);width:var(--size-5)}.container.svelte-1m1zvyj.svelte-1m1zvyj{height:100%}.container.svelte-1m1zvyj .wrap.svelte-1m1zvyj{box-shadow:var(--input-shadow);border:var(--input-border-width) solid var(--border-color-primary)}.wrap.svelte-1m1zvyj.svelte-1m1zvyj{position:relative;border-radius:var(--input-radius);background:var(--input-background-fill)}.wrap.svelte-1m1zvyj.svelte-1m1zvyj:focus-within{box-shadow:var(--input-shadow-focus);border-color:var(--input-border-color-focus)}.wrap-inner.svelte-1m1zvyj.svelte-1m1zvyj{display:flex;position:relative;flex-wrap:wrap;align-items:center;gap:var(--checkbox-label-gap);padding:var(--checkbox-label-padding);height:100%}.secondary-wrap.svelte-1m1zvyj.svelte-1m1zvyj{display:flex;flex:1 1 0%;align-items:center;border:none;min-width:min-content;height:100%}input.svelte-1m1zvyj.svelte-1m1zvyj{margin:var(--spacing-sm);outline:none;border:none;background:inherit;width:var(--size-full);color:var(--body-text-color);font-size:var(--input-text-size);height:100%}input.svelte-1m1zvyj.svelte-1m1zvyj:disabled{-webkit-text-fill-color:var(--body-text-color);-webkit-opacity:1;opacity:1;cursor:not-allowed}.subdued.svelte-1m1zvyj.svelte-1m1zvyj{color:var(--body-text-color-subdued)}input[readonly].svelte-1m1zvyj.svelte-1m1zvyj{cursor:pointer}.gallery.svelte-1gecy8w{padding:var(--size-1) var(--size-2)}.modal.svelte-hkn2q1{position:fixed;left:0;top:0;width:100%;height:100%;z-index:var(--layer-top);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.modal-container.svelte-hkn2q1{border-style:solid;border-width:var(--block-border-width);margin-top:10%;padding:20px;box-shadow:var(--block-shadow);border-color:var(--block-border-color);border-radius:var(--block-radius);background:var(--block-background-fill);position:fixed;left:50%;transform:translate(-50%);width:fit-content}.model-content.svelte-hkn2q1{display:flex;align-items:flex-end}.canvas-annotator.svelte-3rql59{border-color:var(--block-border-color);width:100%;height:100%;display:block;touch-action:none}.canvas-control.svelte-3rql59{display:flex;align-items:center;justify-content:center;border-top:1px solid var(--border-color-primary);width:95%;bottom:0;left:0;right:0;margin-left:auto;margin-right:auto;margin-top:var(--size-2)}.icon.svelte-3rql59{width:22px;height:22px;margin:var(--spacing-lg) var(--spacing-xs);padding:var(--spacing-xs);color:var(--neutral-400);border-radius:var(--radius-md)}.icon.svelte-3rql59:hover,.icon.svelte-3rql59:focus{color:var(--color-accent)}.selected.svelte-3rql59{color:var(--color-accent)}.canvas-container.svelte-3rql59:focus{outline:none}.image-frame.svelte-1gjdske img{width:var(--size-full);height:var(--size-full);object-fit:cover}.image-frame.svelte-1gjdske{object-fit:cover;width:100%}.upload-container.svelte-1gjdske{height:100%;width:100%;flex-shrink:1;max-height:100%}.image-container.svelte-1gjdske{display:flex;height:100%;flex-direction:column;justify-content:center;align-items:center;max-height:100%}.selectable.svelte-1gjdske{cursor:crosshair}.icon-buttons.svelte-1gjdske{display:flex;position:absolute;top:6px;right:6px;gap:var(--size-1)}.container.svelte-1sgcyba img{width:100%;height:100%}.container.selected.svelte-1sgcyba{border-color:var(--border-color-accent)}.border.table.svelte-1sgcyba{border:2px solid var(--border-color-primary)}.container.table.svelte-1sgcyba{margin:0 auto;border-radius:var(--radius-lg);overflow:hidden;width:var(--size-20);height:var(--size-20);object-fit:cover}.container.gallery.svelte-1sgcyba{width:var(--size-20);max-width:var(--size-20);object-fit:cover}
|
|
|
1 |
+
.block.svelte-nl1om8{position:relative;margin:0;box-shadow:var(--block-shadow);border-width:var(--block-border-width);border-color:var(--block-border-color);border-radius:var(--block-radius);background:var(--block-background-fill);width:100%;line-height:var(--line-sm)}.block.border_focus.svelte-nl1om8{border-color:var(--color-accent)}.block.border_contrast.svelte-nl1om8{border-color:var(--body-text-color)}.padded.svelte-nl1om8{padding:var(--block-padding)}.hidden.svelte-nl1om8{display:none}.hide-container.svelte-nl1om8{margin:0;box-shadow:none;--block-border-width:0;background:transparent;padding:0;overflow:visible}div.svelte-1hnfib2{margin-bottom:var(--spacing-lg);color:var(--block-info-text-color);font-weight:var(--block-info-text-weight);font-size:var(--block-info-text-size);line-height:var(--line-sm)}span.has-info.svelte-22c38v{margin-bottom:var(--spacing-xs)}span.svelte-22c38v:not(.has-info){margin-bottom:var(--spacing-lg)}span.svelte-22c38v{display:inline-block;position:relative;z-index:var(--layer-4);border:solid var(--block-title-border-width) var(--block-title-border-color);border-radius:var(--block-title-radius);background:var(--block-title-background-fill);padding:var(--block-title-padding);color:var(--block-title-text-color);font-weight:var(--block-title-text-weight);font-size:var(--block-title-text-size);line-height:var(--line-sm)}.hide.svelte-22c38v{margin:0;height:0}label.svelte-9gxdi0{display:inline-flex;align-items:center;z-index:var(--layer-2);box-shadow:var(--block-label-shadow);border:var(--block-label-border-width) solid var(--border-color-primary);border-top:none;border-left:none;border-radius:var(--block-label-radius);background:var(--block-label-background-fill);padding:var(--block-label-padding);pointer-events:none;color:var(--block-label-text-color);font-weight:var(--block-label-text-weight);font-size:var(--block-label-text-size);line-height:var(--line-sm)}.gr-group label.svelte-9gxdi0{border-top-left-radius:0}label.float.svelte-9gxdi0{position:absolute;top:var(--block-label-margin);left:var(--block-label-margin)}label.svelte-9gxdi0:not(.float){position:static;margin-top:var(--block-label-margin);margin-left:var(--block-label-margin)}.hide.svelte-9gxdi0{height:0}span.svelte-9gxdi0{opacity:.8;margin-right:var(--size-2);width:calc(var(--block-label-text-size) - 1px);height:calc(var(--block-label-text-size) - 1px)}.hide-label.svelte-9gxdi0{box-shadow:none;border-width:0;background:transparent;overflow:visible}button.svelte-1lrphxw{display:flex;justify-content:center;align-items:center;gap:1px;z-index:var(--layer-2);border-radius:var(--radius-sm);color:var(--block-label-text-color);border:1px solid transparent}button[disabled].svelte-1lrphxw{opacity:.5;box-shadow:none}button[disabled].svelte-1lrphxw:hover{cursor:not-allowed}.padded.svelte-1lrphxw{padding:2px;background:var(--bg-color);box-shadow:var(--shadow-drop);border:1px solid var(--button-secondary-border-color)}button.svelte-1lrphxw:hover,button.highlight.svelte-1lrphxw{cursor:pointer;color:var(--color-accent)}.padded.svelte-1lrphxw:hover{border:2px solid var(--button-secondary-border-color-hover);padding:1px;color:var(--block-label-text-color)}span.svelte-1lrphxw{padding:0 1px;font-size:10px}div.svelte-1lrphxw{padding:2px;display:flex;align-items:flex-end}.small.svelte-1lrphxw{width:14px;height:14px}.medium.svelte-1lrphxw{width:20px;height:20px}.large.svelte-1lrphxw{width:22px;height:22px}.pending.svelte-1lrphxw{animation:svelte-1lrphxw-flash .5s infinite}@keyframes svelte-1lrphxw-flash{0%{opacity:.5}50%{opacity:1}to{opacity:.5}}.transparent.svelte-1lrphxw{background:transparent;border:none;box-shadow:none}.empty.svelte-3w3rth{display:flex;justify-content:center;align-items:center;margin-top:calc(0px - var(--size-6));height:var(--size-full)}.icon.svelte-3w3rth{opacity:.5;height:var(--size-5);color:var(--body-text-color)}.small.svelte-3w3rth{min-height:calc(var(--size-32) - 20px)}.large.svelte-3w3rth{min-height:calc(var(--size-64) - 20px)}.unpadded_box.svelte-3w3rth{margin-top:0}.small_parent.svelte-3w3rth{min-height:100%!important}.dropdown-arrow.svelte-145leq6{fill:currentColor}.wrap.svelte-kzcjhc{display:flex;flex-direction:column;justify-content:center;align-items:center;min-height:var(--size-60);color:var(--block-label-text-color);line-height:var(--line-md);height:100%;padding-top:var(--size-3)}.or.svelte-kzcjhc{color:var(--body-text-color-subdued);display:flex}.icon-wrap.svelte-kzcjhc{width:30px;margin-bottom:var(--spacing-lg)}@media (--screen-md){.wrap.svelte-kzcjhc{font-size:var(--text-lg)}}.hovered.svelte-kzcjhc{color:var(--color-accent)}div.svelte-q32hvf{border-top:1px solid transparent;display:flex;max-height:100%;justify-content:center;align-items:center;gap:var(--spacing-sm);height:auto;align-items:flex-end;color:var(--block-label-text-color);flex-shrink:0}.show_border.svelte-q32hvf{border-top:1px solid var(--block-border-color);margin-top:var(--spacing-xxl);box-shadow:var(--shadow-drop)}.source-selection.svelte-1jp3vgd{display:flex;align-items:center;justify-content:center;border-top:1px solid var(--border-color-primary);width:95%;bottom:0;left:0;right:0;margin-left:auto;margin-right:auto}.icon.svelte-1jp3vgd{width:22px;height:22px;margin:var(--spacing-lg) var(--spacing-xs);padding:var(--spacing-xs);color:var(--neutral-400);border-radius:var(--radius-md)}.selected.svelte-1jp3vgd{color:var(--color-accent)}.icon.svelte-1jp3vgd:hover,.icon.svelte-1jp3vgd:focus{color:var(--color-accent)}.wrap.svelte-16nch4a.svelte-16nch4a{display:flex;flex-direction:column;justify-content:center;align-items:center;z-index:var(--layer-2);transition:opacity .1s ease-in-out;border-radius:var(--block-radius);background:var(--block-background-fill);padding:0 var(--size-6);max-height:var(--size-screen-h);overflow:hidden}.wrap.center.svelte-16nch4a.svelte-16nch4a{top:0;right:0;left:0}.wrap.default.svelte-16nch4a.svelte-16nch4a{top:0;right:0;bottom:0;left:0}.hide.svelte-16nch4a.svelte-16nch4a{opacity:0;pointer-events:none}.generating.svelte-16nch4a.svelte-16nch4a{animation:svelte-16nch4a-pulseStart 1s cubic-bezier(.4,0,.6,1),svelte-16nch4a-pulse 2s cubic-bezier(.4,0,.6,1) 1s infinite;border:2px solid var(--color-accent);background:transparent;z-index:var(--layer-1);pointer-events:none}.translucent.svelte-16nch4a.svelte-16nch4a{background:none}@keyframes svelte-16nch4a-pulseStart{0%{opacity:0}to{opacity:1}}@keyframes svelte-16nch4a-pulse{0%,to{opacity:1}50%{opacity:.5}}.loading.svelte-16nch4a.svelte-16nch4a{z-index:var(--layer-2);color:var(--body-text-color)}.eta-bar.svelte-16nch4a.svelte-16nch4a{position:absolute;top:0;right:0;bottom:0;left:0;transform-origin:left;opacity:.8;z-index:var(--layer-1);transition:10ms;background:var(--background-fill-secondary)}.progress-bar-wrap.svelte-16nch4a.svelte-16nch4a{border:1px solid var(--border-color-primary);background:var(--background-fill-primary);width:55.5%;height:var(--size-4)}.progress-bar.svelte-16nch4a.svelte-16nch4a{transform-origin:left;background-color:var(--loader-color);width:var(--size-full);height:var(--size-full)}.progress-level.svelte-16nch4a.svelte-16nch4a{display:flex;flex-direction:column;align-items:center;gap:1;z-index:var(--layer-2);width:var(--size-full)}.progress-level-inner.svelte-16nch4a.svelte-16nch4a{margin:var(--size-2) auto;color:var(--body-text-color);font-size:var(--text-sm);font-family:var(--font-mono)}.meta-text.svelte-16nch4a.svelte-16nch4a{position:absolute;top:0;right:0;z-index:var(--layer-2);padding:var(--size-1) var(--size-2);font-size:var(--text-sm);font-family:var(--font-mono)}.meta-text-center.svelte-16nch4a.svelte-16nch4a{display:flex;position:absolute;top:0;right:0;justify-content:center;align-items:center;transform:translateY(var(--size-6));z-index:var(--layer-2);padding:var(--size-1) var(--size-2);font-size:var(--text-sm);font-family:var(--font-mono);text-align:center}.error.svelte-16nch4a.svelte-16nch4a{box-shadow:var(--shadow-drop);border:solid 1px var(--error-border-color);border-radius:var(--radius-full);background:var(--error-background-fill);padding-right:var(--size-4);padding-left:var(--size-4);color:var(--error-text-color);font-weight:var(--weight-semibold);font-size:var(--text-lg);line-height:var(--line-lg);font-family:var(--font)}.minimal.svelte-16nch4a .progress-text.svelte-16nch4a{background:var(--block-background-fill)}.border.svelte-16nch4a.svelte-16nch4a{border:1px solid var(--border-color-primary)}.clear-status.svelte-16nch4a.svelte-16nch4a{position:absolute;display:flex;top:var(--size-2);right:var(--size-2);justify-content:flex-end;gap:var(--spacing-sm);z-index:var(--layer-1)}.wrap.svelte-cr2edf.svelte-cr2edf{overflow-y:auto;transition:opacity .5s ease-in-out;background:var(--block-background-fill);position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:var(--size-40);width:var(--size-full)}.wrap.svelte-cr2edf.svelte-cr2edf:after{content:"";position:absolute;top:0;left:0;width:var(--upload-progress-width);height:100%;transition:all .5s ease-in-out;z-index:1}.uploading.svelte-cr2edf.svelte-cr2edf{font-size:var(--text-lg);font-family:var(--font);z-index:2}.file-name.svelte-cr2edf.svelte-cr2edf{margin:var(--spacing-md);font-size:var(--text-lg);color:var(--body-text-color-subdued)}.file.svelte-cr2edf.svelte-cr2edf{font-size:var(--text-md);z-index:2;display:flex;align-items:center}.file.svelte-cr2edf progress.svelte-cr2edf{display:inline;height:var(--size-1);width:100%;transition:all .5s ease-in-out;color:var(--color-accent);border:none}.file.svelte-cr2edf progress[value].svelte-cr2edf::-webkit-progress-value{background-color:var(--color-accent);border-radius:20px}.file.svelte-cr2edf progress[value].svelte-cr2edf::-webkit-progress-bar{background-color:var(--border-color-accent);border-radius:20px}.progress-bar.svelte-cr2edf.svelte-cr2edf{width:14px;height:14px;border-radius:50%;background:radial-gradient(closest-side,var(--block-background-fill) 64%,transparent 53% 100%),conic-gradient(var(--color-accent) var(--upload-progress-width),var(--border-color-accent) 0);transition:all .5s ease-in-out}button.svelte-1s26xmt{cursor:pointer;width:var(--size-full)}.hidden.svelte-1s26xmt{display:none;height:0!important;position:absolute;width:0;flex-grow:0}.center.svelte-1s26xmt{display:flex;justify-content:center}.flex.svelte-1s26xmt{display:flex;flex-direction:column;justify-content:center;align-items:center}.disable_click.svelte-1s26xmt{cursor:default}input.svelte-1s26xmt{display:none}div.svelte-1wj0ocy{display:flex;top:var(--size-2);right:var(--size-2);justify-content:flex-end;gap:var(--spacing-sm);z-index:var(--layer-1)}.not-absolute.svelte-1wj0ocy{margin:var(--size-1)}img.svelte-kxeri3{object-fit:cover}.image-container.svelte-n22rtv img,button.svelte-n22rtv{width:var(--size-full);height:var(--size-full);object-fit:contain;display:block;border-radius:var(--radius-lg)}.selectable.svelte-n22rtv{cursor:crosshair}.icon-buttons.svelte-n22rtv{display:flex;position:absolute;top:6px;right:6px;gap:var(--size-1)}button.svelte-fjcd9c{cursor:pointer;width:var(--size-full)}.wrap.svelte-fjcd9c{display:flex;flex-direction:column;justify-content:center;align-items:center;min-height:var(--size-60);color:var(--block-label-text-color);height:100%;padding-top:var(--size-3)}.icon-wrap.svelte-fjcd9c{width:30px;margin-bottom:var(--spacing-lg)}@media (--screen-md){.wrap.svelte-fjcd9c{font-size:var(--text-lg)}}.wrap.svelte-8hqvb6.svelte-8hqvb6{position:relative;width:var(--size-full);height:var(--size-full)}.hide.svelte-8hqvb6.svelte-8hqvb6{display:none}video.svelte-8hqvb6.svelte-8hqvb6{width:var(--size-full);height:var(--size-full);object-fit:cover}.button-wrap.svelte-8hqvb6.svelte-8hqvb6{position:absolute;background-color:var(--block-background-fill);border:1px solid var(--border-color-primary);padding:var(--size-1-5);display:flex;bottom:var(--size-2);left:50%;transform:translate(-50%);box-shadow:var(--shadow-drop-lg);border-radius:var(--radius-xl);line-height:var(--size-3);color:var(--button-secondary-text-color)}@media (--screen-md){button.svelte-8hqvb6.svelte-8hqvb6{bottom:var(--size-4)}}@media (--screen-xl){button.svelte-8hqvb6.svelte-8hqvb6{bottom:var(--size-8)}}.icon.svelte-8hqvb6.svelte-8hqvb6{opacity:.8;width:18px;height:18px;display:flex;justify-content:space-between;align-items:center}.red.svelte-8hqvb6.svelte-8hqvb6{fill:red;stroke:red}.flip.svelte-8hqvb6.svelte-8hqvb6{transform:scaleX(-1)}.select-wrap.svelte-8hqvb6.svelte-8hqvb6{-webkit-appearance:none;-moz-appearance:none;appearance:none;color:var(--button-secondary-text-color);background-color:transparent;width:95%;font-size:var(--text-md);position:absolute;bottom:var(--size-2);background-color:var(--block-background-fill);box-shadow:var(--shadow-drop-lg);border-radius:var(--radius-xl);z-index:var(--layer-top);border:1px solid var(--border-color-primary);text-align:left;line-height:var(--size-4);white-space:nowrap;text-overflow:ellipsis;left:50%;transform:translate(-50%);max-width:var(--size-52)}.select-wrap.svelte-8hqvb6>option.svelte-8hqvb6{padding:.25rem .5rem;border-bottom:1px solid var(--border-color-accent);padding-right:var(--size-8);text-overflow:ellipsis;overflow:hidden}.select-wrap.svelte-8hqvb6>option.svelte-8hqvb6:hover{background-color:var(--color-accent)}.select-wrap.svelte-8hqvb6>option.svelte-8hqvb6:last-child{border:none}.inset-icon.svelte-8hqvb6.svelte-8hqvb6{position:absolute;top:5px;right:-6.5px;width:var(--size-10);height:var(--size-5);opacity:.8}@media (--screen-md){.wrap.svelte-8hqvb6.svelte-8hqvb6{font-size:var(--text-lg)}}div.svelte-1g74h68{display:flex;position:absolute;top:var(--size-2);right:var(--size-2);justify-content:flex-end;gap:var(--spacing-sm);z-index:var(--layer-5)}.image-frame.svelte-xgcoa0 img{width:var(--size-full);height:var(--size-full);object-fit:cover}.image-frame.svelte-xgcoa0{object-fit:cover;width:100%;height:100%}.upload-container.svelte-xgcoa0{height:100%;flex-shrink:1;max-height:100%}.image-container.svelte-xgcoa0{display:flex;height:100%;flex-direction:column;justify-content:center;align-items:center;max-height:100%}.selectable.svelte-xgcoa0{cursor:crosshair}input.svelte-16l8u73{display:block;position:relative;background:var(--background-fill-primary);line-height:var(--line-sm)}svg.svelte-43sxxs.svelte-43sxxs{width:var(--size-20);height:var(--size-20)}svg.svelte-43sxxs path.svelte-43sxxs{fill:var(--loader-color)}div.svelte-43sxxs.svelte-43sxxs{z-index:var(--layer-2)}.margin.svelte-43sxxs.svelte-43sxxs{margin:var(--size-4)}.wrap.svelte-1yserjw.svelte-1yserjw{display:flex;flex-direction:column;justify-content:center;align-items:center;z-index:var(--layer-top);transition:opacity .1s ease-in-out;border-radius:var(--block-radius);background:var(--block-background-fill);padding:0 var(--size-6);max-height:var(--size-screen-h);overflow:hidden;pointer-events:none}.wrap.center.svelte-1yserjw.svelte-1yserjw{top:0;right:0;left:0}.wrap.default.svelte-1yserjw.svelte-1yserjw{top:0;right:0;bottom:0;left:0}.hide.svelte-1yserjw.svelte-1yserjw{opacity:0;pointer-events:none}.generating.svelte-1yserjw.svelte-1yserjw{animation:svelte-1yserjw-pulse 2s cubic-bezier(.4,0,.6,1) infinite;border:2px solid var(--color-accent);background:transparent;z-index:var(--layer-1)}.translucent.svelte-1yserjw.svelte-1yserjw{background:none}@keyframes svelte-1yserjw-pulse{0%,to{opacity:1}50%{opacity:.5}}.loading.svelte-1yserjw.svelte-1yserjw{z-index:var(--layer-2);color:var(--body-text-color)}.eta-bar.svelte-1yserjw.svelte-1yserjw{position:absolute;top:0;right:0;bottom:0;left:0;transform-origin:left;opacity:.8;z-index:var(--layer-1);transition:10ms;background:var(--background-fill-secondary)}.progress-bar-wrap.svelte-1yserjw.svelte-1yserjw{border:1px solid var(--border-color-primary);background:var(--background-fill-primary);width:55.5%;height:var(--size-4)}.progress-bar.svelte-1yserjw.svelte-1yserjw{transform-origin:left;background-color:var(--loader-color);width:var(--size-full);height:var(--size-full)}.progress-level.svelte-1yserjw.svelte-1yserjw{display:flex;flex-direction:column;align-items:center;gap:1;z-index:var(--layer-2);width:var(--size-full)}.progress-level-inner.svelte-1yserjw.svelte-1yserjw{margin:var(--size-2) auto;color:var(--body-text-color);font-size:var(--text-sm);font-family:var(--font-mono)}.meta-text.svelte-1yserjw.svelte-1yserjw{position:absolute;top:0;right:0;z-index:var(--layer-2);padding:var(--size-1) var(--size-2);font-size:var(--text-sm);font-family:var(--font-mono)}.meta-text-center.svelte-1yserjw.svelte-1yserjw{display:flex;position:absolute;top:0;right:0;justify-content:center;align-items:center;transform:translateY(var(--size-6));z-index:var(--layer-2);padding:var(--size-1) var(--size-2);font-size:var(--text-sm);font-family:var(--font-mono);text-align:center}.error.svelte-1yserjw.svelte-1yserjw{box-shadow:var(--shadow-drop);border:solid 1px var(--error-border-color);border-radius:var(--radius-full);background:var(--error-background-fill);padding-right:var(--size-4);padding-left:var(--size-4);color:var(--error-text-color);font-weight:var(--weight-semibold);font-size:var(--text-lg);line-height:var(--line-lg);font-family:var(--font)}.minimal.svelte-1yserjw .progress-text.svelte-1yserjw{background:var(--block-background-fill)}.border.svelte-1yserjw.svelte-1yserjw{border:1px solid var(--border-color-primary)}.toast-body.svelte-solcu7{display:flex;position:relative;right:0;left:0;align-items:center;margin:var(--size-6) var(--size-4);margin:auto;border-radius:var(--container-radius);overflow:hidden;pointer-events:auto}.toast-body.error.svelte-solcu7{border:1px solid var(--color-red-700);background:var(--color-red-50)}.dark .toast-body.error.svelte-solcu7{border:1px solid var(--color-red-500);background-color:var(--color-grey-950)}.toast-body.warning.svelte-solcu7{border:1px solid var(--color-yellow-700);background:var(--color-yellow-50)}.dark .toast-body.warning.svelte-solcu7{border:1px solid var(--color-yellow-500);background-color:var(--color-grey-950)}.toast-body.info.svelte-solcu7{border:1px solid var(--color-grey-700);background:var(--color-grey-50)}.dark .toast-body.info.svelte-solcu7{border:1px solid var(--color-grey-500);background-color:var(--color-grey-950)}.toast-title.svelte-solcu7{display:flex;align-items:center;font-weight:var(--weight-bold);font-size:var(--text-lg);line-height:var(--line-sm);text-transform:capitalize}.toast-title.error.svelte-solcu7{color:var(--color-red-700)}.dark .toast-title.error.svelte-solcu7{color:var(--color-red-50)}.toast-title.warning.svelte-solcu7{color:var(--color-yellow-700)}.dark .toast-title.warning.svelte-solcu7{color:var(--color-yellow-50)}.toast-title.info.svelte-solcu7{color:var(--color-grey-700)}.dark .toast-title.info.svelte-solcu7{color:var(--color-grey-50)}.toast-close.svelte-solcu7{margin:0 var(--size-3);border-radius:var(--size-3);padding:0px var(--size-1-5);font-size:var(--size-5);line-height:var(--size-5)}.toast-close.error.svelte-solcu7{color:var(--color-red-700)}.dark .toast-close.error.svelte-solcu7{color:var(--color-red-500)}.toast-close.warning.svelte-solcu7{color:var(--color-yellow-700)}.dark .toast-close.warning.svelte-solcu7{color:var(--color-yellow-500)}.toast-close.info.svelte-solcu7{color:var(--color-grey-700)}.dark .toast-close.info.svelte-solcu7{color:var(--color-grey-500)}.toast-text.svelte-solcu7{font-size:var(--text-lg)}.toast-text.error.svelte-solcu7{color:var(--color-red-700)}.dark .toast-text.error.svelte-solcu7{color:var(--color-red-50)}.toast-text.warning.svelte-solcu7{color:var(--color-yellow-700)}.dark .toast-text.warning.svelte-solcu7{color:var(--color-yellow-50)}.toast-text.info.svelte-solcu7{color:var(--color-grey-700)}.dark .toast-text.info.svelte-solcu7{color:var(--color-grey-50)}.toast-details.svelte-solcu7{margin:var(--size-3) var(--size-3) var(--size-3) 0;width:100%}.toast-icon.svelte-solcu7{display:flex;position:absolute;position:relative;flex-shrink:0;justify-content:center;align-items:center;margin:var(--size-2);border-radius:var(--radius-full);padding:var(--size-1);padding-left:calc(var(--size-1) - 1px);width:35px;height:35px}.toast-icon.error.svelte-solcu7{color:var(--color-red-700)}.dark .toast-icon.error.svelte-solcu7{color:var(--color-red-500)}.toast-icon.warning.svelte-solcu7{color:var(--color-yellow-700)}.dark .toast-icon.warning.svelte-solcu7{color:var(--color-yellow-500)}.toast-icon.info.svelte-solcu7{color:var(--color-grey-700)}.dark .toast-icon.info.svelte-solcu7{color:var(--color-grey-500)}@keyframes svelte-solcu7-countdown{0%{transform:scaleX(1)}to{transform:scaleX(0)}}.timer.svelte-solcu7{position:absolute;bottom:0;left:0;transform-origin:0 0;animation:svelte-solcu7-countdown 10s linear forwards;width:100%;height:var(--size-1)}.timer.error.svelte-solcu7{background:var(--color-red-700)}.dark .timer.error.svelte-solcu7{background:var(--color-red-500)}.timer.warning.svelte-solcu7{background:var(--color-yellow-700)}.dark .timer.warning.svelte-solcu7{background:var(--color-yellow-500)}.timer.info.svelte-solcu7{background:var(--color-grey-700)}.dark .timer.info.svelte-solcu7{background:var(--color-grey-500)}.toast-wrap.svelte-gatr8h{display:flex;position:fixed;top:var(--size-4);right:var(--size-4);flex-direction:column;align-items:end;gap:var(--size-2);z-index:var(--layer-top);width:calc(100% - var(--size-8))}@media (--screen-sm){.toast-wrap.svelte-gatr8h{width:calc(var(--size-96) + var(--size-10))}}div.svelte-1vvnm05{width:var(--size-10);height:var(--size-10)}.table.svelte-1vvnm05{margin:0 auto}button.svelte-8huxfn,a.svelte-8huxfn{display:inline-flex;justify-content:center;align-items:center;transition:var(--button-transition);box-shadow:var(--button-shadow);padding:var(--size-0-5) var(--size-2);text-align:center}button.svelte-8huxfn:hover,button[disabled].svelte-8huxfn,a.svelte-8huxfn:hover,a.disabled.svelte-8huxfn{box-shadow:var(--button-shadow-hover)}button.svelte-8huxfn:active,a.svelte-8huxfn:active{box-shadow:var(--button-shadow-active)}button[disabled].svelte-8huxfn,a.disabled.svelte-8huxfn{opacity:.5;filter:grayscale(30%);cursor:not-allowed}.hidden.svelte-8huxfn{display:none}.primary.svelte-8huxfn{border:var(--button-border-width) solid var(--button-primary-border-color);background:var(--button-primary-background-fill);color:var(--button-primary-text-color)}.primary.svelte-8huxfn:hover,.primary[disabled].svelte-8huxfn{border-color:var(--button-primary-border-color-hover);background:var(--button-primary-background-fill-hover);color:var(--button-primary-text-color-hover)}.secondary.svelte-8huxfn{border:var(--button-border-width) solid var(--button-secondary-border-color);background:var(--button-secondary-background-fill);color:var(--button-secondary-text-color)}.secondary.svelte-8huxfn:hover,.secondary[disabled].svelte-8huxfn{border-color:var(--button-secondary-border-color-hover);background:var(--button-secondary-background-fill-hover);color:var(--button-secondary-text-color-hover)}.stop.svelte-8huxfn{border:var(--button-border-width) solid var(--button-cancel-border-color);background:var(--button-cancel-background-fill);color:var(--button-cancel-text-color)}.stop.svelte-8huxfn:hover,.stop[disabled].svelte-8huxfn{border-color:var(--button-cancel-border-color-hover);background:var(--button-cancel-background-fill-hover);color:var(--button-cancel-text-color-hover)}.sm.svelte-8huxfn{border-radius:var(--button-small-radius);padding:var(--button-small-padding);font-weight:var(--button-small-text-weight);font-size:var(--button-small-text-size)}.lg.svelte-8huxfn{border-radius:var(--button-large-radius);padding:var(--button-large-padding);font-weight:var(--button-large-text-weight);font-size:var(--button-large-text-size)}.button-icon.svelte-8huxfn{width:var(--text-xl);height:var(--text-xl);margin-right:var(--spacing-xl)}.options.svelte-yuohum{--window-padding:var(--size-8);position:fixed;z-index:var(--layer-top);margin-left:0;box-shadow:var(--shadow-drop-lg);border-radius:var(--container-radius);background:var(--background-fill-primary);min-width:fit-content;max-width:inherit;overflow:auto;color:var(--body-text-color);list-style:none}.item.svelte-yuohum{display:flex;cursor:pointer;padding:var(--size-2)}.item.svelte-yuohum:hover,.active.svelte-yuohum{background:var(--background-fill-secondary)}.inner-item.svelte-yuohum{padding-right:var(--size-1)}.hide.svelte-yuohum{visibility:hidden}.icon-wrap.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg{color:var(--body-text-color);margin-right:var(--size-2);width:var(--size-5)}label.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg:not(.container),label.svelte-xtjjyg:not(.container) .wrap.svelte-xtjjyg.svelte-xtjjyg,label.svelte-xtjjyg:not(.container) .wrap-inner.svelte-xtjjyg.svelte-xtjjyg,label.svelte-xtjjyg:not(.container) .secondary-wrap.svelte-xtjjyg.svelte-xtjjyg,label.svelte-xtjjyg:not(.container) .token.svelte-xtjjyg.svelte-xtjjyg,label.svelte-xtjjyg:not(.container) input.svelte-xtjjyg.svelte-xtjjyg{height:100%}.container.svelte-xtjjyg .wrap.svelte-xtjjyg.svelte-xtjjyg{box-shadow:var(--input-shadow);border:var(--input-border-width) solid var(--border-color-primary)}.wrap.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg{position:relative;border-radius:var(--input-radius);background:var(--input-background-fill)}.wrap.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg:focus-within{box-shadow:var(--input-shadow-focus);border-color:var(--input-border-color-focus)}.wrap-inner.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg{display:flex;position:relative;flex-wrap:wrap;align-items:center;gap:var(--checkbox-label-gap);padding:var(--checkbox-label-padding)}.token.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg{display:flex;align-items:center;transition:var(--button-transition);cursor:pointer;box-shadow:var(--checkbox-label-shadow);border:var(--checkbox-label-border-width) solid var(--checkbox-label-border-color);border-radius:var(--button-small-radius);background:var(--checkbox-label-background-fill);padding:var(--checkbox-label-padding);color:var(--checkbox-label-text-color);font-weight:var(--checkbox-label-text-weight);font-size:var(--checkbox-label-text-size);line-height:var(--line-md)}.token.svelte-xtjjyg>.svelte-xtjjyg+.svelte-xtjjyg{margin-left:var(--size-2)}.token-remove.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg{fill:var(--body-text-color);display:flex;justify-content:center;align-items:center;cursor:pointer;border:var(--checkbox-border-width) solid var(--border-color-primary);border-radius:var(--radius-full);background:var(--background-fill-primary);padding:var(--size-0-5);width:16px;height:16px}.secondary-wrap.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg{display:flex;flex:1 1 0%;align-items:center;border:none;min-width:min-content}input.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg{margin:var(--spacing-sm);outline:none;border:none;background:inherit;width:var(--size-full);color:var(--body-text-color);font-size:var(--input-text-size)}input.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg:disabled{-webkit-text-fill-color:var(--body-text-color);-webkit-opacity:1;opacity:1;cursor:not-allowed}.remove-all.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg{margin-left:var(--size-1);width:20px;height:20px}.subdued.svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg{color:var(--body-text-color-subdued)}input[readonly].svelte-xtjjyg.svelte-xtjjyg.svelte-xtjjyg{cursor:pointer}.icon-wrap.svelte-1m1zvyj.svelte-1m1zvyj{color:var(--body-text-color);margin-right:var(--size-2);width:var(--size-5)}.container.svelte-1m1zvyj.svelte-1m1zvyj{height:100%}.container.svelte-1m1zvyj .wrap.svelte-1m1zvyj{box-shadow:var(--input-shadow);border:var(--input-border-width) solid var(--border-color-primary)}.wrap.svelte-1m1zvyj.svelte-1m1zvyj{position:relative;border-radius:var(--input-radius);background:var(--input-background-fill)}.wrap.svelte-1m1zvyj.svelte-1m1zvyj:focus-within{box-shadow:var(--input-shadow-focus);border-color:var(--input-border-color-focus)}.wrap-inner.svelte-1m1zvyj.svelte-1m1zvyj{display:flex;position:relative;flex-wrap:wrap;align-items:center;gap:var(--checkbox-label-gap);padding:var(--checkbox-label-padding);height:100%}.secondary-wrap.svelte-1m1zvyj.svelte-1m1zvyj{display:flex;flex:1 1 0%;align-items:center;border:none;min-width:min-content;height:100%}input.svelte-1m1zvyj.svelte-1m1zvyj{margin:var(--spacing-sm);outline:none;border:none;background:inherit;width:var(--size-full);color:var(--body-text-color);font-size:var(--input-text-size);height:100%}input.svelte-1m1zvyj.svelte-1m1zvyj:disabled{-webkit-text-fill-color:var(--body-text-color);-webkit-opacity:1;opacity:1;cursor:not-allowed}.subdued.svelte-1m1zvyj.svelte-1m1zvyj{color:var(--body-text-color-subdued)}input[readonly].svelte-1m1zvyj.svelte-1m1zvyj{cursor:pointer}.gallery.svelte-1gecy8w{padding:var(--size-1) var(--size-2)}.modal.svelte-hkn2q1{position:fixed;left:0;top:0;width:100%;height:100%;z-index:var(--layer-top);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.modal-container.svelte-hkn2q1{border-style:solid;border-width:var(--block-border-width);margin-top:10%;padding:20px;box-shadow:var(--block-shadow);border-color:var(--block-border-color);border-radius:var(--block-radius);background:var(--block-background-fill);position:fixed;left:50%;transform:translate(-50%);width:fit-content}.model-content.svelte-hkn2q1{display:flex;align-items:flex-end}.canvas-annotator.svelte-3rql59{border-color:var(--block-border-color);width:100%;height:100%;display:block;touch-action:none}.canvas-control.svelte-3rql59{display:flex;align-items:center;justify-content:center;border-top:1px solid var(--border-color-primary);width:95%;bottom:0;left:0;right:0;margin-left:auto;margin-right:auto;margin-top:var(--size-2)}.icon.svelte-3rql59{width:22px;height:22px;margin:var(--spacing-lg) var(--spacing-xs);padding:var(--spacing-xs);color:var(--neutral-400);border-radius:var(--radius-md)}.icon.svelte-3rql59:hover,.icon.svelte-3rql59:focus{color:var(--color-accent)}.selected.svelte-3rql59{color:var(--color-accent)}.canvas-container.svelte-3rql59:focus{outline:none}.image-frame.svelte-1gjdske img{width:var(--size-full);height:var(--size-full);object-fit:cover}.image-frame.svelte-1gjdske{object-fit:cover;width:100%}.upload-container.svelte-1gjdske{height:100%;width:100%;flex-shrink:1;max-height:100%}.image-container.svelte-1gjdske{display:flex;height:100%;flex-direction:column;justify-content:center;align-items:center;max-height:100%}.selectable.svelte-1gjdske{cursor:crosshair}.icon-buttons.svelte-1gjdske{display:flex;position:absolute;top:6px;right:6px;gap:var(--size-1)}.container.svelte-1sgcyba img{width:100%;height:100%}.container.selected.svelte-1sgcyba{border-color:var(--border-color-accent)}.border.table.svelte-1sgcyba{border:2px solid var(--border-color-primary)}.container.table.svelte-1sgcyba{margin:0 auto;border-radius:var(--radius-lg);overflow:hidden;width:var(--size-20);height:var(--size-20);object-fit:cover}.container.gallery.svelte-1sgcyba{width:var(--size-20);max-width:var(--size-20);object-fit:cover}
|
src/demo/app.py
CHANGED
@@ -67,7 +67,7 @@ def get_boxes_json(annotations):
|
|
67 |
|
68 |
|
69 |
with gr.Blocks() as demo:
|
70 |
-
with gr.Tab("Object annotation"):
|
71 |
annotator = image_annotator(
|
72 |
example_annotation,
|
73 |
label_list=["Person", "Vehicle"],
|
@@ -77,7 +77,7 @@ with gr.Blocks() as demo:
|
|
77 |
json_boxes = gr.JSON()
|
78 |
button_get.click(get_boxes_json, annotator, json_boxes)
|
79 |
|
80 |
-
with gr.Tab("Crop"):
|
81 |
with gr.Row():
|
82 |
annotator_crop = image_annotator(
|
83 |
examples_crop[0],
|
|
|
67 |
|
68 |
|
69 |
with gr.Blocks() as demo:
|
70 |
+
with gr.Tab("Object annotation", id="tab_object_annotation"):
|
71 |
annotator = image_annotator(
|
72 |
example_annotation,
|
73 |
label_list=["Person", "Vehicle"],
|
|
|
77 |
json_boxes = gr.JSON()
|
78 |
button_get.click(get_boxes_json, annotator, json_boxes)
|
79 |
|
80 |
+
with gr.Tab("Crop", id="tab_crop"):
|
81 |
with gr.Row():
|
82 |
annotator_crop = image_annotator(
|
83 |
examples_crop[0],
|
src/demo/space.py
CHANGED
@@ -3,7 +3,7 @@ import gradio as gr
|
|
3 |
from app import demo as app
|
4 |
import os
|
5 |
|
6 |
-
_docs = {'image_annotator': {'description': 'Creates a component to annotate images with bounding boxes. The bounding boxes can be created and edited by the user or be passed by code.\nIt is also possible to predefine a set of valid classes and colors.', 'members': {'__init__': {'value': {'type': 'dict | None', 'default': 'None', 'description': "A dict or None. The dictionary must contain a key 'image' with either an URL to an image, a numpy image or a PIL image. Optionally it may contain a key 'boxes' with a list of boxes. Each box must be a dict wit the keys: 'xmin', 'ymin', 'xmax' and 'ymax' with the absolute image coordinates of the box. Optionally can also include the keys 'label' and 'color' describing the label and color of the box. Color must be a tuple of RGB values (e.g. `(255,255,255)`)."}, 'boxes_alpha': {'type': 'float | None', 'default': 'None', 'description': 'Opacity of the bounding boxes 0 and 1.'}, 'label_list': {'type': 'list[str] | None', 'default': 'None', 'description': 'List of valid labels.'}, 'label_colors': {'type': 'list[str] | None', 'default': 'None', 'description': 'Optional list of colors for each label when `label_list` is used. Colors must be a tuple of RGB values (e.g. `(255,255,255)`).'}, 'box_min_size': {'type': 'int | None', 'default': 'None', 'description': 'Minimum valid bounding box size.'}, 'handle_size': {'type': 'int | None', 'default': 'None', 'description': 'Size of the bounding box resize handles.'}, 'box_thickness': {'type': 'int | None', 'default': 'None', 'description': 'Thickness of the bounding box outline.'}, 'box_selected_thickness': {'type': 'int | None', 'default': 'None', 'description': 'Thickness of the bounding box outline when it is selected.'}, 'disable_edit_boxes': {'type': 'bool | None', 'default': 'None', 'description': 'Disables the ability to set and edit the label and color of the boxes.'}, 'single_box': {'type': 'bool', 'default': 'False', 'description': 'If True, at most one box can be drawn.'}, 'height': {'type': 'int | str | None', 'default': 'None', 'description': 'The height of the displayed image, specified in pixels if a number is passed, or in CSS units if a string is passed.'}, 'width': {'type': 'int | str | None', 'default': 'None', 'description': 'The width of the displayed image, specified in pixels if a number is passed, or in CSS units if a string is passed.'}, 'image_mode': {'type': '"1"\n | "L"\n | "P"\n | "RGB"\n | "RGBA"\n | "CMYK"\n | "YCbCr"\n | "LAB"\n | "HSV"\n | "I"\n | "F"', 'default': '"RGB"', 'description': '"RGB" if color, or "L" if black and white. See https://pillow.readthedocs.io/en/stable/handbook/concepts.html for other supported image modes and their meaning.'}, 'sources': {'type': 'list["upload" | "clipboard"] | None', 'default': '["upload", "clipboard"]', 'description': 'List of sources for the image. "upload" creates a box where user can drop an image file, "clipboard" allows users to paste an image from the clipboard. If None, defaults to ["upload", "clipboard"].'}, 'image_type': {'type': '"numpy" | "pil" | "filepath"', 'default': '"numpy"', 'description': 'The format the image is converted before being passed into the prediction function. "numpy" converts the image to a numpy array with shape (height, width, 3) and values from 0 to 255, "pil" converts the image to a PIL image object, "filepath" passes a str path to a temporary file containing the image. If the image is SVG, the `type` is ignored and the filepath of the SVG is returned.'}, 'label': {'type': 'str | None', 'default': 'None', 'description': 'The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to.'}, 'container': {'type': 'bool', 'default': 'True', 'description': 'If True, will place the component in a container - providing some extra padding around the border.'}, 'scale': {'type': 'int | None', 'default': 'None', 'description': 'relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.'}, 'min_width': {'type': 'int', 'default': '160', 'description': 'minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.'}, 'interactive': {'type': 'bool | None', 'default': 'True', 'description': 'if True, will allow users to upload and annotate an image; if False, can only be used to display annotated images.'}, 'visible': {'type': 'bool', 'default': 'True', 'description': 'If False, component will be hidden.'}, 'elem_id': {'type': 'str | None', 'default': 'None', 'description': 'An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'elem_classes': {'type': 'list[str] | str | None', 'default': 'None', 'description': 'An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'render': {'type': 'bool', 'default': 'True', 'description': 'If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.'}, 'show_label': {'type': 'bool | None', 'default': 'None', 'description': 'if True, will display label.'}, 'show_download_button': {'type': 'bool', 'default': 'True', 'description': 'If True, will show a button to download the image.'}, 'show_share_button': {'type': 'bool | None', 'default': 'None', 'description': 'If True, will show a share icon in the corner of the component that allows user to share outputs to Hugging Face Spaces Discussions. If False, icon does not appear. If set to None (default behavior), then the icon appears if this Gradio app is launched on Spaces, but not otherwise.'}, 'show_clear_button': {'type': 'bool | None', 'default': 'True', 'description': 'If True, will show a button to clear the current image.'}, 'show_remove_button': {'type': 'bool | None', 'default': 'None', 'description': 'If True, will show a button to remove the selected bounding box.'}}, 'postprocess': {'value': {'type': 'dict | None', 'description': 'A dict with an image and an optional list of boxes or None.'}}, 'preprocess': {'return': {'type': 'dict | None', 'description': 'A dict with the image and boxes or None.'}, 'value': None}}, 'events': {'clear': {'type': None, 'default': None, 'description': 'This listener is triggered when the user clears the image_annotator using the X button for the component.'}, 'change': {'type': None, 'default': None, 'description': 'Triggered when the value of the image_annotator changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input.'}, 'upload': {'type': None, 'default': None, 'description': 'This listener is triggered when the user uploads a file into the image_annotator.'}}}, '__meta__': {'additional_interfaces': {}, 'user_fn_refs': {'image_annotator': []}}}
|
7 |
|
8 |
abs_path = os.path.join(os.path.dirname(__file__), "css.css")
|
9 |
|
@@ -107,7 +107,7 @@ def get_boxes_json(annotations):
|
|
107 |
|
108 |
|
109 |
with gr.Blocks() as demo:
|
110 |
-
with gr.Tab("Object annotation"):
|
111 |
annotator = image_annotator(
|
112 |
example_annotation,
|
113 |
label_list=["Person", "Vehicle"],
|
@@ -117,7 +117,7 @@ with gr.Blocks() as demo:
|
|
117 |
json_boxes = gr.JSON()
|
118 |
button_get.click(get_boxes_json, annotator, json_boxes)
|
119 |
|
120 |
-
with gr.Tab("Crop"):
|
121 |
with gr.Row():
|
122 |
annotator_crop = image_annotator(
|
123 |
examples_crop[0],
|
|
|
3 |
from app import demo as app
|
4 |
import os
|
5 |
|
6 |
+
_docs = {'image_annotator': {'description': 'Creates a component to annotate images with bounding boxes. The bounding boxes can be created and edited by the user or be passed by code.\nIt is also possible to predefine a set of valid classes and colors.', 'members': {'__init__': {'value': {'type': 'dict | None', 'default': 'None', 'description': "A dict or None. The dictionary must contain a key 'image' with either an URL to an image, a numpy image or a PIL image. Optionally it may contain a key 'boxes' with a list of boxes. Each box must be a dict wit the keys: 'xmin', 'ymin', 'xmax' and 'ymax' with the absolute image coordinates of the box. Optionally can also include the keys 'label' and 'color' describing the label and color of the box. Color must be a tuple of RGB values (e.g. `(255,255,255)`)."}, 'boxes_alpha': {'type': 'float | None', 'default': 'None', 'description': 'Opacity of the bounding boxes 0 and 1.'}, 'label_list': {'type': 'list[str] | None', 'default': 'None', 'description': 'List of valid labels.'}, 'label_colors': {'type': 'list[str] | None', 'default': 'None', 'description': 'Optional list of colors for each label when `label_list` is used. Colors must be a tuple of RGB values (e.g. `(255,255,255)`).'}, 'box_min_size': {'type': 'int | None', 'default': 'None', 'description': 'Minimum valid bounding box size.'}, 'handle_size': {'type': 'int | None', 'default': 'None', 'description': 'Size of the bounding box resize handles.'}, 'box_thickness': {'type': 'int | None', 'default': 'None', 'description': 'Thickness of the bounding box outline.'}, 'box_selected_thickness': {'type': 'int | None', 'default': 'None', 'description': 'Thickness of the bounding box outline when it is selected.'}, 'disable_edit_boxes': {'type': 'bool | None', 'default': 'None', 'description': 'Disables the ability to set and edit the label and color of the boxes.'}, 'single_box': {'type': 'bool', 'default': 'False', 'description': 'If True, at most one box can be drawn.'}, 'height': {'type': 'int | str | None', 'default': 'None', 'description': 'The height of the displayed image, specified in pixels if a number is passed, or in CSS units if a string is passed.'}, 'width': {'type': 'int | str | None', 'default': 'None', 'description': 'The width of the displayed image, specified in pixels if a number is passed, or in CSS units if a string is passed.'}, 'image_mode': {'type': '"1"\n | "L"\n | "P"\n | "RGB"\n | "RGBA"\n | "CMYK"\n | "YCbCr"\n | "LAB"\n | "HSV"\n | "I"\n | "F"', 'default': '"RGB"', 'description': '"RGB" if color, or "L" if black and white. See https://pillow.readthedocs.io/en/stable/handbook/concepts.html for other supported image modes and their meaning.'}, 'sources': {'type': 'list["upload" | "webcam" | "clipboard"] | None', 'default': '["upload", "webcam", "clipboard"]', 'description': 'List of sources for the image. "upload" creates a box where user can drop an image file, "webcam" allows user to take snapshot from their webcam, "clipboard" allows users to paste an image from the clipboard. If None, defaults to ["upload", "webcam", "clipboard"].'}, 'image_type': {'type': '"numpy" | "pil" | "filepath"', 'default': '"numpy"', 'description': 'The format the image is converted before being passed into the prediction function. "numpy" converts the image to a numpy array with shape (height, width, 3) and values from 0 to 255, "pil" converts the image to a PIL image object, "filepath" passes a str path to a temporary file containing the image. If the image is SVG, the `type` is ignored and the filepath of the SVG is returned.'}, 'label': {'type': 'str | None', 'default': 'None', 'description': 'The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to.'}, 'container': {'type': 'bool', 'default': 'True', 'description': 'If True, will place the component in a container - providing some extra padding around the border.'}, 'scale': {'type': 'int | None', 'default': 'None', 'description': 'relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.'}, 'min_width': {'type': 'int', 'default': '160', 'description': 'minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.'}, 'interactive': {'type': 'bool | None', 'default': 'True', 'description': 'if True, will allow users to upload and annotate an image; if False, can only be used to display annotated images.'}, 'visible': {'type': 'bool', 'default': 'True', 'description': 'If False, component will be hidden.'}, 'elem_id': {'type': 'str | None', 'default': 'None', 'description': 'An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'elem_classes': {'type': 'list[str] | str | None', 'default': 'None', 'description': 'An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'render': {'type': 'bool', 'default': 'True', 'description': 'If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.'}, 'show_label': {'type': 'bool | None', 'default': 'None', 'description': 'if True, will display label.'}, 'show_download_button': {'type': 'bool', 'default': 'True', 'description': 'If True, will show a button to download the image.'}, 'show_share_button': {'type': 'bool | None', 'default': 'None', 'description': 'If True, will show a share icon in the corner of the component that allows user to share outputs to Hugging Face Spaces Discussions. If False, icon does not appear. If set to None (default behavior), then the icon appears if this Gradio app is launched on Spaces, but not otherwise.'}, 'show_clear_button': {'type': 'bool | None', 'default': 'True', 'description': 'If True, will show a button to clear the current image.'}, 'show_remove_button': {'type': 'bool | None', 'default': 'None', 'description': 'If True, will show a button to remove the selected bounding box.'}, 'handles_cursor': {'type': 'bool | None', 'default': 'True', 'description': 'If True, the cursor will change when hovering over box handles in drag mode. Can be CPU-intensive.'}}, 'postprocess': {'value': {'type': 'dict | None', 'description': 'A dict with an image and an optional list of boxes or None.'}}, 'preprocess': {'return': {'type': 'dict | None', 'description': 'A dict with the image and boxes or None.'}, 'value': None}}, 'events': {'clear': {'type': None, 'default': None, 'description': 'This listener is triggered when the user clears the image_annotator using the X button for the component.'}, 'change': {'type': None, 'default': None, 'description': 'Triggered when the value of the image_annotator changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input.'}, 'upload': {'type': None, 'default': None, 'description': 'This listener is triggered when the user uploads a file into the image_annotator.'}}}, '__meta__': {'additional_interfaces': {}, 'user_fn_refs': {'image_annotator': []}}}
|
7 |
|
8 |
abs_path = os.path.join(os.path.dirname(__file__), "css.css")
|
9 |
|
|
|
107 |
|
108 |
|
109 |
with gr.Blocks() as demo:
|
110 |
+
with gr.Tab("Object annotation", id="tab_object_annotation"):
|
111 |
annotator = image_annotator(
|
112 |
example_annotation,
|
113 |
label_list=["Person", "Vehicle"],
|
|
|
117 |
json_boxes = gr.JSON()
|
118 |
button_get.click(get_boxes_json, annotator, json_boxes)
|
119 |
|
120 |
+
with gr.Tab("Crop", id="tab_crop"):
|
121 |
with gr.Row():
|
122 |
annotator_crop = image_annotator(
|
123 |
examples_crop[0],
|
src/frontend/Index.svelte
CHANGED
@@ -13,7 +13,7 @@
|
|
13 |
import AnnotatedImageData from "./shared/AnnotatedImageData";
|
14 |
import ImageAnnotator from "./shared/ImageAnnotator.svelte";
|
15 |
|
16 |
-
type sources = "upload" | "clipboard" | null;
|
17 |
|
18 |
export let elem_id = "";
|
19 |
export let elem_classes: string[] = [];
|
@@ -29,7 +29,11 @@
|
|
29 |
export let scale: number | null = null;
|
30 |
export let min_width: number | undefined = undefined;
|
31 |
export let loading_status: LoadingStatus;
|
32 |
-
export let sources: ("
|
|
|
|
|
|
|
|
|
33 |
export let show_download_button: boolean;
|
34 |
export let show_share_button: boolean;
|
35 |
export let show_clear_button: boolean;
|
@@ -44,6 +48,7 @@
|
|
44 |
export let disable_edit_boxes: boolean;
|
45 |
export let single_box: boolean;
|
46 |
export let show_remove_button: boolean;
|
|
|
47 |
|
48 |
export let gradio: Gradio<{
|
49 |
change: never;
|
@@ -120,6 +125,7 @@
|
|
120 |
disableEditBoxes={disable_edit_boxes}
|
121 |
singleBox={single_box}
|
122 |
showRemoveButton={show_remove_button}
|
|
|
123 |
>
|
124 |
{#if active_source === "upload"}
|
125 |
<UploadText i18n={gradio.i18n} type="image" />
|
|
|
13 |
import AnnotatedImageData from "./shared/AnnotatedImageData";
|
14 |
import ImageAnnotator from "./shared/ImageAnnotator.svelte";
|
15 |
|
16 |
+
type sources = "upload" | "webcam" | "clipboard" | null;
|
17 |
|
18 |
export let elem_id = "";
|
19 |
export let elem_classes: string[] = [];
|
|
|
29 |
export let scale: number | null = null;
|
30 |
export let min_width: number | undefined = undefined;
|
31 |
export let loading_status: LoadingStatus;
|
32 |
+
export let sources: ("upload" | "webcam" | "clipboard")[] = [
|
33 |
+
"upload",
|
34 |
+
"webcam",
|
35 |
+
"clipboard",
|
36 |
+
];
|
37 |
export let show_download_button: boolean;
|
38 |
export let show_share_button: boolean;
|
39 |
export let show_clear_button: boolean;
|
|
|
48 |
export let disable_edit_boxes: boolean;
|
49 |
export let single_box: boolean;
|
50 |
export let show_remove_button: boolean;
|
51 |
+
export let handles_cursor: boolean;
|
52 |
|
53 |
export let gradio: Gradio<{
|
54 |
change: never;
|
|
|
125 |
disableEditBoxes={disable_edit_boxes}
|
126 |
singleBox={single_box}
|
127 |
showRemoveButton={show_remove_button}
|
128 |
+
handlesCursor={handles_cursor}
|
129 |
>
|
130 |
{#if active_source === "upload"}
|
131 |
<UploadText i18n={gradio.i18n} type="image" />
|
src/frontend/package-lock.json
CHANGED
@@ -14,6 +14,7 @@
|
|
14 |
"@gradio/client": "1.1.0",
|
15 |
"@gradio/colorpicker": "^0.2.11",
|
16 |
"@gradio/icons": "0.4.1",
|
|
|
17 |
"@gradio/statustracker": "0.6.0",
|
18 |
"@gradio/upload": "0.11.1",
|
19 |
"@gradio/utils": "0.4.2",
|
@@ -686,6 +687,24 @@
|
|
686 |
"integrity": "sha512-gC6f6UENPsvoN189eqmzDpJzWBrJe4K6vmL6Tdzifq/iPRFh/zIwovXjdudpnJkTWwTci7EP/C9QtdEkTMlMvA==",
|
687 |
"license": "ISC"
|
688 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
689 |
"node_modules/@gradio/preview": {
|
690 |
"version": "0.9.1",
|
691 |
"resolved": "https://registry.npmjs.org/@gradio/preview/-/preview-0.9.1.tgz",
|
|
|
14 |
"@gradio/client": "1.1.0",
|
15 |
"@gradio/colorpicker": "^0.2.11",
|
16 |
"@gradio/icons": "0.4.1",
|
17 |
+
"@gradio/image": "0.11.9",
|
18 |
"@gradio/statustracker": "0.6.0",
|
19 |
"@gradio/upload": "0.11.1",
|
20 |
"@gradio/utils": "0.4.2",
|
|
|
687 |
"integrity": "sha512-gC6f6UENPsvoN189eqmzDpJzWBrJe4K6vmL6Tdzifq/iPRFh/zIwovXjdudpnJkTWwTci7EP/C9QtdEkTMlMvA==",
|
688 |
"license": "ISC"
|
689 |
},
|
690 |
+
"node_modules/@gradio/image": {
|
691 |
+
"version": "0.11.9",
|
692 |
+
"resolved": "https://registry.npmjs.org/@gradio/image/-/image-0.11.9.tgz",
|
693 |
+
"integrity": "sha512-J04COMcHHQfYA4YGUV8M+VBC9Uqwjlci1ykj33gv1RftMqV3inx67FDg130C7+fPXUcc9e98bgATSb0vFVi0Mg==",
|
694 |
+
"license": "ISC",
|
695 |
+
"dependencies": {
|
696 |
+
"@gradio/atoms": "^0.7.4",
|
697 |
+
"@gradio/client": "^1.1.0",
|
698 |
+
"@gradio/icons": "^0.4.1",
|
699 |
+
"@gradio/statustracker": "^0.6.0",
|
700 |
+
"@gradio/upload": "^0.11.1",
|
701 |
+
"@gradio/utils": "^0.4.2",
|
702 |
+
"@gradio/wasm": "^0.10.1",
|
703 |
+
"cropperjs": "^1.5.12",
|
704 |
+
"lazy-brush": "^1.0.1",
|
705 |
+
"resize-observer-polyfill": "^1.5.1"
|
706 |
+
}
|
707 |
+
},
|
708 |
"node_modules/@gradio/preview": {
|
709 |
"version": "0.9.1",
|
710 |
"resolved": "https://registry.npmjs.org/@gradio/preview/-/preview-0.9.1.tgz",
|
src/frontend/package.json
CHANGED
@@ -12,6 +12,7 @@
|
|
12 |
"@gradio/icons": "0.4.1",
|
13 |
"@gradio/statustracker": "0.6.0",
|
14 |
"@gradio/upload": "0.11.1",
|
|
|
15 |
"@gradio/utils": "0.4.2",
|
16 |
"@gradio/wasm": "0.10.1",
|
17 |
"cropperjs": "^1.5.12",
|
|
|
12 |
"@gradio/icons": "0.4.1",
|
13 |
"@gradio/statustracker": "0.6.0",
|
14 |
"@gradio/upload": "0.11.1",
|
15 |
+
"@gradio/image": "0.11.9",
|
16 |
"@gradio/utils": "0.4.2",
|
17 |
"@gradio/wasm": "0.10.1",
|
18 |
"cropperjs": "^1.5.12",
|
src/frontend/shared/Box.ts
CHANGED
@@ -47,6 +47,7 @@ export default class Box {
|
|
47 |
ymin: number;
|
48 |
xmax: number;
|
49 |
ymax: number;
|
|
|
50 |
}[];
|
51 |
|
52 |
constructor(
|
@@ -136,6 +137,7 @@ export default class Box {
|
|
136 |
ymin: this.ymin - halfSize,
|
137 |
xmax: this.xmin + halfSize,
|
138 |
ymax: this.ymin + halfSize,
|
|
|
139 |
},
|
140 |
{
|
141 |
// Top right
|
@@ -143,6 +145,7 @@ export default class Box {
|
|
143 |
ymin: this.ymin - halfSize,
|
144 |
xmax: this.xmax + halfSize,
|
145 |
ymax: this.ymin + halfSize,
|
|
|
146 |
},
|
147 |
{
|
148 |
// Bottom right
|
@@ -150,6 +153,7 @@ export default class Box {
|
|
150 |
ymin: this.ymax - halfSize,
|
151 |
xmax: this.xmax + halfSize,
|
152 |
ymax: this.ymax + halfSize,
|
|
|
153 |
},
|
154 |
{
|
155 |
// Bottom left
|
@@ -157,6 +161,7 @@ export default class Box {
|
|
157 |
ymin: this.ymax - halfSize,
|
158 |
xmax: this.xmin + halfSize,
|
159 |
ymax: this.ymax + halfSize,
|
|
|
160 |
},
|
161 |
{
|
162 |
// Top center
|
@@ -164,6 +169,7 @@ export default class Box {
|
|
164 |
ymin: this.ymin - halfSize,
|
165 |
xmax: this.xmin + (width / 2) + halfSize,
|
166 |
ymax: this.ymin + halfSize,
|
|
|
167 |
},
|
168 |
{
|
169 |
// Right center
|
@@ -171,6 +177,7 @@ export default class Box {
|
|
171 |
ymin: this.ymin + (height / 2) - halfSize,
|
172 |
xmax: this.xmax + halfSize,
|
173 |
ymax: this.ymin + (height / 2) + halfSize,
|
|
|
174 |
},
|
175 |
{
|
176 |
// Bottom center
|
@@ -178,6 +185,7 @@ export default class Box {
|
|
178 |
ymin: this.ymax - halfSize,
|
179 |
xmax: this.xmin + (width / 2) + halfSize,
|
180 |
ymax: this.ymax + halfSize,
|
|
|
181 |
},
|
182 |
{
|
183 |
// Left center
|
@@ -185,6 +193,7 @@ export default class Box {
|
|
185 |
ymin: this.ymin + (height / 2) - halfSize,
|
186 |
xmax: this.xmin + halfSize,
|
187 |
ymax: this.ymin + (height / 2) + halfSize,
|
|
|
188 |
},
|
189 |
];
|
190 |
}
|
|
|
47 |
ymin: number;
|
48 |
xmax: number;
|
49 |
ymax: number;
|
50 |
+
cursor: string;
|
51 |
}[];
|
52 |
|
53 |
constructor(
|
|
|
137 |
ymin: this.ymin - halfSize,
|
138 |
xmax: this.xmin + halfSize,
|
139 |
ymax: this.ymin + halfSize,
|
140 |
+
cursor: "nwse-resize",
|
141 |
},
|
142 |
{
|
143 |
// Top right
|
|
|
145 |
ymin: this.ymin - halfSize,
|
146 |
xmax: this.xmax + halfSize,
|
147 |
ymax: this.ymin + halfSize,
|
148 |
+
cursor: "nesw-resize",
|
149 |
},
|
150 |
{
|
151 |
// Bottom right
|
|
|
153 |
ymin: this.ymax - halfSize,
|
154 |
xmax: this.xmax + halfSize,
|
155 |
ymax: this.ymax + halfSize,
|
156 |
+
cursor: "nwse-resize",
|
157 |
},
|
158 |
{
|
159 |
// Bottom left
|
|
|
161 |
ymin: this.ymax - halfSize,
|
162 |
xmax: this.xmin + halfSize,
|
163 |
ymax: this.ymax + halfSize,
|
164 |
+
cursor: "nesw-resize",
|
165 |
},
|
166 |
{
|
167 |
// Top center
|
|
|
169 |
ymin: this.ymin - halfSize,
|
170 |
xmax: this.xmin + (width / 2) + halfSize,
|
171 |
ymax: this.ymin + halfSize,
|
172 |
+
cursor: "ns-resize",
|
173 |
},
|
174 |
{
|
175 |
// Right center
|
|
|
177 |
ymin: this.ymin + (height / 2) - halfSize,
|
178 |
xmax: this.xmax + halfSize,
|
179 |
ymax: this.ymin + (height / 2) + halfSize,
|
180 |
+
cursor: "ew-resize",
|
181 |
},
|
182 |
{
|
183 |
// Bottom center
|
|
|
185 |
ymin: this.ymax - halfSize,
|
186 |
xmax: this.xmin + (width / 2) + halfSize,
|
187 |
ymax: this.ymax + halfSize,
|
188 |
+
cursor: "ns-resize",
|
189 |
},
|
190 |
{
|
191 |
// Left center
|
|
|
193 |
ymin: this.ymin + (height / 2) - halfSize,
|
194 |
xmax: this.xmin + halfSize,
|
195 |
ymax: this.ymin + (height / 2) + halfSize,
|
196 |
+
cursor: "ew-resize",
|
197 |
},
|
198 |
];
|
199 |
}
|
src/frontend/shared/Canvas.svelte
CHANGED
@@ -21,6 +21,7 @@
|
|
21 |
export let disableEditBoxes: boolean = false;
|
22 |
export let singleBox: boolean = false;
|
23 |
export let showRemoveButton: boolean = null;
|
|
|
24 |
|
25 |
if (showRemoveButton === null) {
|
26 |
showRemoveButton = (disableEditBoxes);
|
@@ -131,13 +132,40 @@
|
|
131 |
return;
|
132 |
}
|
133 |
}
|
134 |
-
|
|
|
|
|
|
|
135 |
}
|
136 |
|
137 |
function handlePointerUp(event: PointerEvent) {
|
138 |
dispatch("change");
|
139 |
}
|
140 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
141 |
function handleKeyPress(event: KeyboardEvent) {
|
142 |
if (!interactive) {
|
143 |
return;
|
@@ -189,6 +217,7 @@
|
|
189 |
box.startCreating(event, rect.left, rect.top);
|
190 |
if (singleBox) {
|
191 |
value.boxes = [box];
|
|
|
192 |
} else {
|
193 |
value.boxes = [box, ...value.boxes];
|
194 |
}
|
@@ -273,6 +302,9 @@
|
|
273 |
if (selectedBox >= 0 && selectedBox < value.boxes.length) {
|
274 |
value.boxes.splice(selectedBox, 1);
|
275 |
selectBox(-1);
|
|
|
|
|
|
|
276 |
dispatch("change");
|
277 |
}
|
278 |
}
|
@@ -398,6 +430,9 @@
|
|
398 |
ctx = canvas.getContext("2d");
|
399 |
observer.observe(canvas);
|
400 |
|
|
|
|
|
|
|
401 |
setImage();
|
402 |
resize();
|
403 |
draw();
|
@@ -427,6 +462,7 @@
|
|
427 |
bind:this={canvas}
|
428 |
on:pointerdown={handlePointerDown}
|
429 |
on:pointerup={handlePointerUp}
|
|
|
430 |
on:dblclick={handleDoubleClick}
|
431 |
class="canvas-annotator"
|
432 |
></canvas>
|
|
|
21 |
export let disableEditBoxes: boolean = false;
|
22 |
export let singleBox: boolean = false;
|
23 |
export let showRemoveButton: boolean = null;
|
24 |
+
export let handlesCursor: boolean = true;
|
25 |
|
26 |
if (showRemoveButton === null) {
|
27 |
showRemoveButton = (disableEditBoxes);
|
|
|
132 |
return;
|
133 |
}
|
134 |
}
|
135 |
+
|
136 |
+
if (!singleBox) {
|
137 |
+
selectBox(-1);
|
138 |
+
}
|
139 |
}
|
140 |
|
141 |
function handlePointerUp(event: PointerEvent) {
|
142 |
dispatch("change");
|
143 |
}
|
144 |
|
145 |
+
function handlePointerMove(event: PointerEvent) {
|
146 |
+
if (value === null) {
|
147 |
+
return;
|
148 |
+
}
|
149 |
+
|
150 |
+
if (mode !== Mode.drag) {
|
151 |
+
return;
|
152 |
+
}
|
153 |
+
|
154 |
+
const rect = canvas.getBoundingClientRect();
|
155 |
+
const mouseX = event.clientX - rect.left;
|
156 |
+
const mouseY = event.clientY - rect.top;
|
157 |
+
|
158 |
+
for (const [_, box] of value.boxes.entries()) {
|
159 |
+
const handleIndex = box.indexOfPointInsideHandle(mouseX, mouseY);
|
160 |
+
if (handleIndex >= 0) {
|
161 |
+
canvas.style.cursor = box.resizeHandles[handleIndex].cursor;
|
162 |
+
return;
|
163 |
+
}
|
164 |
+
}
|
165 |
+
|
166 |
+
canvas.style.cursor = "default";
|
167 |
+
}
|
168 |
+
|
169 |
function handleKeyPress(event: KeyboardEvent) {
|
170 |
if (!interactive) {
|
171 |
return;
|
|
|
217 |
box.startCreating(event, rect.left, rect.top);
|
218 |
if (singleBox) {
|
219 |
value.boxes = [box];
|
220 |
+
setDragMode();
|
221 |
} else {
|
222 |
value.boxes = [box, ...value.boxes];
|
223 |
}
|
|
|
302 |
if (selectedBox >= 0 && selectedBox < value.boxes.length) {
|
303 |
value.boxes.splice(selectedBox, 1);
|
304 |
selectBox(-1);
|
305 |
+
if (singleBox) {
|
306 |
+
setCreateMode();
|
307 |
+
}
|
308 |
dispatch("change");
|
309 |
}
|
310 |
}
|
|
|
430 |
ctx = canvas.getContext("2d");
|
431 |
observer.observe(canvas);
|
432 |
|
433 |
+
if (selectedBox < 0 && value !== null && value.boxes.length > 0) {
|
434 |
+
selectBox(0);
|
435 |
+
}
|
436 |
setImage();
|
437 |
resize();
|
438 |
draw();
|
|
|
462 |
bind:this={canvas}
|
463 |
on:pointerdown={handlePointerDown}
|
464 |
on:pointerup={handlePointerUp}
|
465 |
+
on:pointermove={handlesCursor ? handlePointerMove : null}
|
466 |
on:dblclick={handleDoubleClick}
|
467 |
class="canvas-annotator"
|
468 |
></canvas>
|
src/frontend/shared/ImageAnnotator.svelte
CHANGED
@@ -1,22 +1,23 @@
|
|
1 |
<script lang="ts">
|
2 |
-
import { createEventDispatcher } from "svelte";
|
3 |
import { Download, Image as ImageIcon } from "@gradio/icons";
|
4 |
import { DownloadLink } from "@gradio/wasm/svelte";
|
5 |
import { uploadToHuggingFace } from "@gradio/utils";
|
6 |
import { BlockLabel, IconButton, ShareButton, SelectSource} from "@gradio/atoms";
|
7 |
import { Upload } from "@gradio/upload";
|
|
|
8 |
import type { FileData, Client } from "@gradio/client";
|
9 |
import type { I18nFormatter, SelectData } from "@gradio/utils";
|
10 |
import { Clear } from "@gradio/icons";
|
11 |
import ImageCanvas from "./ImageCanvas.svelte";
|
12 |
import AnnotatedImageData from "./AnnotatedImageData";
|
13 |
-
|
14 |
-
type source_type = "upload" | "clipboard" | null;
|
15 |
|
16 |
export let value: null | AnnotatedImageData;
|
17 |
export let label: string | undefined = undefined;
|
18 |
export let show_label: boolean;
|
19 |
-
export let sources: source_type[] = ["upload", "clipboard"];
|
20 |
export let selectable = false;
|
21 |
export let root: string;
|
22 |
export let interactive: boolean;
|
@@ -33,6 +34,7 @@
|
|
33 |
export let disableEditBoxes: boolean;
|
34 |
export let singleBox: boolean;
|
35 |
export let showRemoveButton: boolean;
|
|
|
36 |
export let boxSelectedThickness: number;
|
37 |
export let max_file_size: number | null = null;
|
38 |
export let cli_upload: Client["upload"];
|
@@ -54,6 +56,19 @@
|
|
54 |
dispatch("change");
|
55 |
}
|
56 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
57 |
$: if (uploading) clear();
|
58 |
|
59 |
const dispatch = createEventDispatcher<{
|
@@ -124,7 +139,7 @@
|
|
124 |
<div data-testid="image" class="image-container">
|
125 |
<div class="upload-container">
|
126 |
<Upload
|
127 |
-
hidden={value !== null}
|
128 |
bind:this={upload}
|
129 |
bind:uploading
|
130 |
bind:dragging
|
@@ -141,6 +156,20 @@
|
|
141 |
<slot />
|
142 |
{/if}
|
143 |
</Upload>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
144 |
{#if value !== null}
|
145 |
<div class:selectable class="image-frame" >
|
146 |
<ImageCanvas
|
@@ -156,7 +185,8 @@
|
|
156 |
{singleBox}
|
157 |
{disableEditBoxes}
|
158 |
{showRemoveButton}
|
159 |
-
|
|
|
160 |
src={value.image.url}
|
161 |
/>
|
162 |
</div>
|
|
|
1 |
<script lang="ts">
|
2 |
+
import { createEventDispatcher, tick } from "svelte";
|
3 |
import { Download, Image as ImageIcon } from "@gradio/icons";
|
4 |
import { DownloadLink } from "@gradio/wasm/svelte";
|
5 |
import { uploadToHuggingFace } from "@gradio/utils";
|
6 |
import { BlockLabel, IconButton, ShareButton, SelectSource} from "@gradio/atoms";
|
7 |
import { Upload } from "@gradio/upload";
|
8 |
+
import { Webcam } from "@gradio/image";
|
9 |
import type { FileData, Client } from "@gradio/client";
|
10 |
import type { I18nFormatter, SelectData } from "@gradio/utils";
|
11 |
import { Clear } from "@gradio/icons";
|
12 |
import ImageCanvas from "./ImageCanvas.svelte";
|
13 |
import AnnotatedImageData from "./AnnotatedImageData";
|
14 |
+
|
15 |
+
type source_type = "upload" | "webcam" | "clipboard" | null;
|
16 |
|
17 |
export let value: null | AnnotatedImageData;
|
18 |
export let label: string | undefined = undefined;
|
19 |
export let show_label: boolean;
|
20 |
+
export let sources: source_type[] = ["upload", "webcam", "clipboard"];
|
21 |
export let selectable = false;
|
22 |
export let root: string;
|
23 |
export let interactive: boolean;
|
|
|
34 |
export let disableEditBoxes: boolean;
|
35 |
export let singleBox: boolean;
|
36 |
export let showRemoveButton: boolean;
|
37 |
+
export let handlesCursor: boolean;
|
38 |
export let boxSelectedThickness: number;
|
39 |
export let max_file_size: number | null = null;
|
40 |
export let cli_upload: Client["upload"];
|
|
|
56 |
dispatch("change");
|
57 |
}
|
58 |
|
59 |
+
async function handle_save(img_blob: Blob | any): Promise<void> {
|
60 |
+
const f = await upload.load_files([new File([img_blob], `webcam.png`)]);
|
61 |
+
const image = f?.[0] || null;
|
62 |
+
if (image) {
|
63 |
+
value = new AnnotatedImageData();
|
64 |
+
value.image = image;
|
65 |
+
} else {
|
66 |
+
value = null;
|
67 |
+
}
|
68 |
+
await tick();
|
69 |
+
dispatch("change");
|
70 |
+
}
|
71 |
+
|
72 |
$: if (uploading) clear();
|
73 |
|
74 |
const dispatch = createEventDispatcher<{
|
|
|
139 |
<div data-testid="image" class="image-container">
|
140 |
<div class="upload-container">
|
141 |
<Upload
|
142 |
+
hidden={value !== null || active_source === "webcam"}
|
143 |
bind:this={upload}
|
144 |
bind:uploading
|
145 |
bind:dragging
|
|
|
156 |
<slot />
|
157 |
{/if}
|
158 |
</Upload>
|
159 |
+
{#if value === null && active_source === "webcam"}
|
160 |
+
<Webcam
|
161 |
+
{root}
|
162 |
+
on:capture={(e) => handle_save(e.detail)}
|
163 |
+
on:stream={(e) => handle_save(e.detail)}
|
164 |
+
on:error
|
165 |
+
on:drag
|
166 |
+
on:upload={(e) => handle_save(e.detail)}
|
167 |
+
mode="image"
|
168 |
+
include_audio={false}
|
169 |
+
{i18n}
|
170 |
+
{upload}
|
171 |
+
/>
|
172 |
+
{/if}
|
173 |
{#if value !== null}
|
174 |
<div class:selectable class="image-frame" >
|
175 |
<ImageCanvas
|
|
|
185 |
{singleBox}
|
186 |
{disableEditBoxes}
|
187 |
{showRemoveButton}
|
188 |
+
{handlesCursor}
|
189 |
+
{boxSelectedThickness}
|
190 |
src={value.image.url}
|
191 |
/>
|
192 |
</div>
|
src/frontend/shared/ImageCanvas.svelte
CHANGED
@@ -22,6 +22,7 @@
|
|
22 |
export let disableEditBoxes: boolean;
|
23 |
export let singleBox: boolean;
|
24 |
export let showRemoveButton: boolean;
|
|
|
25 |
|
26 |
let resolved_src: typeof src;
|
27 |
|
@@ -66,5 +67,6 @@
|
|
66 |
{disableEditBoxes}
|
67 |
{singleBox}
|
68 |
{showRemoveButton}
|
|
|
69 |
imageUrl={resolved_src}
|
70 |
/>
|
|
|
22 |
export let disableEditBoxes: boolean;
|
23 |
export let singleBox: boolean;
|
24 |
export let showRemoveButton: boolean;
|
25 |
+
export let handlesCursor: boolean;
|
26 |
|
27 |
let resolved_src: typeof src;
|
28 |
|
|
|
67 |
{disableEditBoxes}
|
68 |
{singleBox}
|
69 |
{showRemoveButton}
|
70 |
+
{handlesCursor}
|
71 |
imageUrl={resolved_src}
|
72 |
/>
|
src/pyproject.toml
CHANGED
@@ -8,7 +8,7 @@ build-backend = "hatchling.build"
|
|
8 |
|
9 |
[project]
|
10 |
name = "gradio_image_annotation"
|
11 |
-
version = "0.2.
|
12 |
description = "A Gradio component that can be used to annotate images with bounding boxes."
|
13 |
readme = "README.md"
|
14 |
license = "MIT"
|
|
|
8 |
|
9 |
[project]
|
10 |
name = "gradio_image_annotation"
|
11 |
+
version = "0.2.3"
|
12 |
description = "A Gradio component that can be used to annotate images with bounding boxes."
|
13 |
readme = "README.md"
|
14 |
license = "MIT"
|