Spaces:
Paused
Paused
Upload 5 files
Browse files- LICENSE.txt +201 -0
- README.md +68 -13
- app.py +1064 -0
- pre-requirements.txt +1 -0
- 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 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
-
|
| 12 |
-
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|