Avanish11 commited on
Commit
11beacb
·
verified ·
1 Parent(s): e663c61

Upload 5 files

Browse files
Files changed (5) hide show
  1. LICENSE.txt +201 -0
  2. README.md +68 -13
  3. app.py +1064 -0
  4. pre-requirements.txt +1 -0
  5. requirements.txt +14 -0
LICENSE.txt ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
README.md CHANGED
@@ -1,13 +1,68 @@
1
- ---
2
- title: Object Remover
3
- emoji: 💻
4
- colorFrom: yellow
5
- colorTo: red
6
- sdk: gradio
7
- sdk_version: 6.17.3
8
- python_version: '3.13'
9
- app_file: app.py
10
- pinned: false
11
- ---
12
-
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # **QIE-Object-Remover-Bbox**
2
+
3
+ QIE-Object-Remover-Bbox is an advanced, AI-powered image editing application specifically designed to perform precise object removal and background inpainting based on user-defined bounding box coordinates. By leveraging sophisticated vision-language models—likely from the Qwen family, as indicated by the project structure—the tool allows users to accurately isolate unwanted elements within an image. Once a target region is designated, the underlying model seamlessly removes the object and intelligently fills the void with contextually appropriate textures and pixels, ensuring that the final image looks natural and undisturbed. The application is built entirely in Python and features a user-friendly Gradio web interface, making high-precision image manipulation accessible without requiring complex manual editing skills.
4
+
5
+ <img width="1919" height="1681" alt="Screenshot 2026-03-04 at 10-31-02 QIE Object Remover Bbox - a Hugging Face Space by prithivMLmods" src="https://github.com/user-attachments/assets/3d8a6214-641e-4ea5-a72f-3de629429a3d" />
6
+
7
+
8
+ ## Features
9
+
10
+ * **BBox-Based Target Identification:** Accurately specify the exact objects to be removed by providing bounding box coordinates.
11
+ * **Seamless Background Inpainting:** Automatically reconstructs and fills the removed area with context-aware textures that match the surrounding environment.
12
+ * **Interactive Web Interface:** Provides an intuitive, browser-based user interface powered by Gradio for easy image uploading and processing.
13
+ * **High-Precision Vision Logic:** Utilizes advanced Qwen-based vision processing to understand image context and segment objects cleanly.
14
+
15
+ ## Installation
16
+
17
+ ### 1. Clone the Repository
18
+
19
+ ```bash
20
+ git clone https://github.com/PRITHIVSAKTHIUR/QIE-Object-Remover-Bbox.git
21
+ cd QIE-Object-Remover-Bbox
22
+ ```
23
+
24
+ ### 2. Install Pre-requirements
25
+
26
+ System-level or heavy dependencies should be installed first:
27
+
28
+ ```bash
29
+ pip install -r pre-requirements.txt
30
+ ```
31
+
32
+ ### 3. Install Standard Dependencies
33
+
34
+ ```bash
35
+ pip install -r requirements.txt
36
+ ```
37
+
38
+ ## How to Run
39
+
40
+ Start the application by executing the main script:
41
+
42
+ ```bash
43
+ python app.py
44
+ ```
45
+
46
+ Once the script is running, the terminal will provide a local URL (typically `http://127.0.0.1:7860`). Open this URL in your web browser to access the interactive Gradio interface.
47
+
48
+ ## Project Structure
49
+
50
+ * `app.py`: The main entry point of the application containing the Gradio interface logic.
51
+ * `qwenimage/`: The core module housing the model processing and vision-language integration logic.
52
+ * `requirements.txt`: A list of standard Python dependencies required to run the application.
53
+ * `pre-requirements.txt`: Initial setup dependencies (such as PyTorch and torchvision) needed before standard requirements.
54
+ * `LICENSE.txt`: The licensing details for the repository.
55
+
56
+ ## Workflow
57
+
58
+ 1. Upload the target image via the web interface.
59
+ 2. Input the bounding box coordinates to define the area containing the unwanted object.
60
+ 3. The model processes the specified region, removes the object, performs inpainting, and returns the cleaned image.
61
+
62
+ ## License
63
+
64
+ This project is open-source and licensed under the Apache License 2.0. Please refer to the `LICENSE.txt` file within the repository for full terms and conditions.
65
+
66
+ ## Contributing
67
+
68
+ Contributions to the project are highly encouraged. You are welcome to submit a Pull Request or open an issue on GitHub to report bugs, suggest new features, or improve the existing codebase.
app.py ADDED
@@ -0,0 +1,1064 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import numpy as np
3
+ import random
4
+ import torch
5
+ import spaces
6
+ import base64
7
+ from io import BytesIO
8
+ from typing import Iterable
9
+ from PIL import Image, ImageDraw
10
+ from diffusers import FlowMatchEulerDiscreteScheduler
11
+ from qwenimage.pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline
12
+ from qwenimage.transformer_qwenimage import QwenImageTransformer2DModel
13
+ from qwenimage.qwen_fa3_processor import QwenDoubleStreamAttnProcessorFA3
14
+
15
+ from gradio.themes import Soft
16
+ from gradio.themes.utils import colors, fonts, sizes
17
+
18
+ colors.purple = colors.Color(
19
+ name="purple",
20
+ c50="#FAF5FF",
21
+ c100="#F3E8FF",
22
+ c200="#E9D5FF",
23
+ c300="#DAB2FF",
24
+ c400="#C084FC",
25
+ c500="#A855F7",
26
+ c600="#9333EA",
27
+ c700="#7E22CE",
28
+ c800="#6B21A8",
29
+ c900="#581C87",
30
+ c950="#3B0764",
31
+ )
32
+
33
+
34
+ class PurpleTheme(Soft):
35
+ def __init__(
36
+ self,
37
+ *,
38
+ primary_hue: colors.Color | str = colors.gray,
39
+ secondary_hue: colors.Color | str = colors.purple,
40
+ neutral_hue: colors.Color | str = colors.slate,
41
+ text_size: sizes.Size | str = sizes.text_lg,
42
+ font: fonts.Font | str | Iterable[fonts.Font | str] = (
43
+ fonts.GoogleFont("Outfit"), "Arial", "sans-serif",
44
+ ),
45
+ font_mono: fonts.Font | str | Iterable[fonts.Font | str] = (
46
+ fonts.GoogleFont("IBM Plex Mono"), "ui-monospace", "monospace",
47
+ ),
48
+ ):
49
+ super().__init__(
50
+ primary_hue=primary_hue,
51
+ secondary_hue=secondary_hue,
52
+ neutral_hue=neutral_hue,
53
+ text_size=text_size,
54
+ font=font,
55
+ font_mono=font_mono,
56
+ )
57
+ super().set(
58
+ background_fill_primary="*primary_50",
59
+ background_fill_primary_dark="*primary_900",
60
+ body_background_fill="linear-gradient(135deg, *primary_200, *primary_100)",
61
+ body_background_fill_dark="linear-gradient(135deg, *primary_900, *primary_800)",
62
+ button_primary_text_color="white",
63
+ button_primary_text_color_hover="white",
64
+ button_primary_background_fill="linear-gradient(90deg, *secondary_500, *secondary_600)",
65
+ button_primary_background_fill_hover="linear-gradient(90deg, *secondary_600, *secondary_700)",
66
+ button_primary_background_fill_dark="linear-gradient(90deg, *secondary_600, *secondary_700)",
67
+ button_primary_background_fill_hover_dark="linear-gradient(90deg, *secondary_500, *secondary_600)",
68
+ button_secondary_text_color="black",
69
+ button_secondary_text_color_hover="white",
70
+ button_secondary_background_fill="linear-gradient(90deg, *primary_300, *primary_300)",
71
+ button_secondary_background_fill_hover="linear-gradient(90deg, *primary_400, *primary_400)",
72
+ button_secondary_background_fill_dark="linear-gradient(90deg, *primary_500, *primary_600)",
73
+ button_secondary_background_fill_hover_dark="linear-gradient(90deg, *primary_500, *primary_500)",
74
+ slider_color="*secondary_500",
75
+ slider_color_dark="*secondary_600",
76
+ block_title_text_weight="600",
77
+ block_border_width="3px",
78
+ block_shadow="*shadow_drop_lg",
79
+ button_primary_shadow="*shadow_drop_lg",
80
+ button_large_padding="11px",
81
+ color_accent_soft="*primary_100",
82
+ block_label_background_fill="*primary_200",
83
+ )
84
+
85
+
86
+ purple_theme = PurpleTheme()
87
+
88
+ MAX_SEED = np.iinfo(np.int32).max
89
+
90
+ dtype = torch.bfloat16
91
+ device = "cuda" if torch.cuda.is_available() else "cpu"
92
+ pipe = QwenImageEditPlusPipeline.from_pretrained(
93
+ "Qwen/Qwen-Image-Edit-2509",
94
+ transformer=QwenImageTransformer2DModel.from_pretrained(
95
+ "prithivMLmods/Qwen-Image-Edit-Rapid-AIO-V4",
96
+ torch_dtype=dtype,
97
+ device_map="cuda",
98
+ ),
99
+ torch_dtype=dtype,
100
+ ).to(device)
101
+ try:
102
+ pipe.transformer.set_attn_processor(QwenDoubleStreamAttnProcessorFA3())
103
+ print("Flash Attention 3 Processor set successfully.")
104
+ except Exception as e:
105
+ print(f"Warning: Could not set FA3 processor: {e}")
106
+
107
+ ADAPTER_SPECS = {
108
+ "Object-Remover": {
109
+ "repo": "prithivMLmods/QIE-2509-Object-Remover-Bbox",
110
+ "weights": "QIE-2509-Object-Remover-Bbox-5000.safetensors",
111
+ "adapter_name": "object-remover",
112
+ },
113
+ }
114
+ loaded = False
115
+
116
+ DEFAULT_PROMPT = "Remove the red highlighted object from the scene"
117
+
118
+
119
+ def b64_to_pil(b64_str: str) -> Image.Image | None:
120
+ """Helper to decode base64 string from JS into a PIL Image"""
121
+ if not b64_str or not b64_str.startswith("data:image"):
122
+ return None
123
+ try:
124
+ _, data = b64_str.split(',', 1)
125
+ image_data = base64.b64decode(data)
126
+ return Image.open(BytesIO(image_data)).convert("RGB")
127
+ except Exception as e:
128
+ print(f"Error decoding image: {e}")
129
+ return None
130
+
131
+
132
+ def burn_boxes_onto_image(pil_image: Image.Image, boxes_json_str: str) -> Image.Image:
133
+ """Burn red outline-only rectangles onto the image (no fill)."""
134
+ import json
135
+ if not pil_image:
136
+ return pil_image
137
+ try:
138
+ boxes = json.loads(boxes_json_str) if boxes_json_str and boxes_json_str.strip() else []
139
+ except Exception:
140
+ boxes = []
141
+ if not boxes:
142
+ return pil_image
143
+
144
+ img = pil_image.copy().convert("RGB")
145
+ w, h = img.size
146
+ draw = ImageDraw.Draw(img)
147
+ bw = max(3, w // 250)
148
+
149
+ for b in boxes:
150
+ x1 = int(b["x1"] * w)
151
+ y1 = int(b["y1"] * h)
152
+ x2 = int(b["x2"] * w)
153
+ y2 = int(b["y2"] * h)
154
+ lx, rx = min(x1, x2), max(x1, x2)
155
+ ty, by_ = min(y1, y2), max(y1, y2)
156
+ # Red outline only — no fill
157
+ draw.rectangle([lx, ty, rx, by_], outline=(255, 0, 0), width=bw)
158
+
159
+ return img
160
+
161
+
162
+ @spaces.GPU
163
+ def infer_object_removal(
164
+ b64_str: str,
165
+ boxes_json: str,
166
+ prompt: str,
167
+ seed: int = 0,
168
+ randomize_seed: bool = True,
169
+ guidance_scale: float = 1.0,
170
+ num_inference_steps: int = 4,
171
+ height: int = 1024,
172
+ width: int = 1024,
173
+ ):
174
+ global loaded
175
+ progress = gr.Progress(track_tqdm=True)
176
+
177
+ if not loaded:
178
+ pipe.load_lora_weights(
179
+ ADAPTER_SPECS["Object-Remover"]["repo"],
180
+ weight_name=ADAPTER_SPECS["Object-Remover"]["weights"],
181
+ adapter_name=ADAPTER_SPECS["Object-Remover"]["adapter_name"],
182
+ )
183
+ pipe.set_adapters(
184
+ [ADAPTER_SPECS["Object-Remover"]["adapter_name"]], adapter_weights=[1.0]
185
+ )
186
+ loaded = True
187
+
188
+ if not prompt or prompt.strip() == "":
189
+ prompt = DEFAULT_PROMPT
190
+ print(f"Prompt: {prompt}")
191
+ print(f"Boxes JSON received: '{boxes_json}'")
192
+
193
+ source_image = b64_to_pil(b64_str)
194
+ if source_image is None:
195
+ raise gr.Error("Please upload an image first using the Bbox editor area.")
196
+
197
+ import json
198
+ try:
199
+ boxes = json.loads(boxes_json) if boxes_json and boxes_json.strip() else []
200
+ except Exception as e:
201
+ print(f"JSON parse error: {e}")
202
+ boxes = []
203
+
204
+ if not boxes:
205
+ raise gr.Error("Please draw at least one bounding box on the image.")
206
+
207
+ progress(0.3, desc="Burning red boxes onto image...")
208
+ marked = burn_boxes_onto_image(source_image, boxes_json)
209
+
210
+ progress(0.5, desc="Running object removal inference...")
211
+
212
+ if randomize_seed:
213
+ seed = random.randint(0, MAX_SEED)
214
+ generator = torch.Generator(device=device).manual_seed(seed)
215
+
216
+ result = pipe(
217
+ image=[marked],
218
+ prompt=prompt,
219
+ height=height if height != 0 else None,
220
+ width=width if width != 0 else None,
221
+ num_inference_steps=num_inference_steps,
222
+ generator=generator,
223
+ guidance_scale=guidance_scale,
224
+ num_images_per_prompt=1,
225
+ ).images[0]
226
+
227
+ return result, seed, marked
228
+
229
+
230
+ def update_dimensions_on_upload(b64_str: str):
231
+ image = b64_to_pil(b64_str)
232
+ if image is None:
233
+ return 1024, 1024
234
+ original_width, original_height = image.size
235
+ if original_width > original_height:
236
+ new_width = 1024
237
+ aspect_ratio = original_height / original_width
238
+ new_height = int(new_width * aspect_ratio)
239
+ else:
240
+ new_height = 1024
241
+ aspect_ratio = original_width / original_height
242
+ new_width = int(new_height * aspect_ratio)
243
+ new_width = (new_width // 8) * 8
244
+ new_height = (new_height // 8) * 8
245
+ return new_width, new_height
246
+
247
+
248
+ css = r"""
249
+ @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap');
250
+ body,.gradio-container{background-color:#FAF5FF!important;background-image:linear-gradient(#E9D5FF 1px,transparent 1px),linear-gradient(90deg,#E9D5FF 1px,transparent 1px)!important;background-size:40px 40px!important;font-family:'Outfit',sans-serif!important}
251
+ .dark body,.dark .gradio-container{background-color:#1a1a1a!important;background-image:linear-gradient(rgba(168,85,247,.1) 1px,transparent 1px),linear-gradient(90deg,rgba(168,85,247,.1) 1px,transparent 1px)!important;background-size:40px 40px!important}
252
+ #col-container{margin:0 auto;max-width:1200px}
253
+ #main-title{text-align:center!important;padding:1rem 0 .5rem 0}
254
+ #main-title h1{font-size:2.4em!important;font-weight:700!important;background:linear-gradient(135deg,#A855F7 0%,#C084FC 50%,#9333EA 100%);background-size:200% 200%;-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;animation:gradient-shift 4s ease infinite;letter-spacing:-.02em}
255
+ @keyframes gradient-shift{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
256
+ #subtitle{text-align:center!important;margin-bottom:1.5rem}
257
+ #subtitle p{margin:0 auto;color:#666;font-size:1rem;text-align:center!important}
258
+ #subtitle a{color:#A855F7!important;text-decoration:none;font-weight:500}
259
+ #subtitle a:hover{text-decoration:underline}
260
+ .gradio-group{background:rgba(255,255,255,.9)!important;border:2px solid #E9D5FF!important;border-radius:12px!important;box-shadow:0 4px 24px rgba(168,85,247,.08)!important;backdrop-filter:blur(10px);transition:all .3s ease}
261
+ .gradio-group:hover{box-shadow:0 8px 32px rgba(168,85,247,.12)!important;border-color:#C084FC!important}
262
+ .dark .gradio-group{background:rgba(30,30,30,.9)!important;border-color:rgba(168,85,247,.3)!important}
263
+ .primary{border-radius:8px!important;font-weight:600!important;letter-spacing:.02em!important;transition:all .3s ease!important}
264
+ .primary:hover{transform:translateY(-2px)!important}
265
+ .gradio-textbox textarea{font-family:'IBM Plex Mono',monospace!important;font-size:.95rem!important;line-height:1.7!important;background:rgba(255,255,255,.95)!important;border:1px solid #E9D5FF!important;border-radius:8px!important}
266
+ .gradio-accordion{border-radius:10px!important;border:1px solid #E9D5FF!important}
267
+ .gradio-accordion>.label-wrap{background:rgba(168,85,247,.03)!important;border-radius:10px!important}
268
+ footer{display:none!important}
269
+ @keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
270
+ .gradio-row{animation:fadeIn .4s ease-out}
271
+ label{font-weight:600!important;color:#333!important}
272
+ .dark label{color:#eee!important}
273
+ .gradio-slider input[type="range"]{accent-color:#A855F7!important}
274
+ ::-webkit-scrollbar{width:8px;height:8px}
275
+ ::-webkit-scrollbar-track{background:rgba(168,85,247,.05);border-radius:4px}
276
+ ::-webkit-scrollbar-thumb{background:linear-gradient(135deg,#A855F7,#C084FC);border-radius:4px}
277
+ ::-webkit-scrollbar-thumb:hover{background:linear-gradient(135deg,#9333EA,#A855F7)}
278
+
279
+ #bbox-draw-wrap{position:relative;border:2px dashed #C084FC;border-radius:12px;overflow:hidden;background:#1a1a1a;min-height:420px;transition: border-color 0.2s ease;}
280
+ #bbox-draw-wrap:hover{border-color:#A855F7}
281
+ #bbox-draw-canvas{cursor:crosshair;display:block;margin:0 auto}
282
+ .bbox-hint{background:rgba(168,85,247,.08);border:1px solid #E9D5FF;border-radius:8px;padding:10px 16px;margin:8px 0;font-size:.9rem;color:#6B21A8}
283
+ .dark .bbox-hint{background:rgba(168,85,247,.15);border-color:rgba(168,85,247,.3);color:#C084FC}
284
+
285
+ /* Custom Uiverse.io Upload Prompt Component styling */
286
+ .upload-container {
287
+ position: absolute;
288
+ top: 50%; left: 50%;
289
+ transform: translate(-50%, -50%);
290
+ z-index: 20;
291
+ height: 300px;
292
+ width: 300px;
293
+ border-radius: 10px;
294
+ box-shadow: 4px 4px 30px rgba(168, 85, 247, 0.2);
295
+ display: flex;
296
+ flex-direction: column;
297
+ align-items: center;
298
+ justify-content: space-between;
299
+ padding: 10px;
300
+ gap: 5px;
301
+ background-color: rgba(250, 245, 255, 0.95);
302
+ backdrop-filter: blur(8px);
303
+ }
304
+ .dark .upload-container {
305
+ background-color: rgba(30, 30, 30, 0.95);
306
+ box-shadow: 4px 4px 30px rgba(0, 0, 0, 0.5);
307
+ }
308
+
309
+ .upload-header {
310
+ flex: 1;
311
+ width: 100%;
312
+ border: 2px dashed #A855F7;
313
+ border-radius: 10px;
314
+ display: flex;
315
+ align-items: center;
316
+ justify-content: center;
317
+ flex-direction: column;
318
+ cursor: pointer;
319
+ transition: background-color 0.2s;
320
+ color: #A855F7;
321
+ }
322
+ .upload-header:hover {
323
+ background-color: rgba(168, 85, 247, 0.05);
324
+ }
325
+ .dark .upload-header:hover {
326
+ background-color: rgba(168, 85, 247, 0.15);
327
+ }
328
+
329
+ .upload-header svg {
330
+ height: 100px;
331
+ }
332
+
333
+ .upload-header p {
334
+ text-align: center;
335
+ color: #6B21A8;
336
+ font-family: 'Outfit', sans-serif;
337
+ font-weight: 600;
338
+ margin-top: 10px;
339
+ }
340
+ .dark .upload-header p {
341
+ color: #DAB2FF;
342
+ }
343
+
344
+ .upload-footer {
345
+ background-color: rgba(168, 85, 247, 0.08);
346
+ width: 100%;
347
+ height: 40px;
348
+ padding: 8px;
349
+ border-radius: 10px;
350
+ display: flex;
351
+ align-items: center;
352
+ justify-content: flex-end;
353
+ color: #6B21A8;
354
+ border: none;
355
+ box-sizing: border-box;
356
+ }
357
+ .dark .upload-footer {
358
+ background-color: rgba(168, 85, 247, 0.15);
359
+ color: #DAB2FF;
360
+ }
361
+
362
+ .upload-footer svg {
363
+ height: 130%;
364
+ fill: #A855F7;
365
+ background-color: rgba(255, 255, 255, 0.5);
366
+ border-radius: 50%;
367
+ padding: 2px;
368
+ cursor: pointer;
369
+ box-shadow: 0 2px 10px rgba(168, 85, 247, 0.2);
370
+ transition: transform 0.2s;
371
+ }
372
+ .dark .upload-footer svg {
373
+ background-color: rgba(0, 0, 0, 0.3);
374
+ }
375
+ .upload-footer svg:hover {
376
+ transform: scale(1.1);
377
+ }
378
+
379
+ .upload-footer p {
380
+ flex: 1;
381
+ text-align: center;
382
+ font-family: 'Outfit', sans-serif;
383
+ font-size: 0.9rem;
384
+ margin: 0;
385
+ }
386
+
387
+ .bbox-toolbar-section{
388
+ display:flex;
389
+ gap:8px;
390
+ flex-wrap:wrap;
391
+ justify-content:center;
392
+ align-items:center;
393
+ padding:12px 16px;
394
+ margin-top:10px;
395
+ background:rgba(255,255,255,.92);
396
+ border:2px solid #E9D5FF;
397
+ border-radius:10px;
398
+ box-shadow:0 2px 12px rgba(168,85,247,.08);
399
+ }
400
+ .dark .bbox-toolbar-section{
401
+ background:rgba(30,30,30,.9);
402
+ border-color:rgba(168,85,247,.3);
403
+ }
404
+ .bbox-toolbar-section .toolbar-label{
405
+ font-family:'Outfit',sans-serif;
406
+ font-weight:600;
407
+ font-size:13px;
408
+ color:#6B21A8;
409
+ margin-right:6px;
410
+ user-select:none;
411
+ }
412
+ .dark .bbox-toolbar-section .toolbar-label{color:#C084FC}
413
+ .bbox-toolbar-section .toolbar-divider{
414
+ width:1px;
415
+ height:28px;
416
+ background:#E9D5FF;
417
+ margin:0 4px;
418
+ }
419
+ .dark .bbox-toolbar-section .toolbar-divider{background:rgba(168,85,247,.3)}
420
+ .bbox-toolbar-section button{
421
+ color:#fff;
422
+ border:none;
423
+ padding:7px 15px;
424
+ border-radius:7px;
425
+ cursor:pointer;
426
+ font-family:'Outfit',sans-serif;
427
+ font-weight:600;
428
+ font-size:13px;
429
+ box-shadow:0 2px 5px rgba(0,0,0,.15);
430
+ transition:background .2s,transform .15s,box-shadow .2s;
431
+ }
432
+ .bbox-toolbar-section button:hover{transform:translateY(-1px);box-shadow:0 4px 10px rgba(0,0,0,.2)}
433
+ .bbox-toolbar-section button:active{transform:translateY(0)}
434
+ .bbox-tb-draw{background:#9333EA}
435
+ .bbox-tb-draw:hover{background:#A855F7}
436
+ .bbox-tb-draw.active{background:#22c55e;box-shadow:0 0 8px rgba(34,197,94,.5)}
437
+ .bbox-tb-select{background:#6366f1}
438
+ .bbox-tb-select:hover{background:#818cf8}
439
+ .bbox-tb-select.active{background:#22c55e;box-shadow:0 0 8px rgba(34,197,94,.5)}
440
+ .bbox-tb-del{background:#dc2626}
441
+ .bbox-tb-del:hover{background:#ef4444}
442
+ .bbox-tb-undo{background:#7E22CE}
443
+ .bbox-tb-undo:hover{background:#9333EA}
444
+ .bbox-tb-clear{background:#be123c}
445
+ .bbox-tb-clear:hover{background:#e11d48}
446
+ .bbox-tb-change{background:#4b5563}
447
+ .bbox-tb-change:hover{background:#6b7280}
448
+
449
+ #bbox-status{position:absolute;top:10px;left:10px;background:rgba(0,0,0,.75);color:#00ff88;padding:5px 10px;border-radius:6px;font-family:'IBM Plex Mono',monospace;font-size:11px;z-index:10;display:none;pointer-events:none}
450
+ #bbox-count{position:absolute;top:10px;right:10px;background:rgba(147,51,234,.85);color:#fff;padding:4px 10px;border-radius:6px;font-family:'IBM Plex Mono',monospace;font-size:11px;z-index:10;display:none}
451
+
452
+ #bbox-debug-count{
453
+ text-align:center;
454
+ padding:6px 12px;
455
+ margin-top:6px;
456
+ font-family:'IBM Plex Mono',monospace;
457
+ font-size:12px;
458
+ color:#6B21A8;
459
+ background:rgba(168,85,247,.06);
460
+ border:1px dashed #C084FC;
461
+ border-radius:6px;
462
+ }
463
+ .dark #bbox-debug-count{color:#C084FC;background:rgba(168,85,247,.12)}
464
+
465
+ .hidden-input {
466
+ display: none !important;
467
+ }
468
+ """
469
+
470
+ bbox_drawer_js = r"""
471
+ () => {
472
+ function initCanvasBbox() {
473
+ if (window.__bboxInitDone) return;
474
+
475
+ const canvas = document.getElementById('bbox-draw-canvas');
476
+ const wrap = document.getElementById('bbox-draw-wrap');
477
+ const status = document.getElementById('bbox-status');
478
+ const badge = document.getElementById('bbox-count');
479
+ const debugCount = document.getElementById('bbox-debug-count');
480
+
481
+ const btnDraw = document.getElementById('tb-draw');
482
+ const btnSelect = document.getElementById('tb-select');
483
+ const btnDel = document.getElementById('tb-del');
484
+ const btnUndo = document.getElementById('tb-undo');
485
+ const btnClear = document.getElementById('tb-clear');
486
+ const btnChange = document.getElementById('tb-change-img');
487
+
488
+ const uploadPrompt = document.getElementById('upload-prompt');
489
+ const uploadHeader = document.getElementById('upload-header');
490
+ const fileInput = document.getElementById('custom-file-input');
491
+
492
+ if (!canvas || !wrap || !debugCount || !btnDraw || !fileInput) {
493
+ console.log('[BBox] waiting for DOM...');
494
+ setTimeout(initCanvasBbox, 250);
495
+ return;
496
+ }
497
+
498
+ window.__bboxInitDone = true;
499
+ console.log('[BBox] canvas init OK');
500
+ const ctx = canvas.getContext('2d');
501
+
502
+ let boxes = [];
503
+ window.__bboxBoxes = boxes;
504
+
505
+ let baseImg = null;
506
+ let dispW = 512, dispH = 400;
507
+ let selectedIdx = -1;
508
+ let mode = 'draw';
509
+
510
+ let dragging = false;
511
+ let dragType = null;
512
+ let dragStart = {x:0, y:0};
513
+ let dragOrig = null;
514
+ const HANDLE = 7;
515
+ const RED_STROKE = 'rgba(255,0,0,0.95)';
516
+ const RED_STROKE_WIDTH = 3;
517
+ const SEL_STROKE = 'rgba(0,120,255,0.95)';
518
+
519
+ function n2px(b) { return {x1:b.x1*dispW, y1:b.y1*dispH, x2:b.x2*dispW, y2:b.y2*dispH}; }
520
+ function px2n(x1,y1,x2,y2) {
521
+ return {
522
+ x1: Math.min(x1,x2)/dispW, y1: Math.min(y1,y2)/dispH,
523
+ x2: Math.max(x1,x2)/dispW, y2: Math.max(y1,y2)/dispH
524
+ };
525
+ }
526
+ function clamp01(v){return Math.max(0,Math.min(1,v));}
527
+ function fitSize(nw, nh) {
528
+ const mw = wrap.clientWidth || 512, mh = 500;
529
+ const r = Math.min(mw/nw, mh/nh, 1);
530
+ dispW = Math.round(nw*r); dispH = Math.round(nh*r);
531
+ canvas.width = dispW; canvas.height = dispH;
532
+ canvas.style.width = dispW+'px';
533
+ canvas.style.height = dispH+'px';
534
+ }
535
+ function canvasXY(e) {
536
+ const r = canvas.getBoundingClientRect();
537
+ const cx = e.touches ? e.touches[0].clientX : e.clientX;
538
+ const cy = e.touches ? e.touches[0].clientY : e.clientY;
539
+ return {x: Math.max(0,Math.min(dispW, cx-r.left)),
540
+ y: Math.max(0,Math.min(dispH, cy-r.top))};
541
+ }
542
+
543
+ function syncToGradio() {
544
+ window.__bboxBoxes = boxes;
545
+ const jsonStr = JSON.stringify(boxes);
546
+
547
+ if (debugCount) {
548
+ debugCount.textContent = boxes.length > 0
549
+ ? '\u2705 ' + boxes.length + ' box' + (boxes.length > 1 ? 'es' : '') +
550
+ ' ready | JSON: ' + jsonStr.substring(0,80) +
551
+ (jsonStr.length > 80 ? '\u2026' : '')
552
+ : '\u2B1C No boxes drawn yet';
553
+ }
554
+
555
+ const container = document.getElementById('boxes-json-input');
556
+ if (!container) return;
557
+ const targets = [
558
+ ...container.querySelectorAll('textarea'),
559
+ ...container.querySelectorAll('input:not([type="file"])')
560
+ ];
561
+ targets.forEach(el => {
562
+ const proto = el.tagName === 'TEXTAREA' ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
563
+ const ns = Object.getOwnPropertyDescriptor(proto, 'value');
564
+ if (ns && ns.set) {
565
+ ns.set.call(el, jsonStr);
566
+ el.dispatchEvent(new Event('input', {bubbles:true, composed:true}));
567
+ el.dispatchEvent(new Event('change', {bubbles:true, composed:true}));
568
+ }
569
+ });
570
+ }
571
+
572
+ function syncImageToGradio(dataUrl) {
573
+ const container = document.getElementById('hidden-image-b64');
574
+ if (!container) return;
575
+ const targets = [
576
+ ...container.querySelectorAll('textarea'),
577
+ ...container.querySelectorAll('input')
578
+ ];
579
+ targets.forEach(el => {
580
+ const proto = el.tagName === 'TEXTAREA' ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
581
+ const ns = Object.getOwnPropertyDescriptor(proto, 'value');
582
+ if (ns && ns.set) {
583
+ ns.set.call(el, dataUrl);
584
+ el.dispatchEvent(new Event('input', {bubbles:true, composed:true}));
585
+ el.dispatchEvent(new Event('change', {bubbles:true, composed:true}));
586
+ }
587
+ });
588
+ }
589
+
590
+ function redraw(tempRect) {
591
+ ctx.clearRect(0,0,dispW,dispH);
592
+ if (!baseImg) {
593
+ ctx.fillStyle='#1a1a1a'; ctx.fillRect(0,0,dispW,dispH);
594
+ updateBadge(); return;
595
+ }
596
+ ctx.drawImage(baseImg, 0, 0, dispW, dispH);
597
+
598
+ boxes.forEach((b,i) => {
599
+ const p = n2px(b);
600
+ const lx=p.x1, ty=p.y1, w=p.x2-p.x1, h=p.y2-p.y1;
601
+
602
+ /* RED OUTLINE ONLY — no fill */
603
+ if (i === selectedIdx) {
604
+ ctx.strokeStyle = SEL_STROKE;
605
+ ctx.lineWidth = RED_STROKE_WIDTH + 1;
606
+ ctx.setLineDash([6,3]);
607
+ } else {
608
+ ctx.strokeStyle = RED_STROKE;
609
+ ctx.lineWidth = RED_STROKE_WIDTH;
610
+ ctx.setLineDash([]);
611
+ }
612
+ ctx.strokeRect(lx, ty, w, h);
613
+ ctx.setLineDash([]);
614
+
615
+ /* label tag */
616
+ ctx.fillStyle = i===selectedIdx ? 'rgba(0,120,255,0.85)' : 'rgba(255,0,0,0.85)';
617
+ ctx.font = 'bold 11px IBM Plex Mono,monospace';
618
+ ctx.textAlign = 'left'; ctx.textBaseline = 'top';
619
+ const label = '#'+(i+1);
620
+ const tw = ctx.measureText(label).width;
621
+ ctx.fillRect(lx, ty-16, tw+6, 16);
622
+ ctx.fillStyle = '#fff';
623
+ ctx.fillText(label, lx+3, ty-14);
624
+
625
+ if (i === selectedIdx) drawHandles(p);
626
+ });
627
+
628
+ /* temp drawing rect — outline only */
629
+ if (tempRect) {
630
+ const rx = Math.min(tempRect.x1,tempRect.x2);
631
+ const ry = Math.min(tempRect.y1,tempRect.y2);
632
+ const rw = Math.abs(tempRect.x2-tempRect.x1);
633
+ const rh = Math.abs(tempRect.y2-tempRect.y1);
634
+ ctx.strokeStyle = RED_STROKE;
635
+ ctx.lineWidth = RED_STROKE_WIDTH;
636
+ ctx.setLineDash([6,3]);
637
+ ctx.strokeRect(rx, ry, rw, rh);
638
+ ctx.setLineDash([]);
639
+ }
640
+ updateBadge();
641
+ }
642
+
643
+ function drawHandles(p) {
644
+ const pts = handlePoints(p);
645
+ ctx.fillStyle = 'rgba(0,120,255,0.9)';
646
+ ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.5;
647
+ for (const k in pts) {
648
+ const h = pts[k];
649
+ ctx.fillRect(h.x-HANDLE, h.y-HANDLE, HANDLE*2, HANDLE*2);
650
+ ctx.strokeRect(h.x-HANDLE, h.y-HANDLE, HANDLE*2, HANDLE*2);
651
+ }
652
+ }
653
+
654
+ function handlePoints(p) {
655
+ const mx = (p.x1+p.x2)/2, my = (p.y1+p.y2)/2;
656
+ return {
657
+ tl:{x:p.x1,y:p.y1}, tc:{x:mx,y:p.y1}, tr:{x:p.x2,y:p.y1},
658
+ ml:{x:p.x1,y:my}, mr:{x:p.x2,y:my},
659
+ bl:{x:p.x1,y:p.y2}, bc:{x:mx,y:p.y2}, br:{x:p.x2,y:p.y2}
660
+ };
661
+ }
662
+
663
+ function hitHandle(px, py, boxIdx) {
664
+ if (boxIdx < 0) return null;
665
+ const p = n2px(boxes[boxIdx]);
666
+ const pts = handlePoints(p);
667
+ for (const k in pts) {
668
+ if (Math.abs(px-pts[k].x) <= HANDLE+2 && Math.abs(py-pts[k].y) <= HANDLE+2) return k;
669
+ }
670
+ return null;
671
+ }
672
+
673
+ function hitBox(px, py) {
674
+ for (let i = boxes.length-1; i >= 0; i--) {
675
+ const p = n2px(boxes[i]);
676
+ if (px >= p.x1 && px <= p.x2 && py >= p.y1 && py <= p.y2) return i;
677
+ }
678
+ return -1;
679
+ }
680
+
681
+ function updateBadge() {
682
+ if (boxes.length > 0) {
683
+ badge.style.display = 'block';
684
+ badge.textContent = boxes.length + ' box' + (boxes.length>1?'es':'');
685
+ } else {
686
+ badge.style.display = 'none';
687
+ }
688
+ }
689
+
690
+ function setMode(m) {
691
+ mode = m;
692
+ btnDraw.classList.toggle('active', m==='draw');
693
+ btnSelect.classList.toggle('active', m==='select');
694
+ canvas.style.cursor = m==='draw' ? 'crosshair' : 'default';
695
+ if (m==='draw') selectedIdx = -1;
696
+ redraw();
697
+ }
698
+
699
+ function showStatus(txt) {
700
+ status.textContent = txt; status.style.display = 'block';
701
+ }
702
+ function hideStatus() { status.style.display = 'none'; }
703
+
704
+ function onDown(e) {
705
+ if (!baseImg) return;
706
+ e.preventDefault();
707
+ const {x, y} = canvasXY(e);
708
+
709
+ if (mode === 'draw') {
710
+ dragging = true; dragType = 'new';
711
+ dragStart = {x, y};
712
+ selectedIdx = -1;
713
+ } else {
714
+ if (selectedIdx >= 0) {
715
+ const h = hitHandle(x, y, selectedIdx);
716
+ if (h) {
717
+ dragging = true; dragType = h;
718
+ dragStart = {x, y};
719
+ dragOrig = {...boxes[selectedIdx]};
720
+ showStatus('Resizing box #'+(selectedIdx+1));
721
+ return;
722
+ }
723
+ }
724
+ const hi = hitBox(x, y);
725
+ if (hi >= 0) {
726
+ selectedIdx = hi;
727
+ const h2 = hitHandle(x, y, selectedIdx);
728
+ if (h2) {
729
+ dragging = true; dragType = h2;
730
+ dragStart = {x, y};
731
+ dragOrig = {...boxes[selectedIdx]};
732
+ showStatus('Resizing box #'+(selectedIdx+1));
733
+ redraw(); return;
734
+ }
735
+ dragging = true; dragType = 'move';
736
+ dragStart = {x, y};
737
+ dragOrig = {...boxes[selectedIdx]};
738
+ showStatus('Moving box #'+(selectedIdx+1));
739
+ } else {
740
+ selectedIdx = -1;
741
+ hideStatus();
742
+ }
743
+ redraw();
744
+ }
745
+ }
746
+
747
+ function onMove(e) {
748
+ if (!baseImg) return;
749
+ e.preventDefault();
750
+ const {x, y} = canvasXY(e);
751
+
752
+ if (!dragging) {
753
+ if (mode === 'select') {
754
+ if (selectedIdx >= 0 && hitHandle(x,y,selectedIdx)) {
755
+ const h = hitHandle(x,y,selectedIdx);
756
+ const curs = {tl:'nwse-resize',tr:'nesw-resize',bl:'nesw-resize',br:'nwse-resize',
757
+ tc:'ns-resize',bc:'ns-resize',ml:'ew-resize',mr:'ew-resize'};
758
+ canvas.style.cursor = curs[h] || 'move';
759
+ } else if (hitBox(x,y) >= 0) {
760
+ canvas.style.cursor = 'move';
761
+ } else {
762
+ canvas.style.cursor = 'default';
763
+ }
764
+ }
765
+ return;
766
+ }
767
+
768
+ if (dragType === 'new') {
769
+ redraw({x1:dragStart.x, y1:dragStart.y, x2:x, y2:y});
770
+ showStatus(Math.abs(x-dragStart.x).toFixed(0)+'\u00d7'+Math.abs(y-dragStart.y).toFixed(0)+' px');
771
+ return;
772
+ }
773
+
774
+ const dx = (x - dragStart.x) / dispW;
775
+ const dy = (y - dragStart.y) / dispH;
776
+ const b = boxes[selectedIdx];
777
+ const o = dragOrig;
778
+
779
+ if (dragType === 'move') {
780
+ const bw = o.x2-o.x1, bh = o.y2-o.y1;
781
+ let nx1 = o.x1+dx, ny1 = o.y1+dy;
782
+ nx1 = clamp01(nx1); ny1 = clamp01(ny1);
783
+ if (nx1+bw > 1) nx1 = 1-bw;
784
+ if (ny1+bh > 1) ny1 = 1-bh;
785
+ b.x1=nx1; b.y1=ny1; b.x2=nx1+bw; b.y2=ny1+bh;
786
+ } else {
787
+ const t = dragType;
788
+ if (t.includes('l')) b.x1 = clamp01(o.x1 + dx);
789
+ if (t.includes('r')) b.x2 = clamp01(o.x2 + dx);
790
+ if (t.includes('t')) b.y1 = clamp01(o.y1 + dy);
791
+ if (t.includes('b')) b.y2 = clamp01(o.y2 + dy);
792
+ if (Math.abs(b.x2-b.x1) < 0.01) { b.x1=o.x1; b.x2=o.x2; }
793
+ if (Math.abs(b.y2-b.y1) < 0.01) { b.y1=o.y1; b.y2=o.y2; }
794
+ if (b.x1 > b.x2) { const t2=b.x1; b.x1=b.x2; b.x2=t2; }
795
+ if (b.y1 > b.y2) { const t2=b.y1; b.y1=b.y2; b.y2=t2; }
796
+ }
797
+ redraw();
798
+ }
799
+
800
+ function onUp(e) {
801
+ if (!dragging) return;
802
+ if (e) e.preventDefault();
803
+ dragging = false;
804
+
805
+ if (dragType === 'new') {
806
+ const pt = e ? canvasXY(e) : {x:dragStart.x, y:dragStart.y};
807
+ if (Math.abs(pt.x-dragStart.x) > 4 && Math.abs(pt.y-dragStart.y) > 4) {
808
+ const nb = px2n(dragStart.x, dragStart.y, pt.x, pt.y);
809
+ boxes.push(nb);
810
+ window.__bboxBoxes = boxes;
811
+ selectedIdx = boxes.length - 1;
812
+ console.log('[BBox] created box #'+boxes.length, nb);
813
+ showStatus('Box #'+boxes.length+' created');
814
+ } else { hideStatus(); }
815
+ } else {
816
+ showStatus('Box #'+(selectedIdx+1)+' updated');
817
+ }
818
+ dragType = null; dragOrig = null;
819
+ syncToGradio();
820
+ redraw();
821
+ }
822
+
823
+ canvas.addEventListener('mousedown', onDown);
824
+ canvas.addEventListener('mousemove', onMove);
825
+ canvas.addEventListener('mouseup', onUp);
826
+ canvas.addEventListener('mouseleave', (e)=>{if(dragging)onUp(e);});
827
+ canvas.addEventListener('touchstart', onDown, {passive:false});
828
+ canvas.addEventListener('touchmove', onMove, {passive:false});
829
+ canvas.addEventListener('touchend', onUp, {passive:false});
830
+ canvas.addEventListener('touchcancel',(e)=>{e.preventDefault();dragging=false;redraw();},{passive:false});
831
+
832
+ // --- File Upload Logic ---
833
+ function processFile(file) {
834
+ if (!file || !file.type.startsWith('image/')) return;
835
+ const reader = new FileReader();
836
+ reader.onload = (event) => {
837
+ const dataUrl = event.target.result;
838
+ const img = new window.Image();
839
+ img.crossOrigin = 'anonymous';
840
+ img.onload = () => {
841
+ baseImg = img;
842
+ boxes.length = 0;
843
+ window.__bboxBoxes = boxes;
844
+ selectedIdx = -1;
845
+ fitSize(img.naturalWidth, img.naturalHeight);
846
+ syncToGradio(); redraw(); hideStatus();
847
+ uploadPrompt.style.display = 'none';
848
+ syncImageToGradio(dataUrl);
849
+ };
850
+ img.src = dataUrl;
851
+ };
852
+ reader.readAsDataURL(file);
853
+ }
854
+
855
+ uploadHeader.addEventListener('click', () => fileInput.click());
856
+ btnChange.addEventListener('click', () => fileInput.click());
857
+
858
+ fileInput.addEventListener('change', (e) => {
859
+ processFile(e.target.files[0]);
860
+ e.target.value = ''; // Reset input to allow re-upload of same file
861
+ });
862
+
863
+ wrap.addEventListener('dragover', (e) => {
864
+ e.preventDefault();
865
+ wrap.style.borderColor = '#A855F7';
866
+ wrap.style.boxShadow = '0 0 15px rgba(168,85,247,0.3)';
867
+ });
868
+ wrap.addEventListener('dragleave', (e) => {
869
+ e.preventDefault();
870
+ wrap.style.borderColor = '';
871
+ wrap.style.boxShadow = '';
872
+ });
873
+ wrap.addEventListener('drop', (e) => {
874
+ e.preventDefault();
875
+ wrap.style.borderColor = '';
876
+ wrap.style.boxShadow = '';
877
+ if (e.dataTransfer.files.length) {
878
+ processFile(e.dataTransfer.files[0]);
879
+ }
880
+ });
881
+
882
+ // --- Toolbar Logic ---
883
+ btnDraw.addEventListener('click', ()=>setMode('draw'));
884
+ btnSelect.addEventListener('click', ()=>setMode('select'));
885
+
886
+ btnDel.addEventListener('click', () => {
887
+ if (selectedIdx >= 0 && selectedIdx < boxes.length) {
888
+ const removed = selectedIdx + 1;
889
+ boxes.splice(selectedIdx, 1);
890
+ window.__bboxBoxes = boxes;
891
+ selectedIdx = -1;
892
+ syncToGradio(); redraw();
893
+ showStatus('Box #'+removed+' deleted');
894
+ } else {
895
+ showStatus('No box selected');
896
+ }
897
+ });
898
+
899
+ btnUndo.addEventListener('click', () => {
900
+ if (boxes.length > 0) {
901
+ boxes.pop();
902
+ window.__bboxBoxes = boxes;
903
+ selectedIdx = -1;
904
+ syncToGradio(); redraw();
905
+ showStatus('Last box removed');
906
+ }
907
+ });
908
+
909
+ btnClear.addEventListener('click', () => {
910
+ boxes.length = 0;
911
+ window.__bboxBoxes = boxes;
912
+ selectedIdx = -1;
913
+ syncToGradio(); redraw(); hideStatus();
914
+ });
915
+
916
+ new ResizeObserver(() => {
917
+ if (baseImg) { fitSize(baseImg.naturalWidth, baseImg.naturalHeight); redraw(); }
918
+ }).observe(wrap);
919
+
920
+ setMode('draw');
921
+ fitSize(512,400); redraw();
922
+ syncToGradio();
923
+ }
924
+
925
+ initCanvasBbox();
926
+ }
927
+ """
928
+
929
+
930
+ with gr.Blocks() as demo:
931
+ gr.Markdown("# **QIE-Object-Remover-Bbox**", elem_id="main-title")
932
+ gr.Markdown(
933
+ "Perform diverse image edits using a specialized [LoRA](https://huggingface.co/prithivMLmods/QIE-2509-Object-Remover-Bbox). "
934
+ "Upload an image directly into the bounding box editor area below, draw red bounding boxes over the objects you want to remove, and click Remove Object. "
935
+ "Multiple boxes supported. Select, move, resize or delete individual boxes. Open on [GitHub](https://github.com/PRITHIVSAKTHIUR/QIE-Object-Remover-Bbox)",
936
+ elem_id="subtitle",
937
+ )
938
+
939
+ with gr.Row():
940
+ with gr.Column(scale=1):
941
+
942
+ hidden_image_b64 = gr.Textbox(
943
+ elem_id="hidden-image-b64",
944
+ elem_classes="hidden-input",
945
+ container=False
946
+ )
947
+
948
+ #gr.Markdown("### **Bbox Edit Controller**")
949
+
950
+ gr.HTML(
951
+ """
952
+ <div id="bbox-draw-wrap">
953
+ <div id="upload-prompt" class="upload-container">
954
+ <div class="upload-header" id="upload-header">
955
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
956
+ <path d="M7 10V9C7 6.23858 9.23858 4 12 4C14.7614 4 17 6.23858 17 9V10C19.2091 10 21 11.7909 21 14C21 15.4806 20.1956 16.8084 19 17.5M7 10C4.79086 10 3 11.7909 3 14C3 15.4806 3.8044 16.8084 5 17.5M7 10C7.43285 10 7.84965 10.0688 8.24006 10.1959M12 12V21M12 12L15 15M12 12L9 15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
957
+ </svg>
958
+ <p>Browse File to upload!</p>
959
+ </div>
960
+ <div class="upload-footer">
961
+ <svg fill="currentColor" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
962
+ <path d="M15.331 6H8.5v20h15V14.154h-8.169z"></path>
963
+ <path d="M18.153 6h-.009v5.342H23.5v-.002z"></path>
964
+ </svg>
965
+ <p>Not selected file</p>
966
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
967
+ <path d="M5.16565 10.1534C5.07629 8.99181 5.99473 8 7.15975 8H16.8402C18.0053 8 18.9237 8.9918 18.8344 10.1534L18.142 19.1534C18.0619 20.1954 17.193 21 16.1479 21H7.85206C6.80699 21 5.93811 20.1954 5.85795 19.1534L5.16565 10.1534Z" stroke="currentColor" stroke-width="2"></path>
968
+ <path d="M19.5 5H4.5" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path>
969
+ <path d="M10 3C10 2.44772 10.4477 2 11 2H13C13.5523 2 14 2.44772 14 3V5H10V3Z" stroke="currentColor" stroke-width="2"></path>
970
+ </svg>
971
+ </div>
972
+ <input id="custom-file-input" type="file" accept="image/*" style="display:none;" />
973
+ </div>
974
+
975
+ <canvas id="bbox-draw-canvas" width="512" height="400"></canvas>
976
+ <div id="bbox-status"></div>
977
+ <div id="bbox-count"></div>
978
+ </div>
979
+ """
980
+ )
981
+
982
+ gr.HTML(
983
+ """
984
+ <div class="bbox-toolbar-section">
985
+ <span class="toolbar-label">🛠 Tools:</span>
986
+ <button id="tb-draw" class="bbox-tb-draw active" title="Draw new boxes">✏️ Draw</button>
987
+ <button id="tb-select" class="bbox-tb-select" title="Select / move / resize">🔲 Select</button>
988
+ <div class="toolbar-divider"></div>
989
+ <span class="toolbar-label">Actions:</span>
990
+ <button id="tb-del" class="bbox-tb-del" title="Delete selected box">✕ Delete</button>
991
+ <button id="tb-undo" class="bbox-tb-undo" title="Remove last box">↩ Undo</button>
992
+ <button id="tb-clear" class="bbox-tb-clear" title="Remove all boxes">🗑 Clear All</button>
993
+ <div class="toolbar-divider"></div>
994
+ <button id="tb-change-img" class="bbox-tb-change" title="Upload a different image">📸 Change Image</button>
995
+ </div>
996
+ """
997
+ )
998
+
999
+ gr.HTML('<div id="bbox-debug-count">\u2B1C No boxes drawn yet</div>')
1000
+
1001
+ boxes_json = gr.Textbox(
1002
+ value="[]",
1003
+ elem_id="boxes-json-input",
1004
+ elem_classes="hidden-input",
1005
+ container=False
1006
+ )
1007
+
1008
+ gr.HTML(
1009
+ '<div class="bbox-hint">'
1010
+ "<b>Draw mode:</b> Click & drag to create red rectangles. "
1011
+ "<b>Select mode:</b> Click a box to select it \u2192 drag to <b>move</b>, "
1012
+ "drag handles to <b>resize</b>. Use <b>Delete Selected</b> to remove one box."
1013
+ "</div>"
1014
+ )
1015
+
1016
+ prompt = gr.Textbox(
1017
+ label="Prompt",
1018
+ value=DEFAULT_PROMPT,
1019
+ lines=1,
1020
+ info="Edit the prompt if needed",
1021
+ )
1022
+
1023
+ run_btn = gr.Button("\U0001F5D1\uFE0F Remove Object", variant="primary", size="lg")
1024
+
1025
+ with gr.Column(scale=1):
1026
+ result = gr.Image(label="Output Image", height=475, format="png")
1027
+ preview = gr.Image(label="Input Sent to Model (with red boxes)", height=415)
1028
+
1029
+ with gr.Accordion("Advanced Settings", open=False, visible=False):
1030
+ seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=0)
1031
+ randomize_seed = gr.Checkbox(label="Randomize Seed", value=True)
1032
+ with gr.Row():
1033
+ guidance_scale = gr.Slider(label="Guidance Scale", minimum=1.0, maximum=10.0, step=0.1, value=1.0)
1034
+ num_inference_steps = gr.Slider(label="Inference Steps", minimum=1, maximum=20, step=1, value=4)
1035
+ with gr.Row():
1036
+ height_slider = gr.Slider(label="Height", minimum=256, maximum=2048, step=8, value=1024)
1037
+ width_slider = gr.Slider(label="Width", minimum=256, maximum=2048, step=8, value=1024)
1038
+
1039
+ demo.load(fn=None, js=bbox_drawer_js)
1040
+
1041
+ run_btn.click(
1042
+ fn=infer_object_removal,
1043
+ inputs=[hidden_image_b64, boxes_json, prompt, seed, randomize_seed,
1044
+ guidance_scale, num_inference_steps, height_slider, width_slider],
1045
+ outputs=[result, seed, preview],
1046
+ js="""(b64, bj, p, s, rs, gs, nis, h, w) => {
1047
+ const boxes = window.__bboxBoxes || [];
1048
+ const json = JSON.stringify(boxes);
1049
+ console.log('[BBox] submitting', boxes.length, 'boxes:', json);
1050
+ return [b64, json, p, s, rs, gs, nis, h, w];
1051
+ }""",
1052
+ )
1053
+
1054
+ hidden_image_b64.change(
1055
+ fn=update_dimensions_on_upload,
1056
+ inputs=[hidden_image_b64],
1057
+ outputs=[width_slider, height_slider],
1058
+ )
1059
+
1060
+ if __name__ == "__main__":
1061
+ demo.launch(
1062
+ css=css, theme=purple_theme,
1063
+ mcp_server=True, ssr_mode=False, show_error=True
1064
+ )
pre-requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ pip>=26.0.0
requirements.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ git+https://github.com/huggingface/accelerate.git
2
+ git+https://github.com/huggingface/diffusers.git
3
+ git+https://github.com/huggingface/peft.git
4
+ transformers==4.57.6
5
+ huggingface_hub
6
+ sentencepiece
7
+ torchvision
8
+ kernels
9
+ spaces
10
+ hf_xet
11
+ gradio # -> gradio@6.9.0
12
+ torch==2.8.0
13
+ numpy
14
+ av