imagebaker 0.0.51__tar.gz → 0.0.53__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. imagebaker-0.0.53/CONTRIBUTING +5 -0
  2. {imagebaker-0.0.51 → imagebaker-0.0.53}/PKG-INFO +22 -12
  3. {imagebaker-0.0.51 → imagebaker-0.0.53}/README.md +22 -12
  4. imagebaker-0.0.53/docs/index.md +136 -0
  5. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/core/defs/defs.py +6 -0
  6. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/layers/annotable_layer.py +89 -12
  7. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/layers/base_layer.py +9 -1
  8. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/layers/canvas_layer.py +28 -5
  9. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/list_views/annotation_list.py +3 -2
  10. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/list_views/image_list.py +5 -0
  11. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/list_views/layer_settings.py +29 -0
  12. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/tabs/baker_tab.py +49 -1
  13. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/tabs/layerify_tab.py +114 -5
  14. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/utils/state_utils.py +1 -0
  15. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/window/main_window.py +65 -0
  16. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/workers/baker_worker.py +12 -0
  17. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker.egg-info/PKG-INFO +22 -12
  18. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker.egg-info/SOURCES.txt +1 -0
  19. imagebaker-0.0.51/docs/index.md +0 -87
  20. {imagebaker-0.0.51 → imagebaker-0.0.53}/.github/workflows/black-formatter.yml +0 -0
  21. {imagebaker-0.0.51 → imagebaker-0.0.53}/.github/workflows/pypi.yml +0 -0
  22. {imagebaker-0.0.51 → imagebaker-0.0.53}/.gitignore +0 -0
  23. {imagebaker-0.0.51 → imagebaker-0.0.53}/CHANGELOG.md +0 -0
  24. {imagebaker-0.0.51 → imagebaker-0.0.53}/LICENSE +0 -0
  25. {imagebaker-0.0.51 → imagebaker-0.0.53}/TODO.md +0 -0
  26. {imagebaker-0.0.51 → imagebaker-0.0.53}/assets/demo/annotated_veg_smiley.png +0 -0
  27. {imagebaker-0.0.51 → imagebaker-0.0.53}/assets/demo/annotation_page.png +0 -0
  28. {imagebaker-0.0.51 → imagebaker-0.0.53}/assets/demo/baker_page.png +0 -0
  29. {imagebaker-0.0.51 → imagebaker-0.0.53}/assets/demo/drawing.png +0 -0
  30. {imagebaker-0.0.51 → imagebaker-0.0.53}/assets/demo/options.png +0 -0
  31. {imagebaker-0.0.51 → imagebaker-0.0.53}/assets/demo.gif +0 -0
  32. {imagebaker-0.0.51 → imagebaker-0.0.53}/assets/desk.png +0 -0
  33. {imagebaker-0.0.51 → imagebaker-0.0.53}/assets/favicon_io/README.md +0 -0
  34. {imagebaker-0.0.51 → imagebaker-0.0.53}/assets/favicon_io/android-chrome-192x192.png +0 -0
  35. {imagebaker-0.0.51 → imagebaker-0.0.53}/assets/favicon_io/android-chrome-512x512.png +0 -0
  36. {imagebaker-0.0.51 → imagebaker-0.0.53}/assets/favicon_io/apple-touch-icon.png +0 -0
  37. {imagebaker-0.0.51 → imagebaker-0.0.53}/assets/favicon_io/favicon-16x16.png +0 -0
  38. {imagebaker-0.0.51 → imagebaker-0.0.53}/assets/favicon_io/favicon-32x32.png +0 -0
  39. {imagebaker-0.0.51 → imagebaker-0.0.53}/assets/favicon_io/favicon.ico +0 -0
  40. {imagebaker-0.0.51 → imagebaker-0.0.53}/assets/favicon_io/site.webmanifest +0 -0
  41. {imagebaker-0.0.51 → imagebaker-0.0.53}/assets/me.jpg +0 -0
  42. {imagebaker-0.0.51 → imagebaker-0.0.53}/assets/pen.png +0 -0
  43. {imagebaker-0.0.51 → imagebaker-0.0.53}/assets/veg_smiley.jpg +0 -0
  44. {imagebaker-0.0.51 → imagebaker-0.0.53}/docs/api-reference.md +0 -0
  45. {imagebaker-0.0.51 → imagebaker-0.0.53}/examples/app.py +0 -0
  46. {imagebaker-0.0.51 → imagebaker-0.0.53}/examples/example_config.py +0 -0
  47. {imagebaker-0.0.51 → imagebaker-0.0.53}/examples/loaded_models.py +0 -0
  48. {imagebaker-0.0.51 → imagebaker-0.0.53}/examples/rtdetr_v2.py +0 -0
  49. {imagebaker-0.0.51 → imagebaker-0.0.53}/examples/sam_model.py +0 -0
  50. {imagebaker-0.0.51 → imagebaker-0.0.53}/examples/segmentation.py +0 -0
  51. {imagebaker-0.0.51 → imagebaker-0.0.53}/experiments/expt.ipynb +0 -0
  52. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/__init__.py +0 -0
  53. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/core/__init__.py +0 -0
  54. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/core/configs/__init__.py +0 -0
  55. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/core/configs/configs.py +0 -0
  56. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/core/defs/__init__.py +0 -0
  57. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/core/plugins/__init__.py +0 -0
  58. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/core/plugins/base_plugin.py +0 -0
  59. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/core/plugins/cosine_plugin.py +0 -0
  60. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/layers/__init__.py +0 -0
  61. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/list_views/__init__.py +0 -0
  62. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/list_views/canvas_list.py +0 -0
  63. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/list_views/layer_list.py +0 -0
  64. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/models/__init__.py +0 -0
  65. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/models/base_model.py +0 -0
  66. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/tabs/__init__.py +0 -0
  67. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/utils/__init__.py +0 -0
  68. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/utils/image.py +0 -0
  69. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/utils/transform_mask.py +0 -0
  70. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/utils/utils.py +0 -0
  71. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/utils/vis.py +0 -0
  72. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/window/__init__.py +0 -0
  73. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/window/app.py +0 -0
  74. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/workers/__init__.py +0 -0
  75. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/workers/layerify_worker.py +0 -0
  76. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker/workers/model_worker.py +0 -0
  77. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker.egg-info/dependency_links.txt +0 -0
  78. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker.egg-info/entry_points.txt +0 -0
  79. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker.egg-info/requires.txt +0 -0
  80. {imagebaker-0.0.51 → imagebaker-0.0.53}/imagebaker.egg-info/top_level.txt +0 -0
  81. {imagebaker-0.0.51 → imagebaker-0.0.53}/mkdocs.yml +0 -0
  82. {imagebaker-0.0.51 → imagebaker-0.0.53}/requirements.txt +0 -0
  83. {imagebaker-0.0.51 → imagebaker-0.0.53}/setup.cfg +0 -0
  84. {imagebaker-0.0.51 → imagebaker-0.0.53}/setup.py +0 -0
@@ -0,0 +1,5 @@
1
+ ## Contributing Guidelines
2
+
3
+ * Please add a good description of what it does.
4
+ * If relevent, also add how it was before and how it is now after the change. Either by screenshot or by logs.
5
+ * If updated the codebase, please run tests.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: imagebaker
3
- Version: 0.0.51
3
+ Version: 0.0.53
4
4
  Summary: A package for baking images.
5
5
  Home-page: https://github.com/q-viper/Image-Baker
6
6
  Author: Ramkrishna Acharya
@@ -24,7 +24,7 @@ License-File: LICENSE
24
24
  [![PyPI version](https://img.shields.io/pypi/v/imagebaker.svg)](https://pypi.org/imagebaker/)
25
25
 
26
26
  <p align="center">
27
- <img src="assets/demo.gif" alt="Centered Demo" />
27
+ <img src="https://github.com/q-viper/image-baker/blob/main/assets/demo.gif?raw=true" alt="Centered Demo" />
28
28
  </p>
29
29
 
30
30
 
@@ -62,27 +62,28 @@ Run the following command to launch the GUI:
62
62
 
63
63
  `imagebaker`
64
64
 
65
- By default, the above command will not run any models on the backend. So please take a look into the example of model definition at [examples/loaded_models.py](examples/loaded_models.py). Then we need to pass it as:
65
+ By default, the above command will not run any models on the backend. So please take a look into the example of model definition at [examples/loaded_models.py](https://github.com/q-viper/image-baker/blob/main/examples/loaded_models.py). Then we need to pass it as:
66
66
 
67
67
  `imagebaker --models-file examples/loaded_models.py`
68
68
 
69
69
  For more options, please do: `imagebaker --help` It should give the following options.
70
70
 
71
- ![](assets/demo/options.png)
71
+ ![](https://github.com/q-viper/image-baker/blob/main/assets/demo/options.png?raw=true)
72
72
 
73
73
 
74
- * **`--configs-file`** allows us to define custom configs. The custom configs have to inherit LayerConfig and CanvasConfig defined at [imagebaker/core/configs/configs.py](imagebaker/core/configs/configs.py). An example is available at [examples](examples/).
74
+ * **`--configs-file`** allows us to define custom configs. The custom configs have to inherit LayerConfig and CanvasConfig defined at [imagebaker/core/configs/configs.py](https://github.com/q-viper/image-baker/blob/main/imagebaker/core/configs/configs.py). An example is available at [examples](https://github.com/q-viper/image-baker/blob/main/examples/).
75
75
 
76
76
  After cloning and going to the project directory, the following code should work.
77
77
  `imagebaker --models-file examples/loaded_models.py --configs-file examples/example_config.py`
78
78
 
79
79
  ## Features
80
80
  - **Annotating Images**: Load a folder of images and annotate them using bounding boxes or polygons.
81
- - **Model Testing**: Define models for detection, segmentation, and prompts (e.g., points or rectangles) by following the base model structure in [imagebaker/models/base_model.py](imagebaker/models/base_model.py). See [examples/loaded_models.py](examples/loaded_models.py) for a working example.
81
+ - **Model Testing**: Define models for detection, segmentation, and prompts (e.g., points or rectangles) by following the base model structure in [imagebaker/models/base_model.py](https://github.com/q-viper/image-baker/blob/main/imagebaker/models/base_model.py). See [examples/loaded_models.py](https://github.com/q-viper/image-baker/blob/main/examples/loaded_models.py) for a working example.
82
82
  - **Layerifying**: Crop images based on annotations to create reusable layers. Each cropped image represents a single layer.
83
83
  - **Baking States**: Arrange layers to create image variations by dragging, rotating, adjusting opacity, and more. Save the state using the Save State button or Ctrl + S.
84
84
  - **Playing States**: Replay saved states, export them locally, or use them for further predictions.
85
85
  - **Exporting States**: Export the final annotated JSON and the baked multilayer image.
86
+ - **Drawing On Layers**: First select a layer then draw upon it. Only selected layer will be drawn. And if no layers are selected, then the drawing will not be exported.
86
87
 
87
88
  ### Shortcuts
88
89
  * **Ctrl + C**: Copy selected annotation/layer.
@@ -91,37 +92,44 @@ After cloning and going to the project directory, the following code should work
91
92
  * **Left Click**: Select an annotation/layer on mouse position.
92
93
  * **Left Click + Drag**: Drag a selected annotation/layer.
93
94
  * **Double Left Click**: When using polygon annotation, completes the polygon.
94
- * **Right Click**: Deselect an annotation/layer. While annotating the polygon, undo the last point.
95
+ * **Right Click**: Unselect an annotation/layer. While annotating the polygon, undo the last point.
95
96
  * **Ctrl + Mouse Wheel**: Zoom In/Out on the mouse position, i.e., resize the viewport.
96
97
  * **Ctrl + Drag**: If done on the background, the viewport is panned.
97
98
  * **Ctrl + S**: Save State on Baker Tab.
98
99
  * **Ctrl + D**: Draw Mode on Baker Tab. Drawing can happen on a selected or main layer.
99
100
  * **Ctrl + E**: Erase Mode on Baker Tab.
101
+ * **Ctrl + H**: Opens a help window.
100
102
  * **Wheel**: Change the size of the drawing pointer.
101
103
  * **Q**: Point mode on annotation.
102
- * **W**: Polygon mode on annotation.
104
+ * **W**: Polygon mode on annotation. Moves selected layer one step up in layer lists in baker.
105
+ * **S**: Moves selected layer one step down in layer list in baker.
103
106
  * **E**: Rectangle mode on annotation.
107
+ * **H**: Hides/un-hides selected annotation/layer.
108
+ * **L**: Creates layer from an annotation. If any annotation selected, creates only its, else creates layers from all visible annotations.
109
+ * **C**: If any annotation is selected, a input box for Caption is created. It can be edited on baker tab as well and is state aware.
110
+ * **Numerics**: Selecting number 1, 2, till 9 selects label. If not available, asks for a new label.
111
+ * **Escape**: Closes the application.
104
112
 
105
113
  ## Demo
106
114
  ### Annotation Page
107
115
  This is where the loading of the image folder and annotation, connection with the model running in the backend, and layerifying happen.
108
116
 
109
- ![](assets/demo/annotation_page.png)
117
+ ![](https://github.com/q-viper/image-baker/blob/main/assets/demo/annotation_page.png?raw=True)
110
118
 
111
119
  ### Baker Page
112
120
  This is where the layer baking happens. And the extraction of the layers as well.
113
121
 
114
- ![](assets/demo/baker_page.png)
122
+ ![](https://github.com/q-viper/image-baker/blob/main/assets/demo/baker_page.png?raw=True)
115
123
 
116
124
  An example of drawing:
117
125
 
118
- ![](assets/demo/drawing.png)
126
+ ![](https://github.com/q-viper/image-baker/blob/main/assets/demo/drawing.png?raw=True)
119
127
 
120
128
  ### Annotated
121
129
 
122
130
  The JSON and the baked image will be exported to the local folder, and in debug mode, the annotations and the mask for each layer will be exported too.
123
131
 
124
- ![](assets/demo/annotated_veg_smiley.png)
132
+ ![](https://github.com/q-viper/image-baker/blob/main/assets/demo/annotated_veg_smiley.png?raw=True)
125
133
 
126
134
  ### Demo Video
127
135
 
@@ -140,3 +148,5 @@ Click on the image above to play the video on YouTube.
140
148
  Contributions are welcome!
141
149
 
142
150
  Do you find this project to be useful and are you looking for some features that are not implemented yet? Feel free to open issues or submit pull requests to improve the project.
151
+
152
+ For more please visit [CONTRIBUTING](CONTRIBUTING).
@@ -8,7 +8,7 @@
8
8
  [![PyPI version](https://img.shields.io/pypi/v/imagebaker.svg)](https://pypi.org/imagebaker/)
9
9
 
10
10
  <p align="center">
11
- <img src="assets/demo.gif" alt="Centered Demo" />
11
+ <img src="https://github.com/q-viper/image-baker/blob/main/assets/demo.gif?raw=true" alt="Centered Demo" />
12
12
  </p>
13
13
 
14
14
 
@@ -46,27 +46,28 @@ Run the following command to launch the GUI:
46
46
 
47
47
  `imagebaker`
48
48
 
49
- By default, the above command will not run any models on the backend. So please take a look into the example of model definition at [examples/loaded_models.py](examples/loaded_models.py). Then we need to pass it as:
49
+ By default, the above command will not run any models on the backend. So please take a look into the example of model definition at [examples/loaded_models.py](https://github.com/q-viper/image-baker/blob/main/examples/loaded_models.py). Then we need to pass it as:
50
50
 
51
51
  `imagebaker --models-file examples/loaded_models.py`
52
52
 
53
53
  For more options, please do: `imagebaker --help` It should give the following options.
54
54
 
55
- ![](assets/demo/options.png)
55
+ ![](https://github.com/q-viper/image-baker/blob/main/assets/demo/options.png?raw=true)
56
56
 
57
57
 
58
- * **`--configs-file`** allows us to define custom configs. The custom configs have to inherit LayerConfig and CanvasConfig defined at [imagebaker/core/configs/configs.py](imagebaker/core/configs/configs.py). An example is available at [examples](examples/).
58
+ * **`--configs-file`** allows us to define custom configs. The custom configs have to inherit LayerConfig and CanvasConfig defined at [imagebaker/core/configs/configs.py](https://github.com/q-viper/image-baker/blob/main/imagebaker/core/configs/configs.py). An example is available at [examples](https://github.com/q-viper/image-baker/blob/main/examples/).
59
59
 
60
60
  After cloning and going to the project directory, the following code should work.
61
61
  `imagebaker --models-file examples/loaded_models.py --configs-file examples/example_config.py`
62
62
 
63
63
  ## Features
64
64
  - **Annotating Images**: Load a folder of images and annotate them using bounding boxes or polygons.
65
- - **Model Testing**: Define models for detection, segmentation, and prompts (e.g., points or rectangles) by following the base model structure in [imagebaker/models/base_model.py](imagebaker/models/base_model.py). See [examples/loaded_models.py](examples/loaded_models.py) for a working example.
65
+ - **Model Testing**: Define models for detection, segmentation, and prompts (e.g., points or rectangles) by following the base model structure in [imagebaker/models/base_model.py](https://github.com/q-viper/image-baker/blob/main/imagebaker/models/base_model.py). See [examples/loaded_models.py](https://github.com/q-viper/image-baker/blob/main/examples/loaded_models.py) for a working example.
66
66
  - **Layerifying**: Crop images based on annotations to create reusable layers. Each cropped image represents a single layer.
67
67
  - **Baking States**: Arrange layers to create image variations by dragging, rotating, adjusting opacity, and more. Save the state using the Save State button or Ctrl + S.
68
68
  - **Playing States**: Replay saved states, export them locally, or use them for further predictions.
69
69
  - **Exporting States**: Export the final annotated JSON and the baked multilayer image.
70
+ - **Drawing On Layers**: First select a layer then draw upon it. Only selected layer will be drawn. And if no layers are selected, then the drawing will not be exported.
70
71
 
71
72
  ### Shortcuts
72
73
  * **Ctrl + C**: Copy selected annotation/layer.
@@ -75,37 +76,44 @@ After cloning and going to the project directory, the following code should work
75
76
  * **Left Click**: Select an annotation/layer on mouse position.
76
77
  * **Left Click + Drag**: Drag a selected annotation/layer.
77
78
  * **Double Left Click**: When using polygon annotation, completes the polygon.
78
- * **Right Click**: Deselect an annotation/layer. While annotating the polygon, undo the last point.
79
+ * **Right Click**: Unselect an annotation/layer. While annotating the polygon, undo the last point.
79
80
  * **Ctrl + Mouse Wheel**: Zoom In/Out on the mouse position, i.e., resize the viewport.
80
81
  * **Ctrl + Drag**: If done on the background, the viewport is panned.
81
82
  * **Ctrl + S**: Save State on Baker Tab.
82
83
  * **Ctrl + D**: Draw Mode on Baker Tab. Drawing can happen on a selected or main layer.
83
84
  * **Ctrl + E**: Erase Mode on Baker Tab.
85
+ * **Ctrl + H**: Opens a help window.
84
86
  * **Wheel**: Change the size of the drawing pointer.
85
87
  * **Q**: Point mode on annotation.
86
- * **W**: Polygon mode on annotation.
88
+ * **W**: Polygon mode on annotation. Moves selected layer one step up in layer lists in baker.
89
+ * **S**: Moves selected layer one step down in layer list in baker.
87
90
  * **E**: Rectangle mode on annotation.
91
+ * **H**: Hides/un-hides selected annotation/layer.
92
+ * **L**: Creates layer from an annotation. If any annotation selected, creates only its, else creates layers from all visible annotations.
93
+ * **C**: If any annotation is selected, a input box for Caption is created. It can be edited on baker tab as well and is state aware.
94
+ * **Numerics**: Selecting number 1, 2, till 9 selects label. If not available, asks for a new label.
95
+ * **Escape**: Closes the application.
88
96
 
89
97
  ## Demo
90
98
  ### Annotation Page
91
99
  This is where the loading of the image folder and annotation, connection with the model running in the backend, and layerifying happen.
92
100
 
93
- ![](assets/demo/annotation_page.png)
101
+ ![](https://github.com/q-viper/image-baker/blob/main/assets/demo/annotation_page.png?raw=True)
94
102
 
95
103
  ### Baker Page
96
104
  This is where the layer baking happens. And the extraction of the layers as well.
97
105
 
98
- ![](assets/demo/baker_page.png)
106
+ ![](https://github.com/q-viper/image-baker/blob/main/assets/demo/baker_page.png?raw=True)
99
107
 
100
108
  An example of drawing:
101
109
 
102
- ![](assets/demo/drawing.png)
110
+ ![](https://github.com/q-viper/image-baker/blob/main/assets/demo/drawing.png?raw=True)
103
111
 
104
112
  ### Annotated
105
113
 
106
114
  The JSON and the baked image will be exported to the local folder, and in debug mode, the annotations and the mask for each layer will be exported too.
107
115
 
108
- ![](assets/demo/annotated_veg_smiley.png)
116
+ ![](https://github.com/q-viper/image-baker/blob/main/assets/demo/annotated_veg_smiley.png?raw=True)
109
117
 
110
118
  ### Demo Video
111
119
 
@@ -123,4 +131,6 @@ Click on the image above to play the video on YouTube.
123
131
 
124
132
  Contributions are welcome!
125
133
 
126
- Do you find this project to be useful and are you looking for some features that are not implemented yet? Feel free to open issues or submit pull requests to improve the project.
134
+ Do you find this project to be useful and are you looking for some features that are not implemented yet? Feel free to open issues or submit pull requests to improve the project.
135
+
136
+ For more please visit [CONTRIBUTING](CONTRIBUTING).
@@ -0,0 +1,136 @@
1
+ # Image-Baker
2
+ ![Dependabot Status](https://img.shields.io/badge/dependabot-active-brightgreen)
3
+ ![GitHub License](https://img.shields.io/github/license/q-viper/image-baker)
4
+ ![commit activity](https://img.shields.io/github/commit-activity/w/q-viper/SmokeSim/master)
5
+ ![code size in bytes](https://img.shields.io/github/languages/code-size/q-viper/image-baker)
6
+ <!-- ![Tests](https://github.com/q-viper/SmokeSim/actions/workflows/test-on-push.yml/badge.svg) -->
7
+ ![Code Formatting](https://github.com/q-viper/image-baker/actions/workflows/black-formatter.yml/badge.svg)
8
+ [![PyPI version](https://img.shields.io/pypi/v/imagebaker.svg)](https://pypi.org/imagebaker/)
9
+
10
+ <p align="center">
11
+ <img src="https://github.com/q-viper/image-baker/blob/main/assets/demo.gif?raw=true" alt="Centered Demo" />
12
+ </p>
13
+
14
+
15
+ *An example of baked images. (Each object is a layer and an annotation will also be extracted for all layers.)*
16
+
17
+ Let's bake an image.
18
+
19
+ ## Why is it relevant?
20
+
21
+ When training computer vision models (especially for detection and segmentation), labeling large amounts of data is crucial for better model performance. Often, the process involves multiple cycles of labeling, training, and evaluation. By generating multiple realistic labeled datasets from a single image, the time spent on labeling can be significantly reduced.
22
+
23
+ ## What's up with the name?
24
+ The concept involves extracting portions of an image (e.g., objects of interest) using tools like polygons or models such as Segment Anything. These extractions are treated as layers, which can then be copied, pasted, and manipulated to create multiple instances of the desired object. By combining these layers step by step, a new labeled image with annotations in JSON format is created. The term "baking" refers to the process of merging these layers into a single cohesive image.
25
+
26
+ ## Getting Started
27
+ ### Installation
28
+ #### Using PIP
29
+ This project is also available on the PyPI server.
30
+
31
+ ```bash
32
+ pip install imagebaker
33
+ ```
34
+
35
+ #### Developing
36
+ Please, clone this repository and install it locally:
37
+
38
+ ```bash
39
+ git clone https://github.com/q-viper/image-baker.git
40
+ cd image-baker
41
+ pip install -e .
42
+ ```
43
+
44
+ ### First Run
45
+ Run the following command to launch the GUI:
46
+
47
+ `imagebaker`
48
+
49
+ By default, the above command will not run any models on the backend. So please take a look into the example of model definition at [examples/loaded_models.py](https://github.com/q-viper/image-baker/blob/main/examples/loaded_models.py). Then we need to pass it as:
50
+
51
+ `imagebaker --models-file examples/loaded_models.py`
52
+
53
+ For more options, please do: `imagebaker --help` It should give the following options.
54
+
55
+ ![](https://github.com/q-viper/image-baker/blob/main/assets/demo/options.png?raw=true)
56
+
57
+
58
+ * **`--configs-file`** allows us to define custom configs. The custom configs have to inherit LayerConfig and CanvasConfig defined at [imagebaker/core/configs/configs.py](https://github.com/q-viper/image-baker/blob/main/imagebaker/core/configs/configs.py). An example is available at [examples](https://github.com/q-viper/image-baker/blob/main/examples/).
59
+
60
+ After cloning and going to the project directory, the following code should work.
61
+ `imagebaker --models-file examples/loaded_models.py --configs-file examples/example_config.py`
62
+
63
+ ## Features
64
+ - **Annotating Images**: Load a folder of images and annotate them using bounding boxes or polygons.
65
+ - **Model Testing**: Define models for detection, segmentation, and prompts (e.g., points or rectangles) by following the base model structure in [imagebaker/models/base_model.py](https://github.com/q-viper/image-baker/blob/main/imagebaker/models/base_model.py). See [examples/loaded_models.py](https://github.com/q-viper/image-baker/blob/main/examples/loaded_models.py) for a working example.
66
+ - **Layerifying**: Crop images based on annotations to create reusable layers. Each cropped image represents a single layer.
67
+ - **Baking States**: Arrange layers to create image variations by dragging, rotating, adjusting opacity, and more. Save the state using the Save State button or Ctrl + S.
68
+ - **Playing States**: Replay saved states, export them locally, or use them for further predictions.
69
+ - **Exporting States**: Export the final annotated JSON and the baked multilayer image.
70
+ - **Drawing On Layers**: First select a layer then draw upon it. Only selected layer will be drawn. And if no layers are selected, then the drawing will not be exported.
71
+
72
+ ### Shortcuts
73
+ * **Ctrl + C**: Copy selected annotation/layer.
74
+ * **Ctrl + V**: Paste copied annotation/layer in its parent image/layer if it is currently open.
75
+ * **Delete**: Delete selected annotation/layer.
76
+ * **Left Click**: Select an annotation/layer on mouse position.
77
+ * **Left Click + Drag**: Drag a selected annotation/layer.
78
+ * **Double Left Click**: When using polygon annotation, completes the polygon.
79
+ * **Right Click**: Unselect an annotation/layer. While annotating the polygon, undo the last point.
80
+ * **Ctrl + Mouse Wheel**: Zoom In/Out on the mouse position, i.e., resize the viewport.
81
+ * **Ctrl + Drag**: If done on the background, the viewport is panned.
82
+ * **Ctrl + S**: Save State on Baker Tab.
83
+ * **Ctrl + D**: Draw Mode on Baker Tab. Drawing can happen on a selected or main layer.
84
+ * **Ctrl + E**: Erase Mode on Baker Tab.
85
+ * **Ctrl + H**: Opens a help window.
86
+ * **Wheel**: Change the size of the drawing pointer.
87
+ * **Q**: Point mode on annotation.
88
+ * **W**: Polygon mode on annotation. Moves selected layer one step up in layer lists in baker.
89
+ * **S**: Moves selected layer one step down in layer list in baker.
90
+ * **E**: Rectangle mode on annotation.
91
+ * **H**: Hides/un-hides selected annotation/layer.
92
+ * **L**: Creates layer from an annotation. If any annotation selected, creates only its, else creates layers from all visible annotations.
93
+ * **C**: If any annotation is selected, a input box for Caption is created. It can be edited on baker tab as well and is state aware.
94
+ * **Numerics**: Selecting number 1, 2, till 9 selects label. If not available, asks for a new label.
95
+ * **Escape**: Closes the application.
96
+
97
+ ## Demo
98
+ ### Annotation Page
99
+ This is where the loading of the image folder and annotation, connection with the model running in the backend, and layerifying happen.
100
+
101
+ ![](https://github.com/q-viper/image-baker/blob/main/assets/demo/annotation_page.png?raw=True)
102
+
103
+ ### Baker Page
104
+ This is where the layer baking happens. And the extraction of the layers as well.
105
+
106
+ ![](https://github.com/q-viper/image-baker/blob/main/assets/demo/baker_page.png?raw=True)
107
+
108
+ An example of drawing:
109
+
110
+ ![](https://github.com/q-viper/image-baker/blob/main/assets/demo/drawing.png?raw=True)
111
+
112
+ ### Annotated
113
+
114
+ The JSON and the baked image will be exported to the local folder, and in debug mode, the annotations and the mask for each layer will be exported too.
115
+
116
+ ![](https://github.com/q-viper/image-baker/blob/main/assets/demo/annotated_veg_smiley.png?raw=True)
117
+
118
+ ### Demo Video
119
+
120
+ To see the tool in action, check out the demo video below:
121
+
122
+
123
+ [![Demo Video](https://img.youtube.com/vi/WckMT0r-2Lc/0.jpg)](https://youtu.be/WckMT0r-2Lc)
124
+
125
+
126
+ Click on the image above to play the video on YouTube.
127
+
128
+
129
+ ## Contributions
130
+
131
+
132
+ Contributions are welcome!
133
+
134
+ Do you find this project to be useful and are you looking for some features that are not implemented yet? Feel free to open issues or submit pull requests to improve the project.
135
+
136
+ For more please visit [CONTRIBUTING](CONTRIBUTING).
@@ -78,6 +78,7 @@ class LayerState:
78
78
  drawing_states: list[DrawingState] = field(default_factory=list)
79
79
  edge_opacity: int = 100
80
80
  edge_width: int = 10
81
+ caption: str = ""
81
82
 
82
83
  def copy(self):
83
84
  return LayerState(
@@ -109,6 +110,7 @@ class LayerState:
109
110
  ],
110
111
  edge_opacity=self.edge_opacity,
111
112
  edge_width=self.edge_width,
113
+ caption=self.caption,
112
114
  )
113
115
 
114
116
 
@@ -138,6 +140,7 @@ class Annotation:
138
140
  file_path: Path = field(default_factory=lambda: Path("Runtime"))
139
141
  is_model_generated: bool = False
140
142
  model_name: str = None
143
+ caption: str = ""
141
144
 
142
145
  def copy(self):
143
146
  ann = Annotation(
@@ -156,6 +159,7 @@ class Annotation:
156
159
  file_path=self.file_path,
157
160
  is_model_generated=self.is_model_generated,
158
161
  model_name=self.model_name,
162
+ caption=self.caption,
159
163
  )
160
164
  ann.is_selected = False
161
165
  return ann
@@ -200,6 +204,7 @@ class Annotation:
200
204
  "file_path": str(annotation.file_path),
201
205
  "is_model_generated": annotation.is_model_generated,
202
206
  "model_name": annotation.model_name,
207
+ "caption": annotation.caption,
203
208
  }
204
209
  annotations_dict.append(data)
205
210
 
@@ -234,6 +239,7 @@ class Annotation:
234
239
  file_path=Path(d.get("file_path", "Runtime")),
235
240
  is_model_generated=d.get("is_model_generated", False),
236
241
  model_name=d.get("model_name", None),
242
+ caption=d.get("caption", ""),
237
243
  )
238
244
 
239
245
  # Handle points safely
@@ -24,6 +24,7 @@ from PySide6.QtGui import (
24
24
  QWheelEvent,
25
25
  QMouseEvent,
26
26
  QKeyEvent,
27
+ QFont,
27
28
  )
28
29
  from PySide6.QtWidgets import (
29
30
  QApplication,
@@ -68,14 +69,23 @@ class AnnotableLayer(BaseLayer):
68
69
  self.annotationCleared.emit()
69
70
  self.update()
70
71
 
72
+ def toggle_annotation_visibility(self):
73
+ """Toggle visibility of all annotations."""
74
+ selected_annotation = self._get_selected_annotation()
75
+ if selected_annotation is not None:
76
+ selected_annotation.visible = not selected_annotation.visible
77
+ self.annotationUpdated.emit(selected_annotation)
78
+ self.update()
79
+
71
80
  def handle_key_press(self, event: QKeyEvent):
72
81
  # Handle Ctrl key for panning
73
82
  if event.key() == Qt.Key_Control:
74
- if (
75
- self.mouse_mode != MouseMode.POLYGON
76
- ): # Only activate pan mode when not drawing polygons
83
+ # disable this for now to allow pannings
84
+ # if (
85
+ # self.mouse_mode != MouseMode.POLYGON
86
+ # ): # Only activate pan mode when not drawing polygons
77
87
 
78
- self.mouse_mode = MouseMode.PAN
88
+ self.mouse_mode = MouseMode.PAN
79
89
 
80
90
  # Handle Ctrl+C for copy
81
91
  if event.modifiers() & Qt.ControlModifier and event.key() == Qt.Key_C:
@@ -258,7 +268,7 @@ class AnnotableLayer(BaseLayer):
258
268
 
259
269
  # Draw labels
260
270
  if annotation.is_complete and annotation.label:
261
- painter.save()
271
+ # painter.save()
262
272
  label_pos = self.get_label_position(annotation)
263
273
  text = annotation.label
264
274
 
@@ -298,8 +308,58 @@ class AnnotableLayer(BaseLayer):
298
308
  # Draw text
299
309
  painter.setPen(Qt.white)
300
310
  painter.drawText(bg_rect, Qt.AlignCenter, text)
301
- painter.restore()
302
- self.label_rects.append((bg_rect, annotation))
311
+
312
+ # now if the annotations has caption,
313
+ # draw the caption below the label in itallic
314
+ # just below the label, in a font lighter than a label
315
+
316
+ if annotation.caption:
317
+ caption_font = painter.font()
318
+ # Draw a background rectangle for the caption to ensure visibility
319
+ caption_text = annotation.caption
320
+ caption_font.setItalic(True)
321
+ caption_font.setPixelSize(
322
+ self.config.selected_draw_config.label_font_size * self.scale + 2
323
+ )
324
+ caption_font.setWeight(QFont.Light)
325
+ painter.setFont(caption_font)
326
+ metrics = painter.fontMetrics()
327
+ text_width = metrics.horizontalAdvance(caption_text)
328
+ text_height = metrics.height()
329
+ # Draw background rectangle for caption
330
+ caption_rect = QRectF(
331
+ widget_pos.x() - text_width / 2 - 2,
332
+ widget_pos.y() + text_height / 2 - 2,
333
+ text_width + 4,
334
+ text_height + 4,
335
+ )
336
+ painter.setPen(Qt.NoPen)
337
+ painter.setBrush(
338
+ self.config.normal_draw_config.label_font_background_color
339
+ )
340
+ painter.drawRect(caption_rect)
341
+ # Draw caption text
342
+ painter.setPen(Qt.white)
343
+ painter.drawText(caption_rect, Qt.AlignCenter, caption_text)
344
+ caption_font.setItalic(True)
345
+ caption_font.setPixelSize(
346
+ self.config.selected_draw_config.label_font_size * self.scale + 2
347
+ )
348
+ caption_font.setWeight(QFont.Light)
349
+ painter.setFont(caption_font)
350
+ metrics = painter.fontMetrics()
351
+ text_width = metrics.horizontalAdvance(annotation.caption)
352
+ text_height = metrics.height()
353
+ painter.setPen(Qt.white)
354
+ painter.setBrush(Qt.gray)
355
+
356
+ caption_rect = QRectF(
357
+ widget_pos.x() - text_width / 2 - 2,
358
+ widget_pos.y() + text_height / 2 - 2,
359
+ text_width + 4,
360
+ text_height + 4,
361
+ )
362
+ painter.drawText(caption_rect, Qt.AlignCenter, annotation.caption)
303
363
 
304
364
  painter.restore()
305
365
 
@@ -398,7 +458,14 @@ class AnnotableLayer(BaseLayer):
398
458
  MouseMode.ZOOM_IN,
399
459
  MouseMode.ZOOM_OUT,
400
460
  ]:
401
- self.mouse_mode = MouseMode.IDLE
461
+ if self.current_annotation:
462
+ if (
463
+ self.current_annotation.polygon
464
+ and not self.current_annotation.is_complete
465
+ ):
466
+ self.mouse_mode = MouseMode.POLYGON
467
+ else:
468
+ self.mouse_mode = MouseMode.IDLE
402
469
 
403
470
  # Clean up transformation state
404
471
  if hasattr(self, "selected_annotation"):
@@ -602,6 +669,17 @@ class AnnotableLayer(BaseLayer):
602
669
  elif self.mouse_mode == MouseMode.POLYGON:
603
670
  # If not double-click
604
671
  if not self.current_annotation:
672
+ # if this point is equal to the last point of the previous polygon, then ignore it
673
+ if len(self.annotations) > 0:
674
+ last_polygon = self.annotations[-1].polygon
675
+ if last_polygon:
676
+ last_point = last_polygon[-1]
677
+ if last_point == clamped_pos:
678
+ logger.info(
679
+ "Ignoring point, same as last polygon point"
680
+ )
681
+ return
682
+
605
683
  self.current_annotation = Annotation(
606
684
  file_path=self.file_path,
607
685
  annotation_id=len(self.annotations),
@@ -811,8 +889,6 @@ class AnnotableLayer(BaseLayer):
811
889
  self.current_annotation
812
890
  )
813
891
  self.annotationAdded.emit(self.current_annotation)
814
- self.current_annotation = None
815
- self.update()
816
892
  else:
817
893
  # Show custom label dialog
818
894
  label, ok = QInputDialog.getText(self, "Label", "Enter label name:")
@@ -827,8 +903,8 @@ class AnnotableLayer(BaseLayer):
827
903
  )
828
904
  self.annotationAdded.emit(self.current_annotation)
829
905
  self.current_annotation.annotation_id = len(self.annotations)
830
- self.current_annotation = None
831
- self.update()
906
+ self.current_annotation = None
907
+ self.update()
832
908
 
833
909
  # in update, update cursor
834
910
 
@@ -915,6 +991,7 @@ class AnnotableLayer(BaseLayer):
915
991
  new_layer.layer_name = (
916
992
  f"{annotation.label} {annotation.annotation_id} {annotation.annotator}"
917
993
  )
994
+ new_layer.caption = annotation.caption
918
995
 
919
996
  new_layer._apply_edge_opacity()
920
997
  new_layer.update()
@@ -240,7 +240,7 @@ class BaseLayer(QWidget):
240
240
  for step, state in enumerate(intermediate_states):
241
241
  step += self.current_step
242
242
 
243
- logger.info(f"Saving state {step} for layer {layer.layer_id}")
243
+ logger.info(f"Saving state {step} for layer {layer.layer_id}: {state}")
244
244
  state.selected = False
245
245
  if step not in curr_states:
246
246
  curr_states[step] = []
@@ -853,3 +853,11 @@ class BaseLayer(QWidget):
853
853
  @edge_width.setter
854
854
  def edge_width(self, value: int):
855
855
  self.layer_state.edge_width = value
856
+
857
+ @property
858
+ def caption(self) -> str:
859
+ return self.layer_state.caption
860
+
861
+ @caption.setter
862
+ def caption(self, value: str):
863
+ self.layer_state.caption = value
@@ -27,7 +27,6 @@ from PySide6.QtGui import (
27
27
  QMouseEvent,
28
28
  QKeyEvent,
29
29
  QTransform,
30
- QImage,
31
30
  )
32
31
  from PySide6.QtWidgets import (
33
32
  QApplication,
@@ -127,7 +126,6 @@ class CanvasLayer(BaseLayer):
127
126
 
128
127
  ## Helper functions ##
129
128
  def handle_key_press(self, event: QKeyEvent):
130
-
131
129
  # Handle Delete key
132
130
  if event.key() == Qt.Key_Delete:
133
131
  self._delete_layer()
@@ -149,6 +147,15 @@ class CanvasLayer(BaseLayer):
149
147
  if event.modifiers() & Qt.ControlModifier and event.key() == Qt.Key_V:
150
148
  self._paste_layer()
151
149
  return # Important: return after handling
150
+ # if event.key() == Qt.Key_H:
151
+ # if self.selected_layer:
152
+ # # Toggle visibility of the selected layer
153
+ # logger.info(
154
+ # f"Toggling visibility of layer: {self.selected_layer.layer_name}"
155
+ # )
156
+ # self.selected_layer.visible = not self.selected_layer.visible
157
+ # self.selected_layer.update()
158
+ # return # Important: return after handling
152
159
 
153
160
  def paint_layer(self, painter: QPainter):
154
161
  """
@@ -362,7 +369,16 @@ class CanvasLayer(BaseLayer):
362
369
  """
363
370
  """Add a new drawing state."""
364
371
  self.selected_layer = self._get_selected_layer()
365
- layer = self.selected_layer if self.selected_layer else self
372
+ layer = self.selected_layer
373
+ if not layer:
374
+ logger.debug("No layer selected for drawing.")
375
+ # show popup message window that closes in 2 seconds
376
+ QMessageBox.information(
377
+ self.parent(),
378
+ "No Layer Selected",
379
+ "Please select a layer to draw on.",
380
+ )
381
+ return
366
382
 
367
383
  # Convert the position to be relative to the layer
368
384
  relative_pos = pos - layer.position
@@ -384,8 +400,14 @@ class CanvasLayer(BaseLayer):
384
400
  )
385
401
  layer.layer_state.drawing_states.append(drawing_state)
386
402
  self._last_draw_point = relative_pos # Update the last draw point
387
- # logger.debug(f"Added drawing state at position: {relative_pos}")
388
-
403
+ logger.debug(
404
+ f"Added drawing state at position: {relative_pos} to layer {layer.layer_name}"
405
+ )
406
+ else:
407
+ logger.debug(
408
+ f"Mouse mode {self.mouse_mode} does not support drawing states."
409
+ )
410
+ return
389
411
  self.update() # Refresh the canvas to show the new drawing
390
412
 
391
413
  def handle_wheel(self, event: QWheelEvent):
@@ -822,6 +844,7 @@ class CanvasLayer(BaseLayer):
822
844
  filename = self.config.export_folder / f"{filename}.png"
823
845
  logger.info(f"Exporting baked image to {filename}")
824
846
  self.states = {0: [layer.layer_state for layer in self.layers]}
847
+ logger.debug(f"Exporting states: {self.states}")
825
848
 
826
849
  self.loading_dialog = QProgressDialog(
827
850
  "Baking Please wait...", "Cancel", 0, 0, self.parentWidget()