edgargg commited on
Commit
dfd748f
1 Parent(s): a4c6cf5

Upload folder using huggingface_hub

Browse files
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 = ["upload", "clipboard"],
 
 
 
 
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: ("clipboard" | "upload")[] = ["upload", "clipboard"];
 
 
 
 
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
- selectBox(-1);
 
 
 
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
- boxSelectedThickness={boxSelectedThickness}
 
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.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"