Spaces:
Build error
Build error
Commit ·
7ebbf86
0
Parent(s):
initial commit
Browse files- .gitattributes +37 -0
- .gitignore +15 -0
- CHANGELOG.md +51 -0
- LICENSE +177 -0
- NOTICE +62 -0
- README.md +149 -0
- app.py +405 -0
- assets/devaixp_logo-white.png +3 -0
- assets/samples/1.jpg +3 -0
- assets/samples/2.jpg +3 -0
- assets/samples/3.jpg +3 -0
- assets/samples/band.png +3 -0
- assets/samples/band_restored.png +3 -0
- assets/samples/boy.jpg +3 -0
- assets/samples/woman.png +3 -0
- assets/samples/woman_restored.png +3 -0
- configs/settings.json +29 -0
- outputs/examples/1_FaithDiff_Restore_1x_CFGOnly.png +3 -0
- outputs/examples/1_FaithDiff_Restore_1x_FaithDiff_Upscale_2x_CFGOnly.png +3 -0
- outputs/examples/1_FaithDiff_Restore_1x_FaithDiff_Upscale_2x_PAG.png +3 -0
- outputs/examples/1_FaithDiff_Restore_1x_PAG.png +3 -0
- outputs/examples/1_FaithDiff_Restore_1x_SUPIR_Upscale_2x.png +3 -0
- outputs/examples/1_SUPIR_Restore_1x_CFGOnly.png +3 -0
- outputs/examples/1_SUPIR_Restore_1x_FaithDiff_Upscale_2x.png +3 -0
- outputs/examples/1_SUPIR_Restore_1x_PAGOnly.png +3 -0
- outputs/examples/1_SUPIR_Restore_1x_SUPIR_Upscale_2x_CFGOnly.png +3 -0
- outputs/examples/1_SUPIR_Restore_1x_SUPIR_Upscale_2x_PAG.png +3 -0
- outputs/examples/2_FaithDiff_Restore_1x_CFGOnly.png +3 -0
- outputs/examples/2_SUPIR_Restore_1x.png +3 -0
- outputs/examples/2_SUPIR_Restore_1x_ControlNetTIle_Upscale_2x.png +3 -0
- outputs/examples/2_SUPIR_Restore_1x_ControlNetTIle_Upscale_4x.png +3 -0
- outputs/examples/3_ControlnetTile_Upscale_2x.png +3 -0
- outputs/examples/4_ControlnetTile_Upscale_8x.jpg +3 -0
- outputs/examples/5_ControlnetTile_Upscale_4x.png +3 -0
- requirements.txt +25 -0
- ui/globals.py +8 -0
- ui/script.js +194 -0
- ui/style.css +301 -0
- ui/ui_config.py +1360 -0
- ui/ui_data.py +525 -0
- ui/ui_events.py +1789 -0
- ui/ui_layout.py +684 -0
- ui/ui_state.py +38 -0
- ui/util/dataclass_helpers.py +300 -0
.gitattributes
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
*.jpg filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.py[cod]
|
| 3 |
+
/outputs/restored
|
| 4 |
+
/outputs/upscaled
|
| 5 |
+
/models
|
| 6 |
+
/dist
|
| 7 |
+
/.vs
|
| 8 |
+
.vscode/
|
| 9 |
+
.idea/
|
| 10 |
+
/venv
|
| 11 |
+
.venv/
|
| 12 |
+
*.log
|
| 13 |
+
Makefile
|
| 14 |
+
.DS_Store
|
| 15 |
+
.gradio
|
CHANGELOG.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Changelog
|
| 2 |
+
|
| 3 |
+
All notable changes to this project will be documented in this file.
|
| 4 |
+
|
| 5 |
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
| 6 |
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
| 7 |
+
|
| 8 |
+
## [0.1.0] - 2025-11-10
|
| 9 |
+
|
| 10 |
+
This marks the first major stable release after a significant architectural refactor, focusing on robustness, maintainability, and developer experience.
|
| 11 |
+
|
| 12 |
+
### Added
|
| 13 |
+
|
| 14 |
+
- **Modular Architecture:**
|
| 15 |
+
- Introduced a clean, multi-file architecture separating responsibilities:
|
| 16 |
+
- `app.py`: The main application orchestrator.
|
| 17 |
+
- `ui_layout.py`: Defines the complete UI structure and components.
|
| 18 |
+
- `ui_events.py`: Contains all event handling logic and business rules.
|
| 19 |
+
- `ui_state.py`: Provides a centralized `AppState` dataclass for managing shared application state.
|
| 20 |
+
- **State Management:**
|
| 21 |
+
- Implemented a shared `AppState` container to manage application state (e.g., `uidata`, `cancel_event`, pipeline instances) cleanly, removing all global variables.
|
| 22 |
+
- Event logic is now encapsulated within an `EventHandlers` class, which receives the shared state and UI components via dependency injection.
|
| 23 |
+
- **Developer Experience:**
|
| 24 |
+
- Created a `UIComponents` dataclass to hold all Gradio component instances, providing full static type checking and editor Intellisense (auto-completion) for component access.
|
| 25 |
+
- Refactored event bindings in `app.py` to use direct attribute access (e.g., `c.run_btn.click`) instead of error-prone string-based dictionary keys.
|
| 26 |
+
|
| 27 |
+
### Changed
|
| 28 |
+
|
| 29 |
+
- **Event Handling Logic:**
|
| 30 |
+
- Reverted the primary image generation event from using the `@livelog` decorator back to the original, more robust `threading` and `queue` pattern. This ensures the progress bar and logs update correctly even when the browser tab is not in focus.
|
| 31 |
+
- The `generate` method now acts as a generator that spawns a `process_image` worker thread, yielding updates from a queue to the UI.
|
| 32 |
+
- **Event Binding:**
|
| 33 |
+
- The main application class (`GradioApp`) is now solely responsible for orchestrating the app's lifecycle: initializing state, building the UI, and binding events. All implementation logic has been moved to `EventHandlers`.
|
| 34 |
+
- Simplified event binding calls by removing unnecessary `lambda` and `partial` wrappers where methods could be called directly. `partial` is now only used for its intended purpose of pre-filling static arguments.
|
| 35 |
+
- **Code Quality & Style:**
|
| 36 |
+
- Integrated `ruff` as the primary tool for linting and formatting, replacing `isort` and `black`.
|
| 37 |
+
- Refactored code to resolve all major `ruff` warnings, including:
|
| 38 |
+
- Replacing unnecessary list comprehensions with more efficient set comprehensions (e.g., `set([...])` -> `{...}`).
|
| 39 |
+
- Replacing list comprehensions inside `any()` with memory-efficient generator expressions.
|
| 40 |
+
- Eliminating all "bare `except`" blocks by specifying `except Exception` to prevent hiding critical system errors.
|
| 41 |
+
|
| 42 |
+
### Fixed
|
| 43 |
+
|
| 44 |
+
- **Component State Synchronization:**
|
| 45 |
+
- Fixed a critical bug where event handlers were reading stale state from component instances instead of receiving the latest values from the UI. All event handler methods now correctly receive up-to-date component values as arguments from the `inputs` list of the event trigger.
|
| 46 |
+
- **Gradio Context Errors:**
|
| 47 |
+
- Resolved `AttributeError: Cannot call .click outside of a gradio.Blocks context` by centralizing the `gr.Blocks` context management within the `GradioApp` constructor, ensuring both UI creation and event binding occur within the same active context.
|
| 48 |
+
- **`gr.EventData` Injection:**
|
| 49 |
+
- Fixed a bug where `gr.EventData` was being passed as `None` to event handlers. Implemented a robust wrapper pattern for event callbacks that require `EventData`, ensuring Gradio's event data injection mechanism works correctly with the class-based architecture.
|
| 50 |
+
- **Preset Loading Logic:**
|
| 51 |
+
- Corrected a bug in the `load_preset` function where it was mutating the global `RESTORER_CONFIG_MAPPING` dictionary with component instances instead of classes, which caused a `TypeError` on subsequent UI interactions. The function now correctly updates the application state without modifying the original class mapping.
|
LICENSE
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
|
NOTICE
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
DEVAIEXP Scaling-UP ToolBox Application
|
| 2 |
+
Copyright 2025, DEVAIEXP
|
| 3 |
+
|
| 4 |
+
This product is licensed under the Apache License, Version 2.0.
|
| 5 |
+
You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
|
| 6 |
+
|
| 7 |
+
This product includes or is built upon software developed by third parties.
|
| 8 |
+
The following is a list of these components and their respective licenses.
|
| 9 |
+
Full license texts for each component can be found within their respective module directories.
|
| 10 |
+
|
| 11 |
+
===================================================
|
| 12 |
+
Component: SUPIR
|
| 13 |
+
---------------------------------------------------
|
| 14 |
+
This product includes an adaptation of the SUPIR project.
|
| 15 |
+
Copyright (c) 2024 by SupPixel Pty Ltd. All rights reserved.
|
| 16 |
+
Original Project: https://github.com/Fanghua-Yu/SUPIR
|
| 17 |
+
|
| 18 |
+
The use of this component is strictly governed by the SUPIR Software
|
| 19 |
+
License Agreement. This license PROHIBITS ANY COMMERCIAL USE of the
|
| 20 |
+
SUPIR pipeline or its outputs. For commercial licensing, please contact
|
| 21 |
+
the original authors.
|
| 22 |
+
|
| 23 |
+
===================================================
|
| 24 |
+
Component: FaithDiff
|
| 25 |
+
---------------------------------------------------
|
| 26 |
+
This product includes a derivative work based on the FaithDiff project.
|
| 27 |
+
The original work is Copyright (c) 2025, Junyang Chen, and is licensed
|
| 28 |
+
under the MIT License.
|
| 29 |
+
Original Project: https://github.com/JyChen9811/FaithDiff
|
| 30 |
+
|
| 31 |
+
===================================================
|
| 32 |
+
Component: IPAdapter
|
| 33 |
+
---------------------------------------------------
|
| 34 |
+
This product utilizes software from the IP-Adapter project.
|
| 35 |
+
The original work is Copyright (c) 2023, TencentARC, and is licensed
|
| 36 |
+
under the Apache License, Version 2.0.
|
| 37 |
+
Original Project: https://github.com/tencent-ailab/IP-Adapter
|
| 38 |
+
|
| 39 |
+
===================================================
|
| 40 |
+
Component: ControlNet Tile Pipeline
|
| 41 |
+
---------------------------------------------------
|
| 42 |
+
The code for this pipeline is a derivative work based on the ControlNet
|
| 43 |
+
pipeline implementations found in the HuggingFace Diffusers library.
|
| 44 |
+
|
| 45 |
+
The original Diffusers library is Copyright 2024, The HuggingFace Team,
|
| 46 |
+
and is licensed under the Apache License, Version 2.0.
|
| 47 |
+
|
| 48 |
+
===================================================
|
| 49 |
+
Component: Mixture of Diffusers Technique
|
| 50 |
+
---------------------------------------------------
|
| 51 |
+
This product includes an implementation of the "Mixture of Diffusers"
|
| 52 |
+
technique. The original work is Copyright (c) 2022, Álvaro Barbero Jiménez,
|
| 53 |
+
and is licensed under the MIT License.
|
| 54 |
+
Original Project: https://github.com/albarji/mixture-of-diffusers
|
| 55 |
+
|
| 56 |
+
===================================================
|
| 57 |
+
Component: Diffusers Library
|
| 58 |
+
---------------------------------------------------
|
| 59 |
+
This product is built upon and includes software from the Diffusers library.
|
| 60 |
+
The original work is Copyright 2024, The HuggingFace Team, and is licensed
|
| 61 |
+
under the Apache License, Version 2.0.
|
| 62 |
+
Original Project: https://github.com/huggingface/diffusers
|
README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Sup Toolbox App
|
| 3 |
+
emoji: ⚡
|
| 4 |
+
colorFrom: gray
|
| 5 |
+
colorTo: yellow
|
| 6 |
+
sdk: gradio
|
| 7 |
+
sdk_version: 5.49.1
|
| 8 |
+
app_file: app.py
|
| 9 |
+
pinned: false
|
| 10 |
+
license: apache-2.0
|
| 11 |
+
short_description: SUP Toolbox Gradio Demo
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
<h1 align="center">SUP Toolbox App 🎨</h1>
|
| 15 |
+
<p align="center">
|
| 16 |
+
A user-friendly Gradio web interface for the powerful <strong>SUP-Toolbox</strong> image restoration and upscaling library.
|
| 17 |
+
</p>
|
| 18 |
+
|
| 19 |
+
<p align="center">
|
| 20 |
+
<img src="https://huggingface.co/datasets/DEVAIEXP/assets/resolve/main/screen_sup_toolbox.PNG" alt="SUP Toolbox App Interface" width="800"/>
|
| 21 |
+
</p>
|
| 22 |
+
|
| 23 |
+
<p align="center">
|
| 24 |
+
<a href="https://github.com/DEVAIEXP/sup-toolbox-app/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License"></a>
|
| 25 |
+
<a href="https://github.com/DEVAIEXP/sup-toolbox/blob/main/pyproject.toml"><img src="https://img.shields.io/badge/Python-3.10%2B-blue.svg" alt="Python Version"></a>
|
| 26 |
+
<a href="https://github.com/DEVAIEXP/sup-toolbox-app/stargazers"><img src="https://img.shields.io/github/stars/DEVAIEXP/sup-toolbox-app?style=social" alt="GitHub Stars"></a>
|
| 27 |
+
<a href="https://github.com/DEVAIEXP/sup-toolbox"><img src="https://img.shields.io/badge/Powered%20by-SUP--Toolbox-orange" alt="Powered by SUP-Toolbox"></a>
|
| 28 |
+
<a href="https://huggingface.co/spaces/YOUR_HF_USERNAME/sup-toolbox-app"><img src="https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Spaces-yellow" alt="Hugging Face Spaces"></a>
|
| 29 |
+
</p>
|
| 30 |
+
|
| 31 |
+
## About
|
| 32 |
+
|
| 33 |
+
The **SUP Toolbox App** provides an intuitive and interactive web interface for the [SUP-Toolbox library](https://github.com/DEVAIEXP/sup-toolbox). It allows you to harness the power of advanced image enhancement models like **SUPIR**, **FaithDiff**, and **ControlNetTile** through a user-friendly Gradio application, without needing to use the command line.
|
| 34 |
+
|
| 35 |
+
This application is designed for artists, photographers, and enthusiasts who want to visually experiment with different settings, manage presets, and process images one by one with real-time feedback.
|
| 36 |
+
|
| 37 |
+
## Features
|
| 38 |
+
|
| 39 |
+
- **Interactive UI:** A clean and organized Gradio interface for easy access to all features.
|
| 40 |
+
- **Full Engine Support:** Access and combine all restoration (`SUPIR`, `FaithDiff`) and upscaling (`SUPIR`, `FaithDiff`, `ControlNetTile`) engines from the core library.
|
| 41 |
+
- **Preset Management:** Create, save, and load your custom workflows as `.json` presets directly from the UI.
|
| 42 |
+
- **Live Previews & Comparison:** Instantly see the results of your settings with before-and-after image sliders.
|
| 43 |
+
- **Metadata Handling:** Automatically saves all generation parameters within the output image. Load settings from a previously generated image by simply dropping it into the app.
|
| 44 |
+
- **Real-time Logging:** A live log panel shows the progress of the image generation process.
|
| 45 |
+
|
| 46 |
+
## Requirements
|
| 47 |
+
|
| 48 |
+
This application depends on the `sup-toolbox` library. Therefore, it shares the same hardware and software requirements. For a detailed breakdown, please refer to the core library's documentation:
|
| 49 |
+
|
| 50 |
+
➡️ **[View Full Requirements in the SUP-Toolbox README](https://github.com/DEVAIEXP/sup-toolbox#requirements)**
|
| 51 |
+
|
| 52 |
+
In summary:
|
| 53 |
+
- **Python 3.10+**, Git
|
| 54 |
+
- **NVIDIA GPU** with 12GB+ VRAM recommended.
|
| 55 |
+
- **~83 GB** of disk space for models.
|
| 56 |
+
|
| 57 |
+
## Installation
|
| 58 |
+
|
| 59 |
+
The setup process is designed to be simple. It will create a virtual environment and install both the `sup-toolbox` library from its GitHub repository and all other necessary Python packages.
|
| 60 |
+
|
| 61 |
+
### 1. Clone the Application Repository
|
| 62 |
+
```bash
|
| 63 |
+
git clone https://github.com/DEVAIEXP/sup-toolbox-app.git
|
| 64 |
+
cd sup-toolbox-app
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
### 2. Run the Setup Script
|
| 68 |
+
|
| 69 |
+
Choose the script that matches your operating system and terminal.
|
| 70 |
+
|
| 71 |
+
* **On Windows (using PowerShell):**
|
| 72 |
+
This is the recommended method for modern Windows systems.
|
| 73 |
+
|
| 74 |
+
1. **Allow script execution (one-time setup):**
|
| 75 |
+
If you haven't run PowerShell scripts before, open PowerShell **as an Administrator** and run:
|
| 76 |
+
```powershell
|
| 77 |
+
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
|
| 78 |
+
```
|
| 79 |
+
Press `Y` and `Enter` to confirm.
|
| 80 |
+
|
| 81 |
+
2. **Run the script:**
|
| 82 |
+
In a regular PowerShell terminal, run:
|
| 83 |
+
```powershell
|
| 84 |
+
.\setup.ps1
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
* **On Windows (using Command Prompt):**
|
| 88 |
+
```batch
|
| 89 |
+
.\setup.bat
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
* **On Linux or macOS:**
|
| 93 |
+
```bash
|
| 94 |
+
chmod +x setup.sh
|
| 95 |
+
./setup.sh
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
This will set up everything you need. The `requirements.txt` file in this repository is configured to automatically download and install the correct version of the `sup-toolbox` library.
|
| 99 |
+
|
| 100 |
+
## Usage
|
| 101 |
+
|
| 102 |
+
After a successful installation, you can easily start the application using the provided start scripts.
|
| 103 |
+
|
| 104 |
+
* **On Windows:** Double-click `start.bat` or `start.ps1`, or run them from your terminal:
|
| 105 |
+
```powershell
|
| 106 |
+
# In PowerShell
|
| 107 |
+
.\start.ps1
|
| 108 |
+
```
|
| 109 |
+
```batch
|
| 110 |
+
:: In Command Prompt
|
| 111 |
+
.\start.bat
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
* **On Linux or macOS:**
|
| 115 |
+
```bash
|
| 116 |
+
./start.sh
|
| 117 |
+
```
|
| 118 |
+
|
| 119 |
+
This will activate the virtual environment and launch the Gradio application. Open your web browser and navigate to the local URL provided in the terminal (usually `http://127.0.0.1:7860`).
|
| 120 |
+
|
| 121 |
+
## Relationship with `sup-toolbox`
|
| 122 |
+
|
| 123 |
+
This project is the official Gradio UI front-end for the `sup-toolbox` library.
|
| 124 |
+
|
| 125 |
+
- **[sup-toolbox (Library)](https://github.com/DEVAIEXP/sup-toolbox):** The core engine. It's a Python library and CLI tool that contains all the processing logic, model management, and configuration system. It's designed for programmatic use and automation.
|
| 126 |
+
- **sup-toolbox-app (This Project):** A user-friendly graphical interface that *uses* the `sup-toolbox` library as its backend. It provides no image processing logic itself but offers a convenient way to access the library's features.
|
| 127 |
+
|
| 128 |
+
## Releases and Changelog
|
| 129 |
+
|
| 130 |
+
For a detailed list of changes, new features, and bug fixes for each version of this application, please see the **[CHANGELOG.md](CHANGELOG.md)** file.
|
| 131 |
+
|
| 132 |
+
## Licensing
|
| 133 |
+
|
| 134 |
+
The **SUP Toolbox App** (this project) is licensed under the **Apache License, Version 2.0**. You can find the full license text in the `LICENSE` file in this repository.
|
| 135 |
+
|
| 136 |
+
Please be aware that this application is a front-end for the `sup-toolbox` library, which integrates several third-party components, some of which are under **non-commercial licenses** (such as SUPIR).
|
| 137 |
+
|
| 138 |
+
**By using this application, you acknowledge and agree to comply with all the licensing terms of the underlying `sup-toolbox` library and its components.** For a complete breakdown of these licenses, please refer to the library's documentation:
|
| 139 |
+
|
| 140 |
+
➡️ **[View Full Licensing Details in the SUP-Toolbox README](https://github.com/DEVAIEXP/sup-toolbox#licensing)**
|
| 141 |
+
|
| 142 |
+
## Star History
|
| 143 |
+
|
| 144 |
+
[](https://star-history.com/#DEVAIEXP/sup-toolbox-app&Date)
|
| 145 |
+
|
| 146 |
+
## Contact
|
| 147 |
+
For questions or issues related to this Gradio application, please open an issue in this repository. For issues related to the core processing logic, please refer to the [`sup-toolbox`](https://github.com/DEVAIEXP/sup-toolbox/issues) library's repository.
|
| 148 |
+
|
| 149 |
+
Project Contact: contact@devaiexp.com
|
app.py
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2025 The DEVAIEXP Team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import spaces
|
| 16 |
+
import gradio as gr
|
| 17 |
+
import torch
|
| 18 |
+
import argparse
|
| 19 |
+
from functools import partial
|
| 20 |
+
from typing import Any
|
| 21 |
+
|
| 22 |
+
from gradio_folderexplorer.helpers import load_media_from_folder
|
| 23 |
+
from gradio_livelog.utils import livelog
|
| 24 |
+
|
| 25 |
+
from ui.ui_data import UIData
|
| 26 |
+
from ui.ui_events import AppState, EventHandlers
|
| 27 |
+
from ui.ui_layout import UIComponents, create_ui_components
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class GradioApp:
|
| 31 |
+
def __init__(self, args):
|
| 32 |
+
self.args = args
|
| 33 |
+
self.state = AppState(uidata=UIData(always_download_models=args.always_download_models))
|
| 34 |
+
|
| 35 |
+
self.app = gr.Blocks(theme=self.state.uidata.theme, title="Scaling-UP ToolBox")
|
| 36 |
+
with self.app:
|
| 37 |
+
components_dict = create_ui_components(self.state.uidata)
|
| 38 |
+
self.components = UIComponents(**components_dict)
|
| 39 |
+
self.event_handlers = EventHandlers(self.state, self.components)
|
| 40 |
+
self._bind_events()
|
| 41 |
+
|
| 42 |
+
def _bind_events(self):
|
| 43 |
+
c = self.components
|
| 44 |
+
ui_inputs, output_fields = c._get_ui_inputs_and_outputs()
|
| 45 |
+
js_update_flyout = "(jsonData) => { update_flyout_from_state(jsonData); }"
|
| 46 |
+
flyout_data_event = {"fn": None, "inputs": [c.js_data_bridge], "js": js_update_flyout}
|
| 47 |
+
|
| 48 |
+
generate_inputs = list(ui_inputs.values())[4:]
|
| 49 |
+
|
| 50 |
+
@spaces.GPU(duration=60)
|
| 51 |
+
# Here we don't use the @livelog decorator function to ensure the progress bar is synchronized when the browser is not in focus.
|
| 52 |
+
def on_generate_wrapper(*args):
|
| 53 |
+
"""
|
| 54 |
+
This wrapper is decorated by @spaces.GPU and is passed to Gradio.
|
| 55 |
+
It calls the actual logic handler from the EventHandlers instance.
|
| 56 |
+
"""
|
| 57 |
+
yield from self.event_handlers.on_generate(*args)
|
| 58 |
+
|
| 59 |
+
@spaces.GPU(duration=60)
|
| 60 |
+
def on_refresh_mask_gpu_wrapper(*args, **kwargs):
|
| 61 |
+
"""Wrapper for the mask generation."""
|
| 62 |
+
|
| 63 |
+
livelog_decorated_func = livelog(
|
| 64 |
+
log_names=["suptoolbox_app", "suptoolbox"],
|
| 65 |
+
outputs_for_yield=[c.restoration_mask, c.livelog_viewer],
|
| 66 |
+
log_output_index=1,
|
| 67 |
+
result_output_index=0,
|
| 68 |
+
use_tracker=False,
|
| 69 |
+
)(self.event_handlers.on_refresh_restoration_mask)
|
| 70 |
+
|
| 71 |
+
yield from livelog_decorated_func(*args, **kwargs)
|
| 72 |
+
|
| 73 |
+
@spaces.GPU(duration=60)
|
| 74 |
+
def on_generate_caption_gpu_wrapper(*args, **kwargs):
|
| 75 |
+
"""
|
| 76 |
+
This wrapper handles the @spaces.GPU decorator and then applies
|
| 77 |
+
the @livelog decorator to the actual event handler.
|
| 78 |
+
"""
|
| 79 |
+
|
| 80 |
+
livelog_decorated_func = livelog(
|
| 81 |
+
log_names=["suptoolbox_app", "suptoolbox"],
|
| 82 |
+
outputs_for_yield=[kwargs.pop("output_component_1"), kwargs.pop("output_component_2")],
|
| 83 |
+
log_output_index=1,
|
| 84 |
+
result_output_index=0,
|
| 85 |
+
use_tracker=False,
|
| 86 |
+
)(self.event_handlers.on_generate_caption)
|
| 87 |
+
|
| 88 |
+
yield from livelog_decorated_func(*args, **kwargs)
|
| 89 |
+
|
| 90 |
+
def on_load_metadata_from_gallery_wrapper(folder_explorer_value: Any, image_data: gr.EventData):
|
| 91 |
+
return self.event_handlers.on_load_metadata_from_gallery(folder_explorer_value, image_data)
|
| 92 |
+
|
| 93 |
+
# Settings Tab Events
|
| 94 |
+
c.reset_settings_btn.click(fn=self.event_handlers.on_reset_settings).then(fn=self.event_handlers.restart, js="restart_ui")
|
| 95 |
+
c.save_settings_btn.click(fn=self.event_handlers.on_save_settings, inputs=c.settings_sheet).then(fn=self.event_handlers.restart, js="restart_ui")
|
| 96 |
+
|
| 97 |
+
# Flyout Events
|
| 98 |
+
c.flyout_sheet.change(fn=self.event_handlers.on_flyout_change, inputs=[c.flyout_sheet, c.active_anchor_id])
|
| 99 |
+
c.flyout_property_sheet_close_btn.click(
|
| 100 |
+
partial(self.event_handlers.on_close_the_flyout, "flyout_property_sheet_panel_target"),
|
| 101 |
+
outputs=[c.flyout_visible, c.active_anchor_id, c.js_data_bridge],
|
| 102 |
+
).then(**flyout_data_event)
|
| 103 |
+
c.flyout_restoration_image_close_btn.click(
|
| 104 |
+
partial(self.event_handlers.on_close_the_flyout, "flyout_restoration_mask_panel_target"),
|
| 105 |
+
outputs=[c.flyout_visible, c.active_anchor_id, c.js_data_bridge],
|
| 106 |
+
).then(**flyout_data_event)
|
| 107 |
+
c.restorer_sampler.change(
|
| 108 |
+
partial(self.event_handlers.on_update_ear_visibility, c.restorer_sampler.elem_id),
|
| 109 |
+
outputs=[c.restorer_sampler_ear_btn],
|
| 110 |
+
).then(
|
| 111 |
+
partial(self.event_handlers.on_close_the_flyout, "flyout_property_sheet_panel_target"),
|
| 112 |
+
outputs=[c.flyout_visible, c.active_anchor_id, c.js_data_bridge],
|
| 113 |
+
).then(**flyout_data_event)
|
| 114 |
+
c.restorer_sampler_ear_btn.click(
|
| 115 |
+
partial(
|
| 116 |
+
self.event_handlers.on_handle_flyout_toggle,
|
| 117 |
+
clicked_elem_id=c.restorer_sampler.elem_id,
|
| 118 |
+
target_elem_id="flyout_property_sheet_panel_target",
|
| 119 |
+
),
|
| 120 |
+
inputs=[c.flyout_visible, c.active_anchor_id],
|
| 121 |
+
outputs=[c.flyout_visible, c.active_anchor_id, c.flyout_sheet, c.js_data_bridge],
|
| 122 |
+
).then(**flyout_data_event)
|
| 123 |
+
c.upscaler_sampler.change(
|
| 124 |
+
partial(self.event_handlers.on_update_ear_visibility, c.upscaler_sampler.elem_id),
|
| 125 |
+
outputs=[c.upscaler_sampler_ear_btn],
|
| 126 |
+
).then(
|
| 127 |
+
partial(self.event_handlers.on_close_the_flyout, "flyout_property_sheet_panel_target"),
|
| 128 |
+
outputs=[c.flyout_visible, c.active_anchor_id, c.js_data_bridge],
|
| 129 |
+
).then(**flyout_data_event)
|
| 130 |
+
c.upscaler_sampler_ear_btn.click(
|
| 131 |
+
partial(
|
| 132 |
+
self.event_handlers.on_handle_flyout_toggle,
|
| 133 |
+
clicked_elem_id=c.upscaler_sampler.elem_id,
|
| 134 |
+
target_elem_id="flyout_property_sheet_panel_target",
|
| 135 |
+
),
|
| 136 |
+
inputs=[c.flyout_visible, c.active_anchor_id],
|
| 137 |
+
outputs=[c.flyout_visible, c.active_anchor_id, c.flyout_sheet, c.js_data_bridge],
|
| 138 |
+
).then(**flyout_data_event)
|
| 139 |
+
c.preview_restoration_mask_chk.select(
|
| 140 |
+
lambda is_checked: (gr.update(interactive=is_checked), gr.update(interactive=is_checked)),
|
| 141 |
+
inputs=[c.preview_restoration_mask_chk],
|
| 142 |
+
outputs=[c.restoration_mask_prompt, c.preview_restoration_mask_btn],
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
c.preview_restoration_mask_btn.click(
|
| 146 |
+
self.event_handlers.on_check_inputs,
|
| 147 |
+
inputs=[
|
| 148 |
+
c.restorer_engine,
|
| 149 |
+
c.upscaler_engine,
|
| 150 |
+
c.restorer_model,
|
| 151 |
+
c.upscaler_model,
|
| 152 |
+
gr.State("generation_mask"),
|
| 153 |
+
],
|
| 154 |
+
outputs=[c.bottom_bar, c.livelog_viewer],
|
| 155 |
+
show_progress="hidden",
|
| 156 |
+
).success(
|
| 157 |
+
fn=on_refresh_mask_gpu_wrapper,
|
| 158 |
+
inputs=[c.restoration_mask_prompt],
|
| 159 |
+
outputs=[c.restoration_mask, c.livelog_viewer],
|
| 160 |
+
).success(
|
| 161 |
+
partial(
|
| 162 |
+
self.event_handlers.on_handle_flyout_toggle,
|
| 163 |
+
clicked_elem_id=c.preview_restoration_mask_btn.elem_id,
|
| 164 |
+
target_elem_id="flyout_restoration_mask_panel_target",
|
| 165 |
+
),
|
| 166 |
+
inputs=[c.flyout_visible, c.active_anchor_id],
|
| 167 |
+
outputs=[c.flyout_visible, c.active_anchor_id, c.restoration_mask, c.js_data_bridge],
|
| 168 |
+
).success(**flyout_data_event)
|
| 169 |
+
|
| 170 |
+
c.res_prompt_generate_btn.click(
|
| 171 |
+
self.event_handlers.on_check_inputs,
|
| 172 |
+
inputs=[
|
| 173 |
+
c.restorer_engine,
|
| 174 |
+
c.upscaler_engine,
|
| 175 |
+
c.restorer_model,
|
| 176 |
+
c.upscaler_model,
|
| 177 |
+
gr.State("caption_generation"),
|
| 178 |
+
],
|
| 179 |
+
outputs=[c.bottom_bar, c.livelog_viewer],
|
| 180 |
+
show_progress="hidden",
|
| 181 |
+
).success(
|
| 182 |
+
fn=partial(on_generate_caption_gpu_wrapper, output_component_1=c.res_prompt, output_component_2=c.livelog_viewer),
|
| 183 |
+
outputs=[c.res_prompt, c.livelog_viewer],
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
c.ups_prompt_generate_btn.click(
|
| 187 |
+
self.event_handlers.on_check_inputs,
|
| 188 |
+
inputs=[
|
| 189 |
+
c.restorer_engine,
|
| 190 |
+
c.upscaler_engine,
|
| 191 |
+
c.restorer_model,
|
| 192 |
+
c.upscaler_model,
|
| 193 |
+
gr.State("caption_generation"),
|
| 194 |
+
],
|
| 195 |
+
outputs=[c.bottom_bar, c.livelog_viewer],
|
| 196 |
+
show_progress="hidden",
|
| 197 |
+
).success(
|
| 198 |
+
fn=partial(on_generate_caption_gpu_wrapper, output_component_1=c.ups_prompt, output_component_2=c.livelog_viewer),
|
| 199 |
+
outputs=[c.ups_prompt, c.livelog_viewer],
|
| 200 |
+
)
|
| 201 |
+
c.restorer_engine.select(
|
| 202 |
+
self.event_handlers.on_restore_engine_change,
|
| 203 |
+
inputs=[c.restorer_engine, c.upscaler_engine],
|
| 204 |
+
outputs=[c.restorer_tab, c.restorer_sheet_supir_advanced, c.restorer_sheet, c.ec_accordion, c.config_tabs],
|
| 205 |
+
).success(
|
| 206 |
+
self.event_handlers.on_set_default_prompts,
|
| 207 |
+
inputs=[
|
| 208 |
+
c.restorer_engine,
|
| 209 |
+
c.upscaler_engine,
|
| 210 |
+
c.res_prompt,
|
| 211 |
+
c.res_prompt_2,
|
| 212 |
+
c.res_negative_prompt,
|
| 213 |
+
c.ups_prompt,
|
| 214 |
+
c.ups_prompt_2,
|
| 215 |
+
c.ups_negative_prompt,
|
| 216 |
+
],
|
| 217 |
+
outputs=[
|
| 218 |
+
c.res_prompt,
|
| 219 |
+
c.res_prompt_2,
|
| 220 |
+
c.res_negative_prompt,
|
| 221 |
+
c.ups_prompt,
|
| 222 |
+
c.ups_prompt_2,
|
| 223 |
+
c.ups_negative_prompt,
|
| 224 |
+
],
|
| 225 |
+
)
|
| 226 |
+
c.restorer_engine.change(
|
| 227 |
+
self.event_handlers.on_restore_engine_change,
|
| 228 |
+
inputs=[c.restorer_engine, c.upscaler_engine],
|
| 229 |
+
outputs=[c.restorer_tab, c.restorer_sheet_supir_advanced, c.restorer_sheet, c.ec_accordion, c.config_tabs],
|
| 230 |
+
)
|
| 231 |
+
c.upscaler_engine.change(
|
| 232 |
+
self.event_handlers.on_upscaler_engine_change,
|
| 233 |
+
inputs=[c.restorer_engine, c.upscaler_engine],
|
| 234 |
+
outputs=[
|
| 235 |
+
c.upscaler_tab,
|
| 236 |
+
c.upscaler_sheet_supir_advanced,
|
| 237 |
+
c.upscaler_sheet,
|
| 238 |
+
c.ec_accordion,
|
| 239 |
+
c.config_tabs,
|
| 240 |
+
c.ups_prompt_method,
|
| 241 |
+
],
|
| 242 |
+
)
|
| 243 |
+
c.upscaler_engine.select(
|
| 244 |
+
self.event_handlers.on_upscaler_engine_change,
|
| 245 |
+
inputs=[c.restorer_engine, c.upscaler_engine],
|
| 246 |
+
outputs=[
|
| 247 |
+
c.upscaler_tab,
|
| 248 |
+
c.upscaler_sheet_supir_advanced,
|
| 249 |
+
c.upscaler_sheet,
|
| 250 |
+
c.ec_accordion,
|
| 251 |
+
c.config_tabs,
|
| 252 |
+
c.ups_prompt_method,
|
| 253 |
+
],
|
| 254 |
+
).success(
|
| 255 |
+
self.event_handlers.on_set_default_prompts,
|
| 256 |
+
inputs=[
|
| 257 |
+
c.restorer_engine,
|
| 258 |
+
c.upscaler_engine,
|
| 259 |
+
c.res_prompt,
|
| 260 |
+
c.res_prompt_2,
|
| 261 |
+
c.res_negative_prompt,
|
| 262 |
+
c.ups_prompt,
|
| 263 |
+
c.ups_prompt_2,
|
| 264 |
+
c.ups_negative_prompt,
|
| 265 |
+
],
|
| 266 |
+
outputs=[
|
| 267 |
+
c.res_prompt,
|
| 268 |
+
c.res_prompt_2,
|
| 269 |
+
c.res_negative_prompt,
|
| 270 |
+
c.ups_prompt,
|
| 271 |
+
c.ups_prompt_2,
|
| 272 |
+
c.ups_negative_prompt,
|
| 273 |
+
],
|
| 274 |
+
)
|
| 275 |
+
c.restorer_sheet.change(self.event_handlers.on_restorer_sheet_change, inputs=[c.restorer_sheet], outputs=[c.restorer_sheet])
|
| 276 |
+
c.upscaler_sheet.change(self.event_handlers.on_upscaler_sheet_change, inputs=[c.upscaler_sheet], outputs=[c.upscaler_sheet])
|
| 277 |
+
c.settings_sheet.change(self.event_handlers.on_settings_sheet_change, inputs=[c.settings_sheet], outputs=[c.settings_sheet])
|
| 278 |
+
c.restorer_sheet_supir_advanced.change(
|
| 279 |
+
self.event_handlers.on_restorer_supir_advanced_sheet_change, inputs=[c.restorer_sheet_supir_advanced], outputs=[c.restorer_sheet_supir_advanced]
|
| 280 |
+
)
|
| 281 |
+
c.upscaler_sheet_supir_advanced.change(
|
| 282 |
+
self.event_handlers.on_upscaler_supir_advanced_sheet_change, inputs=[c.upscaler_sheet_supir_advanced], outputs=[c.upscaler_sheet_supir_advanced]
|
| 283 |
+
)
|
| 284 |
+
c.settings_tab.select(self.event_handlers.on_settings_tab_select, outputs=[c.settings_sheet])
|
| 285 |
+
update_prompt_helper_from_tab_event = {
|
| 286 |
+
"fn": self.event_handlers.update_prompt_helper_from_tab,
|
| 287 |
+
"outputs": [c.tag_helper_pos, c.tag_helper_neg],
|
| 288 |
+
}
|
| 289 |
+
c.restorer_tab.select(**update_prompt_helper_from_tab_event)
|
| 290 |
+
c.upscaler_tab.select(**update_prompt_helper_from_tab_event)
|
| 291 |
+
c.res_prompt.change(
|
| 292 |
+
self.event_handlers.update_positive_tokenizer,
|
| 293 |
+
inputs=[c.res_prompt, c.res_prompt_2],
|
| 294 |
+
outputs=c.res_tokenizer_pos,
|
| 295 |
+
)
|
| 296 |
+
c.res_prompt_2.change(
|
| 297 |
+
self.event_handlers.update_positive_tokenizer,
|
| 298 |
+
inputs=[c.res_prompt, c.res_prompt_2],
|
| 299 |
+
outputs=c.res_tokenizer_pos,
|
| 300 |
+
)
|
| 301 |
+
c.ups_prompt.change(
|
| 302 |
+
self.event_handlers.update_positive_tokenizer,
|
| 303 |
+
inputs=[c.ups_prompt, c.ups_prompt_2],
|
| 304 |
+
outputs=c.ups_tokenizer_pos,
|
| 305 |
+
)
|
| 306 |
+
c.ups_prompt_2.change(
|
| 307 |
+
self.event_handlers.update_positive_tokenizer,
|
| 308 |
+
inputs=[c.ups_prompt, c.ups_prompt_2],
|
| 309 |
+
outputs=c.ups_tokenizer_pos,
|
| 310 |
+
)
|
| 311 |
+
c.res_negative_prompt.change(lambda p: gr.update(value=p), inputs=c.res_negative_prompt, outputs=c.res_tokenizer_neg)
|
| 312 |
+
c.ups_negative_prompt.change(lambda p: gr.update(value=p), inputs=c.ups_negative_prompt, outputs=c.ups_tokenizer_neg)
|
| 313 |
+
c.res_prompt.focus(self.event_handlers.update_positive_prompt_helper, outputs=c.tag_helper_pos)
|
| 314 |
+
c.res_prompt_2.focus(self.event_handlers.update_positive_prompt_helper, outputs=c.tag_helper_pos)
|
| 315 |
+
c.ups_prompt.focus(self.event_handlers.update_positive_prompt_helper, outputs=c.tag_helper_pos)
|
| 316 |
+
c.ups_prompt_2.focus(self.event_handlers.update_positive_prompt_helper, outputs=c.tag_helper_pos)
|
| 317 |
+
|
| 318 |
+
c.run_btn.click(
|
| 319 |
+
self.event_handlers.on_check_inputs,
|
| 320 |
+
inputs=[c.restorer_engine, c.upscaler_engine, c.restorer_model, c.upscaler_model],
|
| 321 |
+
outputs=[c.bottom_bar, c.livelog_viewer],
|
| 322 |
+
show_progress="hidden",
|
| 323 |
+
).success(
|
| 324 |
+
self.event_handlers.calculate_total_steps,
|
| 325 |
+
inputs=[c.restorer_sheet, c.upscaler_sheet, c.restorer_engine, c.upscaler_engine],
|
| 326 |
+
outputs=c.total_inference_steps,
|
| 327 |
+
).success(
|
| 328 |
+
fn=on_generate_wrapper,
|
| 329 |
+
inputs=[c.total_inference_steps, *generate_inputs],
|
| 330 |
+
outputs=[c.result_slider, c.livelog_viewer, c.run_btn, c.cancel_btn, c.bottom_bar],
|
| 331 |
+
show_progress="hidden",
|
| 332 |
+
)
|
| 333 |
+
c.cancel_btn.click(self.event_handlers.on_cancel_click)
|
| 334 |
+
c.save_preset_btn.click(
|
| 335 |
+
fn=self.event_handlers.save_preset,
|
| 336 |
+
inputs=[c.preset_name, *c.ALL_UI_COMPONENTS.values()],
|
| 337 |
+
).then(self.event_handlers.update_preset_list, inputs=c.preset_name, outputs=[c.presets])
|
| 338 |
+
c.load_preset_btn.click(
|
| 339 |
+
fn=self.event_handlers.load_preset,
|
| 340 |
+
inputs=[c.presets],
|
| 341 |
+
outputs=list(c.ALL_UI_COMPONENTS.values()),
|
| 342 |
+
)
|
| 343 |
+
c.input_image.load_metadata(self.event_handlers.on_load_metadata_from_single_image, inputs=c.input_image, outputs=output_fields)
|
| 344 |
+
c.input_image.change(self.event_handlers.on_input_image_change, inputs=c.input_image)
|
| 345 |
+
c.folder_explorer.change(load_media_from_folder, inputs=c.folder_explorer, outputs=c.generated_image_viewer)
|
| 346 |
+
c.generated_image_viewer.load_metadata(fn=on_load_metadata_from_gallery_wrapper, inputs=[c.folder_explorer], outputs=output_fields).then(
|
| 347 |
+
lambda: gr.update(selected="process-tab"), outputs=c.main_tabs
|
| 348 |
+
)
|
| 349 |
+
c.livelog_viewer.clear(fn=self.event_handlers.on_clear_log_output, outputs=c.livelog_viewer)
|
| 350 |
+
flyout_setup = self.event_handlers.initial_flyout_setup()
|
| 351 |
+
self.app.load(fn=self.state.uidata.inject_assets, inputs=None, outputs=[c.html_injector]).then(
|
| 352 |
+
self.event_handlers.on_restore_engine_change,
|
| 353 |
+
inputs=[c.restorer_engine, c.upscaler_engine],
|
| 354 |
+
outputs=[c.restorer_tab, c.restorer_sheet_supir_advanced, c.restorer_sheet, c.ec_accordion, c.config_tabs],
|
| 355 |
+
).then(
|
| 356 |
+
self.event_handlers.on_upscaler_engine_change,
|
| 357 |
+
inputs=[c.restorer_engine, c.upscaler_engine],
|
| 358 |
+
outputs=[
|
| 359 |
+
c.upscaler_tab,
|
| 360 |
+
c.upscaler_sheet_supir_advanced,
|
| 361 |
+
c.upscaler_sheet,
|
| 362 |
+
c.ec_accordion,
|
| 363 |
+
c.config_tabs,
|
| 364 |
+
c.ups_prompt_method,
|
| 365 |
+
],
|
| 366 |
+
).then(self.event_handlers.on_restorer_sheet_change, inputs=[c.restorer_sheet], outputs=[c.restorer_sheet]).then(
|
| 367 |
+
self.event_handlers.update_positive_tokenizer,
|
| 368 |
+
inputs=[c.res_prompt, c.res_prompt_2],
|
| 369 |
+
outputs=c.res_tokenizer_pos,
|
| 370 |
+
).then(
|
| 371 |
+
self.event_handlers.update_positive_tokenizer,
|
| 372 |
+
inputs=[c.ups_prompt, c.ups_prompt_2],
|
| 373 |
+
outputs=c.ups_tokenizer_pos,
|
| 374 |
+
).then(lambda p: gr.update(value=p), inputs=c.res_negative_prompt, outputs=c.res_tokenizer_neg).then(
|
| 375 |
+
lambda p: gr.update(value=p), inputs=c.ups_negative_prompt, outputs=c.ups_tokenizer_neg
|
| 376 |
+
).then(
|
| 377 |
+
lambda: [flyout_setup["restorer_sampler_ear_btn"], flyout_setup["upscaler_sampler_ear_btn"]],
|
| 378 |
+
outputs=[c.restorer_sampler_ear_btn, c.upscaler_sampler_ear_btn],
|
| 379 |
+
).then(
|
| 380 |
+
fn=None,
|
| 381 |
+
inputs=None,
|
| 382 |
+
outputs=None,
|
| 383 |
+
js="() => { setTimeout(reparent_flyout(['flyout_property_sheet_panel', 'flyout_restoration_mask_panel']), 200); }",
|
| 384 |
+
)
|
| 385 |
+
|
| 386 |
+
def launch(self):
|
| 387 |
+
self.app.queue().launch(debug=True, inbrowser=True, share=self.args.share, server_port=self.args.port, server_name=self.args.listen)
|
| 388 |
+
|
| 389 |
+
|
| 390 |
+
if __name__ == "__main__":
|
| 391 |
+
parser = argparse.ArgumentParser(description="SUP-Toolbox initialization args")
|
| 392 |
+
parser.add_argument(
|
| 393 |
+
"--always-download-models",
|
| 394 |
+
action="store_true",
|
| 395 |
+
default=False,
|
| 396 |
+
help="If specified, forces a full scan and download of the models if necessary.",
|
| 397 |
+
)
|
| 398 |
+
parser.add_argument("-s", "--share", action="store_true", help="Create a public link")
|
| 399 |
+
parser.add_argument("--port", default=7860, type=int, help="Port to run the server on")
|
| 400 |
+
parser.add_argument("--listen", default="0.0.0.0", help="IP address to listen on")
|
| 401 |
+
|
| 402 |
+
args = parser.parse_args()
|
| 403 |
+
|
| 404 |
+
app_instance = GradioApp(args)
|
| 405 |
+
app_instance.launch()
|
assets/devaixp_logo-white.png
ADDED
|
Git LFS Details
|
assets/samples/1.jpg
ADDED
|
Git LFS Details
|
assets/samples/2.jpg
ADDED
|
Git LFS Details
|
assets/samples/3.jpg
ADDED
|
Git LFS Details
|
assets/samples/band.png
ADDED
|
Git LFS Details
|
assets/samples/band_restored.png
ADDED
|
Git LFS Details
|
assets/samples/boy.jpg
ADDED
|
Git LFS Details
|
assets/samples/woman.png
ADDED
|
Git LFS Details
|
assets/samples/woman_restored.png
ADDED
|
Git LFS Details
|
configs/settings.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"save_image_format": ".jpg",
|
| 3 |
+
"latest_preset": "Default",
|
| 4 |
+
"cache_dir": "models",
|
| 5 |
+
"checkpoints_dir": "F:\\models\\Stable-diffusion",
|
| 6 |
+
"vae_dir": "models/VAE",
|
| 7 |
+
"output_dir": "./outputs",
|
| 8 |
+
"save_image_on_upscaling_passes": true,
|
| 9 |
+
"weight_dtype": "Float16",
|
| 10 |
+
"vae_weight_dtype": "Float16",
|
| 11 |
+
"device": "cuda",
|
| 12 |
+
"generator_device": "cpu",
|
| 13 |
+
"enable_cpu_offload": false,
|
| 14 |
+
"enable_vae_tiling": true,
|
| 15 |
+
"enable_vae_slicing": false,
|
| 16 |
+
"memory_attention": "sdp",
|
| 17 |
+
"quantization_method": "Layerwise & Bnb",
|
| 18 |
+
"quantization_mode": "FP8",
|
| 19 |
+
"allow_cuda_tf32": false,
|
| 20 |
+
"allow_cudnn_tf32": false,
|
| 21 |
+
"disable_mmap": true,
|
| 22 |
+
"always_offload_models": true,
|
| 23 |
+
"run_vae_on_cpu": false,
|
| 24 |
+
"enable_llava_quantization": true,
|
| 25 |
+
"llava_quantization_mode": "INT4",
|
| 26 |
+
"llava_offload_model": false,
|
| 27 |
+
"llava_weight_dtype": "Float16",
|
| 28 |
+
"llava_question_prompt": "Describe what you see in this image and put it in prompt format limited to 77 tokens:"
|
| 29 |
+
}
|
outputs/examples/1_FaithDiff_Restore_1x_CFGOnly.png
ADDED
|
Git LFS Details
|
outputs/examples/1_FaithDiff_Restore_1x_FaithDiff_Upscale_2x_CFGOnly.png
ADDED
|
Git LFS Details
|
outputs/examples/1_FaithDiff_Restore_1x_FaithDiff_Upscale_2x_PAG.png
ADDED
|
Git LFS Details
|
outputs/examples/1_FaithDiff_Restore_1x_PAG.png
ADDED
|
Git LFS Details
|
outputs/examples/1_FaithDiff_Restore_1x_SUPIR_Upscale_2x.png
ADDED
|
Git LFS Details
|
outputs/examples/1_SUPIR_Restore_1x_CFGOnly.png
ADDED
|
Git LFS Details
|
outputs/examples/1_SUPIR_Restore_1x_FaithDiff_Upscale_2x.png
ADDED
|
Git LFS Details
|
outputs/examples/1_SUPIR_Restore_1x_PAGOnly.png
ADDED
|
Git LFS Details
|
outputs/examples/1_SUPIR_Restore_1x_SUPIR_Upscale_2x_CFGOnly.png
ADDED
|
Git LFS Details
|
outputs/examples/1_SUPIR_Restore_1x_SUPIR_Upscale_2x_PAG.png
ADDED
|
Git LFS Details
|
outputs/examples/2_FaithDiff_Restore_1x_CFGOnly.png
ADDED
|
Git LFS Details
|
outputs/examples/2_SUPIR_Restore_1x.png
ADDED
|
Git LFS Details
|
outputs/examples/2_SUPIR_Restore_1x_ControlNetTIle_Upscale_2x.png
ADDED
|
Git LFS Details
|
outputs/examples/2_SUPIR_Restore_1x_ControlNetTIle_Upscale_4x.png
ADDED
|
Git LFS Details
|
outputs/examples/3_ControlnetTile_Upscale_2x.png
ADDED
|
Git LFS Details
|
outputs/examples/4_ControlnetTile_Upscale_8x.jpg
ADDED
|
Git LFS Details
|
outputs/examples/5_ControlnetTile_Upscale_4x.png
ADDED
|
Git LFS Details
|
requirements.txt
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio==5.44.1
|
| 2 |
+
gradio_tokenizertextbox
|
| 3 |
+
gradio_taggrouphelper
|
| 4 |
+
gradio_propertysheet
|
| 5 |
+
gradio_htmlinjector
|
| 6 |
+
gradio_imagemeta
|
| 7 |
+
gradio_livelog
|
| 8 |
+
gradio_bottombar
|
| 9 |
+
gradio_topbar
|
| 10 |
+
gradio_textboxplus
|
| 11 |
+
gradio_dropdownplus
|
| 12 |
+
gradio_buttonplus
|
| 13 |
+
gradio_folderexplorer
|
| 14 |
+
gradio_mediagallery
|
| 15 |
+
gradio_creditspanel
|
| 16 |
+
spaces
|
| 17 |
+
optimum-quanto==0.2.7
|
| 18 |
+
torch==2.8.0
|
| 19 |
+
torchvision==0.23.0
|
| 20 |
+
torchaudio==2.8.0
|
| 21 |
+
transformers==4.56.0
|
| 22 |
+
bitsandbytes==0.48.1
|
| 23 |
+
xformers==0.0.32.post2
|
| 24 |
+
hf_xet
|
| 25 |
+
git+https://github.com/DEVAIEXP/sup-toolbox.git@main#egg=sup-toolbox
|
ui/globals.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import threading
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
# These variables live at the module level.
|
| 5 |
+
# When multiple workers import this module, they will all reference
|
| 6 |
+
# the same objects in the parent process's memory (due to how Python forks processes on Linux).
|
| 7 |
+
sup_toolbox_pipe = None
|
| 8 |
+
pipeline_lock = threading.Lock()
|
ui/script.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
function isJsonString(str) {
|
| 2 |
+
if (typeof str !== 'string' || str.trim() === '') {
|
| 3 |
+
return false;
|
| 4 |
+
}
|
| 5 |
+
try {
|
| 6 |
+
const result = JSON.parse(str);
|
| 7 |
+
return typeof result === 'object' && result !== null;
|
| 8 |
+
} catch (e) {
|
| 9 |
+
return false;
|
| 10 |
+
}
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
// Copied and adapted from https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/82a973c04367123ae98bd9abdf80d9eda9b910e2/javascript/extraNetworks.js#L582
|
| 14 |
+
function requestGet(url, data, handler, errorHandler) {
|
| 15 |
+
if (typeof url !== 'string' || url.trim() === '') {
|
| 16 |
+
console.error("requestGet: Invalid or empty URL provided.");
|
| 17 |
+
if (typeof errorHandler === 'function') errorHandler(new Error("Invalid URL"));
|
| 18 |
+
return;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
const requestData = (data && typeof data === 'object') ? data : {};
|
| 22 |
+
const successHandler = typeof handler === 'function' ? handler : () => { };
|
| 23 |
+
const failureHandler = typeof errorHandler === 'function' ? errorHandler : (err) => { console.error("requestGet failed:", err); };
|
| 24 |
+
|
| 25 |
+
let args = '';
|
| 26 |
+
try {
|
| 27 |
+
args = Object.keys(requestData).map(function (k) {
|
| 28 |
+
const key = encodeURIComponent(k);
|
| 29 |
+
const value = encodeURIComponent(requestData[k]);
|
| 30 |
+
return `${key}=${value}`;
|
| 31 |
+
}).join('&');
|
| 32 |
+
} catch (e) {
|
| 33 |
+
console.error("requestGet: Failed to serialize data object.", e);
|
| 34 |
+
failureHandler(e);
|
| 35 |
+
return;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
const fullUrl = args ? `${url}?${args}` : url;
|
| 39 |
+
|
| 40 |
+
const xhr = new XMLHttpRequest();
|
| 41 |
+
xhr.open("GET", fullUrl, true);
|
| 42 |
+
|
| 43 |
+
xhr.onreadystatechange = function () {
|
| 44 |
+
if (xhr.readyState === 4) {
|
| 45 |
+
if (xhr.status === 200) {
|
| 46 |
+
try {
|
| 47 |
+
const responseJson = isJsonString(xhr.responseText) ? JSON.parse(xhr.responseText) : {};
|
| 48 |
+
successHandler(responseJson);
|
| 49 |
+
} catch (error) {
|
| 50 |
+
console.error("requestGet: Error parsing JSON response.", error);
|
| 51 |
+
failureHandler(error);
|
| 52 |
+
}
|
| 53 |
+
} else {
|
| 54 |
+
failureHandler(new Error(`Request failed with status ${xhr.status}: ${xhr.statusText}`));
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
};
|
| 58 |
+
|
| 59 |
+
xhr.onerror = function () {
|
| 60 |
+
failureHandler(new Error("Network error occurred."));
|
| 61 |
+
};
|
| 62 |
+
|
| 63 |
+
xhr.send();
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
// copied and adapted from https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/82a973c04367123ae98bd9abdf80d9eda9b910e2/javascript/ui.js#L348
|
| 67 |
+
function restart_ui() {
|
| 68 |
+
const overlay = document.createElement('div');
|
| 69 |
+
overlay.innerHTML = '<h1 style="font-family:monospace; margin-top:20%; color:lightgray; text-align:center;">Reloading...</h1>';
|
| 70 |
+
overlay.style.position = 'fixed';
|
| 71 |
+
overlay.style.top = '0';
|
| 72 |
+
overlay.style.left = '0';
|
| 73 |
+
overlay.style.width = '100%';
|
| 74 |
+
overlay.style.height = '100%';
|
| 75 |
+
overlay.style.backgroundColor = 'rgba(20, 20, 20, 0.9)';
|
| 76 |
+
overlay.style.zIndex = '10000';
|
| 77 |
+
document.body.appendChild(overlay);
|
| 78 |
+
|
| 79 |
+
const requestPing = function () {
|
| 80 |
+
requestGet(
|
| 81 |
+
window.location.href,
|
| 82 |
+
{},
|
| 83 |
+
function (data) {
|
| 84 |
+
window.location.reload();
|
| 85 |
+
},
|
| 86 |
+
function (error) {
|
| 87 |
+
console.log("Ping failed, retrying...");
|
| 88 |
+
setTimeout(requestPing, 500);
|
| 89 |
+
}
|
| 90 |
+
);
|
| 91 |
+
};
|
| 92 |
+
|
| 93 |
+
setTimeout(requestPing, 2000);
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
/**
|
| 97 |
+
* Moves all child elements from one or more source containers to their corresponding target containers.
|
| 98 |
+
* By convention, the target ID for a source 'my-source-id' is expected to be 'my-source-id_target'.
|
| 99 |
+
* After moving, the source containers are removed from the DOM by default.
|
| 100 |
+
*
|
| 101 |
+
* @param {string | string[]} source_ids - A single source element ID or an array of IDs.
|
| 102 |
+
* @param {object} [options] - Optional settings.
|
| 103 |
+
* @param {boolean} [options.remove_sources=true] - If true, removes the source elements after their content is moved.
|
| 104 |
+
*/
|
| 105 |
+
function reparent_flyout(source_ids, options = { remove_sources: true }) {
|
| 106 |
+
// Ensure the input is always an array to simplify the logic.
|
| 107 |
+
const sourceArray = Array.isArray(source_ids) ? source_ids : [source_ids];
|
| 108 |
+
|
| 109 |
+
// Iterate over each provided source ID.
|
| 110 |
+
sourceArray.forEach(source_id => {
|
| 111 |
+
// Find the source element.
|
| 112 |
+
const source = document.getElementById(source_id);
|
| 113 |
+
|
| 114 |
+
// If the source element doesn't exist, we can't do anything for this ID.
|
| 115 |
+
if (!source) {
|
| 116 |
+
console.warn(`WARNING: Source element with ID '${source_id}' not found. Skipping.`);
|
| 117 |
+
return; // 'return' inside a forEach acts like 'continue' in a for loop.
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
// Derive the target ID from the source ID
|
| 121 |
+
const target_id = `${source_id}_target`;
|
| 122 |
+
const target = document.getElementById(target_id);
|
| 123 |
+
|
| 124 |
+
// If the corresponding target element doesn't exist, log an error.
|
| 125 |
+
if (!target) {
|
| 126 |
+
console.error(`ERROR: Reparenting for '#${source_id}' failed. Corresponding target '#${target_id}' not found.`);
|
| 127 |
+
return;
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
// Move each child node from the source to the target.
|
| 131 |
+
// The `while (source.firstChild)` loop is efficient because `appendChild`
|
| 132 |
+
// automatically removes the child from its original parent.
|
| 133 |
+
while (source.firstChild) {
|
| 134 |
+
target.appendChild(source.firstChild);
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
// Remove the now-empty source container if the option is enabled.
|
| 138 |
+
if (options.remove_sources) {
|
| 139 |
+
source.remove();
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
console.log(`SUCCESS: Content from '#${source_id}' has been reparented to '#${target_id}'.`);
|
| 143 |
+
});
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
function position_flyout(anchorId, target_id) {
|
| 147 |
+
if (!anchorId) { return; }
|
| 148 |
+
|
| 149 |
+
const anchorElem = document.getElementById(anchorId);
|
| 150 |
+
const flyoutElem = document.getElementById(target_id);
|
| 151 |
+
|
| 152 |
+
if (anchorElem && flyoutElem) {
|
| 153 |
+
//console.log("JS: Positioning flyout relative to:", anchorId);
|
| 154 |
+
const anchorRect = anchorElem.getBoundingClientRect();
|
| 155 |
+
const flyoutWidth = flyoutElem.offsetWidth;
|
| 156 |
+
const flyoutHeight = flyoutElem.offsetHeight;
|
| 157 |
+
|
| 158 |
+
let topPosition = anchorRect.top + (anchorRect.height / 2) - (flyoutHeight / 2);
|
| 159 |
+
let leftPosition = anchorRect.left + (anchorRect.width / 2) - (flyoutWidth / 2);
|
| 160 |
+
|
| 161 |
+
const windowWidth = window.innerWidth;
|
| 162 |
+
const windowHeight = window.innerHeight;
|
| 163 |
+
if (leftPosition < 8) leftPosition = 8;
|
| 164 |
+
if (topPosition < 8) topPosition = 8;
|
| 165 |
+
if (leftPosition + flyoutWidth > windowWidth) leftPosition = windowWidth - flyoutWidth - 8;
|
| 166 |
+
if (topPosition + flyoutHeight > windowHeight) topPosition = windowHeight - flyoutHeight - 8;
|
| 167 |
+
|
| 168 |
+
flyoutElem.style.top = `${topPosition}px`;
|
| 169 |
+
flyoutElem.style.left = `${leftPosition}px`;
|
| 170 |
+
}
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
// This is the new main function called by Gradio's .then() event
|
| 174 |
+
function update_flyout_from_state(jsonData) {
|
| 175 |
+
//console.log("JS: update_flyout_from_state() called with data:", jsonData);
|
| 176 |
+
|
| 177 |
+
if (!jsonData) return;
|
| 178 |
+
|
| 179 |
+
const state = JSON.parse(jsonData);
|
| 180 |
+
const { isVisible, anchorId, targetId } = state;
|
| 181 |
+
const flyout = document.getElementById(targetId);
|
| 182 |
+
|
| 183 |
+
if (!flyout) {
|
| 184 |
+
console.error("ERROR: Cannot update UI. Flyout container not found.");
|
| 185 |
+
return;
|
| 186 |
+
}
|
| 187 |
+
//console.log("JS: Parsed state:", { isVisible, anchorId });
|
| 188 |
+
if (isVisible) {
|
| 189 |
+
flyout.style.display = 'flex';
|
| 190 |
+
position_flyout(anchorId, targetId);
|
| 191 |
+
} else {
|
| 192 |
+
flyout.style.display = 'none';
|
| 193 |
+
}
|
| 194 |
+
}
|
ui/style.css
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* ==========================================================================
|
| 2 |
+
1. GLOBAL & LAYOUT STYLES
|
| 3 |
+
========================================================================== */
|
| 4 |
+
|
| 5 |
+
body {
|
| 6 |
+
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
| 7 |
+
margin: 0;
|
| 8 |
+
padding: 0;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
/* Main application container styling */
|
| 12 |
+
.gradio-container {
|
| 13 |
+
border-radius: 15px;
|
| 14 |
+
padding: 10px 10px;
|
| 15 |
+
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
|
| 16 |
+
margin: 40px 350px;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
/* Text shadow for main headings for better readability */
|
| 20 |
+
.gradio-container h1 {
|
| 21 |
+
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
/* Utility class to make elements fill their container's width */
|
| 25 |
+
.fillable {
|
| 26 |
+
width: 100% !important;
|
| 27 |
+
max-width: unset !important;
|
| 28 |
+
}
|
| 29 |
+
.fillable .sidebar-parent {
|
| 30 |
+
padding-left: 10px !important;
|
| 31 |
+
padding-right: 10px !important;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
.credit-panel .outer-credits-wrapper .credits-panel-wrapper {
|
| 35 |
+
min-height: 80vh !important;
|
| 36 |
+
max-height: 700px;
|
| 37 |
+
}
|
| 38 |
+
.media-gallery-row {
|
| 39 |
+
min-height: 80vh !important;
|
| 40 |
+
max-height: 700px;
|
| 41 |
+
}
|
| 42 |
+
/* ==========================================================================
|
| 43 |
+
2. CUSTOM SCROLLBAR STYLES (GLOBAL & SIDEBAR)
|
| 44 |
+
========================================================================== */
|
| 45 |
+
|
| 46 |
+
/* --- For WebKit browsers (Chrome, Safari, Edge, etc.) --- */
|
| 47 |
+
.sidebar-content::-webkit-scrollbar,
|
| 48 |
+
body::-webkit-scrollbar {
|
| 49 |
+
width: 8px;
|
| 50 |
+
height: 8px;
|
| 51 |
+
background-color: transparent;
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
.sidebar-content::-webkit-scrollbar-track,
|
| 55 |
+
body::-webkit-scrollbar-track {
|
| 56 |
+
background: transparent;
|
| 57 |
+
border-radius: 10px;
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
.sidebar-content::-webkit-scrollbar-thumb,
|
| 61 |
+
body::-webkit-scrollbar-thumb {
|
| 62 |
+
background-color: rgba(136, 136, 136, 0.4);
|
| 63 |
+
border-radius: 10px;
|
| 64 |
+
border: 2px solid transparent;
|
| 65 |
+
background-clip: content-box;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
.sidebar-content::-webkit-scrollbar-thumb:hover,
|
| 69 |
+
body::-webkit-scrollbar-thumb:hover {
|
| 70 |
+
background-color: rgba(136, 136, 136, 0.7);
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
/* --- For Firefox --- */
|
| 74 |
+
.sidebar-content,
|
| 75 |
+
html {
|
| 76 |
+
scrollbar-width: thin;
|
| 77 |
+
scrollbar-color: rgba(136, 136, 136, 0.7) transparent;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
/* ==========================================================================
|
| 82 |
+
3. SIDEBAR STYLES
|
| 83 |
+
========================================================================== */
|
| 84 |
+
|
| 85 |
+
.sidebar {
|
| 86 |
+
border-radius: 10px;
|
| 87 |
+
padding: 10px;
|
| 88 |
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
| 89 |
+
}
|
| 90 |
+
/* .top-bar, .bottom-bar {
|
| 91 |
+
border-radius: 10px;
|
| 92 |
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
| 93 |
+
} */
|
| 94 |
+
/* Overrides for the content padding inside the sidebar */
|
| 95 |
+
.sidebar .sidebar-content {
|
| 96 |
+
padding-left: 10px !important;
|
| 97 |
+
padding-right: 10px !important;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
/* Centers text within Markdown blocks inside the sidebar */
|
| 101 |
+
.sidebar .sidebar-content .column .block div .prose {
|
| 102 |
+
text-align: center;
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
/* Stylish override for the sidebar toggle button */
|
| 106 |
+
.sidebar .toggle-button {
|
| 107 |
+
background: linear-gradient(90deg, #34d399, #10b981) !important;
|
| 108 |
+
border: none;
|
| 109 |
+
padding: 12px 18px;
|
| 110 |
+
text-transform: uppercase;
|
| 111 |
+
font-weight: bold;
|
| 112 |
+
letter-spacing: 1px;
|
| 113 |
+
border-radius: 5px;
|
| 114 |
+
position: absolute;
|
| 115 |
+
top: 50%;
|
| 116 |
+
right: -28px !important;
|
| 117 |
+
left: auto !important;
|
| 118 |
+
transform: unset !important;
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
.sidebar.right .toggle-button {
|
| 122 |
+
left: -28px !important;
|
| 123 |
+
right: auto !important;
|
| 124 |
+
transform: rotate(180deg) !important;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
.sidebar.open .chevron-left {
|
| 128 |
+
transform: rotate(-135deg);
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
.top-bar .toggle-top-button, .bottom-bar .toggle-bottom-button {
|
| 132 |
+
background: linear-gradient(90deg, #34d399, #10b981) !important;
|
| 133 |
+
border: none;
|
| 134 |
+
padding: 12px 18px;
|
| 135 |
+
text-transform: uppercase;
|
| 136 |
+
font-weight: bold;
|
| 137 |
+
letter-spacing: 1px;
|
| 138 |
+
border-radius: 5px;
|
| 139 |
+
cursor: pointer;
|
| 140 |
+
transition: transform 0.2s ease-in-out;
|
| 141 |
+
/* right: -20px !important; */
|
| 142 |
+
}
|
| 143 |
+
.toggle-button:hover {
|
| 144 |
+
transform: scale(1.05);
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
/* ==========================================================================
|
| 149 |
+
4. FLYOUT POPOVER STYLES
|
| 150 |
+
========================================================================== */
|
| 151 |
+
|
| 152 |
+
/* The context area from which the flyout is positioned */
|
| 153 |
+
.flyout-context-area {
|
| 154 |
+
position: relative !important;
|
| 155 |
+
overflow: visible !important; /* Allows flyout to render outside its parent */
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
/* The main flyout container (the popover itself) */
|
| 159 |
+
.flyout-container {
|
| 160 |
+
position: fixed !important;
|
| 161 |
+
z-index: 1000;
|
| 162 |
+
width: 350px !important;
|
| 163 |
+
background: var(--panel-background-fill, white);
|
| 164 |
+
border-radius: var(--radius-lg);
|
| 165 |
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
| 166 |
+
padding: var(--spacing-lg) !important;
|
| 167 |
+
/* Reset flex properties that might be inherited */
|
| 168 |
+
flex-grow: 0 !important;
|
| 169 |
+
min-width: unset !important;
|
| 170 |
+
align-self: center !important;
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
/* Allows tooltips inside a PropertySheet within a flyout to be visible */
|
| 174 |
+
.flyout-container .propertysheet-wrapper .content-wrapper .container {
|
| 175 |
+
overflow: visible !important;
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
/* The hidden container for the flyout's initial content */
|
| 179 |
+
.flyout-source-hidden {
|
| 180 |
+
display: none !important;
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
/* The close (X) button for the flyout */
|
| 184 |
+
.flyout-close-btn {
|
| 185 |
+
position: absolute;
|
| 186 |
+
top: 5px;
|
| 187 |
+
right: 8px;
|
| 188 |
+
z-index: 1001; /* Should be on top of flyout content */
|
| 189 |
+
background: transparent !important;
|
| 190 |
+
border: none !important;
|
| 191 |
+
box-shadow: none !important;
|
| 192 |
+
color: #ef0000 !important;
|
| 193 |
+
min-width: 24px !important;
|
| 194 |
+
width: 24px !important;
|
| 195 |
+
height: 24px !important;
|
| 196 |
+
padding: 0 !important;
|
| 197 |
+
font-size: 1.2em !important;
|
| 198 |
+
line-height: 1;
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
/* .flyout-close-btn button:hover {
|
| 202 |
+
color: var(--body-text-color) !important;
|
| 203 |
+
} */
|
| 204 |
+
.flyout-container .block .image-container .icon-button-wrapper {
|
| 205 |
+
right: 20px !important
|
| 206 |
+
}
|
| 207 |
+
.flyout-container .block .image-container > button {
|
| 208 |
+
border: none !important;
|
| 209 |
+
border-radius: 0 !important;
|
| 210 |
+
}
|
| 211 |
+
#flyout_sheet > div.content-wrapper > div > div > div > div {
|
| 212 |
+
color: var(--button-secondary-text-color);
|
| 213 |
+
}
|
| 214 |
+
/* ==========================================================================
|
| 215 |
+
5. COMPONENT-SPECIFIC STYLES & OVERRIDES
|
| 216 |
+
========================================================================== */
|
| 217 |
+
|
| 218 |
+
/* Custom styling for the cancel button */
|
| 219 |
+
#cancel-button { /* Corrected the typo from 'cancell' to 'cancel' */
|
| 220 |
+
background: linear-gradient(120deg, var(--neutral-500) 0%, var(--neutral-600) 60%, var(--neutral-700) 100%) !important;
|
| 221 |
+
}
|
| 222 |
+
.custom-dropdown .wrap-inner input {
|
| 223 |
+
padding-right: 22px;
|
| 224 |
+
}
|
| 225 |
+
/* --- Styles for creating dropdowns with integrated "ear" buttons --- */
|
| 226 |
+
|
| 227 |
+
/* Container that looks like a single input field */
|
| 228 |
+
.fake-input-container {
|
| 229 |
+
border: var(--input-border-width) solid var(--border-color-primary);
|
| 230 |
+
border-radius: var(--input-radius);
|
| 231 |
+
background: var(--block-background-fill) !important;
|
| 232 |
+
box-shadow: var(--input-shadow);
|
| 233 |
+
transition: box-shadow 0.2s, border-color 0.2s;
|
| 234 |
+
padding: 0 !important;
|
| 235 |
+
align-items: stretch !important;
|
| 236 |
+
gap: 0 !important;
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
.fake-input-container:focus-within {
|
| 240 |
+
box-shadow: var(--input-shadow-focus);
|
| 241 |
+
border-color: var(--input-border-color-focus);
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
/* Removes the default border from a Gradio dropdown when it's inside our fake container */
|
| 245 |
+
.no-border-dropdown .form {
|
| 246 |
+
border: none !important;
|
| 247 |
+
box-shadow: none !important;
|
| 248 |
+
background: transparent !important;
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
/* The settings (gear) button next to the dropdown */
|
| 252 |
+
.integrated-ear-btn {
|
| 253 |
+
align-self: center;
|
| 254 |
+
background: none !important;
|
| 255 |
+
border: none !important;
|
| 256 |
+
box-shadow: none !important;
|
| 257 |
+
color: var(--body-text-color-subdued) !important;
|
| 258 |
+
font-size: 1.1em;
|
| 259 |
+
min-width: 28px !important;
|
| 260 |
+
width: 28px !important;
|
| 261 |
+
height: 100% !important;
|
| 262 |
+
padding: 20px var(--spacing-sm) 0 0 !important;
|
| 263 |
+
transition: color 0.2s ease-in-out;
|
| 264 |
+
flex-grow: 0 !important;
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
+
.integrated-ear-btn:hover {
|
| 268 |
+
color: var(--body-text-color) !important;
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
.row-form-size .form {
|
| 272 |
+
flex-grow: 0 !important;
|
| 273 |
+
min-width: 50% !important;
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
.row-form-size-2 .form {
|
| 277 |
+
flex-grow: 0 !important;
|
| 278 |
+
min-width: 97% !important;
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
/* --- Misc Component Tweaks --- */
|
| 282 |
+
|
| 283 |
+
/* Removes the gap from a specific group of settings */
|
| 284 |
+
.group {
|
| 285 |
+
gap: unset !important;
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
/* Targets a styler inside the sampler group to reset form gap */
|
| 289 |
+
.group .styler {
|
| 290 |
+
--form-gap-width: 0px !important;
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
/* Example of how to center content in Gradio's example components */
|
| 294 |
+
#examples_container {
|
| 295 |
+
margin: auto;
|
| 296 |
+
width: 90%;
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
#examples_row {
|
| 300 |
+
justify-content: center;
|
| 301 |
+
}
|
ui/ui_config.py
ADDED
|
@@ -0,0 +1,1360 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2025 The DEVAIEXP Team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
from dataclasses import dataclass, field
|
| 16 |
+
from typing import List, Literal
|
| 17 |
+
|
| 18 |
+
import sup_toolbox.modules.FaithDiff
|
| 19 |
+
import sup_toolbox.modules.IPAdapter
|
| 20 |
+
import sup_toolbox.modules.MoDControlTile
|
| 21 |
+
import sup_toolbox.modules.SUPIR
|
| 22 |
+
from sup_toolbox.config import PAG_LAYERS
|
| 23 |
+
from sup_toolbox.enums import ImageSizeFixMode, SUPIRModel
|
| 24 |
+
from sup_toolbox.utils.system import find_dist_info_files, get_module_file
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
LICENSE_PATHS = {
|
| 28 |
+
"NOTICE": "./NOTICE",
|
| 29 |
+
"SUP-ToolBox License": "./LICENSE",
|
| 30 |
+
"SUPIR Component": get_module_file(sup_toolbox.modules.SUPIR, "LICENSE_SUPIR.md"),
|
| 31 |
+
"FaithDiff Component": get_module_file(sup_toolbox.modules.FaithDiff, "LICENSE_FAITHDIFF.md"),
|
| 32 |
+
"MoDControlTile Component": get_module_file(sup_toolbox.modules.MoDControlTile, "LICENSE_MIXTURE_OF_DIFFUSERS.md"),
|
| 33 |
+
"IPAdapter Component": get_module_file(sup_toolbox.modules.IPAdapter, "LICENSE_IPADAPTER.md"),
|
| 34 |
+
"Diffusers": find_dist_info_files("diffusers", "LICENSE"),
|
| 35 |
+
"SUP-ToolBox App Changelog": "./CHANGELOG.md",
|
| 36 |
+
"SUP-ToolBox CLI Changelog": get_module_file(sup_toolbox, "CHANGELOG.md"),
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
CREDIT_LIST = [
|
| 40 |
+
# Publisher/Developer Credit
|
| 41 |
+
{"title": "Developed By", "name": "DEVAIEXP"},
|
| 42 |
+
# Core Development Section
|
| 43 |
+
{"section_title": "Core Development"},
|
| 44 |
+
{"title": "Principal Developer & Solution Architect", "name": "Eliseu Silva"},
|
| 45 |
+
# SUPIR
|
| 46 |
+
{"section_title": "SUPIR Restoration Pipeline"},
|
| 47 |
+
{"title": "Original work by", "name": "SupPixel Pty Ltd"},
|
| 48 |
+
{"title": "Adapted for Diffusers by", "name": "DEVAIEXP"},
|
| 49 |
+
{"section_title": "SUPIR - Original Authors"},
|
| 50 |
+
{"title": "Lead Researcher", "name": "Dr. Jinjin Gu"},
|
| 51 |
+
{"title": "Contributing Author", "name": "Fanghua Yu"},
|
| 52 |
+
{"title": "Contributing Author", "name": "Zheyuan Li"},
|
| 53 |
+
{"title": "Contributing Author", "name": "Jinfan Hu"},
|
| 54 |
+
{"title": "Contributing Author", "name": "Xiangtao Kong"},
|
| 55 |
+
{"title": "Contributing Author", "name": "Xintao Wang"},
|
| 56 |
+
{"title": "Contributing Author", "name": "Jingwen He"},
|
| 57 |
+
{"title": "Contributing Author", "name": "Yu Qiao"},
|
| 58 |
+
{"title": "Contributing Author", "name": "Chao Dong"},
|
| 59 |
+
# FaithDiff
|
| 60 |
+
{"section_title": "FaithDiff Super-Resolution Pipeline"},
|
| 61 |
+
{"title": "Original work by", "name": "Junyang Chen"},
|
| 62 |
+
{"title": "Adapted and improved by", "name": "DEVAIEXP"},
|
| 63 |
+
{"section_title": "FaithDiff - Original Authors"},
|
| 64 |
+
{"title": "Contributing Author", "name": "Junyang Chen"},
|
| 65 |
+
{"title": "Contributing Author", "name": "Jinshan Pan"},
|
| 66 |
+
{"title": "Contributing Author", "name": "Jiangxin Dong"},
|
| 67 |
+
# MoD ControlNet Tile
|
| 68 |
+
{"section_title": "Mixture of Diffusers for ControlNet Tile Pipeline"},
|
| 69 |
+
{"title": "Original Pipeline Concept by", "name": "Álvaro Barbero Jiménez"},
|
| 70 |
+
{"title": "Adapted and improved for ControlNet Union by", "name": "DEVAIEXP"},
|
| 71 |
+
{"title": "ControlNet Union Model", "name": "Trained by xinsir"},
|
| 72 |
+
# Key Models & Components Section
|
| 73 |
+
{"section_title": "Key Models & Components"},
|
| 74 |
+
{"title": "IP-Adapter Model", "name": "TencentARC"},
|
| 75 |
+
# Foundational Frameworks Section
|
| 76 |
+
# Gradio
|
| 77 |
+
{"section_title": "Gradio Framework"},
|
| 78 |
+
{"section_title": "Gradio - Original Development Team"},
|
| 79 |
+
{"title": "Core Contributor", "name": "Abubakar Abid"},
|
| 80 |
+
{"title": "Core Contributor", "name": "Ali Abdalla"},
|
| 81 |
+
{"title": "Core Contributor", "name": "Ali Abid"},
|
| 82 |
+
{"title": "Core Contributor", "name": "Dawood Khan"},
|
| 83 |
+
{"title": "Core Contributor", "name": "Abdulrahman Alfozan"},
|
| 84 |
+
{"title": "Core Contributor", "name": "James Zou"},
|
| 85 |
+
# Diffusers
|
| 86 |
+
{"section_title": "Diffusers Framework"},
|
| 87 |
+
{"section_title": "Diffusers - Original Development Team"},
|
| 88 |
+
{"title": "Core Contributor", "name": "Patrick von Platen"},
|
| 89 |
+
{"title": "Core Contributor", "name": "Suraj Patil"},
|
| 90 |
+
{"title": "Core Contributor", "name": "Anton Lozhkov"},
|
| 91 |
+
{"title": "Core Contributor", "name": "Pedro Cuenca"},
|
| 92 |
+
{"title": "Core Contributor", "name": "Nathan Lambert"},
|
| 93 |
+
{"title": "Core Contributor", "name": "Kashif Rasul"},
|
| 94 |
+
{"title": "Core Contributor", "name": "Mishig Davaadorj"},
|
| 95 |
+
{"title": "Core Contributor", "name": "Dhruv Nair"},
|
| 96 |
+
{"title": "Core Contributor", "name": "Sayak Paul"},
|
| 97 |
+
{"title": "Core Contributor", "name": "William Berman"},
|
| 98 |
+
{"title": "Core Contributor", "name": "Yiyi Xu"},
|
| 99 |
+
{"title": "Core Contributor", "name": "Steven Liu"},
|
| 100 |
+
{"title": "Core Contributor", "name": "Thomas Wolf"},
|
| 101 |
+
]
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
# Dataclass for Application Settings
|
| 105 |
+
@dataclass
|
| 106 |
+
class AppSettings:
|
| 107 |
+
"""Dataclass to hold all general application settings, replacing the old dictionary."""
|
| 108 |
+
|
| 109 |
+
# General
|
| 110 |
+
# civitai_token: str = field(default="", metadata={"component": "password", "label": "CivitAI API Token", "help": "CivitAI API token for downloading models."})
|
| 111 |
+
save_image_format: Literal[".png", ".jpg", ".webp"] = field(
|
| 112 |
+
default=".png",
|
| 113 |
+
metadata={
|
| 114 |
+
"component": "dropdown",
|
| 115 |
+
"label": "Save Image Format",
|
| 116 |
+
"help": "Default format for saving generated images.",
|
| 117 |
+
},
|
| 118 |
+
)
|
| 119 |
+
latest_preset: str = field(
|
| 120 |
+
default="Default",
|
| 121 |
+
metadata={"label": "Latest Preset", "help": "The most recently used preset."},
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
# Paths
|
| 125 |
+
cache_dir: str = field(
|
| 126 |
+
default="models",
|
| 127 |
+
metadata={
|
| 128 |
+
"label": "Cache Path",
|
| 129 |
+
"help": "Path to a directory to download and cache pre-trained model weights.",
|
| 130 |
+
},
|
| 131 |
+
)
|
| 132 |
+
checkpoints_dir: str = field(
|
| 133 |
+
default="models/Checkpoints",
|
| 134 |
+
metadata={
|
| 135 |
+
"label": "Checkpoints Path",
|
| 136 |
+
"help": "Directory path for local '.safetensors' model weights.",
|
| 137 |
+
},
|
| 138 |
+
)
|
| 139 |
+
vae_dir: str = field(
|
| 140 |
+
default="models/VAE",
|
| 141 |
+
metadata={
|
| 142 |
+
"label": "VAE Models Path",
|
| 143 |
+
"help": "Directory path for local VAE '.safetensors' model weights.",
|
| 144 |
+
},
|
| 145 |
+
)
|
| 146 |
+
output_dir: str = field(
|
| 147 |
+
default="./outputs",
|
| 148 |
+
metadata={
|
| 149 |
+
"label": "Output Path",
|
| 150 |
+
"help": "Directory where generated images will be saved.",
|
| 151 |
+
},
|
| 152 |
+
)
|
| 153 |
+
save_image_on_upscaling_passes: bool = field(
|
| 154 |
+
default=False,
|
| 155 |
+
metadata={
|
| 156 |
+
"label": "Save image in upscaling passes",
|
| 157 |
+
"help": "Saves intermediate images between image upscaling passes",
|
| 158 |
+
},
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
# Hardware & Optimizations
|
| 162 |
+
weight_dtype: Literal["Float16", "Bfloat16", "Float32"] = field(
|
| 163 |
+
default="Float16",
|
| 164 |
+
metadata={
|
| 165 |
+
"component": "dropdown",
|
| 166 |
+
"label": "Weight Precision",
|
| 167 |
+
"help": "Precision for loading model weights (e.g., UNet, ControlNet).",
|
| 168 |
+
},
|
| 169 |
+
)
|
| 170 |
+
vae_weight_dtype: Literal["Float16", "Float32"] = field(
|
| 171 |
+
default="Float16",
|
| 172 |
+
metadata={
|
| 173 |
+
"component": "dropdown",
|
| 174 |
+
"label": "VAE Precision",
|
| 175 |
+
"help": "Precision for loading the VAE weights. FP16 is often faster.",
|
| 176 |
+
},
|
| 177 |
+
)
|
| 178 |
+
device: Literal["cpu", "cuda", "mps"] = field(
|
| 179 |
+
default="cuda",
|
| 180 |
+
metadata={
|
| 181 |
+
"component": "dropdown",
|
| 182 |
+
"label": "Computation Device",
|
| 183 |
+
"help": "The primary device (GPU/CPU) for running the pipelines.",
|
| 184 |
+
},
|
| 185 |
+
)
|
| 186 |
+
generator_device: Literal["cpu", "cuda", "mps"] = field(
|
| 187 |
+
default="cpu",
|
| 188 |
+
metadata={
|
| 189 |
+
"component": "dropdown",
|
| 190 |
+
"label": "Generator Device",
|
| 191 |
+
"help": "The primary device (GPU/CPU) for generator seeds.",
|
| 192 |
+
},
|
| 193 |
+
)
|
| 194 |
+
enable_cpu_offload: bool = field(
|
| 195 |
+
default=True,
|
| 196 |
+
metadata={
|
| 197 |
+
"interactive_if": {"field": "device", "neq": "cpu"},
|
| 198 |
+
"label": "Enable CPU Offload",
|
| 199 |
+
"help": "Saves VRAM by keeping models on the CPU and moving them to the GPU only when needed during inference.",
|
| 200 |
+
},
|
| 201 |
+
)
|
| 202 |
+
enable_vae_tiling: bool = field(
|
| 203 |
+
default=True,
|
| 204 |
+
metadata={
|
| 205 |
+
"label": "Enable VAE Tiling",
|
| 206 |
+
"help": "Processes the VAE in tiles to decode large images with less VRAM.",
|
| 207 |
+
},
|
| 208 |
+
)
|
| 209 |
+
enable_vae_slicing: bool = field(
|
| 210 |
+
default=False,
|
| 211 |
+
metadata={
|
| 212 |
+
"label": "Enable VAE Slicing",
|
| 213 |
+
"help": "Processes image batches one slice at a time through the VAE to save VRAM.",
|
| 214 |
+
},
|
| 215 |
+
)
|
| 216 |
+
memory_attention: Literal["xformers", "sdp"] = field(
|
| 217 |
+
default="xformers",
|
| 218 |
+
metadata={
|
| 219 |
+
"component": "dropdown",
|
| 220 |
+
"label": "Memory Attention",
|
| 221 |
+
"help": "Optimized attention mechanism to save VRAM and increase speed (xformers recommended).",
|
| 222 |
+
},
|
| 223 |
+
)
|
| 224 |
+
quantization_method: Literal["None", "Quanto Library", "Layerwise & Bnb"] = field(
|
| 225 |
+
default="Layerwise & Bnb",
|
| 226 |
+
metadata={
|
| 227 |
+
"component": "radio",
|
| 228 |
+
"label": "Quantization Method",
|
| 229 |
+
"help": "Quantization mechanism to save VRAM and increase speed.",
|
| 230 |
+
},
|
| 231 |
+
)
|
| 232 |
+
quantization_mode: Literal["FP8", "NF4"] = field(
|
| 233 |
+
default="FP8",
|
| 234 |
+
metadata={
|
| 235 |
+
"interactive_if": {"field": "quantization_method", "neq": "None"},
|
| 236 |
+
"component": "radio",
|
| 237 |
+
"label": "Quantization Mode",
|
| 238 |
+
"help": "Quantization mechanism to save VRAM and increase speed.",
|
| 239 |
+
},
|
| 240 |
+
)
|
| 241 |
+
allow_cuda_tf32: bool = field(
|
| 242 |
+
default=False,
|
| 243 |
+
metadata={
|
| 244 |
+
"label": "Enable cuda TF32",
|
| 245 |
+
"help": "Enable TensorFloat-32 tensor cores to be used in matrix multiplications on Ampere or newer GPUs. Enable it only if your card is Ampere+ and weight precision type is Float32",
|
| 246 |
+
},
|
| 247 |
+
)
|
| 248 |
+
allow_cudnn_tf32: bool = field(
|
| 249 |
+
default=False,
|
| 250 |
+
metadata={
|
| 251 |
+
"label": "Enable cudnn TF32",
|
| 252 |
+
"help": "Enable TensorFloat-32 tensor cores to be used in cuDNN convolutions on Ampere or newer GPU. Enable it only if your card is Ampere+ and weight precision type is Float32",
|
| 253 |
+
},
|
| 254 |
+
)
|
| 255 |
+
disable_mmap: bool = field(
|
| 256 |
+
default=False,
|
| 257 |
+
metadata={
|
| 258 |
+
"label": "Disable mmap",
|
| 259 |
+
"help": "Disable memmapping for loading model files. Enable this if you encounter issues loading models on shared drives or network locations.",
|
| 260 |
+
},
|
| 261 |
+
)
|
| 262 |
+
always_offload_models: bool = field(
|
| 263 |
+
default=False,
|
| 264 |
+
metadata={
|
| 265 |
+
"label": "Always Offload Models",
|
| 266 |
+
"help": "Offload models to CPU immediately after finishing inference to free up GPU memory. May slow down performance switching between pipelines.",
|
| 267 |
+
},
|
| 268 |
+
)
|
| 269 |
+
run_vae_on_cpu: bool = field(
|
| 270 |
+
default=False,
|
| 271 |
+
metadata={
|
| 272 |
+
"label": "Run VAE on CPU",
|
| 273 |
+
"help": "Run the VAE on CPU to save GPU memory. This may slow down performance.",
|
| 274 |
+
},
|
| 275 |
+
)
|
| 276 |
+
enable_llava_quantization: bool = field(
|
| 277 |
+
default=True,
|
| 278 |
+
metadata={
|
| 279 |
+
"label": "Enable LLaVA Quantization",
|
| 280 |
+
"help": "Enable quantization for LLaVA models to save VRAM.",
|
| 281 |
+
},
|
| 282 |
+
)
|
| 283 |
+
llava_quantization_mode: Literal["INT4", "INT8"] = field(
|
| 284 |
+
default="INT4",
|
| 285 |
+
metadata={
|
| 286 |
+
"interactive_if": {"field": "enable_llava_quantization", "value": True},
|
| 287 |
+
"component": "radio",
|
| 288 |
+
"label": "LLaVA Quantization Mode",
|
| 289 |
+
"help": "Quantization mode used for LLaVA model.",
|
| 290 |
+
},
|
| 291 |
+
)
|
| 292 |
+
llava_offload_model: bool = field(
|
| 293 |
+
default=False,
|
| 294 |
+
metadata={
|
| 295 |
+
"label": "LLaVA Offload Model",
|
| 296 |
+
"help": "Offload the LLaVA model to CPU to save GPU memory. It may slow down performance.",
|
| 297 |
+
},
|
| 298 |
+
)
|
| 299 |
+
llava_weight_dtype: Literal["Float16", "Bfloat16", "Float32"] = field(
|
| 300 |
+
default="Float16",
|
| 301 |
+
metadata={
|
| 302 |
+
"component": "dropdown",
|
| 303 |
+
"label": "LLaVA Weight Precision",
|
| 304 |
+
"help": "Precision for loading LLaVA model weights.",
|
| 305 |
+
},
|
| 306 |
+
)
|
| 307 |
+
llava_question_prompt: str = field(
|
| 308 |
+
default="Describe what you see in this image and put it in prompt format limited to 77 tokens:",
|
| 309 |
+
metadata={
|
| 310 |
+
"label": "LLaVA Question Prompt",
|
| 311 |
+
"help": "Prompt used to query LLaVA for image descriptions.",
|
| 312 |
+
},
|
| 313 |
+
)
|
| 314 |
+
|
| 315 |
+
|
| 316 |
+
# UI-Specific Dataclass for SUPIR Injection
|
| 317 |
+
@dataclass
|
| 318 |
+
class InjectionScaleConfig:
|
| 319 |
+
"""Configuration for a single dynamic injection scale."""
|
| 320 |
+
|
| 321 |
+
scale_end: float = field(
|
| 322 |
+
default=1.0,
|
| 323 |
+
metadata={
|
| 324 |
+
"interactive_if": {"field": "enable_custom_scale", "value": True},
|
| 325 |
+
"component": "slider",
|
| 326 |
+
"minimum": 0.0,
|
| 327 |
+
"maximum": 2.0,
|
| 328 |
+
"step": 0.1,
|
| 329 |
+
"label": "ControlNet Scale",
|
| 330 |
+
"help": "The weight of the ControlNet guidance.",
|
| 331 |
+
},
|
| 332 |
+
)
|
| 333 |
+
linear: bool = field(
|
| 334 |
+
default=False,
|
| 335 |
+
metadata={
|
| 336 |
+
"interactive_if": {"field": "enable_custom_scale", "value": True},
|
| 337 |
+
"label": "Use Linear Control",
|
| 338 |
+
"help": "Linearly increase Control scale during sampling.",
|
| 339 |
+
},
|
| 340 |
+
)
|
| 341 |
+
scale_start: float = field(
|
| 342 |
+
default=1.0,
|
| 343 |
+
metadata={
|
| 344 |
+
"interactive_if": {"field": "linear", "value": True},
|
| 345 |
+
"component": "slider",
|
| 346 |
+
"minimum": 0.0,
|
| 347 |
+
"maximum": 20.0,
|
| 348 |
+
"step": 0.1,
|
| 349 |
+
"label": "ControlNet Scale Start",
|
| 350 |
+
"help": "The starting value for linear ControlNet guidance.",
|
| 351 |
+
},
|
| 352 |
+
)
|
| 353 |
+
reverse: bool = field(
|
| 354 |
+
default=False,
|
| 355 |
+
metadata={
|
| 356 |
+
"interactive_if": {"field": "linear", "value": True},
|
| 357 |
+
"label": "Reverse Linear Control",
|
| 358 |
+
"help": "Linearly decrease ControlNet scale during sampling.",
|
| 359 |
+
},
|
| 360 |
+
)
|
| 361 |
+
|
| 362 |
+
|
| 363 |
+
@dataclass
|
| 364 |
+
class SUPIRInjectionConfig(InjectionScaleConfig):
|
| 365 |
+
sft_active: bool = field(
|
| 366 |
+
default=True,
|
| 367 |
+
metadata={
|
| 368 |
+
"label": "Enable injection",
|
| 369 |
+
"help": "Enable or disable SFT Injection on this stage.",
|
| 370 |
+
},
|
| 371 |
+
)
|
| 372 |
+
enable_custom_scale: bool = field(
|
| 373 |
+
default=False,
|
| 374 |
+
metadata={
|
| 375 |
+
"interactive_if": {"field": "sft_active", "value": True},
|
| 376 |
+
"label": "Enable custom scale",
|
| 377 |
+
"help": "Use customizable controlnet scale at this stage.",
|
| 378 |
+
},
|
| 379 |
+
)
|
| 380 |
+
|
| 381 |
+
|
| 382 |
+
# Reusable Building Blocks for PropertySheet
|
| 383 |
+
@dataclass
|
| 384 |
+
class GenerationSettings:
|
| 385 |
+
"""Defines the core generation parameters, reused in each pipeline config."""
|
| 386 |
+
|
| 387 |
+
seed: int = field(
|
| 388 |
+
default=-1,
|
| 389 |
+
metadata={
|
| 390 |
+
"component": "number_integer",
|
| 391 |
+
"label": "Seed",
|
| 392 |
+
"help": "The random seed for generation. -1 means a random seed.",
|
| 393 |
+
},
|
| 394 |
+
)
|
| 395 |
+
randomize_seed: bool = field(default=True, metadata={"label": "Randomize Seed"})
|
| 396 |
+
num_steps: int = field(
|
| 397 |
+
default=30,
|
| 398 |
+
metadata={
|
| 399 |
+
"component": "slider",
|
| 400 |
+
"minimum": 1,
|
| 401 |
+
"maximum": 150,
|
| 402 |
+
"step": 1,
|
| 403 |
+
"label": "Inference Steps",
|
| 404 |
+
"help": "Number of denoising steps. More steps can improve quality but take longer.",
|
| 405 |
+
},
|
| 406 |
+
)
|
| 407 |
+
guidance_scale: float = field(
|
| 408 |
+
default=7.0,
|
| 409 |
+
metadata={
|
| 410 |
+
"component": "slider",
|
| 411 |
+
"minimum": 0.0,
|
| 412 |
+
"maximum": 20.0,
|
| 413 |
+
"step": 0.1,
|
| 414 |
+
"label": "Guidance Scale (CFG)",
|
| 415 |
+
"help": "How strongly the prompt is adhered to.",
|
| 416 |
+
},
|
| 417 |
+
)
|
| 418 |
+
guidance_rescale: float = field(
|
| 419 |
+
default=0.5,
|
| 420 |
+
metadata={
|
| 421 |
+
"component": "slider",
|
| 422 |
+
"minimum": 0.0,
|
| 423 |
+
"maximum": 1.0,
|
| 424 |
+
"step": 0.01,
|
| 425 |
+
"label": "Guidance Rescale",
|
| 426 |
+
"help": "Guidance rescale factor (phi).",
|
| 427 |
+
},
|
| 428 |
+
)
|
| 429 |
+
num_images: int = field(
|
| 430 |
+
default=1,
|
| 431 |
+
metadata={
|
| 432 |
+
"component": "slider",
|
| 433 |
+
"minimum": 1,
|
| 434 |
+
"maximum": 16,
|
| 435 |
+
"step": 1,
|
| 436 |
+
"label": "Number of Images",
|
| 437 |
+
"help": "Number of output images.",
|
| 438 |
+
},
|
| 439 |
+
)
|
| 440 |
+
image_size_fix_mode: Literal[tuple(member.value for member in ImageSizeFixMode)] = field(
|
| 441 |
+
default=ImageSizeFixMode.ProgressiveResize.value,
|
| 442 |
+
metadata={
|
| 443 |
+
"component": "dropdown",
|
| 444 |
+
"label": "Image Size Fix Mode",
|
| 445 |
+
"help": "Method to handle aspect ratio mismatches during processing.",
|
| 446 |
+
},
|
| 447 |
+
)
|
| 448 |
+
tile_size: Literal[1024, 1280] = field(
|
| 449 |
+
default=1280,
|
| 450 |
+
metadata={
|
| 451 |
+
"component": "dropdown",
|
| 452 |
+
"label": "Tile Size",
|
| 453 |
+
"help": "The size (in pixels) of the square tiles to use when latent tiling is enabled.",
|
| 454 |
+
},
|
| 455 |
+
)
|
| 456 |
+
upscaling_mode: Literal["Progressive", "Direct"] = field(
|
| 457 |
+
default="Direct",
|
| 458 |
+
metadata={
|
| 459 |
+
"component": "radio",
|
| 460 |
+
"label": "Upscaling Mode",
|
| 461 |
+
"help": "Choose 'Progressive' for maximum final quality, especially at high scaling factors. Choose 'Direct' for faster processing and previews.",
|
| 462 |
+
},
|
| 463 |
+
)
|
| 464 |
+
upscale_factor: Literal[
|
| 465 |
+
"2x",
|
| 466 |
+
"3x",
|
| 467 |
+
"4x",
|
| 468 |
+
"5x",
|
| 469 |
+
"6x",
|
| 470 |
+
"7x",
|
| 471 |
+
"8x",
|
| 472 |
+
"9x",
|
| 473 |
+
"10x",
|
| 474 |
+
"11x",
|
| 475 |
+
"12x",
|
| 476 |
+
"13x",
|
| 477 |
+
"14x",
|
| 478 |
+
"15x",
|
| 479 |
+
"16x",
|
| 480 |
+
] = field(
|
| 481 |
+
default="2x",
|
| 482 |
+
metadata={
|
| 483 |
+
"component": "radio",
|
| 484 |
+
"label": "Upscale Factor",
|
| 485 |
+
"help": "Resolution upscale x1 to x10",
|
| 486 |
+
},
|
| 487 |
+
)
|
| 488 |
+
cfg_decay_rate: float = field(
|
| 489 |
+
default=0.5,
|
| 490 |
+
metadata={
|
| 491 |
+
"interactive_if": {"field": "upscaling_mode", "value": "Progressive"},
|
| 492 |
+
"component": "slider",
|
| 493 |
+
"label": "CFG Decay Rate",
|
| 494 |
+
"minimum": 0.0,
|
| 495 |
+
"maximum": 0.9,
|
| 496 |
+
"step": 0.05,
|
| 497 |
+
"help": (
|
| 498 |
+
"The percentage to reduce the Guidance Scale (CFG) at each progressive "
|
| 499 |
+
"upscaling pass. A value of 0.5 means the CFG is halved at each step. "
|
| 500 |
+
"Set to 0.0 to keep the CFG constant."
|
| 501 |
+
),
|
| 502 |
+
},
|
| 503 |
+
)
|
| 504 |
+
strength_decay_rate: float = field(
|
| 505 |
+
default=0.5,
|
| 506 |
+
metadata={
|
| 507 |
+
"interactive_if": {"field": "upscaling_mode", "value": "Progressive"},
|
| 508 |
+
"component": "slider",
|
| 509 |
+
"label": "Strength Decay Rate",
|
| 510 |
+
"minimum": 0.0,
|
| 511 |
+
"maximum": 0.9,
|
| 512 |
+
"step": 0.05,
|
| 513 |
+
"help": (
|
| 514 |
+
"The percentage to reduce the Denoising Strength at each progressive "
|
| 515 |
+
"upscaling pass. A value of 0.5 means the strength is halved at each step. "
|
| 516 |
+
"Set to 0.0 to keep the strength constant."
|
| 517 |
+
),
|
| 518 |
+
},
|
| 519 |
+
)
|
| 520 |
+
|
| 521 |
+
|
| 522 |
+
@dataclass
|
| 523 |
+
class CFGSettings:
|
| 524 |
+
"""Defines advanced settings for Classifier-Free Guidance."""
|
| 525 |
+
|
| 526 |
+
use_linear_CFG: bool = field(
|
| 527 |
+
default=False,
|
| 528 |
+
metadata={
|
| 529 |
+
"label": "Use Linear CFG",
|
| 530 |
+
"help": "Linearly increase CFG scale during sampling.",
|
| 531 |
+
},
|
| 532 |
+
)
|
| 533 |
+
reverse_linear_CFG: bool = field(
|
| 534 |
+
default=False,
|
| 535 |
+
metadata={
|
| 536 |
+
"interactive_if": {"field": "use_linear_CFG", "value": True},
|
| 537 |
+
"label": "Reverse Linear CFG",
|
| 538 |
+
"help": "Linearly decrease CFG scale during sampling.",
|
| 539 |
+
},
|
| 540 |
+
)
|
| 541 |
+
guidance_scale_start: float = field(
|
| 542 |
+
default=1.0,
|
| 543 |
+
metadata={
|
| 544 |
+
"interactive_if": {"field": "use_linear_CFG", "value": True},
|
| 545 |
+
"component": "slider",
|
| 546 |
+
"minimum": 0.0,
|
| 547 |
+
"maximum": 20.0,
|
| 548 |
+
"step": 0.1,
|
| 549 |
+
"label": "Guidance Scale Start",
|
| 550 |
+
"help": "The starting value for linear CFG scaling.",
|
| 551 |
+
},
|
| 552 |
+
)
|
| 553 |
+
|
| 554 |
+
|
| 555 |
+
@dataclass
|
| 556 |
+
class ControlNetScaleSettings:
|
| 557 |
+
"""Defines settings for ControlNet conditioning scale."""
|
| 558 |
+
|
| 559 |
+
controlnet_conditioning_scale: float = field(
|
| 560 |
+
default=1.0,
|
| 561 |
+
metadata={
|
| 562 |
+
"component": "slider",
|
| 563 |
+
"minimum": 0.0,
|
| 564 |
+
"maximum": 2.0,
|
| 565 |
+
"step": 0.05,
|
| 566 |
+
"label": "ControlNet Scale",
|
| 567 |
+
"help": "The weight of the ControlNet guidance.",
|
| 568 |
+
},
|
| 569 |
+
)
|
| 570 |
+
use_linear_control_scale: bool = field(
|
| 571 |
+
default=False,
|
| 572 |
+
metadata={
|
| 573 |
+
"label": "Use Linear Control Scale",
|
| 574 |
+
"help": "Linearly increase ControlNet scale during sampling.",
|
| 575 |
+
},
|
| 576 |
+
)
|
| 577 |
+
reverse_linear_control_scale: bool = field(
|
| 578 |
+
default=False,
|
| 579 |
+
metadata={
|
| 580 |
+
"interactive_if": {"field": "use_linear_control_scale", "value": True},
|
| 581 |
+
"label": "Reverse Linear Control Scale",
|
| 582 |
+
"help": "Linearly decrease ControlNet scale during sampling.",
|
| 583 |
+
},
|
| 584 |
+
)
|
| 585 |
+
control_scale_start: float = field(
|
| 586 |
+
default=0.7,
|
| 587 |
+
metadata={
|
| 588 |
+
"interactive_if": {"field": "use_linear_control_scale", "value": True},
|
| 589 |
+
"component": "slider",
|
| 590 |
+
"minimum": 0.0,
|
| 591 |
+
"maximum": 1.0,
|
| 592 |
+
"step": 0.05,
|
| 593 |
+
"label": "Control Scale Start",
|
| 594 |
+
"help": "The starting value for linear ControlNet scaling.",
|
| 595 |
+
},
|
| 596 |
+
)
|
| 597 |
+
|
| 598 |
+
|
| 599 |
+
@dataclass
|
| 600 |
+
class PAGSettings:
|
| 601 |
+
"""Defines settings for Perturbed Attention Guidance."""
|
| 602 |
+
|
| 603 |
+
enable_PAG: bool = field(default=False, metadata={"label": "Enable PAG"})
|
| 604 |
+
pag_scale: float = field(
|
| 605 |
+
default=3.0,
|
| 606 |
+
metadata={
|
| 607 |
+
"component": "slider",
|
| 608 |
+
"minimum": 0.0,
|
| 609 |
+
"maximum": 10.0,
|
| 610 |
+
"step": 0.1,
|
| 611 |
+
"label": "PAG Scale",
|
| 612 |
+
"interactive_if": {"field": "enable_PAG", "value": True},
|
| 613 |
+
"help": "The scale factor for the perturbed attention guidance.",
|
| 614 |
+
},
|
| 615 |
+
)
|
| 616 |
+
use_linear_PAG: bool = field(
|
| 617 |
+
default=False,
|
| 618 |
+
metadata={
|
| 619 |
+
"label": "Use Linear PAG",
|
| 620 |
+
"interactive_if": {"field": "enable_PAG", "value": True},
|
| 621 |
+
"help": "Linearly increase PAG scale during sampling.",
|
| 622 |
+
},
|
| 623 |
+
)
|
| 624 |
+
reverse_linear_PAG: bool = field(
|
| 625 |
+
default=False,
|
| 626 |
+
metadata={
|
| 627 |
+
"label": "Reverse Linear PAG",
|
| 628 |
+
"interactive_if": {"field": "enable_PAG", "value": True},
|
| 629 |
+
"help": "Linearly decrease PAG scale during sampling.",
|
| 630 |
+
},
|
| 631 |
+
)
|
| 632 |
+
pag_scale_start: float = field(
|
| 633 |
+
default=1.0,
|
| 634 |
+
metadata={
|
| 635 |
+
"component": "slider",
|
| 636 |
+
"minimum": 0.0,
|
| 637 |
+
"maximum": 10.0,
|
| 638 |
+
"step": 0.1,
|
| 639 |
+
"label": "PAG Scale Start",
|
| 640 |
+
"interactive_if": {"field": "enable_PAG", "value": True},
|
| 641 |
+
"help": "The starting value for linear PAG scaling.",
|
| 642 |
+
},
|
| 643 |
+
)
|
| 644 |
+
pag_layers: List[str] = field(
|
| 645 |
+
default_factory=lambda: ["mid"],
|
| 646 |
+
metadata={
|
| 647 |
+
"component": "multiselect_checkbox",
|
| 648 |
+
"choices": list(PAG_LAYERS.keys()),
|
| 649 |
+
"label": "PAG Layers",
|
| 650 |
+
"interactive_if": {"field": "enable_PAG", "value": True},
|
| 651 |
+
"help": "Select the UNet layers where Perturbed Attention Guidance should be applied.",
|
| 652 |
+
},
|
| 653 |
+
)
|
| 654 |
+
|
| 655 |
+
|
| 656 |
+
@dataclass
|
| 657 |
+
class PostprocessingSettings:
|
| 658 |
+
color_fix_mode: Literal["None", "Adain", "Wavelet"] = field(
|
| 659 |
+
default="None",
|
| 660 |
+
metadata={
|
| 661 |
+
"component": "radio",
|
| 662 |
+
"label": "Color Fix Mode",
|
| 663 |
+
"help": "Applies the color profile of the input image to the generated image using one of the chosen algorithms.",
|
| 664 |
+
},
|
| 665 |
+
)
|
| 666 |
+
|
| 667 |
+
|
| 668 |
+
# Main Configuration Dataclasses for Each Pipeline
|
| 669 |
+
@dataclass
|
| 670 |
+
class SUPIR_Config:
|
| 671 |
+
"""Complete configuration for a SUPIR pipeline run."""
|
| 672 |
+
|
| 673 |
+
apply_prompt_2: bool = field(
|
| 674 |
+
default=True,
|
| 675 |
+
metadata={
|
| 676 |
+
"label": "Apply Prompt 2",
|
| 677 |
+
"help": "If enabled, concatenates prompt 2 with prompt 1.",
|
| 678 |
+
},
|
| 679 |
+
)
|
| 680 |
+
supir_model: Literal[tuple(member.value for member in SUPIRModel)] = field(
|
| 681 |
+
default=SUPIRModel.Quality.value,
|
| 682 |
+
metadata={
|
| 683 |
+
"component": "radio",
|
| 684 |
+
"label": "Model Type",
|
| 685 |
+
"help": "Weights of the SUPIR model used. Quality or Fidelity.",
|
| 686 |
+
},
|
| 687 |
+
)
|
| 688 |
+
restoration_scale: float = field(
|
| 689 |
+
default=4.0,
|
| 690 |
+
metadata={
|
| 691 |
+
"component": "slider",
|
| 692 |
+
"label": "Restoration Scale",
|
| 693 |
+
"minimum": 0.0,
|
| 694 |
+
"maximum": 4.0,
|
| 695 |
+
"step": 0.1,
|
| 696 |
+
"help": "Strength of the SUPIR restoration guidance.",
|
| 697 |
+
},
|
| 698 |
+
)
|
| 699 |
+
s_churn: float = field(
|
| 700 |
+
default=0.0,
|
| 701 |
+
metadata={
|
| 702 |
+
"component": "slider",
|
| 703 |
+
"label": "S Churn",
|
| 704 |
+
"minimum": 0.0,
|
| 705 |
+
"maximum": 1.0,
|
| 706 |
+
"step": 0.01,
|
| 707 |
+
"help": "Stochasticity churn factor.",
|
| 708 |
+
},
|
| 709 |
+
)
|
| 710 |
+
s_noise: float = field(
|
| 711 |
+
default=1.003,
|
| 712 |
+
metadata={
|
| 713 |
+
"component": "slider",
|
| 714 |
+
"label": "S Noise",
|
| 715 |
+
"minimum": 1.0,
|
| 716 |
+
"maximum": 1.01,
|
| 717 |
+
"step": 0.001,
|
| 718 |
+
"help": "Stochasticity noise factor.",
|
| 719 |
+
},
|
| 720 |
+
)
|
| 721 |
+
start_point: Literal["lr", "noise"] = field(
|
| 722 |
+
default="lr",
|
| 723 |
+
metadata={
|
| 724 |
+
"component": "dropdown",
|
| 725 |
+
"label": "Start Point",
|
| 726 |
+
"help": "Start from low-res latents ('lr') or pure noise ('noise').",
|
| 727 |
+
},
|
| 728 |
+
)
|
| 729 |
+
strength: float = field(
|
| 730 |
+
default=1.0,
|
| 731 |
+
metadata={
|
| 732 |
+
"component": "slider",
|
| 733 |
+
"minimum": 0.0,
|
| 734 |
+
"maximum": 1.0,
|
| 735 |
+
"step": 0.01,
|
| 736 |
+
"label": "Denoising Strength",
|
| 737 |
+
"help": "How much to denoise the original image. 1.0 is full denoising.",
|
| 738 |
+
},
|
| 739 |
+
)
|
| 740 |
+
general: GenerationSettings = field(default_factory=GenerationSettings, metadata={"label": "Image Generation"})
|
| 741 |
+
cfg_settings: CFGSettings = field(default_factory=CFGSettings, metadata={"label": "Guidance Scale (CFG) - Advanced"})
|
| 742 |
+
controlnet_settings: ControlNetScaleSettings = field(default_factory=ControlNetScaleSettings, metadata={"label": "ControlNet - Advanced"})
|
| 743 |
+
pag_settings: PAGSettings = field(default_factory=PAGSettings, metadata={"label": "PAG - Perturbed Attention Guidance"})
|
| 744 |
+
post_processsing_settings: PostprocessingSettings = field(default_factory=PostprocessingSettings, metadata={"label": "Image Post-Processing"})
|
| 745 |
+
|
| 746 |
+
|
| 747 |
+
@dataclass
|
| 748 |
+
class SUPIRAdvanced_Config:
|
| 749 |
+
"""SFT Injection advanced configuration settings."""
|
| 750 |
+
|
| 751 |
+
sft_post_mid: SUPIRInjectionConfig = field(default_factory=SUPIRInjectionConfig, metadata={"label": "SFT Post Mid"})
|
| 752 |
+
sft_up_block_0_stage0: SUPIRInjectionConfig = field(default_factory=SUPIRInjectionConfig, metadata={"label": "SFT Up Block 0 Stage 0"})
|
| 753 |
+
sft_up_block_0_stage1: SUPIRInjectionConfig = field(default_factory=SUPIRInjectionConfig, metadata={"label": "SFT Up Block 0 Stage 1"})
|
| 754 |
+
sft_up_block_0_stage2: SUPIRInjectionConfig = field(default_factory=SUPIRInjectionConfig, metadata={"label": "SFT Up Block 0 Stage 2"})
|
| 755 |
+
sft_up_block_1_stage0: SUPIRInjectionConfig = field(default_factory=SUPIRInjectionConfig, metadata={"label": "SFT Up Block 1 Stage 0"})
|
| 756 |
+
sft_up_block_1_stage1: SUPIRInjectionConfig = field(default_factory=SUPIRInjectionConfig, metadata={"label": "SFT Up Block 1 Stage 1"})
|
| 757 |
+
sft_up_block_1_stage2: SUPIRInjectionConfig = field(default_factory=SUPIRInjectionConfig, metadata={"label": "SFT Up Block 1 Stage 2"})
|
| 758 |
+
sft_up_block_2_stage0: SUPIRInjectionConfig = field(default_factory=SUPIRInjectionConfig, metadata={"label": "SFT Up Block 2 Stage 0"})
|
| 759 |
+
sft_up_block_2_stage1: SUPIRInjectionConfig = field(default_factory=SUPIRInjectionConfig, metadata={"label": "SFT Up Block 2 Stage 1"})
|
| 760 |
+
sft_up_block_2_stage2: SUPIRInjectionConfig = field(default_factory=SUPIRInjectionConfig, metadata={"label": "SFT Up Block 2 Stage 2"})
|
| 761 |
+
cross_up_block_0_stage1: SUPIRInjectionConfig = field(default_factory=SUPIRInjectionConfig, metadata={"label": "Cross Up Block 0 Stage 1"})
|
| 762 |
+
cross_up_block_0_stage2: SUPIRInjectionConfig = field(default_factory=SUPIRInjectionConfig, metadata={"label": "Cross Up Block 0 Stage 2"})
|
| 763 |
+
cross_up_block_1_stage1: SUPIRInjectionConfig = field(default_factory=SUPIRInjectionConfig, metadata={"label": "Cross Up Block 1 Stage 1"})
|
| 764 |
+
cross_up_block_1_stage2: SUPIRInjectionConfig = field(default_factory=SUPIRInjectionConfig, metadata={"label": "Cross Up Block 1 Stage 2"})
|
| 765 |
+
|
| 766 |
+
|
| 767 |
+
@dataclass
|
| 768 |
+
class FaithDiff_Config:
|
| 769 |
+
"""Complete configuration for a FaithDiff pipeline run."""
|
| 770 |
+
|
| 771 |
+
apply_prompt_2: bool = field(
|
| 772 |
+
default=True,
|
| 773 |
+
metadata={
|
| 774 |
+
"label": "Apply Prompt 2",
|
| 775 |
+
"help": "If enabled, concatenates prompt 2 with prompt 1.",
|
| 776 |
+
},
|
| 777 |
+
)
|
| 778 |
+
invert_prompts: bool = field(
|
| 779 |
+
default=True,
|
| 780 |
+
metadata={
|
| 781 |
+
"label": "Invert Prompts",
|
| 782 |
+
"help": "Starts the send prompt with prompt 2 and ends with prompt 1.",
|
| 783 |
+
},
|
| 784 |
+
)
|
| 785 |
+
apply_ipa_embeds: bool = field(
|
| 786 |
+
default=True,
|
| 787 |
+
metadata={
|
| 788 |
+
"label": "Apply IPA Embeds",
|
| 789 |
+
"help": "Apply IP-Adapter embeddings during diffusion.",
|
| 790 |
+
},
|
| 791 |
+
)
|
| 792 |
+
s_churn: float = field(
|
| 793 |
+
default=0.0,
|
| 794 |
+
metadata={
|
| 795 |
+
"component": "slider",
|
| 796 |
+
"label": "S Churn",
|
| 797 |
+
"minimum": 0.0,
|
| 798 |
+
"maximum": 1.0,
|
| 799 |
+
"step": 0.01,
|
| 800 |
+
"help": "Stochasticity churn factor.",
|
| 801 |
+
},
|
| 802 |
+
)
|
| 803 |
+
s_noise: float = field(
|
| 804 |
+
default=1.003,
|
| 805 |
+
metadata={
|
| 806 |
+
"component": "slider",
|
| 807 |
+
"label": "S Noise",
|
| 808 |
+
"minimum": 1.0,
|
| 809 |
+
"maximum": 1.01,
|
| 810 |
+
"step": 0.001,
|
| 811 |
+
"help": "Stochasticity noise factor.",
|
| 812 |
+
},
|
| 813 |
+
)
|
| 814 |
+
start_point: Literal["lr", "noise"] = field(
|
| 815 |
+
default="lr",
|
| 816 |
+
metadata={
|
| 817 |
+
"component": "dropdown",
|
| 818 |
+
"label": "Start Point",
|
| 819 |
+
"help": "Start from low-res latents ('lr') or pure noise ('noise').",
|
| 820 |
+
},
|
| 821 |
+
)
|
| 822 |
+
strength: float = field(
|
| 823 |
+
default=1.0,
|
| 824 |
+
metadata={
|
| 825 |
+
"component": "slider",
|
| 826 |
+
"minimum": 0.0,
|
| 827 |
+
"maximum": 1.0,
|
| 828 |
+
"step": 0.01,
|
| 829 |
+
"label": "Denoising Strength",
|
| 830 |
+
"help": "How much to denoise the original image. 1.0 is full denoising.",
|
| 831 |
+
},
|
| 832 |
+
)
|
| 833 |
+
general: GenerationSettings = field(default_factory=GenerationSettings, metadata={"label": "Image Generation"})
|
| 834 |
+
controlnet_settings: ControlNetScaleSettings = field(default_factory=ControlNetScaleSettings, metadata={"label": "ControlNet - Advanced"})
|
| 835 |
+
pag_settings: PAGSettings = field(default_factory=PAGSettings, metadata={"label": "PAG - Perturbed Attention Guidance"})
|
| 836 |
+
post_processsing_settings: PostprocessingSettings = field(default_factory=PostprocessingSettings, metadata={"label": "Image Post-Processing"})
|
| 837 |
+
|
| 838 |
+
|
| 839 |
+
@dataclass
|
| 840 |
+
class ControlNetTile_Config:
|
| 841 |
+
"""Complete configuration for a ControlNetTile pipeline run."""
|
| 842 |
+
|
| 843 |
+
apply_prompt_2: bool = field(
|
| 844 |
+
default=True,
|
| 845 |
+
metadata={
|
| 846 |
+
"label": "Apply Prompt 2",
|
| 847 |
+
"help": "If enabled, concatenates prompt 2 with prompt 1.",
|
| 848 |
+
},
|
| 849 |
+
)
|
| 850 |
+
controlnet_conditioning_scale: float = field(
|
| 851 |
+
default=1.0,
|
| 852 |
+
metadata={
|
| 853 |
+
"component": "slider",
|
| 854 |
+
"minimum": 0.0,
|
| 855 |
+
"maximum": 2.0,
|
| 856 |
+
"step": 0.05,
|
| 857 |
+
"label": "ControlNet Scale",
|
| 858 |
+
"help": "The weight of the ControlNet Tile guidance.",
|
| 859 |
+
},
|
| 860 |
+
)
|
| 861 |
+
tile_overlap: int = field(
|
| 862 |
+
default=128,
|
| 863 |
+
metadata={
|
| 864 |
+
"component": "slider",
|
| 865 |
+
"label": "Tile Overlap",
|
| 866 |
+
"minimum": 64,
|
| 867 |
+
"maximum": 512,
|
| 868 |
+
"step": 16,
|
| 869 |
+
"help": "Overlap in pixels between tiles to reduce seams.",
|
| 870 |
+
},
|
| 871 |
+
)
|
| 872 |
+
tile_weighting_method: Literal["Cosine", "Gaussian"] = field(
|
| 873 |
+
default="Cosine",
|
| 874 |
+
metadata={
|
| 875 |
+
"component": "radio",
|
| 876 |
+
"label": "Tile Weighting Method",
|
| 877 |
+
"help": "Method for blending tile edges.",
|
| 878 |
+
},
|
| 879 |
+
)
|
| 880 |
+
tile_gaussian_sigma: float = field(
|
| 881 |
+
default=0.3,
|
| 882 |
+
metadata={
|
| 883 |
+
"interactive_if": {"field": "tile_weighting_method", "value": "Gaussian"},
|
| 884 |
+
"component": "slider",
|
| 885 |
+
"label": "Tile Guassian Sigma",
|
| 886 |
+
"minimum": 0.05,
|
| 887 |
+
"maximum": 2.0,
|
| 888 |
+
"step": 0.01,
|
| 889 |
+
"help": "Sigma parameter for Gaussian weighting of tiles.",
|
| 890 |
+
},
|
| 891 |
+
)
|
| 892 |
+
strength: float = field(
|
| 893 |
+
default=0.65,
|
| 894 |
+
metadata={
|
| 895 |
+
"component": "slider",
|
| 896 |
+
"minimum": 0.0,
|
| 897 |
+
"maximum": 1.0,
|
| 898 |
+
"step": 0.01,
|
| 899 |
+
"label": "Denoising Strength",
|
| 900 |
+
"help": "How much to denoise the original image. 1.0 is full denoising.",
|
| 901 |
+
},
|
| 902 |
+
)
|
| 903 |
+
general: GenerationSettings = field(default_factory=GenerationSettings, metadata={"label": "Image Generation"})
|
| 904 |
+
post_processsing_settings: PostprocessingSettings = field(default_factory=PostprocessingSettings, metadata={"label": "Image Post-Processing"})
|
| 905 |
+
|
| 906 |
+
|
| 907 |
+
# Data for Flyout popups
|
| 908 |
+
@dataclass
|
| 909 |
+
class SchedulerSettings:
|
| 910 |
+
"""Settings to the sampler."""
|
| 911 |
+
|
| 912 |
+
scale_linear_exponent: float = field(
|
| 913 |
+
default=1.93,
|
| 914 |
+
metadata={
|
| 915 |
+
"component": "slider",
|
| 916 |
+
"label": "Scale Linear Exponent",
|
| 917 |
+
"minimum": 0.0,
|
| 918 |
+
"maximum": 2.0,
|
| 919 |
+
"step": 0.01,
|
| 920 |
+
"help": "Exponent for the scale linear beta schedule.",
|
| 921 |
+
},
|
| 922 |
+
)
|
| 923 |
+
|
| 924 |
+
|
| 925 |
+
# PropertySheet value Mappings
|
| 926 |
+
RESTORER_CONFIG_MAPPING = {
|
| 927 |
+
"SUPIR": SUPIR_Config(),
|
| 928 |
+
"SUPIRAdvanced": SUPIRAdvanced_Config(),
|
| 929 |
+
"FaithDiff": FaithDiff_Config(),
|
| 930 |
+
}
|
| 931 |
+
|
| 932 |
+
UPSCALER_CONFIG_MAPPING = {
|
| 933 |
+
"SUPIR": SUPIR_Config(),
|
| 934 |
+
"SUPIRAdvanced": SUPIRAdvanced_Config(),
|
| 935 |
+
"FaithDiff": FaithDiff_Config(),
|
| 936 |
+
"ControlNetTile": ControlNetTile_Config(),
|
| 937 |
+
}
|
| 938 |
+
SAMPLER_MAPPING = {
|
| 939 |
+
"restorer_sampler": SchedulerSettings(scale_linear_exponent=1.93),
|
| 940 |
+
"upscaler_sampler": SchedulerSettings(scale_linear_exponent=2.0),
|
| 941 |
+
}
|
| 942 |
+
|
| 943 |
+
# PropertySheet change rules mapping
|
| 944 |
+
APPSETTINGS_SHEET_DEPENDENCY_RULES = {
|
| 945 |
+
"dynamic_dependencies": {
|
| 946 |
+
"quantization_method": {
|
| 947 |
+
"actions": [
|
| 948 |
+
{
|
| 949 |
+
"type": "update_options",
|
| 950 |
+
"target_field_path": "quantization_mode",
|
| 951 |
+
"mapping": {
|
| 952 |
+
"None": {"options": ["FP8", "NF4"], "new_default": "FP8"},
|
| 953 |
+
"Layerwise & Bnb": {"options": ["FP8", "NF4"], "new_default": "FP8"},
|
| 954 |
+
"Quanto Library": {"options": ["INT8", "INT4"], "new_default": "INT8"},
|
| 955 |
+
},
|
| 956 |
+
},
|
| 957 |
+
]
|
| 958 |
+
},
|
| 959 |
+
"device": {
|
| 960 |
+
"actions": [
|
| 961 |
+
{
|
| 962 |
+
"type": "update_options",
|
| 963 |
+
"target_field_path": "weight_dtype",
|
| 964 |
+
"mapping": {
|
| 965 |
+
"balanced": {
|
| 966 |
+
"options": ["Float16", "Bfloat16", "Float32"],
|
| 967 |
+
"new_default": None,
|
| 968 |
+
},
|
| 969 |
+
"cpu": {"options": ["Float32"], "new_default": "Float32"},
|
| 970 |
+
"cuda": {
|
| 971 |
+
"options": ["Float16", "Bfloat16", "Float32"],
|
| 972 |
+
"new_default": None,
|
| 973 |
+
},
|
| 974 |
+
"mps": {"options": ["Float16", "Bfloat16", "Float32"], "new_default": None},
|
| 975 |
+
},
|
| 976 |
+
},
|
| 977 |
+
{
|
| 978 |
+
"type": "update_options",
|
| 979 |
+
"target_field_path": "vae_weight_dtype",
|
| 980 |
+
"mapping": {
|
| 981 |
+
"balanced": {"options": ["Float16", "Float32"], "new_default": None},
|
| 982 |
+
"cpu": {"options": ["Float32"], "new_default": "Float32"},
|
| 983 |
+
"cuda": {"options": ["Float16", "Float32"], "new_default": None},
|
| 984 |
+
"mps": {"options": ["Float16", "Float32"], "new_default": None},
|
| 985 |
+
},
|
| 986 |
+
},
|
| 987 |
+
{
|
| 988 |
+
"type": "set_value",
|
| 989 |
+
"target_field_path": "enable_cpu_offload",
|
| 990 |
+
"mapping": {"cpu": False},
|
| 991 |
+
},
|
| 992 |
+
]
|
| 993 |
+
},
|
| 994 |
+
},
|
| 995 |
+
"on_load_actions": [],
|
| 996 |
+
}
|
| 997 |
+
|
| 998 |
+
UPSCALER_SHEET_DEPENDENCY_RULES = {
|
| 999 |
+
"dynamic_dependencies": {
|
| 1000 |
+
"general.upscaling_mode": {
|
| 1001 |
+
"actions": [
|
| 1002 |
+
{
|
| 1003 |
+
"type": "update_options",
|
| 1004 |
+
"target_field_path": "general.upscale_factor",
|
| 1005 |
+
"mapping": {
|
| 1006 |
+
"Progressive": {
|
| 1007 |
+
"options": ["2x", "4x", "6x", "8x", "16x"],
|
| 1008 |
+
"new_default": None,
|
| 1009 |
+
},
|
| 1010 |
+
"Direct": {
|
| 1011 |
+
"options": [
|
| 1012 |
+
"2x",
|
| 1013 |
+
"3x",
|
| 1014 |
+
"4x",
|
| 1015 |
+
"5x",
|
| 1016 |
+
"6x",
|
| 1017 |
+
"7x",
|
| 1018 |
+
"8x",
|
| 1019 |
+
"9x",
|
| 1020 |
+
"10x",
|
| 1021 |
+
"11x",
|
| 1022 |
+
"12x",
|
| 1023 |
+
"13x",
|
| 1024 |
+
"14x",
|
| 1025 |
+
"15x",
|
| 1026 |
+
"16x",
|
| 1027 |
+
],
|
| 1028 |
+
"new_default": None,
|
| 1029 |
+
},
|
| 1030 |
+
},
|
| 1031 |
+
},
|
| 1032 |
+
]
|
| 1033 |
+
}
|
| 1034 |
+
},
|
| 1035 |
+
"on_load_actions": [],
|
| 1036 |
+
}
|
| 1037 |
+
RESTORER_SHEET_DEPENDENCY_RULES = {
|
| 1038 |
+
"dynamic_dependencies": {
|
| 1039 |
+
"general.upscaling_mode": {
|
| 1040 |
+
"actions": [
|
| 1041 |
+
{
|
| 1042 |
+
"type": "update_options",
|
| 1043 |
+
"target_field_path": "general.upscale_factor",
|
| 1044 |
+
"mapping": {
|
| 1045 |
+
"Progressive": {
|
| 1046 |
+
"options": ["2x", "4x", "6x", "8x", "16x"],
|
| 1047 |
+
"new_default": None,
|
| 1048 |
+
},
|
| 1049 |
+
"Direct": {"options": ["1x", "2x", "3x", "4x"], "new_default": "1x"},
|
| 1050 |
+
},
|
| 1051 |
+
},
|
| 1052 |
+
]
|
| 1053 |
+
}
|
| 1054 |
+
},
|
| 1055 |
+
"on_load_actions": [
|
| 1056 |
+
# Each item in the list is an action to perform.
|
| 1057 |
+
{
|
| 1058 |
+
"type": "update_visibility",
|
| 1059 |
+
"target_field_path": "general.upscaling_mode",
|
| 1060 |
+
"visible": False,
|
| 1061 |
+
},
|
| 1062 |
+
{
|
| 1063 |
+
"type": "update_visibility",
|
| 1064 |
+
"target_field_path": "general.cfg_decay_rate",
|
| 1065 |
+
"visible": False,
|
| 1066 |
+
},
|
| 1067 |
+
{
|
| 1068 |
+
"type": "update_visibility",
|
| 1069 |
+
"target_field_path": "general.strength_decay_rate",
|
| 1070 |
+
"visible": False,
|
| 1071 |
+
},
|
| 1072 |
+
{
|
| 1073 |
+
"type": "update_visibility",
|
| 1074 |
+
"target_field_path": "general.upscale_factor",
|
| 1075 |
+
"visible": False,
|
| 1076 |
+
},
|
| 1077 |
+
# You could add more direct actions here..
|
| 1078 |
+
],
|
| 1079 |
+
}
|
| 1080 |
+
SUPIR_ADVANCED_RULES = {"dynamic_dependencies": {}, "on_load_actions": []}
|
| 1081 |
+
|
| 1082 |
+
# Default prompt dictionary for each engine type
|
| 1083 |
+
DEFAULT_PROMPTS = {
|
| 1084 |
+
"Restorer": {
|
| 1085 |
+
"SUPIR": {
|
| 1086 |
+
"prompt": "Direct flash photography. [subject].",
|
| 1087 |
+
"prompt_2": "Extremely detailed faces, flawless natural skin texture, skin pore detailing, 4k, 8k, clean image, no noise, shot on Fujifilm Superia 400, sharp focus, faithful colors.",
|
| 1088 |
+
"negative_prompt": "low-res, disfigured, analog artifacts, smudged, animate, (out of focus:1.2), catchlights, over-smooth, extra eyes, worst quality, unreal engine, art, aberrations, surreal, pastel drawing, harsh lighting, dead eyes, deformed fingers, undistinct fingers outlines",
|
| 1089 |
+
},
|
| 1090 |
+
"FaithDiff": {
|
| 1091 |
+
"prompt": "[subject].",
|
| 1092 |
+
"prompt_2": "Ultra-quality photography, flawless natural skin texture, shot on Fujifilm Superia 400, clean image, sharp focus.",
|
| 1093 |
+
"negative_prompt": "low res, worst quality, blurry, out of focus, analog artifacts, oil painting, illustration, art, anime, cartoon, CG Style, unreal engine, render, artwork, (wrinkles:1.4), fine lines, smile lines, age spots, (blemishes:1.2), acne, scars, freckles, blotchy skin, deformed mouth, catchlights",
|
| 1094 |
+
},
|
| 1095 |
+
},
|
| 1096 |
+
"Upscaler": {
|
| 1097 |
+
"SUPIR": {
|
| 1098 |
+
"prompt": "Extremely detailed faces, flawless natural skin texture, skin pore detailing, 4k, 8k, clean image, no noise, shot on Fujifilm Superia 400, sharp focus, faithful colors.",
|
| 1099 |
+
"prompt_2": "",
|
| 1100 |
+
"negative_prompt": "blurry, pixelated, noisy, low resolution, artifacts, poor details, (oversaturated:2.5), (overexposed:2.5)",
|
| 1101 |
+
},
|
| 1102 |
+
"FaithDiff": {
|
| 1103 |
+
"prompt": "Ultra-quality photography, flawless natural skin texture, shot on Fujifilm Superia 400, clean image, sharp focus.",
|
| 1104 |
+
"prompt_2": "",
|
| 1105 |
+
"negative_prompt": "low res, worst quality, blurry, out of focus, analog artifacts, oil painting, illustration, art, anime, cartoon, CG Style, unreal engine, render, artwork, (wrinkles:1.4), fine lines, smile lines, age spots, (blemishes:1.2), acne, scars, freckles, blotchy skin, deformed mouth, catchlights",
|
| 1106 |
+
},
|
| 1107 |
+
"ControlNetTile": {
|
| 1108 |
+
"prompt": "high-quality, noise-free edges, high quality, 4k, hd, 8k",
|
| 1109 |
+
"prompt_2": "",
|
| 1110 |
+
"negative_prompt": "blurry, pixelated, noisy, low resolution, artifacts, poor details",
|
| 1111 |
+
},
|
| 1112 |
+
},
|
| 1113 |
+
}
|
| 1114 |
+
|
| 1115 |
+
|
| 1116 |
+
TAG_DATA_POSITIVE = {
|
| 1117 |
+
# POSITIVE PROMPTS
|
| 1118 |
+
"Quality": [
|
| 1119 |
+
"best quality",
|
| 1120 |
+
"high quality",
|
| 1121 |
+
"masterpiece",
|
| 1122 |
+
"ultra high resolution",
|
| 1123 |
+
"high resolution",
|
| 1124 |
+
"4k",
|
| 1125 |
+
"8k",
|
| 1126 |
+
"extremely detailed",
|
| 1127 |
+
"ultra-detailed",
|
| 1128 |
+
"hyper detailed",
|
| 1129 |
+
"intricate details",
|
| 1130 |
+
"sharp focus",
|
| 1131 |
+
"crisp details",
|
| 1132 |
+
"sharp",
|
| 1133 |
+
"hyper sharpness",
|
| 1134 |
+
],
|
| 1135 |
+
"Style": [
|
| 1136 |
+
"photorealistic",
|
| 1137 |
+
"realistic photo",
|
| 1138 |
+
"photograph",
|
| 1139 |
+
"digital photograph",
|
| 1140 |
+
"professional photography",
|
| 1141 |
+
"direct flash photography",
|
| 1142 |
+
"soft studio lighting",
|
| 1143 |
+
"natural lighting",
|
| 1144 |
+
"softbox lighting",
|
| 1145 |
+
"cinematic",
|
| 1146 |
+
"filmic",
|
| 1147 |
+
"shot on [Camera Name]",
|
| 1148 |
+
"Fujifilm Superia 400",
|
| 1149 |
+
"Kodak Portra 400",
|
| 1150 |
+
"early 2000s aesthetic",
|
| 1151 |
+
"modern look",
|
| 1152 |
+
"editorial fashion photo",
|
| 1153 |
+
"beauty portrait",
|
| 1154 |
+
],
|
| 1155 |
+
"Subject & Details": [
|
| 1156 |
+
"natural skin texture",
|
| 1157 |
+
"realistic skin",
|
| 1158 |
+
"subtle skin variations",
|
| 1159 |
+
"visible micro-details",
|
| 1160 |
+
"matte skin finish",
|
| 1161 |
+
"poreless skin",
|
| 1162 |
+
"flawless skin",
|
| 1163 |
+
"realistic eyes",
|
| 1164 |
+
"detailed eyes",
|
| 1165 |
+
"natural catchlights",
|
| 1166 |
+
"crisp whiskers",
|
| 1167 |
+
"dense fur",
|
| 1168 |
+
"realistic fur texture",
|
| 1169 |
+
"individual hair strands",
|
| 1170 |
+
"multi-layered fur texture",
|
| 1171 |
+
"detailed clothing",
|
| 1172 |
+
"realistic fabric texture",
|
| 1173 |
+
"clothing folds",
|
| 1174 |
+
],
|
| 1175 |
+
}
|
| 1176 |
+
|
| 1177 |
+
TAG_DATA_NEGATIVE = {
|
| 1178 |
+
# NEGATIVE PROMPTS
|
| 1179 |
+
"Negative - General Quality & Artifacts": [
|
| 1180 |
+
"worst quality",
|
| 1181 |
+
"low quality",
|
| 1182 |
+
"normal quality",
|
| 1183 |
+
"bad quality",
|
| 1184 |
+
"low-res",
|
| 1185 |
+
"low resolution",
|
| 1186 |
+
"jpeg artifacts",
|
| 1187 |
+
"compression artifacts",
|
| 1188 |
+
"grain",
|
| 1189 |
+
"grainy",
|
| 1190 |
+
"noisy",
|
| 1191 |
+
"blurry",
|
| 1192 |
+
"unsharp",
|
| 1193 |
+
"blur",
|
| 1194 |
+
"out of focus",
|
| 1195 |
+
"unfocused",
|
| 1196 |
+
"smudged",
|
| 1197 |
+
"hazy",
|
| 1198 |
+
"watermark",
|
| 1199 |
+
"signature",
|
| 1200 |
+
"username",
|
| 1201 |
+
"artist name",
|
| 1202 |
+
"text",
|
| 1203 |
+
"logo",
|
| 1204 |
+
"symbol",
|
| 1205 |
+
"hieroglyph",
|
| 1206 |
+
"script",
|
| 1207 |
+
"printed words",
|
| 1208 |
+
"written language",
|
| 1209 |
+
],
|
| 1210 |
+
"Negative - Unrealistic Styles": [
|
| 1211 |
+
"cartoon",
|
| 1212 |
+
"3d",
|
| 1213 |
+
"3d render",
|
| 1214 |
+
"cgi",
|
| 1215 |
+
"2d",
|
| 1216 |
+
"sketch",
|
| 1217 |
+
"drawing",
|
| 1218 |
+
"illustration",
|
| 1219 |
+
"bad art",
|
| 1220 |
+
"bad illustration",
|
| 1221 |
+
"painting",
|
| 1222 |
+
"oil painting",
|
| 1223 |
+
"pastel drawing",
|
| 1224 |
+
"art",
|
| 1225 |
+
"unreal engine",
|
| 1226 |
+
"cinema 4d",
|
| 1227 |
+
"artstation",
|
| 1228 |
+
"octane render",
|
| 1229 |
+
"photoshop",
|
| 1230 |
+
"video game",
|
| 1231 |
+
"surreal",
|
| 1232 |
+
"kitsch",
|
| 1233 |
+
"abstract",
|
| 1234 |
+
"creative",
|
| 1235 |
+
],
|
| 1236 |
+
"Negative - Anatomy & Deformities (General)": [
|
| 1237 |
+
"ugly",
|
| 1238 |
+
"disfigured",
|
| 1239 |
+
"deformed",
|
| 1240 |
+
"mutation",
|
| 1241 |
+
"mutated",
|
| 1242 |
+
"mutilated",
|
| 1243 |
+
"mangled",
|
| 1244 |
+
"bad anatomy",
|
| 1245 |
+
"incorrect physiology",
|
| 1246 |
+
"bad proportions",
|
| 1247 |
+
"gross proportions",
|
| 1248 |
+
"disproportioned",
|
| 1249 |
+
"morbid",
|
| 1250 |
+
"disgusting",
|
| 1251 |
+
"repellent",
|
| 1252 |
+
"revolting dimensions",
|
| 1253 |
+
"corpse",
|
| 1254 |
+
"2 heads",
|
| 1255 |
+
"2 faces",
|
| 1256 |
+
"conjoined",
|
| 1257 |
+
"long neck",
|
| 1258 |
+
"long body",
|
| 1259 |
+
"childish",
|
| 1260 |
+
"old",
|
| 1261 |
+
],
|
| 1262 |
+
"Negative - Limbs, Hands & Fingers": [
|
| 1263 |
+
"extra limbs",
|
| 1264 |
+
"missing limb",
|
| 1265 |
+
"extra legs",
|
| 1266 |
+
"extra arms",
|
| 1267 |
+
"missing arms",
|
| 1268 |
+
"missing legs",
|
| 1269 |
+
"malformed limbs",
|
| 1270 |
+
"floating limbs",
|
| 1271 |
+
"disconnected limbs",
|
| 1272 |
+
"disembodied limb",
|
| 1273 |
+
"linked limb",
|
| 1274 |
+
"connected limb",
|
| 1275 |
+
"interconnected limb",
|
| 1276 |
+
"split limbs",
|
| 1277 |
+
"split arms",
|
| 1278 |
+
"split hands",
|
| 1279 |
+
"severed",
|
| 1280 |
+
"dismembered",
|
| 1281 |
+
"amputee",
|
| 1282 |
+
"extra knee",
|
| 1283 |
+
"extra elbow",
|
| 1284 |
+
"three crus",
|
| 1285 |
+
"extra crus",
|
| 1286 |
+
"fused crus",
|
| 1287 |
+
"three feet",
|
| 1288 |
+
"fused feet",
|
| 1289 |
+
"worst feet",
|
| 1290 |
+
"poorly drawn feet",
|
| 1291 |
+
"fused thigh",
|
| 1292 |
+
"three thigh",
|
| 1293 |
+
"extra thigh",
|
| 1294 |
+
"worst thigh",
|
| 1295 |
+
"bad hands",
|
| 1296 |
+
"poorly drawn hands",
|
| 1297 |
+
"poorly rendered hands",
|
| 1298 |
+
"mutated hands",
|
| 1299 |
+
"malformed hands",
|
| 1300 |
+
"disfigured hand",
|
| 1301 |
+
"fused hands",
|
| 1302 |
+
"three hands",
|
| 1303 |
+
"missing hand",
|
| 1304 |
+
"no thumb",
|
| 1305 |
+
"broken hand",
|
| 1306 |
+
"broken wrist",
|
| 1307 |
+
"broken leg",
|
| 1308 |
+
"extra fingers",
|
| 1309 |
+
"missing fingers",
|
| 1310 |
+
"fused fingers",
|
| 1311 |
+
"too many fingers",
|
| 1312 |
+
"extra digit",
|
| 1313 |
+
"fewer digits",
|
| 1314 |
+
"ugly fingers",
|
| 1315 |
+
"long fingers",
|
| 1316 |
+
"twisted fingers",
|
| 1317 |
+
"bad digit",
|
| 1318 |
+
"missing digit",
|
| 1319 |
+
"broken finger",
|
| 1320 |
+
"six fingers per hand",
|
| 1321 |
+
"four fingers per hand",
|
| 1322 |
+
],
|
| 1323 |
+
"Negative - Face & Eyes": [
|
| 1324 |
+
"poorly drawn face",
|
| 1325 |
+
"bad face",
|
| 1326 |
+
"fused face",
|
| 1327 |
+
"cloned face",
|
| 1328 |
+
"worst face",
|
| 1329 |
+
"deformed face",
|
| 1330 |
+
"ugly face",
|
| 1331 |
+
"irregular face",
|
| 1332 |
+
"asymmetrical",
|
| 1333 |
+
"squint",
|
| 1334 |
+
"extra eyes",
|
| 1335 |
+
"huge eyes",
|
| 1336 |
+
"ugly eyes",
|
| 1337 |
+
"deformed pupils",
|
| 1338 |
+
"deformed iris",
|
| 1339 |
+
"cross-eye",
|
| 1340 |
+
],
|
| 1341 |
+
"Negative - Composition & Color": [
|
| 1342 |
+
"out of frame",
|
| 1343 |
+
"body out of frame",
|
| 1344 |
+
"poorly framed",
|
| 1345 |
+
"cut off",
|
| 1346 |
+
"trimmed",
|
| 1347 |
+
"cropped",
|
| 1348 |
+
"canvas frame",
|
| 1349 |
+
"picture frame",
|
| 1350 |
+
"storyboard",
|
| 1351 |
+
"split image",
|
| 1352 |
+
"tiling",
|
| 1353 |
+
"b&w",
|
| 1354 |
+
"black and white",
|
| 1355 |
+
"monochrome",
|
| 1356 |
+
"weird colors",
|
| 1357 |
+
"oversaturated",
|
| 1358 |
+
"low saturation",
|
| 1359 |
+
],
|
| 1360 |
+
}
|
ui/ui_data.py
ADDED
|
@@ -0,0 +1,525 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2025 The DEVAIEXP Team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import json
|
| 16 |
+
import os
|
| 17 |
+
import traceback
|
| 18 |
+
from dataclasses import fields
|
| 19 |
+
from enum import Enum
|
| 20 |
+
from importlib import resources
|
| 21 |
+
from importlib.resources import files
|
| 22 |
+
from pathlib import Path
|
| 23 |
+
from typing import Any, Dict, List, Optional, get_type_hints
|
| 24 |
+
|
| 25 |
+
import gradio as gr
|
| 26 |
+
import numpy as np
|
| 27 |
+
|
| 28 |
+
# Project Imports
|
| 29 |
+
from sup_toolbox.config import (
|
| 30 |
+
SAMPLERS_OTHERS,
|
| 31 |
+
SAMPLERS_SUPIR,
|
| 32 |
+
Config,
|
| 33 |
+
SchedulerConfig,
|
| 34 |
+
)
|
| 35 |
+
from sup_toolbox.enums import ModelType, PromptMethod
|
| 36 |
+
from sup_toolbox.modules.model_manager import ModelManager
|
| 37 |
+
from sup_toolbox.modules.SUPIR.pipeline_supir_stable_diffusion_xl import (
|
| 38 |
+
InjectionConfigs,
|
| 39 |
+
InjectionFlags,
|
| 40 |
+
InjectionScaleConfig,
|
| 41 |
+
)
|
| 42 |
+
from sup_toolbox.utils.logging import logger
|
| 43 |
+
from ui.ui_config import AppSettings, SchedulerSettings, SUPIRAdvanced_Config, SUPIRInjectionConfig
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class UIData:
|
| 47 |
+
"""
|
| 48 |
+
This class acts as a service provider for the Gradio UI.
|
| 49 |
+
It loads dynamic data like model lists, manages application settings and presets,
|
| 50 |
+
and contains helper functions for UI data conversion.
|
| 51 |
+
"""
|
| 52 |
+
|
| 53 |
+
# Symbols and Constants
|
| 54 |
+
folder_symbol = "\U0001f4c2"
|
| 55 |
+
refresh_symbol = "\U0001f504"
|
| 56 |
+
save_symbol = "\U0001f4be"
|
| 57 |
+
add_symbol = "\U00002795"
|
| 58 |
+
remove_symbol = "\U0000274c"
|
| 59 |
+
clean_symbol = "\U0001f9f9"
|
| 60 |
+
download_symbol = "\U0001f4e5"
|
| 61 |
+
engine_symbol = "\U00002699"
|
| 62 |
+
tools_symbol = "\U0001f6e0"
|
| 63 |
+
settings_symbol = "⚙️"
|
| 64 |
+
process_symbol = "\U00002728"
|
| 65 |
+
stop_symbol = "\U0001f6d1"
|
| 66 |
+
preview_symbol = "\U0001f441"
|
| 67 |
+
about_symbol = "ℹ️"
|
| 68 |
+
caption_symbol = "🤖"
|
| 69 |
+
|
| 70 |
+
APPLICATION_TITLE = "SUP Toolbox UI"
|
| 71 |
+
MAX_SEED = np.iinfo(np.int32).max
|
| 72 |
+
|
| 73 |
+
# Static UI Option Lists
|
| 74 |
+
PROMPT_METHODS = [e.value for e in PromptMethod]
|
| 75 |
+
SAMPLER_SUPIR_LIST = sorted(SAMPLERS_SUPIR.keys())
|
| 76 |
+
SAMPLER_OTHERS_LIST = sorted(SAMPLERS_OTHERS.keys())
|
| 77 |
+
|
| 78 |
+
# Dynamically Populated Lists
|
| 79 |
+
MODEL_CHOICES: list[str] = []
|
| 80 |
+
VAE_CHOICES: list[str] = []
|
| 81 |
+
RESTORER_ENGINE_CHOICES: list[str] = []
|
| 82 |
+
UPSCALER_ENGINE_CHOICES: list[str] = []
|
| 83 |
+
PRESETS_LIST: list[str] = []
|
| 84 |
+
PRETRAINED_MODELS_LIST: list[str] = []
|
| 85 |
+
PRETRAINED_VAE_MODELS_LIST: list[str] = []
|
| 86 |
+
|
| 87 |
+
APP_ROOT_DIR = Path(__file__).resolve().parent.parent
|
| 88 |
+
|
| 89 |
+
def __init__(self, **kwargs):
|
| 90 |
+
self.USER_PRESETS_DIR = Path("./presets")
|
| 91 |
+
self.USER_PRESETS_DIR.mkdir(exist_ok=True)
|
| 92 |
+
self.PRESETS_LIST = []
|
| 93 |
+
self.config = Config(models_root_path=self.APP_ROOT_DIR)
|
| 94 |
+
Path(self.config.output_dir).mkdir(exist_ok=True)
|
| 95 |
+
self.settings = AppSettings()
|
| 96 |
+
self.model_manager = ModelManager(self.config)
|
| 97 |
+
self.loaded_preset = {}
|
| 98 |
+
always_download_models = kwargs.pop("always_download_models", None)
|
| 99 |
+
|
| 100 |
+
# Main initialization sequence
|
| 101 |
+
self.load_settings()
|
| 102 |
+
|
| 103 |
+
# Prepare models
|
| 104 |
+
self.model_manager.prepare_models(always_download_models)
|
| 105 |
+
|
| 106 |
+
# Populate dynamic lists based on loaded settings
|
| 107 |
+
self.get_model_list()
|
| 108 |
+
self.get_vae_model_list()
|
| 109 |
+
self.get_preset_list()
|
| 110 |
+
|
| 111 |
+
# Populate engine choices from the config mappings
|
| 112 |
+
from ui.ui_config import RESTORER_CONFIG_MAPPING, UPSCALER_CONFIG_MAPPING
|
| 113 |
+
|
| 114 |
+
self.RESTORER_ENGINE_CHOICES = ["None"] + [k for k in RESTORER_CONFIG_MAPPING.keys() if k != "SUPIRAdvanced"]
|
| 115 |
+
self.UPSCALER_ENGINE_CHOICES = ["None"] + [k for k in UPSCALER_CONFIG_MAPPING.keys() if k != "SUPIRAdvanced"]
|
| 116 |
+
|
| 117 |
+
# Theme
|
| 118 |
+
self.theme = gr.themes.Ocean()
|
| 119 |
+
|
| 120 |
+
def load_settings(self):
|
| 121 |
+
"""Loads settings from a JSON file into the AppSettings dataclass and the main Config object."""
|
| 122 |
+
print("Loading settings...")
|
| 123 |
+
|
| 124 |
+
settings_file_path = "configs/settings.json"
|
| 125 |
+
if os.path.exists(settings_file_path):
|
| 126 |
+
with open(settings_file_path, "r") as f:
|
| 127 |
+
data = json.load(f)
|
| 128 |
+
# Populate the dataclass with only the keys that exist in it
|
| 129 |
+
valid_keys = {f.name for f in fields(AppSettings)}
|
| 130 |
+
filtered_data = {k: v for k, v in data.items() if k in valid_keys}
|
| 131 |
+
self.settings = AppSettings(**filtered_data)
|
| 132 |
+
config_class = type(self.config)
|
| 133 |
+
type_hints = get_type_hints(config_class)
|
| 134 |
+
|
| 135 |
+
# Populate the backend config
|
| 136 |
+
for key, value in data.items():
|
| 137 |
+
if hasattr(self.config, key):
|
| 138 |
+
expected_type = type_hints.get(key)
|
| 139 |
+
final_value = value
|
| 140 |
+
if expected_type and isinstance(expected_type, type) and issubclass(expected_type, Enum):
|
| 141 |
+
try:
|
| 142 |
+
if hasattr(expected_type, "from_str") and callable(getattr(expected_type, "from_str")):
|
| 143 |
+
final_value = expected_type.from_str(value)
|
| 144 |
+
else:
|
| 145 |
+
final_value = expected_type(value)
|
| 146 |
+
except ValueError:
|
| 147 |
+
print(f"Warning: '{value}' is not a valid member of {expected_type.__name__} for field '{key}'. Skipping update.")
|
| 148 |
+
continue
|
| 149 |
+
setattr(self.config, key, final_value)
|
| 150 |
+
|
| 151 |
+
# This part is crucial for making the dynamic lists work
|
| 152 |
+
self.config.checkpoints_dir = self.settings.checkpoints_dir
|
| 153 |
+
self.config.vae_dir = self.settings.vae_dir
|
| 154 |
+
pass
|
| 155 |
+
|
| 156 |
+
def load_defaults(self):
|
| 157 |
+
default_settings_path = files("sup_toolbox.configs").joinpath("settings.json")
|
| 158 |
+
if not os.path.exists(default_settings_path):
|
| 159 |
+
print(f"Settings configuration file {default_settings_path} does not exist. Loading will be aborted!")
|
| 160 |
+
return
|
| 161 |
+
|
| 162 |
+
with open(default_settings_path, "r") as f:
|
| 163 |
+
config_dict = json.load(f)
|
| 164 |
+
|
| 165 |
+
self.save_settings(config_dict)
|
| 166 |
+
|
| 167 |
+
def save_settings(self, values_dict: dict):
|
| 168 |
+
status = "Settings was updated!"
|
| 169 |
+
try:
|
| 170 |
+
if not os.path.exists("configs"):
|
| 171 |
+
os.makedirs("configs")
|
| 172 |
+
settings_path = "configs/settings.json"
|
| 173 |
+
with open(settings_path, "w") as f:
|
| 174 |
+
json.dump(values_dict, f, indent=2)
|
| 175 |
+
|
| 176 |
+
except Exception as e:
|
| 177 |
+
logger.error(f"Error in save_settings: {str(e)}")
|
| 178 |
+
print(traceback.format_exc())
|
| 179 |
+
status = "There was an error saving the settings!"
|
| 180 |
+
pass
|
| 181 |
+
return status
|
| 182 |
+
|
| 183 |
+
def get_preset_list(self):
|
| 184 |
+
"""
|
| 185 |
+
Scans for both default library presets and local user presets, then updates the instance's PRESETS_LIST.
|
| 186 |
+
|
| 187 |
+
This method performs a comprehensive scan to build a unified list of available presets for the UI.
|
| 188 |
+
It follows these steps:
|
| 189 |
+
1. Scans the user-defined presets directory (`self.USER_PRESETS_DIR`) for any custom `.json` files.
|
| 190 |
+
This directory is created if it does not exist.
|
| 191 |
+
2. Uses the modern `importlib.resources` library to safely locate and scan the `presets`
|
| 192 |
+
directory bundled within the installed `sup_toolbox` package. This approach works
|
| 193 |
+
reliably across all installation types (standard, editable, etc.).
|
| 194 |
+
3. Prefixes the names of default presets with "Default: " to distinguish them in the UI.
|
| 195 |
+
4. Merges the two lists (user and default), sorts them alphabetically, and stores the
|
| 196 |
+
final list in `self.PRESETS_LIST`.
|
| 197 |
+
|
| 198 |
+
Raises:
|
| 199 |
+
- Logs an error via the `logging` module if scanning the user presets directory fails.
|
| 200 |
+
- Logs a warning if the `sup_toolbox` package or its presets directory cannot be found.
|
| 201 |
+
- Logs an error for any other unexpected exceptions during the process.
|
| 202 |
+
"""
|
| 203 |
+
|
| 204 |
+
print("Scanning for user and default presets...")
|
| 205 |
+
|
| 206 |
+
user_presets = set()
|
| 207 |
+
default_presets = set()
|
| 208 |
+
|
| 209 |
+
# 1. Load User-Defined Presets
|
| 210 |
+
try:
|
| 211 |
+
# Ensure the user presets directory exists before scanning.
|
| 212 |
+
self.USER_PRESETS_DIR.mkdir(parents=True, exist_ok=True)
|
| 213 |
+
for f in self.USER_PRESETS_DIR.glob("*.json"):
|
| 214 |
+
user_presets.add(f.stem)
|
| 215 |
+
except Exception as e:
|
| 216 |
+
logger.error(f"Could not scan user presets directory at '{self.USER_PRESETS_DIR}': {e}")
|
| 217 |
+
|
| 218 |
+
# 2. Load Bundled Default Presets
|
| 219 |
+
try:
|
| 220 |
+
# Use `importlib.resources.files` to get a traversable path object
|
| 221 |
+
# to the 'presets' directory inside the installed 'sup_toolbox' package.
|
| 222 |
+
# This is the modern, standard way to access package data.
|
| 223 |
+
preset_path = resources.files("sup_toolbox").joinpath("presets")
|
| 224 |
+
|
| 225 |
+
# Iterate through the contents of the bundled presets directory.
|
| 226 |
+
for item in preset_path.iterdir():
|
| 227 |
+
if item.is_file() and item.name.endswith(".json"):
|
| 228 |
+
# Add with "Default: " prefix for UI clarity.
|
| 229 |
+
default_presets.add(f"Default: {item.stem}")
|
| 230 |
+
|
| 231 |
+
except ModuleNotFoundError:
|
| 232 |
+
# This occurs if the `sup_toolbox` package is not installed in the environment.
|
| 233 |
+
logger.warning("Could not find default presets because the 'sup-toolbox' library is not installed.")
|
| 234 |
+
except FileNotFoundError:
|
| 235 |
+
# This might occur if the package is installed but the 'presets' folder is missing.
|
| 236 |
+
logger.warning("Located 'sup-toolbox' library, but its 'presets' directory could not be found.")
|
| 237 |
+
except Exception as e:
|
| 238 |
+
# Catch any other unexpected errors during resource loading.
|
| 239 |
+
logger.error(f"An unexpected error occurred while loading default presets: {e}")
|
| 240 |
+
|
| 241 |
+
# 3. Merge, Sort, and Finalize the List
|
| 242 |
+
# Convert sets to lists, sort each one, and then combine.
|
| 243 |
+
# User presets are listed first.
|
| 244 |
+
sorted_user_presets = sorted(user_presets)
|
| 245 |
+
sorted_default_presets = sorted(default_presets)
|
| 246 |
+
|
| 247 |
+
self.PRESETS_LIST = sorted_user_presets + sorted_default_presets
|
| 248 |
+
print(f"Finished loading presets. Found {len(user_presets)} user preset(s) and {len(default_presets)} default preset(s).")
|
| 249 |
+
|
| 250 |
+
def load_preset(self, preset_name: str) -> dict:
|
| 251 |
+
"""
|
| 252 |
+
Loads a specific preset from a JSON file, returning its content as a dictionary.
|
| 253 |
+
|
| 254 |
+
This method intelligently searches for the preset in two locations:
|
| 255 |
+
1. If the `preset_name` starts with "Default: ", it uses `importlib.resources`
|
| 256 |
+
to securely read the corresponding bundled preset file from within the
|
| 257 |
+
installed `sup_toolbox` package.
|
| 258 |
+
2. Otherwise, it assumes the preset is a user-defined file located in the
|
| 259 |
+
`self.USER_PRESETS_DIR` directory.
|
| 260 |
+
|
| 261 |
+
In case of any errors (e.g., file not found, invalid JSON), it logs the
|
| 262 |
+
error and returns an empty dictionary to ensure the application can
|
| 263 |
+
continue gracefully.
|
| 264 |
+
|
| 265 |
+
Args:
|
| 266 |
+
preset_name (str): The name of the preset to load. Default presets
|
| 267 |
+
should be prefixed with "Default: ".
|
| 268 |
+
|
| 269 |
+
Returns:
|
| 270 |
+
dict: A dictionary containing the loaded preset data, or an empty
|
| 271 |
+
dictionary if the preset could not be loaded.
|
| 272 |
+
"""
|
| 273 |
+
|
| 274 |
+
if not preset_name or not preset_name.strip():
|
| 275 |
+
logger.warning("`load_preset` called with an empty name.")
|
| 276 |
+
return {}
|
| 277 |
+
|
| 278 |
+
if preset_name.startswith("Default: "):
|
| 279 |
+
# Handle Bundled Default Presets
|
| 280 |
+
base_name = preset_name.replace("Default: ", "")
|
| 281 |
+
logger.info(f"Loading default preset: '{base_name}'")
|
| 282 |
+
try:
|
| 283 |
+
# Use `importlib.resources` to safely access the package data file.
|
| 284 |
+
# `files()` returns a traversable object representing the package.
|
| 285 |
+
preset_file_path = resources.files("sup_toolbox").joinpath("presets").joinpath(f"{base_name}.json")
|
| 286 |
+
|
| 287 |
+
# `read_text()` is a convenient and safe way to read the file content.
|
| 288 |
+
json_content = preset_file_path.read_text(encoding="utf-8")
|
| 289 |
+
return json.loads(json_content)
|
| 290 |
+
|
| 291 |
+
except ModuleNotFoundError:
|
| 292 |
+
logger.error("Cannot load default preset because the 'sup-toolbox' library is not installed.")
|
| 293 |
+
return {}
|
| 294 |
+
except FileNotFoundError:
|
| 295 |
+
logger.error(f"Default preset file '{base_name}.json' not found within the 'sup_toolbox' package.")
|
| 296 |
+
return {}
|
| 297 |
+
except json.JSONDecodeError as e:
|
| 298 |
+
logger.error(f"Failed to parse default preset '{base_name}.json'. Invalid JSON: {e}")
|
| 299 |
+
return {}
|
| 300 |
+
except Exception as e:
|
| 301 |
+
logger.error(f"An unexpected error occurred while loading default preset '{base_name}': {e}")
|
| 302 |
+
return {}
|
| 303 |
+
else:
|
| 304 |
+
# Handle User-Defined Presets
|
| 305 |
+
logger.info(f"Loading user preset: '{preset_name}'")
|
| 306 |
+
preset_path = self.USER_PRESETS_DIR / f"{preset_name}.json"
|
| 307 |
+
|
| 308 |
+
if not preset_path.is_file():
|
| 309 |
+
logger.warning(f"User preset '{preset_name}' not found at path: {preset_path}")
|
| 310 |
+
return {}
|
| 311 |
+
|
| 312 |
+
try:
|
| 313 |
+
with open(preset_path, "r", encoding="utf-8") as file:
|
| 314 |
+
return json.load(file)
|
| 315 |
+
except json.JSONDecodeError as e:
|
| 316 |
+
logger.error(f"Failed to parse user preset '{preset_name}.json'. Invalid JSON: {e}")
|
| 317 |
+
return {}
|
| 318 |
+
except Exception as e:
|
| 319 |
+
logger.error(f"An unexpected error occurred while loading user preset '{preset_name}': {e}")
|
| 320 |
+
return {}
|
| 321 |
+
|
| 322 |
+
def save_preset(self, preset_name: str, values_dict: dict) -> str:
|
| 323 |
+
"""Saves a preset dictionary to the local user presets directory."""
|
| 324 |
+
|
| 325 |
+
if not preset_name or not preset_name.strip():
|
| 326 |
+
raise gr.Error("Preset name cannot be empty.")
|
| 327 |
+
|
| 328 |
+
if preset_name.startswith("Default: "):
|
| 329 |
+
raise gr.Error("Cannot overwrite default presets. Please choose a name without the 'Default:' prefix.")
|
| 330 |
+
|
| 331 |
+
status = f"Preset '{preset_name}' was saved!"
|
| 332 |
+
preset_path = self.USER_PRESETS_DIR / f"{preset_name}.json"
|
| 333 |
+
try:
|
| 334 |
+
with open(preset_path, "w", encoding="utf-8") as f:
|
| 335 |
+
json.dump(values_dict, f, indent=4)
|
| 336 |
+
self.get_preset_list()
|
| 337 |
+
except Exception as e:
|
| 338 |
+
logger.error(f"Error in save_preset: {e}")
|
| 339 |
+
traceback.print_exc()
|
| 340 |
+
status = f"Error saving preset {preset_name}"
|
| 341 |
+
return status
|
| 342 |
+
|
| 343 |
+
def get_model_list(self):
|
| 344 |
+
"""Populates MODEL_CHOICES from pretrained list and local checkpoint directory."""
|
| 345 |
+
print("Loading model list...")
|
| 346 |
+
try:
|
| 347 |
+
self.PRETRAINED_MODELS_LIST = self.model_manager.filter_models_by_model_type(ModelType.Diffusers.value)
|
| 348 |
+
pretrained_names = [m["model_name"] for m in self.PRETRAINED_MODELS_LIST]
|
| 349 |
+
local_files = []
|
| 350 |
+
if self.settings.checkpoints_dir and os.path.isdir(self.settings.checkpoints_dir):
|
| 351 |
+
local_files = sorted([f for f in os.listdir(self.settings.checkpoints_dir) if f.endswith((".safetensors", ".ckpt"))])
|
| 352 |
+
self.MODEL_CHOICES = ["None"] + pretrained_names + local_files
|
| 353 |
+
except Exception as e:
|
| 354 |
+
logger.error(f"Error in get_model_list: {e}")
|
| 355 |
+
self.MODEL_CHOICES = ["Error: Check checkpoints_dir path in settings."]
|
| 356 |
+
|
| 357 |
+
def get_vae_model_list(self):
|
| 358 |
+
"""Populates VAE_CHOICES from pretrained list and local VAE directory."""
|
| 359 |
+
print("Loading VAE list...")
|
| 360 |
+
try:
|
| 361 |
+
self.PRETRAINED_VAE_MODELS_LIST = self.model_manager.filter_models_by_model_type(ModelType.VAE.value)
|
| 362 |
+
pretrained_names = [m["model_name"] for m in self.PRETRAINED_VAE_MODELS_LIST]
|
| 363 |
+
local_files = []
|
| 364 |
+
if self.settings.vae_dir and os.path.isdir(self.settings.vae_dir):
|
| 365 |
+
local_files = sorted([f for f in os.listdir(self.settings.vae_dir) if f.endswith((".safetensors", ".pt"))])
|
| 366 |
+
self.VAE_CHOICES = ["Default"] + pretrained_names + local_files
|
| 367 |
+
except Exception as e:
|
| 368 |
+
logger.error(f"Error in get_vae_model_list: {e}")
|
| 369 |
+
self.VAE_CHOICES = ["Error: Check vae_dir path in settings."]
|
| 370 |
+
|
| 371 |
+
def inject_assets(self):
|
| 372 |
+
"""
|
| 373 |
+
This function prepares the payload of CSS and JS code. It's called by the
|
| 374 |
+
app.load() event listener when the Gradio app starts.
|
| 375 |
+
"""
|
| 376 |
+
# Inline code
|
| 377 |
+
css_code = ""
|
| 378 |
+
js_code = ""
|
| 379 |
+
popup_html = """
|
| 380 |
+
<div id="flyout_property_sheet_panel_target" class="flyout-container" style="display: none;">
|
| 381 |
+
</div>
|
| 382 |
+
<div id="flyout_restoration_mask_panel_target" class="flyout-container" style="display: none;">
|
| 383 |
+
</div>
|
| 384 |
+
"""
|
| 385 |
+
# Read from files
|
| 386 |
+
try:
|
| 387 |
+
with open("ui/style.css", "r", encoding="utf-8") as f:
|
| 388 |
+
css_code += f.read() + "\n"
|
| 389 |
+
with open("ui/script.js", "r", encoding="utf-8") as f:
|
| 390 |
+
js_code += f.read() + "\n"
|
| 391 |
+
except FileNotFoundError as e:
|
| 392 |
+
print(f"Warning: Could not read asset file: {e}")
|
| 393 |
+
|
| 394 |
+
return {"js": js_code, "css": css_code, "body_html": popup_html}
|
| 395 |
+
|
| 396 |
+
def map_ui_supir_injection_to_pipeline_params(self, ui_config: SUPIRAdvanced_Config) -> tuple[Optional[InjectionConfigs], Optional[InjectionFlags]]:
|
| 397 |
+
"""
|
| 398 |
+
Maps the configuration from the PropertySheet UI to the pipeline's
|
| 399 |
+
InjectionConfigs and InjectionFlags dataclasses by modifying their fields in place.
|
| 400 |
+
|
| 401 |
+
Args:
|
| 402 |
+
ui_config: An instance of SUPIRAdvanced_Config, as returned by the PropertySheet.
|
| 403 |
+
|
| 404 |
+
Returns:
|
| 405 |
+
A tuple containing populated instances of (InjectionConfigs, InjectionFlags),
|
| 406 |
+
or (None, None) if SFT settings are not applied.
|
| 407 |
+
"""
|
| 408 |
+
|
| 409 |
+
# 1. Initialize the target dataclasses. Their nested fields are also initialized.
|
| 410 |
+
injection_configs = InjectionConfigs()
|
| 411 |
+
injection_flags = InjectionFlags()
|
| 412 |
+
|
| 413 |
+
# 2. Iterate through the fields of the source UI config (SUPIRAdvanced_Config).
|
| 414 |
+
for ui_field in fields(ui_config):
|
| 415 |
+
# Skip fields that are not part of the mapping logic
|
| 416 |
+
if not isinstance(getattr(ui_config, ui_field.name), SUPIRInjectionConfig):
|
| 417 |
+
continue
|
| 418 |
+
|
| 419 |
+
target_field_name = ui_field.name # e.g., "sft_post_mid"
|
| 420 |
+
source_injection_config: SUPIRInjectionConfig = getattr(ui_config, target_field_name)
|
| 421 |
+
|
| 422 |
+
# 3. Map the activation flag.
|
| 423 |
+
flag_name = f"{target_field_name}_active"
|
| 424 |
+
if hasattr(injection_flags, flag_name):
|
| 425 |
+
setattr(injection_flags, flag_name, source_injection_config.sft_active)
|
| 426 |
+
|
| 427 |
+
# 4. Map the scale configuration by modifying the existing nested object.
|
| 428 |
+
if hasattr(injection_configs, target_field_name):
|
| 429 |
+
# Get a direct reference to the nested dataclass instance
|
| 430 |
+
target_scale_config: InjectionScaleConfig = getattr(injection_configs, target_field_name)
|
| 431 |
+
|
| 432 |
+
# If custom scale is enabled, update the fields of the existing instance.
|
| 433 |
+
# Otherwise, it will keep its default values from initialization.
|
| 434 |
+
if source_injection_config.enable_custom_scale:
|
| 435 |
+
target_scale_config.scale_end = float(source_injection_config.scale_end)
|
| 436 |
+
target_scale_config.linear = source_injection_config.linear
|
| 437 |
+
target_scale_config.scale_start = float(source_injection_config.scale_start)
|
| 438 |
+
target_scale_config.reverse = source_injection_config.reverse
|
| 439 |
+
# No 'else' is needed, as the defaults are already set.
|
| 440 |
+
|
| 441 |
+
return injection_configs, injection_flags
|
| 442 |
+
|
| 443 |
+
def map_scheduler_settings_to_config(self, ui_settings: SchedulerSettings) -> SchedulerConfig:
|
| 444 |
+
"""
|
| 445 |
+
Maps the UI-facing SchedulerSettings to the internal SchedulerConfig.
|
| 446 |
+
|
| 447 |
+
This function iterates through the fields of the source `ui_settings`
|
| 448 |
+
and transfers the values to a new `SchedulerConfig` instance if a field
|
| 449 |
+
with the same name exists in the destination. Fields present in
|
| 450 |
+
`SchedulerConfig` but not in `SchedulerSettings` will retain their
|
| 451 |
+
default values.
|
| 452 |
+
|
| 453 |
+
Args:
|
| 454 |
+
ui_settings: An instance of SchedulerSettings, as configured by the user.
|
| 455 |
+
|
| 456 |
+
Returns:
|
| 457 |
+
A populated instance of SchedulerConfig ready for use in the pipeline.
|
| 458 |
+
"""
|
| 459 |
+
# 1. Initialize the target dataclass. This populates it with default values.
|
| 460 |
+
pipeline_config = SchedulerConfig()
|
| 461 |
+
|
| 462 |
+
# 2. Iterate through all fields defined in the source UI settings dataclass.
|
| 463 |
+
for source_field in fields(ui_settings):
|
| 464 |
+
field_name = source_field.name
|
| 465 |
+
|
| 466 |
+
# 3. Check if the destination dataclass has a field with the same name.
|
| 467 |
+
if hasattr(pipeline_config, field_name):
|
| 468 |
+
# 4. If it exists, get the value from the source and set it on the destination.
|
| 469 |
+
source_value = getattr(ui_settings, field_name)
|
| 470 |
+
setattr(pipeline_config, field_name, source_value)
|
| 471 |
+
|
| 472 |
+
return pipeline_config
|
| 473 |
+
|
| 474 |
+
def add_visibility_rules(self, rules_dict: Dict[str, Any], new_visibility_rules: List[Dict[str, bool]]) -> Dict[str, Any]:
|
| 475 |
+
"""
|
| 476 |
+
Adds or updates unconditional visibility rules in the "on_load_actions" list.
|
| 477 |
+
|
| 478 |
+
This function takes a main UI rules dictionary and a list of new
|
| 479 |
+
visibility settings. It then iterates through the list and updates or adds
|
| 480 |
+
"update_visibility" actions to the `on_load_actions` section.
|
| 481 |
+
|
| 482 |
+
If an action for a specific field path already exists, its visibility
|
| 483 |
+
rule will be updated. If it doesn't exist, a new action will be appended.
|
| 484 |
+
|
| 485 |
+
Args:
|
| 486 |
+
rules_dict:
|
| 487 |
+
The main UI rules dictionary, expected to have keys like
|
| 488 |
+
`"dynamic_dependencies"` and `"on_load_actions"`.
|
| 489 |
+
new_visibility_rules:
|
| 490 |
+
A list of dictionaries, where each dictionary contains a single
|
| 491 |
+
key-value pair: the field path (e.g., "general.upscaling_mode")
|
| 492 |
+
and its desired visibility (True or False).
|
| 493 |
+
|
| 494 |
+
Returns:
|
| 495 |
+
The modified rules_dict with the new visibility rules applied.
|
| 496 |
+
"""
|
| 497 |
+
# Safely get the 'on_load_actions' list.
|
| 498 |
+
# If it doesn't exist, create it as an empty list to avoid errors.
|
| 499 |
+
on_load_actions = rules_dict.setdefault("on_load_actions", [])
|
| 500 |
+
|
| 501 |
+
# Iterate through the list of new rules provided by the user.
|
| 502 |
+
for rule_item in new_visibility_rules:
|
| 503 |
+
# Each 'rule_item' is a dictionary like {"general.upscaling_mode": False}.
|
| 504 |
+
for field_path, is_visible in rule_item.items():
|
| 505 |
+
found_existing_rule = False
|
| 506 |
+
# Check if an action for this field_path already exists.
|
| 507 |
+
for action in on_load_actions:
|
| 508 |
+
if action.get("target_field_path") == field_path and action.get("type") == "update_visibility":
|
| 509 |
+
# If it exists, update its "visible" value.
|
| 510 |
+
action["visible"] = is_visible
|
| 511 |
+
found_existing_rule = True
|
| 512 |
+
break # Stop searching once found
|
| 513 |
+
|
| 514 |
+
# If no existing rule was found after checking the whole list...
|
| 515 |
+
if not found_existing_rule:
|
| 516 |
+
# append a new action dictionary in the correct format.
|
| 517 |
+
on_load_actions.append(
|
| 518 |
+
{
|
| 519 |
+
"type": "update_visibility",
|
| 520 |
+
"target_field_path": field_path,
|
| 521 |
+
"visible": is_visible,
|
| 522 |
+
}
|
| 523 |
+
)
|
| 524 |
+
|
| 525 |
+
return rules_dict
|
ui/ui_events.py
ADDED
|
@@ -0,0 +1,1789 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2025 The DEVAIEXP Team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import dataclasses
|
| 16 |
+
import json
|
| 17 |
+
import logging
|
| 18 |
+
import math
|
| 19 |
+
import os
|
| 20 |
+
import queue
|
| 21 |
+
import random
|
| 22 |
+
import sys
|
| 23 |
+
import threading
|
| 24 |
+
from dataclasses import asdict, fields, is_dataclass
|
| 25 |
+
from pathlib import Path
|
| 26 |
+
from typing import Any, Union, cast
|
| 27 |
+
|
| 28 |
+
import gradio as gr
|
| 29 |
+
from gradio_imagemeta.helpers import extract_metadata, transfer_metadata
|
| 30 |
+
from gradio_livelog.utils import ProgressTracker, Tee, TqdmToQueueWriter, capture_logs
|
| 31 |
+
from gradio_propertysheet import PropertySheet
|
| 32 |
+
from gradio_propertysheet.helpers import flatten_dataclass_with_labels
|
| 33 |
+
from PIL import Image
|
| 34 |
+
|
| 35 |
+
from sup_toolbox.enums import (
|
| 36 |
+
ColorFix,
|
| 37 |
+
ImageSizeFixMode,
|
| 38 |
+
PromptMethod,
|
| 39 |
+
RestorerEngine,
|
| 40 |
+
Sampler,
|
| 41 |
+
StartPoint,
|
| 42 |
+
SUPIRModel,
|
| 43 |
+
UpscalerEngine,
|
| 44 |
+
UpscalingMode,
|
| 45 |
+
WeightingMethod,
|
| 46 |
+
)
|
| 47 |
+
from sup_toolbox.utils.system import infer_type
|
| 48 |
+
from ui.globals import pipeline_lock, sup_toolbox_pipe
|
| 49 |
+
from ui.ui_config import (
|
| 50 |
+
APPSETTINGS_SHEET_DEPENDENCY_RULES,
|
| 51 |
+
DEFAULT_PROMPTS,
|
| 52 |
+
RESTORER_CONFIG_MAPPING,
|
| 53 |
+
RESTORER_SHEET_DEPENDENCY_RULES,
|
| 54 |
+
SAMPLER_MAPPING,
|
| 55 |
+
SUPIR_ADVANCED_RULES,
|
| 56 |
+
UPSCALER_CONFIG_MAPPING,
|
| 57 |
+
UPSCALER_SHEET_DEPENDENCY_RULES,
|
| 58 |
+
ControlNetTile_Config,
|
| 59 |
+
FaithDiff_Config,
|
| 60 |
+
SUPIR_Config,
|
| 61 |
+
SUPIRAdvanced_Config,
|
| 62 |
+
)
|
| 63 |
+
from ui.ui_layout import UIComponents
|
| 64 |
+
from ui.ui_state import AppState
|
| 65 |
+
from ui.util.dataclass_helpers import (
|
| 66 |
+
apply_dynamic_changes,
|
| 67 |
+
dataclass_from_dict,
|
| 68 |
+
get_nested_attr,
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
class EventHandlers:
|
| 73 |
+
def __init__(self, state: AppState, components: UIComponents):
|
| 74 |
+
self.state = state
|
| 75 |
+
self.components = components
|
| 76 |
+
|
| 77 |
+
def update_pipeline(self, log_callback=None, progress_bar_handler=None):
|
| 78 |
+
"""
|
| 79 |
+
Initializes or updates the shared SUPToolBoxPipeline instance in a thread-safe manner.
|
| 80 |
+
|
| 81 |
+
This method ensures that the module-level pipeline object (`sup_toolbox_pipe`)
|
| 82 |
+
is created and configured according to the current application state. It uses a
|
| 83 |
+
thread lock (`pipeline_lock`) to guarantee that the pipeline is initialized
|
| 84 |
+
only once, even if multiple threads or processes attempt to call this method
|
| 85 |
+
concurrently.
|
| 86 |
+
|
| 87 |
+
If a pipeline instance does not yet exist, it constructs a new `SUPToolBoxPipeline`
|
| 88 |
+
using the configuration from `self.state.uidata.config` and the provided callbacks.
|
| 89 |
+
If an instance already exists, it simply updates the instance's configuration and
|
| 90 |
+
callback attributes with the latest values from the application state.
|
| 91 |
+
|
| 92 |
+
Parameters
|
| 93 |
+
----------
|
| 94 |
+
log_callback : callable, optional
|
| 95 |
+
A callback function used by the pipeline to emit log messages. Defaults to `None`.
|
| 96 |
+
progress_bar_handler : callable, optional
|
| 97 |
+
A handler for reporting progress, typically for UI progress bars. Defaults to `None`.
|
| 98 |
+
|
| 99 |
+
Accesses
|
| 100 |
+
--------
|
| 101 |
+
self.state.uidata.config : Config
|
| 102 |
+
The main configuration object used to initialize or update the pipeline.
|
| 103 |
+
self.state.cancel_event : threading.Event
|
| 104 |
+
The cancellation event object passed to the pipeline.
|
| 105 |
+
|
| 106 |
+
Side Effects
|
| 107 |
+
------------
|
| 108 |
+
- Initializes the module-level `sup_toolbox_pipe` variable if it is `None`.
|
| 109 |
+
- Mutates the attributes (`config`, `log_callback`, etc.) of an existing
|
| 110 |
+
`sup_toolbox_pipe` instance.
|
| 111 |
+
- The import of `SUPToolBoxPipeline` is done locally within the function
|
| 112 |
+
to support the lazy-loading architecture.
|
| 113 |
+
|
| 114 |
+
Returns
|
| 115 |
+
-------
|
| 116 |
+
None
|
| 117 |
+
"""
|
| 118 |
+
global sup_toolbox_pipe
|
| 119 |
+
from sup_toolbox.sup_toolbox_pipeline import (
|
| 120 |
+
SUPToolBoxPipeline,
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
with pipeline_lock:
|
| 124 |
+
if sup_toolbox_pipe is None:
|
| 125 |
+
sup_toolbox_pipe = SUPToolBoxPipeline(
|
| 126 |
+
self.state.uidata.config,
|
| 127 |
+
log_callback=log_callback,
|
| 128 |
+
progress_bar_handler=progress_bar_handler,
|
| 129 |
+
cancel_event=self.state.cancel_event,
|
| 130 |
+
)
|
| 131 |
+
else:
|
| 132 |
+
sup_toolbox_pipe.config = self.state.uidata.config
|
| 133 |
+
sup_toolbox_pipe.log_callback = log_callback
|
| 134 |
+
sup_toolbox_pipe.progress_bar_handler = progress_bar_handler
|
| 135 |
+
sup_toolbox_pipe.cancel_event = self.state.cancel_event
|
| 136 |
+
|
| 137 |
+
def get_supir_advanced_values(self):
|
| 138 |
+
"""
|
| 139 |
+
Create and return a SUPIRAdvanced_Config instance with specific advanced flags disabled.
|
| 140 |
+
|
| 141 |
+
This function constructs a new SUPIRAdvanced_Config object initialized with its
|
| 142 |
+
default settings and then explicitly disables the "sft_active" flag for two
|
| 143 |
+
cross-up blocks used in stage 1:
|
| 144 |
+
|
| 145 |
+
- cross_up_block_0_stage1.sft_active is set to False
|
| 146 |
+
- cross_up_block_1_stage1.sft_active is set to False
|
| 147 |
+
|
| 148 |
+
Returns:
|
| 149 |
+
SUPIRAdvanced_Config: A configuration object reflecting the default advanced
|
| 150 |
+
settings with the two stage-1 cross-up block SFT flags disabled.
|
| 151 |
+
|
| 152 |
+
Notes:
|
| 153 |
+
- The function does not modify any global state; it returns a freshly
|
| 154 |
+
created configuration instance.
|
| 155 |
+
- It assumes SUPIRAdvanced_Config and its nested attributes
|
| 156 |
+
(cross_up_block_0_stage1, cross_up_block_1_stage1, and their sft_active
|
| 157 |
+
attributes) are defined and accessible.
|
| 158 |
+
- No parameters are required.
|
| 159 |
+
|
| 160 |
+
Example:
|
| 161 |
+
cfg = get_supir_advanced_values()
|
| 162 |
+
assert cfg.cross_up_block_0_stage1.sft_active is False
|
| 163 |
+
assert cfg.cross_up_block_1_stage1.sft_active is False
|
| 164 |
+
"""
|
| 165 |
+
initial_supir_advanced_settings = SUPIRAdvanced_Config()
|
| 166 |
+
initial_supir_advanced_settings.cross_up_block_0_stage1.sft_active = False
|
| 167 |
+
initial_supir_advanced_settings.cross_up_block_1_stage1.sft_active = False
|
| 168 |
+
return initial_supir_advanced_settings
|
| 169 |
+
|
| 170 |
+
def calculate_effective_steps(
|
| 171 |
+
self,
|
| 172 |
+
config: Union[SUPIR_Config, FaithDiff_Config, ControlNetTile_Config],
|
| 173 |
+
is_upscaler: bool,
|
| 174 |
+
) -> int:
|
| 175 |
+
"""
|
| 176 |
+
Calculates the total effective number of inference steps for a given engine configuration.
|
| 177 |
+
|
| 178 |
+
For upscalers in 'Progressive' mode, it simulates the distributed decay of the 'strength'
|
| 179 |
+
parameter across multiple passes to provide an accurate total step count.
|
| 180 |
+
|
| 181 |
+
Args:
|
| 182 |
+
config: The configuration object for the engine.
|
| 183 |
+
is_upscaler: A boolean flag indicating if this config is for an upscaler.
|
| 184 |
+
|
| 185 |
+
Returns:
|
| 186 |
+
The total calculated effective number of steps for the configuration.
|
| 187 |
+
"""
|
| 188 |
+
if not config or not hasattr(config, "general"):
|
| 189 |
+
return 0
|
| 190 |
+
|
| 191 |
+
num_images = getattr(config.general, "num_images", 1)
|
| 192 |
+
num_steps = getattr(config.general, "num_steps", 0)
|
| 193 |
+
initial_strength = getattr(config, "strength", 1.0)
|
| 194 |
+
total_steps_for_one_image = 0
|
| 195 |
+
|
| 196 |
+
# 1. Calculate steps for the first (or only) pass.
|
| 197 |
+
# The strength used is the initial strength.
|
| 198 |
+
first_pass_steps = min(int(num_steps * initial_strength), num_steps)
|
| 199 |
+
total_steps_for_one_image += first_pass_steps
|
| 200 |
+
|
| 201 |
+
# 2. If it's an upscaler in progressive mode, simulate subsequent passes with distributed decay.
|
| 202 |
+
is_progressive_mode = is_upscaler and hasattr(config.general, "upscaling_mode") and config.general.upscaling_mode == UpscalingMode.Progressive.value
|
| 203 |
+
|
| 204 |
+
if is_progressive_mode:
|
| 205 |
+
scale_factor_str = getattr(config.general, "upscale_factor", "1x")
|
| 206 |
+
# Ensure the string is not empty before trying to slice
|
| 207 |
+
if scale_factor_str:
|
| 208 |
+
try:
|
| 209 |
+
target_scale_factor = int(scale_factor_str[:-1])
|
| 210 |
+
except (ValueError, IndexError):
|
| 211 |
+
target_scale_factor = 1
|
| 212 |
+
else:
|
| 213 |
+
target_scale_factor = 1
|
| 214 |
+
|
| 215 |
+
# Only calculate decay if there are multiple passes
|
| 216 |
+
if target_scale_factor > 1: # Changed from > 2 to handle 2x upscale correctly
|
| 217 |
+
num_2x_passes = math.ceil(math.log2(target_scale_factor))
|
| 218 |
+
|
| 219 |
+
if num_2x_passes > 1:
|
| 220 |
+
strength_decay_rate = getattr(config.general, "strength_decay_rate", 0.5)
|
| 221 |
+
|
| 222 |
+
# Calculate the total amount of strength to be reduced over all passes
|
| 223 |
+
total_strength_decay = initial_strength * strength_decay_rate
|
| 224 |
+
|
| 225 |
+
# Distribute this total decay amount over the subsequent passes
|
| 226 |
+
num_decay_steps = num_2x_passes - 1
|
| 227 |
+
strength_decay_per_step = total_strength_decay / num_decay_steps if num_decay_steps > 0 else 0
|
| 228 |
+
|
| 229 |
+
current_strength = initial_strength
|
| 230 |
+
|
| 231 |
+
# Simulate the remaining passes (first pass is already counted)
|
| 232 |
+
for _ in range(num_decay_steps):
|
| 233 |
+
# Apply the linear decay step
|
| 234 |
+
current_strength -= strength_decay_per_step
|
| 235 |
+
|
| 236 |
+
# Apply the safety floor, same as in the pipeline
|
| 237 |
+
final_strength_for_pass = round(max(current_strength, 0.1), 2)
|
| 238 |
+
|
| 239 |
+
# Calculate steps for this pass and add to total
|
| 240 |
+
pass_steps = min(int(num_steps * final_strength_for_pass), num_steps)
|
| 241 |
+
total_steps_for_one_image += pass_steps
|
| 242 |
+
|
| 243 |
+
# 3. Multiply by the number of images to get the overall total
|
| 244 |
+
total_effective_steps = num_images * total_steps_for_one_image
|
| 245 |
+
|
| 246 |
+
return total_effective_steps
|
| 247 |
+
|
| 248 |
+
def prepare_engine_configs(
|
| 249 |
+
self,
|
| 250 |
+
restorer_engine: str,
|
| 251 |
+
restorer_config: dict,
|
| 252 |
+
upscaler_engine: str,
|
| 253 |
+
upscaler_config: dict,
|
| 254 |
+
):
|
| 255 |
+
"""
|
| 256 |
+
Prepares and casts the engine configuration objects based on the selected engine names.
|
| 257 |
+
This function is responsible only for creating the correct dataclass instances.
|
| 258 |
+
|
| 259 |
+
Args:
|
| 260 |
+
restorer_engine: The name of the selected restorer engine.
|
| 261 |
+
restorer_config: The raw configuration data for the restorer from the UI.
|
| 262 |
+
upscaler_engine: The name of the selected upscaler engine.
|
| 263 |
+
upscaler_config: The raw configuration data for the upscaler from the UI.
|
| 264 |
+
|
| 265 |
+
Returns:
|
| 266 |
+
A tuple containing the prepared restorer config and upscaler config objects,
|
| 267 |
+
which may be None if the corresponding engine is not selected.
|
| 268 |
+
"""
|
| 269 |
+
res_config, ups_config = None, None
|
| 270 |
+
if restorer_engine in [
|
| 271 |
+
RestorerEngine.SUPIR.value,
|
| 272 |
+
RestorerEngine.FaithDiff.value,
|
| 273 |
+
]:
|
| 274 |
+
res_config = cast(Union[SUPIR_Config, FaithDiff_Config], restorer_config)
|
| 275 |
+
|
| 276 |
+
if upscaler_engine in [
|
| 277 |
+
UpscalerEngine.SUPIR.value,
|
| 278 |
+
UpscalerEngine.FaithDiff.value,
|
| 279 |
+
UpscalerEngine.ControlNetTile.value,
|
| 280 |
+
]:
|
| 281 |
+
if upscaler_engine == UpscalerEngine.ControlNetTile.value:
|
| 282 |
+
ups_config = cast(ControlNetTile_Config, upscaler_config)
|
| 283 |
+
else:
|
| 284 |
+
ups_config = cast(Union[SUPIR_Config, FaithDiff_Config], upscaler_config)
|
| 285 |
+
|
| 286 |
+
return res_config, ups_config
|
| 287 |
+
|
| 288 |
+
def calculate_total_steps(self, res_config, ups_config, res_engine_name, ups_engine_name):
|
| 289 |
+
"""
|
| 290 |
+
Calculates the total effective inference steps based on the prepared engine configurations.
|
| 291 |
+
|
| 292 |
+
Args:
|
| 293 |
+
res_config: The prepared configuration object for the restorer.
|
| 294 |
+
ups_config: The prepared configuration object for the upscaler.
|
| 295 |
+
res_engine_name: The selected restorer engine name.
|
| 296 |
+
ups_engine_name: The selected upscaler engine name.
|
| 297 |
+
|
| 298 |
+
Returns:
|
| 299 |
+
The total number of effective inference steps.
|
| 300 |
+
"""
|
| 301 |
+
|
| 302 |
+
total_inference_steps = 0
|
| 303 |
+
if res_config and res_engine_name != "None":
|
| 304 |
+
total_inference_steps += self.calculate_effective_steps(res_config, is_upscaler=False)
|
| 305 |
+
|
| 306 |
+
if ups_config and ups_engine_name != "None":
|
| 307 |
+
total_inference_steps += self.calculate_effective_steps(ups_config, is_upscaler=True)
|
| 308 |
+
|
| 309 |
+
return total_inference_steps
|
| 310 |
+
|
| 311 |
+
def generate_image_metadata(
|
| 312 |
+
self,
|
| 313 |
+
res_config_class,
|
| 314 |
+
ups_config_class,
|
| 315 |
+
res_supir_advanced_config_class,
|
| 316 |
+
ups_supir_advanced_config_class,
|
| 317 |
+
input_params,
|
| 318 |
+
res_sampler_config_class,
|
| 319 |
+
ups_sampler_config_class,
|
| 320 |
+
):
|
| 321 |
+
"""
|
| 322 |
+
Generates metadata by aggregating configuration values from various sources.
|
| 323 |
+
This function takes multiple configuration classes and input parameters, flattens them,
|
| 324 |
+
and combines them into a single dictionary with prefixed keys for metadata tracking.
|
| 325 |
+
Args:
|
| 326 |
+
res_config_class (dict): Configuration for the image restoration.
|
| 327 |
+
ups_config_class (dict): Configuration for the image upscaling.
|
| 328 |
+
res_supir_advanced_config_class (dict): Advanced SUPIR configuration for restoration.
|
| 329 |
+
ups_supir_advanced_config_class (dict): Advanced SUPIR configuration for upscaling.
|
| 330 |
+
input_params (dict): User input parameters including engine selections.
|
| 331 |
+
res_sampler_config_class (dict): Sampler configuration for restoration.
|
| 332 |
+
ups_sampler_config_class (dict): Sampler configuration for upscaling.
|
| 333 |
+
Returns:
|
| 334 |
+
Dict[str, Any]: A flattened dictionary containing all configuration values with
|
| 335 |
+
prefixed keys that identify their source and purpose. For example:
|
| 336 |
+
{
|
| 337 |
+
"Restorer - Engine1 - param1": value1,
|
| 338 |
+
"Upscaler - Engine2 - param2": value2,
|
| 339 |
+
...
|
| 340 |
+
}
|
| 341 |
+
Notes:
|
| 342 |
+
- Keys are prefixed based on their source (Restorer/Upscaler) and engine type
|
| 343 |
+
- Processing only occurs if the respective engine is selected and not "none"
|
| 344 |
+
- Input parameters are preserved in the output dictionary
|
| 345 |
+
"""
|
| 346 |
+
res_engine, ups_engine = (
|
| 347 |
+
input_params["Image Restore Engine"],
|
| 348 |
+
input_params["Image Upscale Engine"],
|
| 349 |
+
)
|
| 350 |
+
|
| 351 |
+
all_values = input_params.copy()
|
| 352 |
+
|
| 353 |
+
def process_and_prefix(instance: Any, prefix: str):
|
| 354 |
+
"""Helper to flatten a dataclass and add a final prefix to its keys."""
|
| 355 |
+
if instance:
|
| 356 |
+
for key, value in flatten_dataclass_with_labels(instance).items():
|
| 357 |
+
all_values[f"{prefix} - {key}"] = value
|
| 358 |
+
|
| 359 |
+
if res_engine and res_engine.lower() != "none":
|
| 360 |
+
process_and_prefix(res_config_class, f"Restorer - {res_engine}")
|
| 361 |
+
process_and_prefix(res_supir_advanced_config_class, "Restorer")
|
| 362 |
+
process_and_prefix(res_sampler_config_class, "Restorer - Sampler")
|
| 363 |
+
|
| 364 |
+
if ups_engine and ups_engine.lower() != "none":
|
| 365 |
+
process_and_prefix(ups_config_class, f"Upscaler - {ups_engine}")
|
| 366 |
+
process_and_prefix(ups_supir_advanced_config_class, "Upscaler")
|
| 367 |
+
process_and_prefix(ups_sampler_config_class, "Upscaler - Sampler")
|
| 368 |
+
|
| 369 |
+
return all_values
|
| 370 |
+
|
| 371 |
+
def update_preset_list(self, preset):
|
| 372 |
+
"""
|
| 373 |
+
Updates the preset list in the UI dropdown with the latest presets and sets a specific value.
|
| 374 |
+
|
| 375 |
+
Args:
|
| 376 |
+
preset (str): The preset value to be selected in the dropdown after updating the list
|
| 377 |
+
|
| 378 |
+
Returns:
|
| 379 |
+
gr.update: A Gradio update object containing the new preset choices and selected value
|
| 380 |
+
"""
|
| 381 |
+
self.state.uidata.get_preset_list()
|
| 382 |
+
return gr.update(choices=self.state.uidata.PRESETS_LIST, value=preset)
|
| 383 |
+
|
| 384 |
+
def save_preset(self, preset_name: str, *all_component_values):
|
| 385 |
+
"""
|
| 386 |
+
Parameters:
|
| 387 |
+
preset_name (str): The name of the preset to be saved. Must not be empty or a default preset name.
|
| 388 |
+
*all_component_values: A variable number of values representing the current state of UI components.
|
| 389 |
+
Raises:
|
| 390 |
+
gr.Error: If the preset name is empty or if an attempt is made to overwrite a default preset.
|
| 391 |
+
Notes:
|
| 392 |
+
- Maps the received values back to the components using the order of the input list passed to the .click() event.
|
| 393 |
+
- It is safer to map by elem_id.
|
| 394 |
+
- Removes 'restorer_supir_advanced_settings' if the 'restorer_engine' is not set to SUPIR.
|
| 395 |
+
- Removes 'upscaler_supir_advanced_settings' if the 'upscaler_engine' is not set to SUPIR.
|
| 396 |
+
- For PropertySheets, if the value is a dataclass, it is converted to a dictionary before saving.
|
| 397 |
+
"""
|
| 398 |
+
if not preset_name or not preset_name.strip():
|
| 399 |
+
raise gr.Error("Please enter a preset name.")
|
| 400 |
+
|
| 401 |
+
if preset_name in self.state.uidata.PRESETS_LIST and "Default:" in preset_name:
|
| 402 |
+
raise gr.Error("A default preset cannot be overwritten; please set a different name.")
|
| 403 |
+
|
| 404 |
+
preset_data = {}
|
| 405 |
+
ALL_UI_COMPONENTS = self.components.ALL_UI_COMPONENTS
|
| 406 |
+
component_values = dict(zip(ALL_UI_COMPONENTS.keys(), all_component_values))
|
| 407 |
+
component_values["restorer_sampler_settings"] = SAMPLER_MAPPING["restorer_sampler"]
|
| 408 |
+
component_values["upscaler_sampler_settings"] = SAMPLER_MAPPING["upscaler_sampler"]
|
| 409 |
+
|
| 410 |
+
if component_values.get("restorer_engine") != RestorerEngine.SUPIR.value:
|
| 411 |
+
component_values.pop("restorer_supir_advanced_settings", None)
|
| 412 |
+
|
| 413 |
+
if component_values.get("upscaler_engine") != RestorerEngine.SUPIR.value:
|
| 414 |
+
component_values.pop("upscaler_supir_advanced_settings", None)
|
| 415 |
+
|
| 416 |
+
for elem_id, value in component_values.items():
|
| 417 |
+
if dataclasses.is_dataclass(value):
|
| 418 |
+
preset_data[elem_id] = asdict(value)
|
| 419 |
+
else:
|
| 420 |
+
preset_data[elem_id] = value
|
| 421 |
+
|
| 422 |
+
self.state.uidata.save_preset(preset_name.strip(), preset_data)
|
| 423 |
+
gr.Info(f"Preset '{preset_name}' saved successfully!")
|
| 424 |
+
|
| 425 |
+
def load_preset(self, preset_name: str):
|
| 426 |
+
"""
|
| 427 |
+
Load Presets to UI components. It also updates the backend state, such as
|
| 428 |
+
SAMPLER_MAPPING, based on the loaded preset.
|
| 429 |
+
Parameters:
|
| 430 |
+
preset_name (str): The name of the preset to load. Must be a non-empty string.
|
| 431 |
+
Returns:
|
| 432 |
+
List[gr.Update]: A list of updates for the UI components, where each update
|
| 433 |
+
corresponds to the state of a component after loading the preset.
|
| 434 |
+
Raises:
|
| 435 |
+
gr.Error: If no preset is selected, if the preset is empty or cannot be found,
|
| 436 |
+
or if there is an error during the loading process.
|
| 437 |
+
The function handles different types of UI components, including PropertySheets,
|
| 438 |
+
and updates their values based on the data retrieved from the preset. It also
|
| 439 |
+
ensures that the backend state is synchronized with the loaded preset data.
|
| 440 |
+
"""
|
| 441 |
+
|
| 442 |
+
ALL_UI_COMPONENTS = self.components.ALL_UI_COMPONENTS
|
| 443 |
+
# Prepare a default output list. `gr.skip()` means "do not change this component".
|
| 444 |
+
output_updates = [gr.skip()] * len(ALL_UI_COMPONENTS)
|
| 445 |
+
if not preset_name or not preset_name.strip():
|
| 446 |
+
raise gr.Error("No preset selected to load.")
|
| 447 |
+
|
| 448 |
+
try:
|
| 449 |
+
# Load the preset data from the JSON file
|
| 450 |
+
preset_data = self.state.uidata.load_preset(preset_name.strip())
|
| 451 |
+
if not preset_data:
|
| 452 |
+
raise gr.Error(f"Preset '{preset_name}' is empty or could not be found.")
|
| 453 |
+
|
| 454 |
+
# Create a mapping of components to their indices in the output list for easy access
|
| 455 |
+
component_to_index = {id(comp): i for i, comp in enumerate(ALL_UI_COMPONENTS.values())}
|
| 456 |
+
|
| 457 |
+
for elem_id, component in ALL_UI_COMPONENTS.items():
|
| 458 |
+
# Check if there is a value for this component in the preset
|
| 459 |
+
if elem_id in preset_data:
|
| 460 |
+
value_from_preset = preset_data[elem_id]
|
| 461 |
+
output_index = component_to_index.get(id(component))
|
| 462 |
+
if output_index is None:
|
| 463 |
+
continue
|
| 464 |
+
|
| 465 |
+
# If the component is a PropertySheet, reconstruct the dataclass instance
|
| 466 |
+
if isinstance(component, PropertySheet):
|
| 467 |
+
dc_type = type(getattr(component, "_dataclass_value", None))
|
| 468 |
+
|
| 469 |
+
# If the component is a PropertySheet, reconstruct the dataclass instance
|
| 470 |
+
if dc_type and is_dataclass(dc_type) and isinstance(value_from_preset, dict):
|
| 471 |
+
if component.elem_id == "restorer_settings" and output_updates[0]["value"] == RestorerEngine.SUPIR.value:
|
| 472 |
+
dc_type = type(RESTORER_CONFIG_MAPPING[RestorerEngine.SUPIR.value])
|
| 473 |
+
instance = dataclass_from_dict(dc_type, value_from_preset)
|
| 474 |
+
instance = apply_dynamic_changes(instance, RESTORER_SHEET_DEPENDENCY_RULES)
|
| 475 |
+
RESTORER_CONFIG_MAPPING[RestorerEngine.SUPIR.value] = instance
|
| 476 |
+
elif component.elem_id == "restorer_supir_advanced_settings" and output_updates[0]["value"] == RestorerEngine.SUPIR.value:
|
| 477 |
+
dc_type = type(RESTORER_CONFIG_MAPPING["SUPIRAdvanced"])
|
| 478 |
+
instance = dataclass_from_dict(dc_type, value_from_preset)
|
| 479 |
+
instance = apply_dynamic_changes(instance, SUPIR_ADVANCED_RULES)
|
| 480 |
+
RESTORER_CONFIG_MAPPING["SUPIRAdvanced"] = instance
|
| 481 |
+
elif component.elem_id == "restorer_settings" and output_updates[0]["value"] == RestorerEngine.FaithDiff.value:
|
| 482 |
+
dc_type = type(RESTORER_CONFIG_MAPPING[RestorerEngine.FaithDiff.value])
|
| 483 |
+
instance = dataclass_from_dict(dc_type, value_from_preset)
|
| 484 |
+
instance = apply_dynamic_changes(instance, RESTORER_SHEET_DEPENDENCY_RULES)
|
| 485 |
+
RESTORER_CONFIG_MAPPING[RestorerEngine.FaithDiff.value] = instance
|
| 486 |
+
|
| 487 |
+
if component.elem_id == "upscaler_settings" and output_updates[1]["value"] == UpscalerEngine.SUPIR.value:
|
| 488 |
+
dc_type = type(UPSCALER_CONFIG_MAPPING[UpscalerEngine.SUPIR.value])
|
| 489 |
+
instance = dataclass_from_dict(dc_type, value_from_preset)
|
| 490 |
+
UPSCALER_CONFIG_MAPPING[UpscalerEngine.SUPIR.value] = instance
|
| 491 |
+
elif component.elem_id == "upscaler_supir_advanced_settings" and output_updates[1]["value"] == UpscalerEngine.SUPIR.value:
|
| 492 |
+
dc_type = type(UPSCALER_CONFIG_MAPPING["SUPIRAdvanced"])
|
| 493 |
+
instance = dataclass_from_dict(dc_type, value_from_preset)
|
| 494 |
+
instance = apply_dynamic_changes(instance, SUPIR_ADVANCED_RULES)
|
| 495 |
+
UPSCALER_CONFIG_MAPPING["SUPIRAdvanced"] = instance
|
| 496 |
+
elif component.elem_id == "upscaler_settings" and output_updates[1]["value"] == UpscalerEngine.FaithDiff.value:
|
| 497 |
+
dc_type = type(UPSCALER_CONFIG_MAPPING[UpscalerEngine.FaithDiff.value])
|
| 498 |
+
instance = dataclass_from_dict(dc_type, value_from_preset)
|
| 499 |
+
UPSCALER_CONFIG_MAPPING[UpscalerEngine.FaithDiff.value] = instance
|
| 500 |
+
elif component.elem_id == "upscaler_settings" and output_updates[1]["value"] == UpscalerEngine.ControlNetTile.value:
|
| 501 |
+
dc_type = type(UPSCALER_CONFIG_MAPPING[UpscalerEngine.ControlNetTile.value])
|
| 502 |
+
instance = dataclass_from_dict(dc_type, value_from_preset)
|
| 503 |
+
UPSCALER_CONFIG_MAPPING[UpscalerEngine.ControlNetTile.value] = instance
|
| 504 |
+
|
| 505 |
+
if instance is None:
|
| 506 |
+
instance = dataclass_from_dict(dc_type, value_from_preset)
|
| 507 |
+
|
| 508 |
+
output_updates[output_index] = gr.update(value=instance)
|
| 509 |
+
# For all other standard Gradio components
|
| 510 |
+
else:
|
| 511 |
+
output_updates[output_index] = gr.update(value=value_from_preset)
|
| 512 |
+
|
| 513 |
+
# Update the backend state (SAMPLER_MAPPING)
|
| 514 |
+
# Iterate over the special keys you saved for the samplers
|
| 515 |
+
for sampler_key in [
|
| 516 |
+
"restorer_sampler_settings",
|
| 517 |
+
"upscaler_sampler_settings",
|
| 518 |
+
]:
|
| 519 |
+
if sampler_key in preset_data:
|
| 520 |
+
sampler_data = preset_data[sampler_key]
|
| 521 |
+
# The key in SAMPLER_MAPPING is the elem_id (e.g., "restorer_sampler_settings")
|
| 522 |
+
target_instance = SAMPLER_MAPPING.get(sampler_key.rpartition("_")[0])
|
| 523 |
+
|
| 524 |
+
if target_instance and is_dataclass(target_instance) and isinstance(sampler_data, dict):
|
| 525 |
+
# Populate the existing instance in SAMPLER_MAPPING with the preset data
|
| 526 |
+
# This is similar to how on_flyout_change works
|
| 527 |
+
for field_name, value in sampler_data.items():
|
| 528 |
+
if hasattr(target_instance, field_name):
|
| 529 |
+
setattr(target_instance, field_name, value)
|
| 530 |
+
|
| 531 |
+
gr.Info(f"Preset '{preset_name}' loaded successfully.")
|
| 532 |
+
return output_updates
|
| 533 |
+
except Exception as e:
|
| 534 |
+
raise gr.Error(f"Failed to load or apply preset '{preset_name}': {e}")
|
| 535 |
+
|
| 536 |
+
def restart(self):
|
| 537 |
+
"""Triggers a restart of the Python script."""
|
| 538 |
+
print("Please wait. The UI is being restarted...")
|
| 539 |
+
os.execv(sys.executable, [os.path.basename(sys.executable)] + sys.argv)
|
| 540 |
+
|
| 541 |
+
# region Flyout Event Function Logic
|
| 542 |
+
def on_handle_flyout_toggle(self, is_vis, current_anchor, *, clicked_elem_id, target_elem_id):
|
| 543 |
+
"""
|
| 544 |
+
Manages the visibility and content of a flyout panel based on user interaction.
|
| 545 |
+
|
| 546 |
+
This function determines whether to show, hide, or update a flyout panel.
|
| 547 |
+
- If the clicked element is already the active anchor, it hides the flyout.
|
| 548 |
+
- Otherwise, it shows the flyout, positioning it relative to the clicked element,
|
| 549 |
+
and populates it with the correct settings from `SAMPLER_MAPPING`.
|
| 550 |
+
|
| 551 |
+
Args:
|
| 552 |
+
is_vis (bool): The current visibility state of the flyout.
|
| 553 |
+
current_anchor (str): The elem_id of the current element the flyout is anchored to.
|
| 554 |
+
clicked_elem_id (str): The elem_id of the element that was just clicked.
|
| 555 |
+
target_elem_id (str): The elem_id of the flyout panel to control.
|
| 556 |
+
|
| 557 |
+
Returns:
|
| 558 |
+
Tuple[bool, Optional[str], gr.update, gr.update]: A tuple of updates for:
|
| 559 |
+
- flyout_visible (gr.State)
|
| 560 |
+
- active_anchor_id (gr.State)
|
| 561 |
+
- flyout_sheet (PropertySheet content)
|
| 562 |
+
- js_data_bridge (JSON data for the frontend)
|
| 563 |
+
"""
|
| 564 |
+
settings_obj = SAMPLER_MAPPING.get(clicked_elem_id)
|
| 565 |
+
|
| 566 |
+
if settings_obj is None: # not a propertysheet
|
| 567 |
+
# Command JS to show and position
|
| 568 |
+
js_data = json.dumps(
|
| 569 |
+
{
|
| 570 |
+
"isVisible": True,
|
| 571 |
+
"anchorId": clicked_elem_id,
|
| 572 |
+
"targetId": target_elem_id,
|
| 573 |
+
}
|
| 574 |
+
)
|
| 575 |
+
return True, clicked_elem_id, gr.skip(), gr.update(value=js_data)
|
| 576 |
+
|
| 577 |
+
if is_vis and current_anchor == clicked_elem_id:
|
| 578 |
+
# Command JS to hide
|
| 579 |
+
js_data = json.dumps({"isVisible": False, "anchorId": None, "targetId": target_elem_id})
|
| 580 |
+
return False, None, gr.update(), gr.update(value=js_data)
|
| 581 |
+
else:
|
| 582 |
+
# Command JS to show and position
|
| 583 |
+
js_data = json.dumps(
|
| 584 |
+
{
|
| 585 |
+
"isVisible": True,
|
| 586 |
+
"anchorId": clicked_elem_id,
|
| 587 |
+
"targetId": target_elem_id,
|
| 588 |
+
}
|
| 589 |
+
)
|
| 590 |
+
return (
|
| 591 |
+
True,
|
| 592 |
+
clicked_elem_id,
|
| 593 |
+
gr.update(value=settings_obj),
|
| 594 |
+
gr.update(value=js_data),
|
| 595 |
+
)
|
| 596 |
+
|
| 597 |
+
def on_update_ear_visibility(self, elem_id: str):
|
| 598 |
+
"""
|
| 599 |
+
Controls the visibility of an 'ear' button next to a sampler dropdown.
|
| 600 |
+
|
| 601 |
+
The button is made visible only if the selected sampler has advanced settings
|
| 602 |
+
defined in the global `SAMPLER_MAPPING`.
|
| 603 |
+
|
| 604 |
+
Args:
|
| 605 |
+
elem_id (str): The elem_id of the sampler dropdown component.
|
| 606 |
+
|
| 607 |
+
Returns:
|
| 608 |
+
gr.update: A Gradio update object to set the visibility of the ear button.
|
| 609 |
+
"""
|
| 610 |
+
has_settings = elem_id in SAMPLER_MAPPING
|
| 611 |
+
return gr.update(visible=has_settings)
|
| 612 |
+
|
| 613 |
+
def on_flyout_change(self, updated_settings, active_id):
|
| 614 |
+
"""
|
| 615 |
+
Callback for when the flyout PropertySheet's value changes.
|
| 616 |
+
|
| 617 |
+
It updates the corresponding sampler settings object in the global
|
| 618 |
+
`SAMPLER_MAPPING` dictionary with the new values from the flyout.
|
| 619 |
+
|
| 620 |
+
Args:
|
| 621 |
+
updated_settings (dataclass): The new settings object from the PropertySheet.
|
| 622 |
+
active_id (str): The elem_id of the component that triggered the flyout,
|
| 623 |
+
used as a key in `SAMPLER_MAPPING`.
|
| 624 |
+
"""
|
| 625 |
+
if updated_settings is None or active_id is None:
|
| 626 |
+
return
|
| 627 |
+
|
| 628 |
+
if active_id in SAMPLER_MAPPING:
|
| 629 |
+
original_settings_obj = SAMPLER_MAPPING[active_id]
|
| 630 |
+
for f in dataclasses.fields(original_settings_obj):
|
| 631 |
+
if hasattr(updated_settings, f.name):
|
| 632 |
+
setattr(original_settings_obj, f.name, getattr(updated_settings, f.name))
|
| 633 |
+
|
| 634 |
+
def on_close_the_flyout(self, target_elem_id):
|
| 635 |
+
"""
|
| 636 |
+
Closes the flyout panel.
|
| 637 |
+
|
| 638 |
+
This function prepares the necessary state and JS data to command the frontend
|
| 639 |
+
to hide the flyout panel.
|
| 640 |
+
|
| 641 |
+
Args:
|
| 642 |
+
target_elem_id (str): The elem_id of the flyout panel to close.
|
| 643 |
+
|
| 644 |
+
Returns:
|
| 645 |
+
Tuple[bool, None, gr.update]: Updates for flyout visibility state,
|
| 646 |
+
active anchor ID, and the JS data bridge.
|
| 647 |
+
"""
|
| 648 |
+
js_data = json.dumps({"isVisible": False, "anchorId": None, "targetId": target_elem_id})
|
| 649 |
+
return False, None, gr.update(value=js_data)
|
| 650 |
+
|
| 651 |
+
def initial_flyout_setup(self):
|
| 652 |
+
"""
|
| 653 |
+
Sets the initial visibility for all ear buttons on application load.
|
| 654 |
+
|
| 655 |
+
Returns:
|
| 656 |
+
Dict[gr.Button, gr.update]: A dictionary mapping each ear button
|
| 657 |
+
component to its visibility update.
|
| 658 |
+
"""
|
| 659 |
+
return {
|
| 660 |
+
"restorer_sampler_ear_btn": self.on_update_ear_visibility("restorer_sampler"),
|
| 661 |
+
"upscaler_sampler_ear_btn": self.on_update_ear_visibility("upscaler_sampler"),
|
| 662 |
+
}
|
| 663 |
+
|
| 664 |
+
# endregion
|
| 665 |
+
|
| 666 |
+
# region Tokenizer Event Function Logic
|
| 667 |
+
def update_positive_tokenizer(self, p1, p2):
|
| 668 |
+
"""
|
| 669 |
+
Combines two positive prompt textboxes into a single string for the tokenizer.
|
| 670 |
+
|
| 671 |
+
Args:
|
| 672 |
+
p1 (str): Content of the first prompt textbox.
|
| 673 |
+
p2 (str): Content of the second prompt textbox.
|
| 674 |
+
|
| 675 |
+
Returns:
|
| 676 |
+
gr.update: An update for the TokenizerTextBox with the combined prompt.
|
| 677 |
+
"""
|
| 678 |
+
return gr.update(value=f"{p1}\n{p2}".strip())
|
| 679 |
+
|
| 680 |
+
def update_positive_prompt_helper(self, evt: gr.EventData):
|
| 681 |
+
"""
|
| 682 |
+
Updates the target textbox for the positive TagGroupHelper.
|
| 683 |
+
|
| 684 |
+
This is triggered on focus for a prompt textbox, ensuring that when a tag
|
| 685 |
+
is clicked in the helper, it's inserted into the currently active prompt box.
|
| 686 |
+
|
| 687 |
+
Args:
|
| 688 |
+
evt (gr.EventData): Event data from Gradio, containing the target component.
|
| 689 |
+
|
| 690 |
+
Returns:
|
| 691 |
+
gr.update: An update for the TagGroupHelper to set its target textbox ID.
|
| 692 |
+
"""
|
| 693 |
+
return gr.update(target_textbox_id=evt.target.elem_id)
|
| 694 |
+
|
| 695 |
+
def update_prompt_helper_from_tab(self, evt: gr.EventData):
|
| 696 |
+
"""
|
| 697 |
+
Updates the target textboxes for both TagGroupHelpers when a tab is selected.
|
| 698 |
+
|
| 699 |
+
This ensures the helpers target the correct prompt and negative prompt
|
| 700 |
+
textboxes based on whether the 'Restoration' or 'Upscaling' tab is active.
|
| 701 |
+
|
| 702 |
+
Args:
|
| 703 |
+
evt (gr.EventData): Event data from Gradio, containing the selected tab.
|
| 704 |
+
|
| 705 |
+
Returns:
|
| 706 |
+
Dict[TagGroupHelper, gr.update]: A dictionary of updates for both the
|
| 707 |
+
positive and negative tag helpers.
|
| 708 |
+
"""
|
| 709 |
+
if evt.target.elem_id == "res-tab":
|
| 710 |
+
return (
|
| 711 |
+
gr.update(target_textbox_id="restorer_prompt_1"),
|
| 712 |
+
gr.update(target_textbox_id="restorer_negative_prompt"),
|
| 713 |
+
)
|
| 714 |
+
else:
|
| 715 |
+
return (
|
| 716 |
+
gr.update(target_textbox_id="upscaler_prompt_1"),
|
| 717 |
+
gr.update(target_textbox_id="upscaler_negative_prompt"),
|
| 718 |
+
)
|
| 719 |
+
|
| 720 |
+
# endregion
|
| 721 |
+
|
| 722 |
+
# region Others UI Event Function Logic
|
| 723 |
+
def on_reset_settings(self):
|
| 724 |
+
"""
|
| 725 |
+
Handles the 'reset settings' button click. Loads default
|
| 726 |
+
settings via `uidata` and displays an info message to the user before
|
| 727 |
+
the UI is restarted.
|
| 728 |
+
"""
|
| 729 |
+
self.state.uidata.load_defaults()
|
| 730 |
+
gr.Info("Defaults loaded! UI will be restarted!")
|
| 731 |
+
|
| 732 |
+
def on_save_settings(self, settings_dict):
|
| 733 |
+
"""
|
| 734 |
+
Handles the 'save settings' button click. Converts the settings
|
| 735 |
+
dataclass to a dictionary and saves it using `uidata`. Displays a
|
| 736 |
+
confirmation message before the UI is restarted.
|
| 737 |
+
|
| 738 |
+
Args:
|
| 739 |
+
settings_dict (AppSettings): The settings dataclass instance from the PropertySheet.
|
| 740 |
+
"""
|
| 741 |
+
values_dict = asdict(settings_dict)
|
| 742 |
+
self.state.uidata.save_settings(values_dict)
|
| 743 |
+
gr.Info("Settings saved! UI will be restarted!")
|
| 744 |
+
|
| 745 |
+
def on_settings_sheet_change(self, updated_settings: Any):
|
| 746 |
+
"""
|
| 747 |
+
Handles changes in the AppSettings PropertySheet.
|
| 748 |
+
|
| 749 |
+
It applies dynamic visibility rules to the settings sheet based on the
|
| 750 |
+
current values (e.g., hiding `quantization_mode` if `quantization_method`
|
| 751 |
+
is 'None').
|
| 752 |
+
|
| 753 |
+
Args:
|
| 754 |
+
updated_settings (Any): The updated AppSettings dataclass instance
|
| 755 |
+
from the PropertySheet.
|
| 756 |
+
|
| 757 |
+
Returns:
|
| 758 |
+
AppSettings: The modified AppSettings instance with dynamic rules applied.
|
| 759 |
+
"""
|
| 760 |
+
if updated_settings is None:
|
| 761 |
+
return updated_settings
|
| 762 |
+
|
| 763 |
+
rules_to_apply = []
|
| 764 |
+
|
| 765 |
+
if updated_settings.quantization_method == "None":
|
| 766 |
+
rules_to_apply.append({"quantization_mode": False})
|
| 767 |
+
else:
|
| 768 |
+
rules_to_apply.append({"quantization_mode": True})
|
| 769 |
+
|
| 770 |
+
self.state.uidata.add_visibility_rules(APPSETTINGS_SHEET_DEPENDENCY_RULES, rules_to_apply)
|
| 771 |
+
return apply_dynamic_changes(updated_settings, APPSETTINGS_SHEET_DEPENDENCY_RULES)
|
| 772 |
+
|
| 773 |
+
def on_set_default_prompts(
|
| 774 |
+
self,
|
| 775 |
+
res_engine_name,
|
| 776 |
+
ups_engine_name,
|
| 777 |
+
res_prompt_value,
|
| 778 |
+
res_prompt_2_value,
|
| 779 |
+
res_negative_prompt_value,
|
| 780 |
+
ups_prompt_value,
|
| 781 |
+
ups_prompt_2_value,
|
| 782 |
+
ups_negative_prompt_value,
|
| 783 |
+
):
|
| 784 |
+
"""
|
| 785 |
+
Sets default prompts in the UI when an engine is selected or changed.
|
| 786 |
+
|
| 787 |
+
This function checks if the current prompt fields are empty or if the engine
|
| 788 |
+
has changed. If so, it populates the respective prompt fields with
|
| 789 |
+
pre-defined default values for the newly selected engine.
|
| 790 |
+
|
| 791 |
+
Args:
|
| 792 |
+
res_engine_name (str): The selected restorer engine name.
|
| 793 |
+
ups_engine_name (str): The selected upscaler engine name.
|
| 794 |
+
res_prompt_value (str): Current value of the restorer's prompt 1.
|
| 795 |
+
res_prompt_2_value (str): Current value of the restorer's prompt 2.
|
| 796 |
+
res_negative_prompt_value (str): Current value of the restorer's negative prompt.
|
| 797 |
+
ups_prompt_value (str): Current value of the upscaler's prompt 1.
|
| 798 |
+
ups_prompt_2_value (str): Current value of the upscaler's prompt 2.
|
| 799 |
+
ups_negative_prompt_value (str): Current value of the upscaler's negative prompt.
|
| 800 |
+
|
| 801 |
+
Returns:
|
| 802 |
+
Tuple[str, str, str, str, str, str]: A tuple containing the new values
|
| 803 |
+
for all six prompt textboxes.
|
| 804 |
+
"""
|
| 805 |
+
|
| 806 |
+
restorer_engine_selected = self.state.restorer_engine_selected
|
| 807 |
+
upscaler_engine_selected = self.state.upscaler_engine_selected
|
| 808 |
+
|
| 809 |
+
# Set defaults for restorer prompts only if input is empty/None
|
| 810 |
+
if res_engine_name == "None":
|
| 811 |
+
res_prompt = ""
|
| 812 |
+
elif not res_prompt_value or (restorer_engine_selected != res_engine_name):
|
| 813 |
+
res_prompt = DEFAULT_PROMPTS["Restorer"][res_engine_name]["prompt"]
|
| 814 |
+
else:
|
| 815 |
+
res_prompt = res_prompt_value
|
| 816 |
+
|
| 817 |
+
if res_engine_name == "None":
|
| 818 |
+
res_prompt_2 = ""
|
| 819 |
+
elif not res_prompt_2_value or (restorer_engine_selected != res_engine_name):
|
| 820 |
+
res_prompt_2 = DEFAULT_PROMPTS["Restorer"][res_engine_name]["prompt_2"]
|
| 821 |
+
else:
|
| 822 |
+
res_prompt_2 = res_prompt_2_value
|
| 823 |
+
|
| 824 |
+
if res_engine_name == "None":
|
| 825 |
+
res_negative = ""
|
| 826 |
+
elif not res_negative_prompt_value or (restorer_engine_selected != res_engine_name):
|
| 827 |
+
res_negative = DEFAULT_PROMPTS["Restorer"][res_engine_name]["negative_prompt"]
|
| 828 |
+
else:
|
| 829 |
+
res_negative = res_negative_prompt_value
|
| 830 |
+
|
| 831 |
+
# Set defaults for upscaler prompts only if input is empty/None
|
| 832 |
+
if ups_engine_name == "None":
|
| 833 |
+
ups_prompt = ""
|
| 834 |
+
elif not ups_prompt_value or (upscaler_engine_selected != ups_engine_name):
|
| 835 |
+
ups_prompt = DEFAULT_PROMPTS["Upscaler"][ups_engine_name]["prompt"]
|
| 836 |
+
else:
|
| 837 |
+
ups_prompt = ups_prompt_value
|
| 838 |
+
|
| 839 |
+
if ups_engine_name == "None":
|
| 840 |
+
ups_prompt_2 = ""
|
| 841 |
+
elif not ups_prompt_2_value or (upscaler_engine_selected != ups_engine_name):
|
| 842 |
+
ups_prompt_2 = DEFAULT_PROMPTS["Upscaler"][ups_engine_name]["prompt_2"]
|
| 843 |
+
else:
|
| 844 |
+
ups_prompt_2 = ups_prompt_2_value
|
| 845 |
+
|
| 846 |
+
if ups_engine_name == "None":
|
| 847 |
+
ups_negative = ""
|
| 848 |
+
elif not ups_negative_prompt_value or (upscaler_engine_selected != ups_engine_name):
|
| 849 |
+
ups_negative = DEFAULT_PROMPTS["Upscaler"][ups_engine_name]["negative_prompt"]
|
| 850 |
+
else:
|
| 851 |
+
ups_negative = ups_negative_prompt_value
|
| 852 |
+
|
| 853 |
+
self.state.restorer_engine_selected = res_engine_name
|
| 854 |
+
self.state.upscaler_engine_selected = ups_engine_name
|
| 855 |
+
|
| 856 |
+
return (
|
| 857 |
+
res_prompt,
|
| 858 |
+
res_prompt_2,
|
| 859 |
+
res_negative,
|
| 860 |
+
ups_prompt,
|
| 861 |
+
ups_prompt_2,
|
| 862 |
+
ups_negative,
|
| 863 |
+
)
|
| 864 |
+
|
| 865 |
+
def _update_sheet_changes(self, updated_config, mode="Restorer"):
|
| 866 |
+
"""
|
| 867 |
+
Internal helper to apply dynamic changes to a PropertySheet's dataclass.
|
| 868 |
+
|
| 869 |
+
This function applies visibility rules and handles seed randomization logic
|
| 870 |
+
common to both the restorer and upscaler PropertySheets.
|
| 871 |
+
|
| 872 |
+
Args:
|
| 873 |
+
updated_config (dataclass): The configuration dataclass instance to update.
|
| 874 |
+
mode (str): Either "Restorer" or "Upscaler" to apply the correct rules.
|
| 875 |
+
|
| 876 |
+
Returns:
|
| 877 |
+
dataclass: The modified configuration dataclass.
|
| 878 |
+
"""
|
| 879 |
+
|
| 880 |
+
if mode == "Restorer":
|
| 881 |
+
updated_config = apply_dynamic_changes(updated_config, RESTORER_SHEET_DEPENDENCY_RULES)
|
| 882 |
+
previous_randomize_state = get_nested_attr(self.state.restorer_config_class, "general.randomize_seed")
|
| 883 |
+
else:
|
| 884 |
+
rules_to_apply = []
|
| 885 |
+
if updated_config.general.upscaling_mode == "Direct":
|
| 886 |
+
rules_to_apply.extend(
|
| 887 |
+
[
|
| 888 |
+
{"general.cfg_decay_rate": False},
|
| 889 |
+
{"general.strength_decay_rate": False},
|
| 890 |
+
]
|
| 891 |
+
)
|
| 892 |
+
else:
|
| 893 |
+
rules_to_apply.extend(
|
| 894 |
+
[
|
| 895 |
+
{"general.cfg_decay_rate": True},
|
| 896 |
+
{"general.strength_decay_rate": True},
|
| 897 |
+
]
|
| 898 |
+
)
|
| 899 |
+
self.state.uidata.add_visibility_rules(UPSCALER_SHEET_DEPENDENCY_RULES, rules_to_apply)
|
| 900 |
+
updated_config = apply_dynamic_changes(updated_config, UPSCALER_SHEET_DEPENDENCY_RULES)
|
| 901 |
+
previous_randomize_state = get_nested_attr(self.state.upscaler_config_class, "general.randomize_seed")
|
| 902 |
+
|
| 903 |
+
should_generate_new_seed = (updated_config.general.randomize_seed and not previous_randomize_state) or (
|
| 904 |
+
updated_config.general.randomize_seed and updated_config.general.seed == -1
|
| 905 |
+
)
|
| 906 |
+
if should_generate_new_seed:
|
| 907 |
+
updated_config.general.seed = random.randint(0, self.state.uidata.MAX_SEED)
|
| 908 |
+
|
| 909 |
+
return updated_config
|
| 910 |
+
|
| 911 |
+
def on_restore_engine_change(self, restorer_engine_name: str, upscaler_engine_name: str):
|
| 912 |
+
"""
|
| 913 |
+
Handles UI changes when the restorer engine dropdown is modified.
|
| 914 |
+
|
| 915 |
+
This updates the visibility of the restoration tab, loads the correct
|
| 916 |
+
configuration object into the `restorer_sheet` PropertySheet, and shows/hides
|
| 917 |
+
the advanced SUPIR settings sheet accordingly.
|
| 918 |
+
|
| 919 |
+
Args:
|
| 920 |
+
restorer_engine_name (str): The newly selected restorer engine.
|
| 921 |
+
upscaler_engine_name (str): The current upscaler engine.
|
| 922 |
+
|
| 923 |
+
Returns:
|
| 924 |
+
Tuple[gr.update, gr.update, gr.update, gr.update, gr.update]: A tuple of
|
| 925 |
+
updates for the restoration tab, advanced settings, main settings sheet,
|
| 926 |
+
engine configuration accordion, and the config tabs selector.
|
| 927 |
+
"""
|
| 928 |
+
is_restorer_active = restorer_engine_name != "None" and restorer_engine_name in RESTORER_CONFIG_MAPPING
|
| 929 |
+
is_upscaler_active = upscaler_engine_name != "None" and upscaler_engine_name in UPSCALER_CONFIG_MAPPING
|
| 930 |
+
is_supir = restorer_engine_name == "SUPIR"
|
| 931 |
+
|
| 932 |
+
if is_restorer_active:
|
| 933 |
+
config_class = RESTORER_CONFIG_MAPPING.get(restorer_engine_name, SUPIR_Config)
|
| 934 |
+
if is_supir:
|
| 935 |
+
self.state.restorer_supir_advanced_config_class = self.get_supir_advanced_values()
|
| 936 |
+
self.state.restorer_config_class = self._update_sheet_changes(config_class, "Restorer")
|
| 937 |
+
else:
|
| 938 |
+
self.state.restorer_config_class = None
|
| 939 |
+
if is_supir:
|
| 940 |
+
self.state.restorer_supir_advanced_config_class = None
|
| 941 |
+
|
| 942 |
+
ec_visible = is_restorer_active or is_upscaler_active
|
| 943 |
+
selected_tab = 1 if is_upscaler_active and not is_restorer_active else 0
|
| 944 |
+
return (
|
| 945 |
+
gr.update(visible=is_restorer_active),
|
| 946 |
+
gr.update(value=self.state.restorer_supir_advanced_config_class, visible=is_supir),
|
| 947 |
+
gr.update(
|
| 948 |
+
value=self.state.restorer_config_class,
|
| 949 |
+
label=f"{restorer_engine_name} Settings",
|
| 950 |
+
),
|
| 951 |
+
gr.update(visible=ec_visible),
|
| 952 |
+
gr.update(selected=selected_tab),
|
| 953 |
+
)
|
| 954 |
+
|
| 955 |
+
def on_upscaler_engine_change(self, restorer_engine_name: str, upscaler_engine_name: str):
|
| 956 |
+
"""
|
| 957 |
+
Handles UI changes when the upscaler engine dropdown is modified.
|
| 958 |
+
|
| 959 |
+
This updates the visibility of the upscaling tab, loads the correct
|
| 960 |
+
configuration object into the `upscaler_sheet` PropertySheet, and manages
|
| 961 |
+
the visibility of related UI elements like advanced SUPIR settings.
|
| 962 |
+
|
| 963 |
+
Args:
|
| 964 |
+
restorer_engine_name (str): The current restorer engine.
|
| 965 |
+
upscaler_engine_name (str): The newly selected upscaler engine.
|
| 966 |
+
|
| 967 |
+
Returns:
|
| 968 |
+
Tuple[gr.update, ...]: A tuple of updates for the upscaling tab,
|
| 969 |
+
advanced settings, main settings sheet, engine accordion, config tabs,
|
| 970 |
+
and the prompt method dropdown.
|
| 971 |
+
"""
|
| 972 |
+
|
| 973 |
+
is_restorer_active = restorer_engine_name != "None" and restorer_engine_name in RESTORER_CONFIG_MAPPING
|
| 974 |
+
is_upscaler_active = upscaler_engine_name != "None" and upscaler_engine_name in UPSCALER_CONFIG_MAPPING
|
| 975 |
+
is_supir = upscaler_engine_name == "SUPIR"
|
| 976 |
+
is_controlnettile = upscaler_engine_name == "ControlNetTile"
|
| 977 |
+
|
| 978 |
+
if is_upscaler_active:
|
| 979 |
+
config_class = UPSCALER_CONFIG_MAPPING.get(upscaler_engine_name, ControlNetTile_Config)
|
| 980 |
+
if is_supir:
|
| 981 |
+
self.state.upscaler_supir_advanced_config_class = self.get_supir_advanced_values()
|
| 982 |
+
self.state.upscaler_config_class = self._update_sheet_changes(config_class, "Upscaler")
|
| 983 |
+
else:
|
| 984 |
+
self.state.upscaler_config_class = None
|
| 985 |
+
if is_supir:
|
| 986 |
+
self.state.upscaler_supir_advanced_config_class = None
|
| 987 |
+
|
| 988 |
+
ec_visible = is_restorer_active or is_upscaler_active
|
| 989 |
+
selected_tab = 1 if is_upscaler_active and not is_restorer_active else 0
|
| 990 |
+
return (
|
| 991 |
+
gr.update(visible=is_upscaler_active),
|
| 992 |
+
gr.update(value=self.state.upscaler_supir_advanced_config_class, visible=is_supir),
|
| 993 |
+
gr.update(
|
| 994 |
+
value=self.state.upscaler_config_class,
|
| 995 |
+
label=f"{upscaler_engine_name} Settings",
|
| 996 |
+
),
|
| 997 |
+
gr.update(visible=ec_visible),
|
| 998 |
+
gr.update(selected=selected_tab),
|
| 999 |
+
gr.update(visible=not is_controlnettile),
|
| 1000 |
+
)
|
| 1001 |
+
|
| 1002 |
+
def on_restorer_sheet_change(self, updated_config: Union[SUPIR_Config, FaithDiff_Config, None]):
|
| 1003 |
+
"""
|
| 1004 |
+
Handles changes from the restorer configuration PropertySheet.
|
| 1005 |
+
|
| 1006 |
+
It calls the internal `_update_sheet_changes` helper to apply dynamic rules
|
| 1007 |
+
and manage seed randomization, then updates the global state.
|
| 1008 |
+
|
| 1009 |
+
Args:
|
| 1010 |
+
updated_config (dataclass | None): The new configuration from the sheet.
|
| 1011 |
+
|
| 1012 |
+
Returns:
|
| 1013 |
+
dataclass: The updated and processed configuration dataclass.
|
| 1014 |
+
"""
|
| 1015 |
+
if updated_config is None:
|
| 1016 |
+
return self.state.restorer_config_class
|
| 1017 |
+
|
| 1018 |
+
self.state.restorer_config_class = self._update_sheet_changes(updated_config, "Restorer")
|
| 1019 |
+
|
| 1020 |
+
return self.state.restorer_config_class
|
| 1021 |
+
|
| 1022 |
+
def on_restorer_supir_advanced_sheet_change(self, updated_config: SUPIRAdvanced_Config | None):
|
| 1023 |
+
"""
|
| 1024 |
+
Handles changes from the restorer supir advanced configuration PropertySheet.
|
| 1025 |
+
|
| 1026 |
+
It calls the internal `apply_dynamic_changes` helper to apply dynamic rules
|
| 1027 |
+
and manage seed randomization, then updates the global state.
|
| 1028 |
+
|
| 1029 |
+
Args:
|
| 1030 |
+
updated_config (dataclass | None): The new configuration from the sheet.
|
| 1031 |
+
|
| 1032 |
+
Returns:
|
| 1033 |
+
dataclass: The updated and processed configuration dataclass.
|
| 1034 |
+
"""
|
| 1035 |
+
if updated_config is None:
|
| 1036 |
+
return self.state.restorer_supir_advanced_config_class
|
| 1037 |
+
|
| 1038 |
+
self.state.restorer_supir_advanced_config_class = apply_dynamic_changes(updated_config, SUPIR_ADVANCED_RULES)
|
| 1039 |
+
|
| 1040 |
+
return self.state.restorer_supir_advanced_config_class
|
| 1041 |
+
|
| 1042 |
+
def on_upscaler_sheet_change(
|
| 1043 |
+
self,
|
| 1044 |
+
updated_config: Union[SUPIR_Config, ControlNetTile_Config, FaithDiff_Config, None],
|
| 1045 |
+
):
|
| 1046 |
+
"""
|
| 1047 |
+
Handles changes from the upscaler configuration PropertySheet.
|
| 1048 |
+
|
| 1049 |
+
It calls the internal `_update_sheet_changes` helper to apply dynamic rules
|
| 1050 |
+
(like for progressive upscaling) and manage seed randomization, then
|
| 1051 |
+
updates the global state.
|
| 1052 |
+
|
| 1053 |
+
Args:
|
| 1054 |
+
updated_config (dataclass | None): The new configuration from the sheet.
|
| 1055 |
+
|
| 1056 |
+
Returns:
|
| 1057 |
+
dataclass: The updated and processed configuration dataclass.
|
| 1058 |
+
"""
|
| 1059 |
+
if updated_config is None:
|
| 1060 |
+
return self.state.upscaler_config_class
|
| 1061 |
+
|
| 1062 |
+
self.state.upscaler_config_class = self._update_sheet_changes(updated_config, "Upscaler")
|
| 1063 |
+
|
| 1064 |
+
return self.state.upscaler_config_class
|
| 1065 |
+
|
| 1066 |
+
def on_upscaler_supir_advanced_sheet_change(self, updated_config: SUPIRAdvanced_Config | None):
|
| 1067 |
+
"""
|
| 1068 |
+
Handles changes from the upscaler supir advanced configuration PropertySheet.
|
| 1069 |
+
|
| 1070 |
+
It calls the internal `apply_dynamic_changes` helper to apply dynamic rules
|
| 1071 |
+
and manage seed randomization, then updates the global state.
|
| 1072 |
+
|
| 1073 |
+
Args:
|
| 1074 |
+
updated_config (dataclass | None): The new configuration from the sheet.
|
| 1075 |
+
|
| 1076 |
+
Returns:
|
| 1077 |
+
dataclass: The updated and processed configuration dataclass.
|
| 1078 |
+
"""
|
| 1079 |
+
if updated_config is None:
|
| 1080 |
+
return self.state.upscaler_supir_advanced_config_class
|
| 1081 |
+
|
| 1082 |
+
self.state.upscaler_supir_advanced_config_class = apply_dynamic_changes(updated_config, SUPIR_ADVANCED_RULES)
|
| 1083 |
+
|
| 1084 |
+
return self.state.upscaler_supir_advanced_config_class
|
| 1085 |
+
|
| 1086 |
+
def on_settings_tab_select(self):
|
| 1087 |
+
"""
|
| 1088 |
+
Callback triggered when the 'Settings' tab is selected.
|
| 1089 |
+
|
| 1090 |
+
Ensures the settings sheet is correctly rendered with any dynamic rules applied.
|
| 1091 |
+
|
| 1092 |
+
Returns:
|
| 1093 |
+
AppSettings: The processed AppSettings dataclass to be rendered.
|
| 1094 |
+
"""
|
| 1095 |
+
return self.on_settings_sheet_change(self.state.uidata.settings)
|
| 1096 |
+
|
| 1097 |
+
def on_cancel_click(self):
|
| 1098 |
+
"""
|
| 1099 |
+
Handles the 'Cancel' button click by setting a global threading event.
|
| 1100 |
+
The running pipeline periodically checks this event and will stop execution
|
| 1101 |
+
if it is set.
|
| 1102 |
+
"""
|
| 1103 |
+
self.state.cancel_event.set()
|
| 1104 |
+
|
| 1105 |
+
def load_metadata(self, metadata: dict):
|
| 1106 |
+
"""
|
| 1107 |
+
Loads settings from an image's metadata dictionary into the UI components.
|
| 1108 |
+
|
| 1109 |
+
This function acts as a bridge between the raw metadata and the UI. It defines
|
| 1110 |
+
mappings for complex components like PropertySheets and standard Gradio
|
| 1111 |
+
components, then uses a helper (`transfer_metadata`) to apply the values.
|
| 1112 |
+
|
| 1113 |
+
Args:
|
| 1114 |
+
metadata (dict): A dictionary of key-value pairs extracted from image metadata.
|
| 1115 |
+
|
| 1116 |
+
Returns:
|
| 1117 |
+
List[Any]: A list of values in the correct order to update the UI
|
| 1118 |
+
output components.
|
| 1119 |
+
|
| 1120 |
+
Raises:
|
| 1121 |
+
gr.Error: If metadata conversion for a sampler setting fails.
|
| 1122 |
+
"""
|
| 1123 |
+
# Get UI components
|
| 1124 |
+
ui_inputs, output_fields = self.components._get_ui_inputs_and_outputs()
|
| 1125 |
+
ui_inputs_for_metadata = ui_inputs.copy()
|
| 1126 |
+
restorer_sheet = self.components.restorer_sheet
|
| 1127 |
+
upscaler_sheet = self.components.upscaler_sheet
|
| 1128 |
+
restorer_sheet_supir_advanced = self.components.restorer_sheet_supir_advanced
|
| 1129 |
+
upscaler_sheet_supir_advanced = self.components.upscaler_sheet_supir_advanced
|
| 1130 |
+
|
| 1131 |
+
# Define the map that tells the helper how to process each PropertySheet.
|
| 1132 |
+
# This is the "glue" between your generic helper and your specific app.
|
| 1133 |
+
source_restorer_engine = RestorerEngine.from_str(metadata["Image Restore Engine"])
|
| 1134 |
+
source_upscaler_engine = UpscalerEngine.from_str(metadata["Image Upscale Engine"])
|
| 1135 |
+
source_restorer_class = RESTORER_CONFIG_MAPPING.get(source_restorer_engine.value)
|
| 1136 |
+
source_upscaler_class = UPSCALER_CONFIG_MAPPING.get(source_upscaler_engine.value)
|
| 1137 |
+
sheet_map = {}
|
| 1138 |
+
if source_restorer_class:
|
| 1139 |
+
sheet_map[id(restorer_sheet)] = {
|
| 1140 |
+
"type": source_restorer_class.__class__,
|
| 1141 |
+
"prefixes": ["Restorer", "Image Restore Engine"],
|
| 1142 |
+
}
|
| 1143 |
+
|
| 1144 |
+
if source_restorer_engine == RestorerEngine.SUPIR:
|
| 1145 |
+
sheet_map[id(restorer_sheet_supir_advanced)] = {
|
| 1146 |
+
"type": restorer_sheet_supir_advanced._dataclass_type,
|
| 1147 |
+
"prefixes": ["Restorer"],
|
| 1148 |
+
}
|
| 1149 |
+
|
| 1150 |
+
if source_upscaler_class:
|
| 1151 |
+
sheet_map[id(upscaler_sheet)] = {
|
| 1152 |
+
"type": source_upscaler_class.__class__,
|
| 1153 |
+
"prefixes": ["Upscaler", "Image Upscale Engine"],
|
| 1154 |
+
}
|
| 1155 |
+
if source_upscaler_engine == UpscalerEngine.SUPIR:
|
| 1156 |
+
sheet_map[id(upscaler_sheet_supir_advanced)] = {
|
| 1157 |
+
"type": upscaler_sheet_supir_advanced._dataclass_type,
|
| 1158 |
+
"prefixes": ["Upscaler"],
|
| 1159 |
+
}
|
| 1160 |
+
|
| 1161 |
+
gradio_map = {id(component): label for label, component in ui_inputs_for_metadata.items()}
|
| 1162 |
+
|
| 1163 |
+
output_values = transfer_metadata(
|
| 1164 |
+
output_fields=output_fields,
|
| 1165 |
+
metadata=metadata,
|
| 1166 |
+
propertysheet_map=sheet_map,
|
| 1167 |
+
gradio_component_map=gradio_map,
|
| 1168 |
+
)
|
| 1169 |
+
|
| 1170 |
+
sampler_map_data = {
|
| 1171 |
+
"restorer_sampler_settings": {
|
| 1172 |
+
"prefix": "Restorer - Sampler",
|
| 1173 |
+
"instance": SAMPLER_MAPPING.get("restorer_sampler"),
|
| 1174 |
+
},
|
| 1175 |
+
"upscaler_sampler_settings": {
|
| 1176 |
+
"prefix": "Upscaler - Sampler",
|
| 1177 |
+
"instance": SAMPLER_MAPPING.get("upscaler_sampler"),
|
| 1178 |
+
},
|
| 1179 |
+
}
|
| 1180 |
+
|
| 1181 |
+
for _, data in sampler_map_data.items():
|
| 1182 |
+
sampler_instance, prefix = data["instance"], data["prefix"]
|
| 1183 |
+
if not (sampler_instance and is_dataclass(sampler_instance)):
|
| 1184 |
+
continue
|
| 1185 |
+
|
| 1186 |
+
for field in fields(sampler_instance):
|
| 1187 |
+
label = field.metadata.get("label", field.name.replace("_", " ").title())
|
| 1188 |
+
metadata_key = f"{prefix} - {label}"
|
| 1189 |
+
|
| 1190 |
+
if metadata_key in metadata:
|
| 1191 |
+
try:
|
| 1192 |
+
setattr(
|
| 1193 |
+
sampler_instance,
|
| 1194 |
+
field.name,
|
| 1195 |
+
infer_type(metadata[metadata_key]),
|
| 1196 |
+
)
|
| 1197 |
+
except (ValueError, TypeError):
|
| 1198 |
+
print(f"Warning: Could not convert metadata value '{metadata[metadata_key]}' for sampler field '{field.name}'.")
|
| 1199 |
+
raise gr.Error("Error loading Image metadata, see console log.")
|
| 1200 |
+
|
| 1201 |
+
return output_values
|
| 1202 |
+
|
| 1203 |
+
def on_load_metadata_from_gallery(self, folder_explorer, image_data: gr.EventData):
|
| 1204 |
+
"""
|
| 1205 |
+
Callback to load metadata from an image selected in the 'Generated' gallery.
|
| 1206 |
+
|
| 1207 |
+
It extracts metadata from the selected image, calls the `load_metadata`
|
| 1208 |
+
helper to process it, and applies the final settings to the UI components.
|
| 1209 |
+
It also handles path corrections for example images.
|
| 1210 |
+
|
| 1211 |
+
Args:
|
| 1212 |
+
folder_explorer (Any): The current value of the folder explorer component.
|
| 1213 |
+
image_data (gr.EventData): Event data for the selected image, containing metadata.
|
| 1214 |
+
|
| 1215 |
+
Returns:
|
| 1216 |
+
List[Any]: A list of values to update the UI components with the loaded settings.
|
| 1217 |
+
"""
|
| 1218 |
+
# Get output_fields
|
| 1219 |
+
_, output_fields = self.components._get_ui_inputs_and_outputs()
|
| 1220 |
+
|
| 1221 |
+
gallery_path = Path(folder_explorer)
|
| 1222 |
+
is_example = all(part in gallery_path.parts for part in ["outputs", "examples"])
|
| 1223 |
+
|
| 1224 |
+
# Initial checks for valid input
|
| 1225 |
+
if not image_data or not hasattr(image_data, "_data"):
|
| 1226 |
+
return [gr.skip()] * len(output_fields)
|
| 1227 |
+
|
| 1228 |
+
metadata = image_data._data
|
| 1229 |
+
output_values = self.load_metadata(metadata)
|
| 1230 |
+
if output_values[0]:
|
| 1231 |
+
self.state.restorer_config_class = self._update_sheet_changes(output_values[0], "Restorer")
|
| 1232 |
+
RESTORER_CONFIG_MAPPING[metadata["Image Restore Engine"]] = self.state.restorer_config_class
|
| 1233 |
+
if output_values[2]:
|
| 1234 |
+
self.state.upscaler_config_class = self._update_sheet_changes(output_values[2], "Upscaler")
|
| 1235 |
+
UPSCALER_CONFIG_MAPPING[metadata["Image Upscale Engine"]] = self.state.upscaler_config_class
|
| 1236 |
+
|
| 1237 |
+
output_values[0], output_values[2] = (
|
| 1238 |
+
self.state.restorer_config_class,
|
| 1239 |
+
self.state.upscaler_config_class,
|
| 1240 |
+
)
|
| 1241 |
+
input_image_name = output_values[11]
|
| 1242 |
+
output_values[11] = os.path.join("assets/samples", input_image_name) if is_example and input_image_name else None
|
| 1243 |
+
|
| 1244 |
+
gr.Info("Image metadata loaded.")
|
| 1245 |
+
return output_values
|
| 1246 |
+
|
| 1247 |
+
def on_input_image_change(self, input_image):
|
| 1248 |
+
self.state.input_image_path = getattr(input_image, "path", None)
|
| 1249 |
+
|
| 1250 |
+
def on_load_metadata_from_single_image(self, image_data):
|
| 1251 |
+
"""
|
| 1252 |
+
Callback to load metadata from the main input image component.
|
| 1253 |
+
|
| 1254 |
+
Triggered when an image with embedded metadata is uploaded. It extracts the
|
| 1255 |
+
metadata and calls the `load_metadata` helper to apply it to the UI.
|
| 1256 |
+
|
| 1257 |
+
Args:
|
| 1258 |
+
image_data (Image.Image | None): The image object from the ImageMeta component.
|
| 1259 |
+
|
| 1260 |
+
Returns:
|
| 1261 |
+
List[Any]: A list of values/updates for the UI components.
|
| 1262 |
+
"""
|
| 1263 |
+
# Get output_fields
|
| 1264 |
+
_, output_fields = self.components._get_ui_inputs_and_outputs()
|
| 1265 |
+
|
| 1266 |
+
# Initial checks for valid input
|
| 1267 |
+
if not image_data or not hasattr(image_data, "path"):
|
| 1268 |
+
return [gr.skip()] * len(output_fields)
|
| 1269 |
+
|
| 1270 |
+
# Extract the flat metadata dictionary from the image
|
| 1271 |
+
metadata = extract_metadata(image_data, only_custom_metadata=True)
|
| 1272 |
+
if not metadata:
|
| 1273 |
+
return [gr.skip()] * len(output_fields)
|
| 1274 |
+
|
| 1275 |
+
output_values = self.load_metadata(metadata)
|
| 1276 |
+
if output_values[0]:
|
| 1277 |
+
self.state.restorer_config_class = self._update_sheet_changes(output_values[0], "Restorer")
|
| 1278 |
+
RESTORER_CONFIG_MAPPING[metadata["Image Restore Engine"]] = self.state.restorer_config_class
|
| 1279 |
+
if output_values[2]:
|
| 1280 |
+
self.state.upscaler_config_class = self._update_sheet_changes(output_values[2], "Upscaler")
|
| 1281 |
+
UPSCALER_CONFIG_MAPPING[metadata["Image Upscale Engine"]] = self.state.upscaler_config_class
|
| 1282 |
+
|
| 1283 |
+
output_values[0], output_values[2] = (
|
| 1284 |
+
self.state.restorer_config_class,
|
| 1285 |
+
self.state.upscaler_config_class,
|
| 1286 |
+
)
|
| 1287 |
+
output_values[11] = None
|
| 1288 |
+
|
| 1289 |
+
gr.Info("Image metadata loaded.")
|
| 1290 |
+
return output_values
|
| 1291 |
+
|
| 1292 |
+
def on_clear_log_output(self):
|
| 1293 |
+
"""
|
| 1294 |
+
Clears the content of the LiveLog viewer component.
|
| 1295 |
+
|
| 1296 |
+
Returns:
|
| 1297 |
+
None: Sets the value of the LiveLog component to None, clearing it.
|
| 1298 |
+
"""
|
| 1299 |
+
return None
|
| 1300 |
+
|
| 1301 |
+
def on_check_inputs(
|
| 1302 |
+
self,
|
| 1303 |
+
restorer_engine,
|
| 1304 |
+
upscaler_engine,
|
| 1305 |
+
restorer_model_name,
|
| 1306 |
+
upscaler_model_name,
|
| 1307 |
+
action="generation_process",
|
| 1308 |
+
):
|
| 1309 |
+
"""
|
| 1310 |
+
Validates the core inputs before starting a process like generation or masking.
|
| 1311 |
+
|
| 1312 |
+
Raises a gr.Error with a user-friendly message if validation fails.
|
| 1313 |
+
Also configures the bottom bar and logger for the upcoming process.
|
| 1314 |
+
|
| 1315 |
+
Args:
|
| 1316 |
+
restorer_engine (str): The selected restorer engine.
|
| 1317 |
+
upscaler_engine (str): The selected upscaler engine.
|
| 1318 |
+
restorer_model_name (str): The selected restorer model.
|
| 1319 |
+
upscaler_model_name (str): The selected upscaler model.
|
| 1320 |
+
action (str): The type of action being initiated, used to tailor checks.
|
| 1321 |
+
|
| 1322 |
+
Returns:
|
| 1323 |
+
Tuple[gr.update, gr.update]: Updates to open the bottom bar and configure
|
| 1324 |
+
the LiveLog display mode.
|
| 1325 |
+
"""
|
| 1326 |
+
if self.state.input_image_path is None:
|
| 1327 |
+
raise gr.Error("Input image is required. Please upload an image to proceed.")
|
| 1328 |
+
|
| 1329 |
+
if action == "generation_process":
|
| 1330 |
+
if restorer_engine == "None" and upscaler_engine == "None":
|
| 1331 |
+
raise gr.Error("Please select at least a Restorer or an Upscaler engine.")
|
| 1332 |
+
if restorer_engine != "None" and restorer_model_name == "None":
|
| 1333 |
+
raise gr.Error("Please select a Restorer model when using the Restore engine.")
|
| 1334 |
+
if upscaler_engine != "None" and upscaler_model_name == "None":
|
| 1335 |
+
raise gr.Error("Please select an Upscaler model when using the Upscale engine.")
|
| 1336 |
+
|
| 1337 |
+
return gr.update(open=True), gr.update(display_mode="full" if action == "generation_process" else "log")
|
| 1338 |
+
|
| 1339 |
+
def on_refresh_restoration_mask(self, mask_prompt, **kwargs):
|
| 1340 |
+
"""
|
| 1341 |
+
Generates a face restoration mask based on a text prompt.
|
| 1342 |
+
|
| 1343 |
+
This function is decorated with `@livelog` to stream log outputs to the UI.
|
| 1344 |
+
It initializes the pipeline and calls the `generate_prompt_mask` method.
|
| 1345 |
+
|
| 1346 |
+
Args:
|
| 1347 |
+
mask_prompt (str): The prompt used to identify the area to mask (e.g., "head").
|
| 1348 |
+
lq_image_path (Any): The file object for the low-quality input image.
|
| 1349 |
+
**kwargs: Injected by the `@livelog` decorator (e.g., `log_callback`).
|
| 1350 |
+
|
| 1351 |
+
Returns:
|
| 1352 |
+
PIL.Image: The generated mask image.
|
| 1353 |
+
"""
|
| 1354 |
+
log_callback = kwargs.get("log_callback")
|
| 1355 |
+
logger = logging.getLogger(kwargs.get("log_name", "suptoolbox_app"))
|
| 1356 |
+
log_callback(log_content="Starting masking generation...")
|
| 1357 |
+
|
| 1358 |
+
if self.state.input_image_path is None:
|
| 1359 |
+
raise gr.Error("Input image must be provided!")
|
| 1360 |
+
|
| 1361 |
+
lq_image = Image.open(self.state.input_image_path).convert("RGB")
|
| 1362 |
+
self.state.cancel_event.clear()
|
| 1363 |
+
self.update_pipeline(log_callback=log_callback)
|
| 1364 |
+
|
| 1365 |
+
try:
|
| 1366 |
+
mask, _ = sup_toolbox_pipe.generate_prompt_mask(lq_image, mask_prompt)
|
| 1367 |
+
if mask is None:
|
| 1368 |
+
gr.Warning("Mask couldn't be generated!")
|
| 1369 |
+
return mask
|
| 1370 |
+
except Exception as e:
|
| 1371 |
+
logger.error(f"Error in generation object mask: {e}, process aborted!", exc_info=True)
|
| 1372 |
+
raise e
|
| 1373 |
+
|
| 1374 |
+
def on_generate_caption(self, **kwargs):
|
| 1375 |
+
"""
|
| 1376 |
+
Generates a descriptive caption for the input image.
|
| 1377 |
+
|
| 1378 |
+
Decorated with `@livelog` to stream logs. It initializes the pipeline
|
| 1379 |
+
and uses the `generate_caption` method.
|
| 1380 |
+
|
| 1381 |
+
Args:
|
| 1382 |
+
lq_image_path (Any): The file object for the low-quality input image.
|
| 1383 |
+
**kwargs: Injected by the `@livelog` decorator.
|
| 1384 |
+
|
| 1385 |
+
Returns:
|
| 1386 |
+
str: The generated image caption.
|
| 1387 |
+
"""
|
| 1388 |
+
log_callback = kwargs.get("log_callback")
|
| 1389 |
+
logger = logging.getLogger(kwargs.get("log_name", "suptoolbox_app"))
|
| 1390 |
+
log_callback(log_content="Starting caption generation...")
|
| 1391 |
+
|
| 1392 |
+
if self.state.input_image_path is None:
|
| 1393 |
+
raise gr.Error("Input image must be provided!")
|
| 1394 |
+
|
| 1395 |
+
lq_image = Image.open(self.state.input_image_path).convert("RGB")
|
| 1396 |
+
self.state.cancel_event.clear()
|
| 1397 |
+
self.update_pipeline(log_callback=log_callback)
|
| 1398 |
+
|
| 1399 |
+
try:
|
| 1400 |
+
caption = sup_toolbox_pipe.generate_caption(lq_image)
|
| 1401 |
+
if caption is None:
|
| 1402 |
+
gr.Warning("Caption couldn't be generated!")
|
| 1403 |
+
return caption
|
| 1404 |
+
except Exception as e:
|
| 1405 |
+
logger.error(f"Error in caption generation: {e}, process aborted!", exc_info=True)
|
| 1406 |
+
raise e
|
| 1407 |
+
|
| 1408 |
+
def on_generate(self, *args):
|
| 1409 |
+
"""
|
| 1410 |
+
Starts the image processing thread and yields updates from a queue.
|
| 1411 |
+
This method is the entry point for the Gradio event chain.
|
| 1412 |
+
"""
|
| 1413 |
+
|
| 1414 |
+
# 1. Send initial UI state update (disable buttons, show progress).
|
| 1415 |
+
yield None, None, gr.update(interactive=False), gr.update(visible=True), gr.update(open=True)
|
| 1416 |
+
update_queue = queue.Queue()
|
| 1417 |
+
|
| 1418 |
+
# 2. Package arguments for the worker thread.
|
| 1419 |
+
thread_args = (update_queue, *args)
|
| 1420 |
+
|
| 1421 |
+
# 3. Start the image processing worker thread.
|
| 1422 |
+
diffusion_thread = threading.Thread(target=self._process_image, args=thread_args)
|
| 1423 |
+
diffusion_thread.start()
|
| 1424 |
+
|
| 1425 |
+
# 4. Loop to read from the queue and yield updates to the UI.
|
| 1426 |
+
final_images, log_update = None, None
|
| 1427 |
+
while True:
|
| 1428 |
+
update = update_queue.get()
|
| 1429 |
+
if update is None: # Sentinel value indicating the thread has finished.
|
| 1430 |
+
break
|
| 1431 |
+
|
| 1432 |
+
images, log_update = update
|
| 1433 |
+
|
| 1434 |
+
if images:
|
| 1435 |
+
final_images = images
|
| 1436 |
+
|
| 1437 |
+
# Yield the update to the Gradio outputs.
|
| 1438 |
+
yield final_images, log_update, gr.skip(), gr.skip(), gr.skip()
|
| 1439 |
+
|
| 1440 |
+
# 5. Send final UI state update (re-enable buttons).
|
| 1441 |
+
yield final_images, log_update, gr.update(interactive=True), gr.update(visible=False), gr.skip()
|
| 1442 |
+
|
| 1443 |
+
def _process_image(self, update_queue: queue.Queue, total_steps: int, *input_params: Any, **kwargs):
|
| 1444 |
+
"""
|
| 1445 |
+
Executes the main image processing pipeline in a worker thread.
|
| 1446 |
+
|
| 1447 |
+
This method receives simple, serializable data types from the UI event.
|
| 1448 |
+
It retrieves complex configuration objects (like PropertySheet values)
|
| 1449 |
+
directly from the shared application state (`self.state`), which are kept
|
| 1450 |
+
up-to-date by their respective `.change()` events.
|
| 1451 |
+
|
| 1452 |
+
Args:
|
| 1453 |
+
update_queue (queue.Queue): The queue for sending status updates back to the UI.
|
| 1454 |
+
total_steps (int): The pre-calculated total number of diffusion steps.
|
| 1455 |
+
*input_params (Any): A tuple of the remaining simple UI input values
|
| 1456 |
+
(e.g., engine selections, prompts), passed positionally.
|
| 1457 |
+
**kwargs: Catches any extra keyword arguments.
|
| 1458 |
+
|
| 1459 |
+
Side Effects:
|
| 1460 |
+
- Puts multiple update dictionaries and a final `None` sentinel onto the `update_queue`.
|
| 1461 |
+
- Modifies and uses the shared `sup_toolbox_pipe` instance via `self.update_pipeline`.
|
| 1462 |
+
- Catches all exceptions and reports them via the queue.
|
| 1463 |
+
"""
|
| 1464 |
+
from sup_toolbox.sup_toolbox_pipeline import PipelineCancelationRequested
|
| 1465 |
+
|
| 1466 |
+
# 1. Prepare input params
|
| 1467 |
+
restorer_config = self.state.restorer_config_class
|
| 1468 |
+
upscaler_config = self.state.upscaler_config_class
|
| 1469 |
+
restorer_supir_advanced_config = self.state.restorer_supir_advanced_config_class
|
| 1470 |
+
upscaler_supir_advanced_config = self.state.upscaler_supir_advanced_config_class
|
| 1471 |
+
ui_inputs, _ = self.components._get_ui_inputs_and_outputs()
|
| 1472 |
+
ui_inputs_keys_sliced = list(ui_inputs.keys())[4:]
|
| 1473 |
+
input_param_with_values = dict(zip(ui_inputs_keys_sliced, input_params))
|
| 1474 |
+
input_image, res_engine, ups_engine = (
|
| 1475 |
+
self.state.input_image_path,
|
| 1476 |
+
input_param_with_values.get("Image Restore Engine"),
|
| 1477 |
+
input_param_with_values.get("Image Upscale Engine"),
|
| 1478 |
+
)
|
| 1479 |
+
|
| 1480 |
+
if hasattr(input_image, "orig_name"):
|
| 1481 |
+
input_param_with_values["Input Image"] = input_image.orig_name
|
| 1482 |
+
else:
|
| 1483 |
+
input_param_with_values.pop("Input Image", None)
|
| 1484 |
+
|
| 1485 |
+
if input_image is None:
|
| 1486 |
+
update_queue.put((None, {"logs": [{"type": "log", "level": "ERROR", "content": "Error: Input image is required."}]}))
|
| 1487 |
+
update_queue.put(None)
|
| 1488 |
+
return
|
| 1489 |
+
|
| 1490 |
+
# 2. Generate image metadata
|
| 1491 |
+
image_metadata = self.generate_image_metadata(
|
| 1492 |
+
restorer_config,
|
| 1493 |
+
upscaler_config,
|
| 1494 |
+
(restorer_supir_advanced_config if res_engine == RestorerEngine.SUPIR.value else None),
|
| 1495 |
+
(upscaler_supir_advanced_config if ups_engine == UpscalerEngine.SUPIR.value else None),
|
| 1496 |
+
input_param_with_values,
|
| 1497 |
+
SAMPLER_MAPPING["restorer_sampler"],
|
| 1498 |
+
SAMPLER_MAPPING["upscaler_sampler"],
|
| 1499 |
+
)
|
| 1500 |
+
|
| 1501 |
+
tracker = None
|
| 1502 |
+
self.state.cancel_event.clear()
|
| 1503 |
+
|
| 1504 |
+
with capture_logs(log_level=logging.INFO, log_name=["suptoolbox_app", "suptoolbox"]) as get_logs:
|
| 1505 |
+
try:
|
| 1506 |
+
rate_queue = queue.Queue()
|
| 1507 |
+
tqdm_writer = TqdmToQueueWriter(rate_queue)
|
| 1508 |
+
progress_bar_handler = Tee(sys.stderr, tqdm_writer)
|
| 1509 |
+
all_logs, last_known_rate_data = [], None
|
| 1510 |
+
|
| 1511 |
+
# 2. Prepare selected engines
|
| 1512 |
+
config = self.state.uidata.config
|
| 1513 |
+
|
| 1514 |
+
_restorer_config, _upscaler_config = self.prepare_engine_configs(res_engine, restorer_config, ups_engine, upscaler_config)
|
| 1515 |
+
|
| 1516 |
+
# 3. Define update callback
|
| 1517 |
+
def process_and_send_updates(status="running", advance=0, final_image_payload=None):
|
| 1518 |
+
nonlocal all_logs, last_known_rate_data
|
| 1519 |
+
new_rate_data = None
|
| 1520 |
+
while not rate_queue.empty():
|
| 1521 |
+
try:
|
| 1522 |
+
new_rate_data = rate_queue.get_nowait()
|
| 1523 |
+
except queue.Empty:
|
| 1524 |
+
break
|
| 1525 |
+
|
| 1526 |
+
if new_rate_data:
|
| 1527 |
+
last_known_rate_data = new_rate_data
|
| 1528 |
+
|
| 1529 |
+
new_records = get_logs()
|
| 1530 |
+
if new_records:
|
| 1531 |
+
new_logs = [
|
| 1532 |
+
{"type": "log", "level": "SUCCESS" if r.levelno == logging.INFO + 5 else r.levelname, "content": r.getMessage()}
|
| 1533 |
+
for r in new_records
|
| 1534 |
+
]
|
| 1535 |
+
all_logs.extend(new_logs)
|
| 1536 |
+
|
| 1537 |
+
update_dict = (
|
| 1538 |
+
tracker.update(advance=advance, status=status, logs=all_logs, rate_data=last_known_rate_data)
|
| 1539 |
+
if tracker
|
| 1540 |
+
else {"type": "progress", "logs": all_logs, "current": 0, "total": total_steps, "desc": "Diffusion Steps"}
|
| 1541 |
+
)
|
| 1542 |
+
update_queue.put((final_image_payload, update_dict))
|
| 1543 |
+
|
| 1544 |
+
logger = logging.getLogger("suptoolbox_app")
|
| 1545 |
+
logger.info("Starting diffusion process...")
|
| 1546 |
+
process_and_send_updates()
|
| 1547 |
+
|
| 1548 |
+
# 4. Create tracker
|
| 1549 |
+
tracker = ProgressTracker(total=total_steps, description="Diffusion Steps", rate_unit="s/it")
|
| 1550 |
+
|
| 1551 |
+
# 5. Define pipeline progress callback
|
| 1552 |
+
def progress_callback(_, __, ___, callback_kwargs):
|
| 1553 |
+
process_and_send_updates(advance=callback_kwargs["advance"])
|
| 1554 |
+
return callback_kwargs
|
| 1555 |
+
|
| 1556 |
+
# 6. Map all pipeline configuration from UI to SUP-Toolbox Pipeline
|
| 1557 |
+
config = self.state.uidata.config
|
| 1558 |
+
config.restorer_engine, config.upscaler_engine = (
|
| 1559 |
+
RestorerEngine.from_str(res_engine),
|
| 1560 |
+
UpscalerEngine.from_str(ups_engine),
|
| 1561 |
+
)
|
| 1562 |
+
config.selected_vae_model = input_param_with_values["VAE Model"]
|
| 1563 |
+
if res_engine == RestorerEngine.SUPIR.value:
|
| 1564 |
+
_restorer_config = cast(SUPIR_Config, _restorer_config)
|
| 1565 |
+
config.selected_restorer_checkpoint_model = input_param_with_values["Image Restore Model"]
|
| 1566 |
+
config.restorer_engine = RestorerEngine.SUPIR
|
| 1567 |
+
config.selected_restorer_sampler = Sampler.from_str(input_param_with_values["Image Restore Sampler"])
|
| 1568 |
+
config.restorer_pipeline_params.supir_model = SUPIRModel.from_str(_restorer_config.supir_model)
|
| 1569 |
+
config.restorer_pipeline_params.seed = _restorer_config.general.seed
|
| 1570 |
+
config.restorer_pipeline_params.upscale_factor = _restorer_config.general.upscale_factor
|
| 1571 |
+
config.restorer_pipeline_params.prompt = input_param_with_values["Image Restore - Prompt 1"]
|
| 1572 |
+
config.restorer_pipeline_params.prompt_2 = input_param_with_values["Image Restore - Prompt 2"]
|
| 1573 |
+
config.restorer_pipeline_params.negative_prompt = input_param_with_values["Image Restore - Negative prompt"]
|
| 1574 |
+
config.restore_face = input_param_with_values["Enable Face Restoration"]
|
| 1575 |
+
config.mask_prompt = input_param_with_values["Mask Prompt"]
|
| 1576 |
+
config.restorer_pipeline_params.num_images = _restorer_config.general.num_images
|
| 1577 |
+
config.restorer_pipeline_params.num_steps = _restorer_config.general.num_steps
|
| 1578 |
+
config.restorer_pipeline_params.use_lpw_prompt = (
|
| 1579 |
+
True if input_param_with_values["Image Restore - Prompt method"] == PromptMethod.Weighted.value else False
|
| 1580 |
+
)
|
| 1581 |
+
config.restorer_pipeline_params.tile_size = _restorer_config.general.tile_size
|
| 1582 |
+
config.restorer_pipeline_params.restoration_scale = float(_restorer_config.restoration_scale)
|
| 1583 |
+
config.restorer_pipeline_params.s_churn = float(_restorer_config.s_churn)
|
| 1584 |
+
config.restorer_pipeline_params.s_noise = float(_restorer_config.s_noise)
|
| 1585 |
+
config.restorer_pipeline_params.strength = float(_restorer_config.strength)
|
| 1586 |
+
config.restorer_pipeline_params.use_linear_CFG = _restorer_config.cfg_settings.use_linear_CFG
|
| 1587 |
+
config.restorer_pipeline_params.guidance_scale = float(_restorer_config.general.guidance_scale)
|
| 1588 |
+
config.restorer_pipeline_params.guidance_rescale = float(_restorer_config.general.guidance_rescale)
|
| 1589 |
+
config.restorer_pipeline_params.reverse_linear_CFG = float(_restorer_config.cfg_settings.reverse_linear_CFG)
|
| 1590 |
+
config.restorer_pipeline_params.guidance_scale_start = float(_restorer_config.cfg_settings.guidance_scale_start)
|
| 1591 |
+
config.restorer_pipeline_params.use_linear_control_scale = _restorer_config.controlnet_settings.use_linear_control_scale
|
| 1592 |
+
config.restorer_pipeline_params.reverse_linear_control_scale = _restorer_config.controlnet_settings.reverse_linear_control_scale
|
| 1593 |
+
config.restorer_pipeline_params.controlnet_conditioning_scale = float(_restorer_config.controlnet_settings.controlnet_conditioning_scale)
|
| 1594 |
+
config.restorer_pipeline_params.control_scale_start = float(_restorer_config.controlnet_settings.control_scale_start)
|
| 1595 |
+
config.restorer_pipeline_params.enable_PAG = _restorer_config.pag_settings.enable_PAG and len(_restorer_config.pag_settings.pag_layers) > 0
|
| 1596 |
+
config.restorer_pipeline_params.use_linear_PAG = _restorer_config.pag_settings.use_linear_PAG
|
| 1597 |
+
config.restorer_pipeline_params.reverse_linear_PAG = _restorer_config.pag_settings.reverse_linear_PAG
|
| 1598 |
+
config.restorer_pipeline_params.pag_scale = float(_restorer_config.pag_settings.pag_scale)
|
| 1599 |
+
config.restorer_pipeline_params.pag_scale_start = float(_restorer_config.pag_settings.pag_scale_start)
|
| 1600 |
+
config.restorer_pipeline_params.pag_layers = _restorer_config.pag_settings.pag_layers
|
| 1601 |
+
config.restorer_pipeline_params.start_point = StartPoint.from_str(_restorer_config.start_point)
|
| 1602 |
+
config.restorer_pipeline_params.image_size_fix_mode = ImageSizeFixMode.from_str(_restorer_config.general.image_size_fix_mode)
|
| 1603 |
+
config.restorer_pipeline_params.color_fix_mode = ColorFix.from_str(_restorer_config.post_processsing_settings.color_fix_mode)
|
| 1604 |
+
(
|
| 1605 |
+
config.restorer_pipeline_params.zero_sft_injection_configs,
|
| 1606 |
+
config.restorer_pipeline_params.zero_sft_injection_flags,
|
| 1607 |
+
) = self.state.uidata.map_ui_supir_injection_to_pipeline_params(restorer_supir_advanced_config)
|
| 1608 |
+
config.restorer_pipeline_params.callback_on_step_end = progress_callback
|
| 1609 |
+
config.restorer_sampler_config = self.state.uidata.map_scheduler_settings_to_config(SAMPLER_MAPPING["restorer_sampler"])
|
| 1610 |
+
elif res_engine == RestorerEngine.FaithDiff.value:
|
| 1611 |
+
_restorer_config = cast(FaithDiff_Config, _restorer_config)
|
| 1612 |
+
config.selected_restorer_checkpoint_model = input_param_with_values["Image Restore Model"]
|
| 1613 |
+
config.restorer_engine = RestorerEngine.FaithDiff
|
| 1614 |
+
config.selected_restorer_sampler = Sampler.from_str(input_param_with_values["Image Restore Sampler"])
|
| 1615 |
+
config.restorer_pipeline_params.seed = _restorer_config.general.seed
|
| 1616 |
+
config.restorer_pipeline_params.upscale_factor = _restorer_config.general.upscale_factor
|
| 1617 |
+
config.restorer_pipeline_params.prompt = input_param_with_values["Image Restore - Prompt 1"]
|
| 1618 |
+
config.restorer_pipeline_params.prompt_2 = input_param_with_values["Image Restore - Prompt 2"]
|
| 1619 |
+
config.restorer_pipeline_params.negative_prompt = input_param_with_values["Image Restore - Negative prompt"]
|
| 1620 |
+
config.restore_face = input_param_with_values["Enable Face Restoration"]
|
| 1621 |
+
config.mask_prompt = input_param_with_values["Mask Prompt"]
|
| 1622 |
+
config.restorer_pipeline_params.num_images = _restorer_config.general.num_images
|
| 1623 |
+
config.restorer_pipeline_params.num_steps = _restorer_config.general.num_steps
|
| 1624 |
+
config.restorer_pipeline_params.use_lpw_prompt = (
|
| 1625 |
+
True if input_param_with_values["Image Restore - Prompt method"] == PromptMethod.Weighted.value else False
|
| 1626 |
+
)
|
| 1627 |
+
config.restorer_pipeline_params.tile_size = _restorer_config.general.tile_size
|
| 1628 |
+
config.restorer_pipeline_params.s_churn = float(_restorer_config.s_churn)
|
| 1629 |
+
config.restorer_pipeline_params.s_noise = float(_restorer_config.s_noise)
|
| 1630 |
+
config.restorer_pipeline_params.strength = float(_restorer_config.strength)
|
| 1631 |
+
config.restorer_pipeline_params.guidance_scale = float(_restorer_config.general.guidance_scale)
|
| 1632 |
+
config.restorer_pipeline_params.guidance_rescale = float(_restorer_config.general.guidance_rescale)
|
| 1633 |
+
config.restorer_pipeline_params.use_linear_control_scale = _restorer_config.controlnet_settings.use_linear_control_scale
|
| 1634 |
+
config.restorer_pipeline_params.reverse_linear_control_scale = _restorer_config.controlnet_settings.reverse_linear_control_scale
|
| 1635 |
+
config.restorer_pipeline_params.controlnet_conditioning_scale = float(_restorer_config.controlnet_settings.controlnet_conditioning_scale)
|
| 1636 |
+
config.restorer_pipeline_params.control_scale_start = float(_restorer_config.controlnet_settings.control_scale_start)
|
| 1637 |
+
config.restorer_pipeline_params.enable_PAG = _restorer_config.pag_settings.enable_PAG and len(_restorer_config.pag_settings.pag_layers) > 0
|
| 1638 |
+
config.restorer_pipeline_params.use_linear_PAG = _restorer_config.pag_settings.use_linear_PAG
|
| 1639 |
+
config.restorer_pipeline_params.reverse_linear_PAG = _restorer_config.pag_settings.reverse_linear_PAG
|
| 1640 |
+
config.restorer_pipeline_params.pag_scale = float(_restorer_config.pag_settings.pag_scale)
|
| 1641 |
+
config.restorer_pipeline_params.pag_scale_start = float(_restorer_config.pag_settings.pag_scale_start)
|
| 1642 |
+
config.restorer_pipeline_params.pag_layers = _restorer_config.pag_settings.pag_layers
|
| 1643 |
+
config.restorer_pipeline_params.start_point = StartPoint.from_str(_restorer_config.start_point)
|
| 1644 |
+
config.restorer_pipeline_params.image_size_fix_mode = ImageSizeFixMode.from_str(_restorer_config.general.image_size_fix_mode)
|
| 1645 |
+
config.restorer_pipeline_params.color_fix_mode = ColorFix.from_str(_restorer_config.post_processsing_settings.color_fix_mode)
|
| 1646 |
+
config.restorer_pipeline_params.invert_prompts = _restorer_config.invert_prompts
|
| 1647 |
+
config.restorer_pipeline_params.apply_ipa_embeds = _restorer_config.apply_ipa_embeds
|
| 1648 |
+
config.restorer_pipeline_params.callback_on_step_end = progress_callback
|
| 1649 |
+
config.restorer_sampler_config = self.state.uidata.map_scheduler_settings_to_config(SAMPLER_MAPPING["restorer_sampler"])
|
| 1650 |
+
if ups_engine == UpscalerEngine.SUPIR.value:
|
| 1651 |
+
_upscaler_config = cast(SUPIR_Config, _upscaler_config)
|
| 1652 |
+
config.selected_upscaler_checkpoint_model = input_param_with_values["Image Upscale Model"]
|
| 1653 |
+
config.upscaler_engine = UpscalerEngine.SUPIR
|
| 1654 |
+
config.selected_upscaler_sampler = Sampler.from_str(input_param_with_values["Image Upscale Sampler"])
|
| 1655 |
+
config.upscaler_pipeline_params.supir_model = SUPIRModel.from_str(_upscaler_config.supir_model)
|
| 1656 |
+
config.upscaler_pipeline_params.seed = _upscaler_config.general.seed
|
| 1657 |
+
config.upscaler_pipeline_params.upscale_factor = _upscaler_config.general.upscale_factor
|
| 1658 |
+
config.upscaler_pipeline_params.prompt = input_param_with_values["Image Upscale - Prompt 1"]
|
| 1659 |
+
config.upscaler_pipeline_params.prompt_2 = input_param_with_values["Image Upscale - Prompt 2"]
|
| 1660 |
+
config.upscaler_pipeline_params.negative_prompt = input_param_with_values["Image Upscale - Negative prompt"]
|
| 1661 |
+
config.upscaler_pipeline_params.num_images = _upscaler_config.general.num_images
|
| 1662 |
+
config.upscaler_pipeline_params.num_steps = _upscaler_config.general.num_steps
|
| 1663 |
+
config.upscaler_pipeline_params.use_lpw_prompt = (
|
| 1664 |
+
True if input_param_with_values["Image Upscale - Prompt method"] == PromptMethod.Weighted.value else False
|
| 1665 |
+
)
|
| 1666 |
+
config.upscaler_pipeline_params.tile_size = _upscaler_config.general.tile_size
|
| 1667 |
+
config.upscaler_pipeline_params.restoration_scale = float(_upscaler_config.restoration_scale)
|
| 1668 |
+
config.upscaler_pipeline_params.s_churn = float(_upscaler_config.s_churn)
|
| 1669 |
+
config.upscaler_pipeline_params.s_noise = float(_upscaler_config.s_noise)
|
| 1670 |
+
config.upscaler_pipeline_params.strength = float(_upscaler_config.strength)
|
| 1671 |
+
config.upscaler_pipeline_params.use_linear_CFG = _upscaler_config.cfg_settings.use_linear_CFG
|
| 1672 |
+
config.upscaler_pipeline_params.guidance_scale = float(_upscaler_config.general.guidance_scale)
|
| 1673 |
+
config.upscaler_pipeline_params.guidance_rescale = float(_upscaler_config.general.guidance_rescale)
|
| 1674 |
+
config.upscaler_pipeline_params.reverse_linear_CFG = float(_upscaler_config.cfg_settings.reverse_linear_CFG)
|
| 1675 |
+
config.upscaler_pipeline_params.guidance_scale_start = float(_upscaler_config.cfg_settings.guidance_scale_start)
|
| 1676 |
+
config.upscaler_pipeline_params.use_linear_control_scale = _upscaler_config.controlnet_settings.use_linear_control_scale
|
| 1677 |
+
config.upscaler_pipeline_params.reverse_linear_control_scale = _upscaler_config.controlnet_settings.reverse_linear_control_scale
|
| 1678 |
+
config.upscaler_pipeline_params.controlnet_conditioning_scale = float(_upscaler_config.controlnet_settings.controlnet_conditioning_scale)
|
| 1679 |
+
config.upscaler_pipeline_params.control_scale_start = float(_upscaler_config.controlnet_settings.control_scale_start)
|
| 1680 |
+
config.upscaler_pipeline_params.enable_PAG = _upscaler_config.pag_settings.enable_PAG
|
| 1681 |
+
config.upscaler_pipeline_params.use_linear_PAG = _upscaler_config.pag_settings.use_linear_PAG
|
| 1682 |
+
config.upscaler_pipeline_params.reverse_linear_PAG = (
|
| 1683 |
+
_upscaler_config.pag_settings.reverse_linear_PAG and len(_upscaler_config.pag_settings.pag_layers) > 0
|
| 1684 |
+
)
|
| 1685 |
+
config.upscaler_pipeline_params.pag_scale = float(_upscaler_config.pag_settings.pag_scale)
|
| 1686 |
+
config.upscaler_pipeline_params.pag_scale_start = float(_upscaler_config.pag_settings.pag_scale_start)
|
| 1687 |
+
config.upscaler_pipeline_params.pag_layers = _upscaler_config.pag_settings.pag_layers
|
| 1688 |
+
config.upscaler_pipeline_params.start_point = StartPoint.from_str(_upscaler_config.start_point)
|
| 1689 |
+
config.upscaler_pipeline_params.image_size_fix_mode = ImageSizeFixMode.from_str(_upscaler_config.general.image_size_fix_mode)
|
| 1690 |
+
config.upscaler_pipeline_params.upscaling_mode = UpscalingMode.from_str(_upscaler_config.general.upscaling_mode)
|
| 1691 |
+
(
|
| 1692 |
+
config.upscaler_pipeline_params.zero_sft_injection_configs,
|
| 1693 |
+
config.upscaler_pipeline_params.zero_sft_injection_flags,
|
| 1694 |
+
) = self.state.uidata.map_ui_supir_injection_to_pipeline_params(upscaler_supir_advanced_config)
|
| 1695 |
+
config.upscaler_pipeline_params.cfg_decay_rate = _upscaler_config.general.cfg_decay_rate
|
| 1696 |
+
config.upscaler_pipeline_params.strength_decay_rate = _upscaler_config.general.strength_decay_rate
|
| 1697 |
+
config.upscaler_pipeline_params.color_fix_mode = ColorFix.from_str(_upscaler_config.post_processsing_settings.color_fix_mode)
|
| 1698 |
+
config.upscaler_pipeline_params.callback_on_step_end = progress_callback
|
| 1699 |
+
config.upscaler_sampler_config = self.state.uidata.map_scheduler_settings_to_config(SAMPLER_MAPPING["upscaler_sampler"])
|
| 1700 |
+
elif ups_engine == UpscalerEngine.FaithDiff.value:
|
| 1701 |
+
_upscaler_config = cast(FaithDiff_Config, _upscaler_config)
|
| 1702 |
+
config.selected_upscaler_checkpoint_model = input_param_with_values["Image Upscale Model"]
|
| 1703 |
+
config.upscaler_engine = UpscalerEngine.FaithDiff
|
| 1704 |
+
config.selected_upscaler_sampler = Sampler.from_str(input_param_with_values["Image Upscale Sampler"])
|
| 1705 |
+
config.upscaler_pipeline_params.seed = _upscaler_config.general.seed
|
| 1706 |
+
config.upscaler_pipeline_params.upscale_factor = _upscaler_config.general.upscale_factor
|
| 1707 |
+
config.upscaler_pipeline_params.prompt = input_param_with_values["Image Upscale - Prompt 1"]
|
| 1708 |
+
config.upscaler_pipeline_params.prompt_2 = input_param_with_values["Image Upscale - Prompt 2"]
|
| 1709 |
+
config.upscaler_pipeline_params.negative_prompt = input_param_with_values["Image Upscale - Negative prompt"]
|
| 1710 |
+
config.upscaler_pipeline_params.num_images = _upscaler_config.general.num_images
|
| 1711 |
+
config.upscaler_pipeline_params.num_steps = _upscaler_config.general.num_steps
|
| 1712 |
+
config.upscaler_pipeline_params.use_lpw_prompt = (
|
| 1713 |
+
True if input_param_with_values["Image Upscale - Prompt method"] == PromptMethod.Weighted.value else False
|
| 1714 |
+
)
|
| 1715 |
+
config.upscaler_pipeline_params.tile_size = _upscaler_config.general.tile_size
|
| 1716 |
+
config.upscaler_pipeline_params.s_churn = float(_upscaler_config.s_churn)
|
| 1717 |
+
config.upscaler_pipeline_params.s_noise = float(_upscaler_config.s_noise)
|
| 1718 |
+
config.upscaler_pipeline_params.strength = float(_upscaler_config.strength)
|
| 1719 |
+
config.upscaler_pipeline_params.guidance_scale = float(_upscaler_config.general.guidance_scale)
|
| 1720 |
+
config.upscaler_pipeline_params.guidance_rescale = float(_upscaler_config.general.guidance_rescale)
|
| 1721 |
+
config.upscaler_pipeline_params.use_linear_control_scale = _upscaler_config.controlnet_settings.use_linear_control_scale
|
| 1722 |
+
config.upscaler_pipeline_params.reverse_linear_control_scale = _upscaler_config.controlnet_settings.reverse_linear_control_scale
|
| 1723 |
+
config.upscaler_pipeline_params.controlnet_conditioning_scale = float(_upscaler_config.controlnet_settings.controlnet_conditioning_scale)
|
| 1724 |
+
config.upscaler_pipeline_params.control_scale_start = float(_upscaler_config.controlnet_settings.control_scale_start)
|
| 1725 |
+
config.upscaler_pipeline_params.enable_PAG = _upscaler_config.pag_settings.enable_PAG and len(_upscaler_config.pag_settings.pag_layers) > 0
|
| 1726 |
+
config.upscaler_pipeline_params.use_linear_PAG = _upscaler_config.pag_settings.use_linear_PAG
|
| 1727 |
+
config.upscaler_pipeline_params.reverse_linear_PAG = _upscaler_config.pag_settings.reverse_linear_PAG
|
| 1728 |
+
config.upscaler_pipeline_params.pag_scale = float(_upscaler_config.pag_settings.pag_scale)
|
| 1729 |
+
config.upscaler_pipeline_params.pag_scale_start = float(_upscaler_config.pag_settings.pag_scale_start)
|
| 1730 |
+
config.upscaler_pipeline_params.pag_layers = _upscaler_config.pag_settings.pag_layers
|
| 1731 |
+
config.upscaler_pipeline_params.start_point = StartPoint.from_str(_upscaler_config.start_point)
|
| 1732 |
+
config.upscaler_pipeline_params.image_size_fix_mode = ImageSizeFixMode.from_str(_upscaler_config.general.image_size_fix_mode)
|
| 1733 |
+
config.upscaler_pipeline_params.upscaling_mode = UpscalingMode.from_str(_upscaler_config.general.upscaling_mode)
|
| 1734 |
+
config.upscaler_pipeline_params.invert_prompts = _upscaler_config.invert_prompts
|
| 1735 |
+
config.upscaler_pipeline_params.apply_ipa_embeds = _upscaler_config.apply_ipa_embeds
|
| 1736 |
+
config.upscaler_pipeline_params.cfg_decay_rate = _upscaler_config.general.cfg_decay_rate
|
| 1737 |
+
config.upscaler_pipeline_params.strength_decay_rate = _upscaler_config.general.strength_decay_rate
|
| 1738 |
+
config.upscaler_pipeline_params.color_fix_mode = ColorFix.from_str(_upscaler_config.post_processsing_settings.color_fix_mode)
|
| 1739 |
+
config.upscaler_pipeline_params.callback_on_step_end = progress_callback
|
| 1740 |
+
config.upscaler_sampler_config = self.state.uidata.map_scheduler_settings_to_config(SAMPLER_MAPPING["upscaler_sampler"])
|
| 1741 |
+
elif ups_engine == UpscalerEngine.ControlNetTile.value:
|
| 1742 |
+
_upscaler_config = cast(ControlNetTile_Config, _upscaler_config)
|
| 1743 |
+
config.selected_upscaler_checkpoint_model = input_param_with_values["Image Upscale Model"]
|
| 1744 |
+
config.upscaler_engine = UpscalerEngine.ControlNetTile
|
| 1745 |
+
config.selected_upscaler_sampler = Sampler.from_str(input_param_with_values["Image Upscale Sampler"])
|
| 1746 |
+
config.upscaler_pipeline_params.seed = _upscaler_config.general.seed
|
| 1747 |
+
config.upscaler_pipeline_params.upscale_factor = _upscaler_config.general.upscale_factor
|
| 1748 |
+
config.upscaler_pipeline_params.prompt = input_param_with_values["Image Upscale - Prompt 1"]
|
| 1749 |
+
config.upscaler_pipeline_params.prompt_2 = input_param_with_values["Image Upscale - Prompt 2"]
|
| 1750 |
+
config.upscaler_pipeline_params.negative_prompt = input_param_with_values["Image Upscale - Negative prompt"]
|
| 1751 |
+
config.upscaler_pipeline_params.num_images = _upscaler_config.general.num_images
|
| 1752 |
+
config.upscaler_pipeline_params.num_steps = _upscaler_config.general.num_steps
|
| 1753 |
+
config.upscaler_pipeline_params.tile_size = _upscaler_config.general.tile_size
|
| 1754 |
+
config.upscaler_pipeline_params.tile_overlap = _upscaler_config.tile_overlap
|
| 1755 |
+
config.upscaler_pipeline_params.tile_weighting_method = WeightingMethod.from_str(_upscaler_config.tile_weighting_method)
|
| 1756 |
+
config.upscaler_pipeline_params.tile_gaussian_sigma = _upscaler_config.tile_gaussian_sigma
|
| 1757 |
+
config.upscaler_pipeline_params.strength = float(_upscaler_config.strength)
|
| 1758 |
+
config.upscaler_pipeline_params.guidance_scale = float(_upscaler_config.general.guidance_scale)
|
| 1759 |
+
config.upscaler_pipeline_params.guidance_rescale = float(_upscaler_config.general.guidance_rescale)
|
| 1760 |
+
config.upscaler_pipeline_params.image_size_fix_mode = ImageSizeFixMode.from_str(_upscaler_config.general.image_size_fix_mode)
|
| 1761 |
+
config.upscaler_pipeline_params.upscaling_mode = UpscalingMode.from_str(_upscaler_config.general.upscaling_mode)
|
| 1762 |
+
config.upscaler_pipeline_params.cfg_decay_rate = _upscaler_config.general.cfg_decay_rate
|
| 1763 |
+
config.upscaler_pipeline_params.strength_decay_rate = _upscaler_config.general.strength_decay_rate
|
| 1764 |
+
config.upscaler_pipeline_params.color_fix_mode = ColorFix.from_str(_upscaler_config.post_processsing_settings.color_fix_mode)
|
| 1765 |
+
config.upscaler_pipeline_params.callback_on_step_end = progress_callback
|
| 1766 |
+
config.upscaler_sampler_config = self.state.uidata.map_scheduler_settings_to_config(SAMPLER_MAPPING["upscaler_sampler"])
|
| 1767 |
+
|
| 1768 |
+
config.image_path = input_image
|
| 1769 |
+
self.update_pipeline(log_callback=process_and_send_updates, progress_bar_handler=progress_bar_handler)
|
| 1770 |
+
|
| 1771 |
+
initialize_status = sup_toolbox_pipe.initialize()
|
| 1772 |
+
if initialize_status:
|
| 1773 |
+
result, process_status = sup_toolbox_pipe.predict(metadata=image_metadata)
|
| 1774 |
+
if process_status:
|
| 1775 |
+
logger.log(logging.INFO + 5, "Image generated successfully!")
|
| 1776 |
+
process_and_send_updates(status="success", final_image_payload=(input_image, result))
|
| 1777 |
+
else:
|
| 1778 |
+
raise RuntimeError("Pipeline prediction returned a failure status.")
|
| 1779 |
+
else:
|
| 1780 |
+
raise RuntimeError("Pipeline initialization failed.")
|
| 1781 |
+
|
| 1782 |
+
except PipelineCancelationRequested as ce:
|
| 1783 |
+
logger.warning(str(ce))
|
| 1784 |
+
process_and_send_updates(status="error")
|
| 1785 |
+
except Exception as e:
|
| 1786 |
+
logger.error(f"Error in diffusion thread: {e}, process aborted!", exc_info=True)
|
| 1787 |
+
process_and_send_updates(status="error")
|
| 1788 |
+
finally:
|
| 1789 |
+
update_queue.put(None)
|
ui/ui_layout.py
ADDED
|
@@ -0,0 +1,684 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2025 The DEVAIEXP Team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
from dataclasses import dataclass, field
|
| 16 |
+
from typing import Any, Dict
|
| 17 |
+
|
| 18 |
+
import gradio as gr
|
| 19 |
+
|
| 20 |
+
# Custom Component Imports
|
| 21 |
+
from gradio_bottombar import BottomBar
|
| 22 |
+
from gradio_buttonplus import ButtonPlus
|
| 23 |
+
from gradio_creditspanel import CreditsPanel
|
| 24 |
+
from gradio_dropdownplus import DropdownPlus
|
| 25 |
+
from gradio_folderexplorer import FolderExplorer
|
| 26 |
+
from gradio_htmlinjector import HTMLInjector
|
| 27 |
+
from gradio_imagemeta import ImageMeta
|
| 28 |
+
from gradio_livelog import LiveLog
|
| 29 |
+
from gradio_mediagallery import MediaGallery
|
| 30 |
+
from gradio_propertysheet import PropertySheet
|
| 31 |
+
from gradio_taggrouphelper import TagGroupHelper
|
| 32 |
+
from gradio_textboxplus import TextboxPlus
|
| 33 |
+
from gradio_tokenizertextbox import TokenizerTextBox
|
| 34 |
+
from gradio_topbar import TopBar
|
| 35 |
+
|
| 36 |
+
from ui.ui_config import (
|
| 37 |
+
CREDIT_LIST,
|
| 38 |
+
LICENSE_PATHS,
|
| 39 |
+
TAG_DATA_NEGATIVE,
|
| 40 |
+
TAG_DATA_POSITIVE,
|
| 41 |
+
AppSettings,
|
| 42 |
+
ControlNetTile_Config,
|
| 43 |
+
SUPIR_Config,
|
| 44 |
+
SUPIRAdvanced_Config,
|
| 45 |
+
)
|
| 46 |
+
from ui.ui_data import UIData
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
@dataclass
|
| 50 |
+
class UIComponents:
|
| 51 |
+
"""A dataclass to hold all interactive UI components for type safety and Intellisense."""
|
| 52 |
+
|
| 53 |
+
html_injector: HTMLInjector
|
| 54 |
+
flyout_visible: gr.State
|
| 55 |
+
active_anchor_id: gr.State
|
| 56 |
+
js_data_bridge: gr.Textbox
|
| 57 |
+
total_inference_steps: gr.State
|
| 58 |
+
run_btn: gr.Button
|
| 59 |
+
cancel_btn: gr.Button
|
| 60 |
+
preset_name: TextboxPlus
|
| 61 |
+
presets: DropdownPlus
|
| 62 |
+
save_preset_btn: gr.Button
|
| 63 |
+
load_preset_btn: gr.Button
|
| 64 |
+
restorer_engine: DropdownPlus
|
| 65 |
+
upscaler_engine: DropdownPlus
|
| 66 |
+
restorer_model: DropdownPlus
|
| 67 |
+
upscaler_model: DropdownPlus
|
| 68 |
+
vae_model: gr.Dropdown
|
| 69 |
+
restorer_sampler: DropdownPlus
|
| 70 |
+
restorer_sampler_ear_btn: ButtonPlus
|
| 71 |
+
upscaler_sampler: DropdownPlus
|
| 72 |
+
upscaler_sampler_ear_btn: ButtonPlus
|
| 73 |
+
flyout_property_sheet_close_btn: gr.Button
|
| 74 |
+
flyout_sheet: PropertySheet
|
| 75 |
+
main_tabs: gr.Tabs
|
| 76 |
+
input_image: ImageMeta
|
| 77 |
+
result_slider: gr.ImageSlider
|
| 78 |
+
ec_accordion: gr.Accordion
|
| 79 |
+
config_tabs: gr.Tabs
|
| 80 |
+
restorer_tab: gr.Tab
|
| 81 |
+
res_prompt_method: gr.Dropdown
|
| 82 |
+
res_prompt: gr.Textbox
|
| 83 |
+
res_prompt_generate_btn: ButtonPlus
|
| 84 |
+
res_prompt_2: gr.Textbox
|
| 85 |
+
res_tokenizer_pos: TokenizerTextBox
|
| 86 |
+
res_negative_prompt: gr.Textbox
|
| 87 |
+
res_tokenizer_neg: TokenizerTextBox
|
| 88 |
+
preview_restoration_mask_chk: gr.Checkbox
|
| 89 |
+
restoration_mask_prompt: gr.Textbox
|
| 90 |
+
preview_restoration_mask_btn: ButtonPlus
|
| 91 |
+
flyout_restoration_image_close_btn: gr.Button
|
| 92 |
+
restoration_mask: gr.Image
|
| 93 |
+
restorer_sheet: PropertySheet
|
| 94 |
+
restorer_sheet_supir_advanced: PropertySheet
|
| 95 |
+
upscaler_tab: gr.Tab
|
| 96 |
+
ups_prompt_method: gr.Dropdown
|
| 97 |
+
ups_prompt: gr.Textbox
|
| 98 |
+
ups_prompt_generate_btn: ButtonPlus
|
| 99 |
+
ups_prompt_2: gr.Textbox
|
| 100 |
+
ups_tokenizer_pos: TokenizerTextBox
|
| 101 |
+
ups_negative_prompt: gr.Textbox
|
| 102 |
+
ups_tokenizer_neg: TokenizerTextBox
|
| 103 |
+
upscaler_sheet: PropertySheet
|
| 104 |
+
upscaler_sheet_supir_advanced: PropertySheet
|
| 105 |
+
generated_tab: gr.Tab
|
| 106 |
+
folder_explorer: FolderExplorer
|
| 107 |
+
generated_image_viewer: MediaGallery
|
| 108 |
+
settings_tab: gr.Tab
|
| 109 |
+
settings_sheet: PropertySheet
|
| 110 |
+
reset_settings_btn: gr.Button
|
| 111 |
+
save_settings_btn: gr.Button
|
| 112 |
+
about_tab: gr.Tab
|
| 113 |
+
tag_helper_pos: TagGroupHelper
|
| 114 |
+
tag_helper_neg: TagGroupHelper
|
| 115 |
+
bottom_bar: BottomBar
|
| 116 |
+
livelog_viewer: LiveLog
|
| 117 |
+
ALL_UI_COMPONENTS: Dict[str, Any] = field(default_factory=dict)
|
| 118 |
+
|
| 119 |
+
def _get_ui_inputs_and_outputs(self):
|
| 120 |
+
"""Internal helper to reconstruct UI input/output lists when needed."""
|
| 121 |
+
|
| 122 |
+
ui_inputs = {
|
| 123 |
+
"Restorer Sheet": self.restorer_sheet,
|
| 124 |
+
"Restorer Sheet SUPIR Advanced": self.restorer_sheet_supir_advanced,
|
| 125 |
+
"Upscaler Sheet": self.upscaler_sheet,
|
| 126 |
+
"Upscaler Sheet SUPIR Advanced": self.upscaler_sheet_supir_advanced,
|
| 127 |
+
"Image Restore Engine": self.restorer_engine,
|
| 128 |
+
"Image Upscale Engine": self.upscaler_engine,
|
| 129 |
+
"Image Restore Model": self.restorer_model,
|
| 130 |
+
"Image Upscale Model": self.upscaler_model,
|
| 131 |
+
"VAE Model": self.vae_model,
|
| 132 |
+
"Image Restore Sampler": self.restorer_sampler,
|
| 133 |
+
"Image Upscale Sampler": self.upscaler_sampler,
|
| 134 |
+
"Input Image": self.input_image,
|
| 135 |
+
"Image Restore - Prompt method": self.res_prompt_method,
|
| 136 |
+
"Image Restore - Prompt 1": self.res_prompt,
|
| 137 |
+
"Image Restore - Prompt 2": self.res_prompt_2,
|
| 138 |
+
"Image Restore - Negative prompt": self.res_negative_prompt,
|
| 139 |
+
"Enable Face Restoration": self.preview_restoration_mask_chk,
|
| 140 |
+
"Mask Prompt": self.restoration_mask_prompt,
|
| 141 |
+
"Image Upscale - Prompt method": self.ups_prompt_method,
|
| 142 |
+
"Image Upscale - Prompt 1": self.ups_prompt,
|
| 143 |
+
"Image Upscale - Prompt 2": self.ups_prompt_2,
|
| 144 |
+
"Image Upscale - Negative prompt": self.ups_negative_prompt,
|
| 145 |
+
}
|
| 146 |
+
output_fields = [*ui_inputs.copy().values()]
|
| 147 |
+
ui_inputs.pop("Input Image", None)
|
| 148 |
+
return ui_inputs, output_fields
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def get_initial_supir_advanced_values():
|
| 152 |
+
"""Helper to provide initial default values to the UI component."""
|
| 153 |
+
initial_supir_advanced_settings = SUPIRAdvanced_Config()
|
| 154 |
+
initial_supir_advanced_settings.cross_up_block_0_stage1.sft_active = False
|
| 155 |
+
initial_supir_advanced_settings.cross_up_block_1_stage1.sft_active = False
|
| 156 |
+
return initial_supir_advanced_settings
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def create_ui_components(uidata: UIData) -> UIComponents:
|
| 160 |
+
"""
|
| 161 |
+
Builds and returns a dictionary of UI components.
|
| 162 |
+
This function MUST be called from within a `gr.Blocks()` context.
|
| 163 |
+
"""
|
| 164 |
+
|
| 165 |
+
html_injector = HTMLInjector()
|
| 166 |
+
flyout_visible = gr.State(False)
|
| 167 |
+
active_anchor_id = gr.State(None)
|
| 168 |
+
js_data_bridge = gr.Textbox(visible=False, elem_id="js_data_bridge")
|
| 169 |
+
total_inference_steps = gr.State(0)
|
| 170 |
+
gr.Markdown(
|
| 171 |
+
"""
|
| 172 |
+
<div align="center">
|
| 173 |
+
|
| 174 |
+
# SUP Toolbox App Demo
|
| 175 |
+
|
| 176 |
+
> **Note:** This is a demonstration version with limited features.
|
| 177 |
+
> The **Face Restoration** and **Auto Caption Generation** features are disabled in this online demo
|
| 178 |
+
> to ensure stability and reduce resource usage. For access to all features, please run the
|
| 179 |
+
> [full application locally](https://github.com/DEVAIEXP/sup-toolbox-app).
|
| 180 |
+
|
| 181 |
+
</div>
|
| 182 |
+
"""
|
| 183 |
+
)
|
| 184 |
+
with gr.Row():
|
| 185 |
+
with TopBar(width="30%", height=80, bring_to_front=True, rounded_borders=True):
|
| 186 |
+
with gr.Row():
|
| 187 |
+
run_btn = gr.Button(f"{uidata.process_symbol} Run Process", variant="primary", elem_id="run-button")
|
| 188 |
+
cancel_btn = gr.Button(
|
| 189 |
+
f"{uidata.stop_symbol} Cancel",
|
| 190 |
+
variant="stop",
|
| 191 |
+
elem_id="cancel-button",
|
| 192 |
+
visible=False,
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
with gr.Sidebar(width=360):
|
| 196 |
+
with gr.Column(elem_classes=["flyout-context-area"], min_width=250):
|
| 197 |
+
gr.Markdown("### Preset Selection")
|
| 198 |
+
with gr.Row():
|
| 199 |
+
preset_name = TextboxPlus(
|
| 200 |
+
elem_id="preset_name",
|
| 201 |
+
label="Preset name",
|
| 202 |
+
help="Name of the preset to be saved or loaded",
|
| 203 |
+
)
|
| 204 |
+
presets = DropdownPlus(
|
| 205 |
+
elem_id="presets",
|
| 206 |
+
label="Presets",
|
| 207 |
+
interactive=True,
|
| 208 |
+
choices=uidata.PRESETS_LIST,
|
| 209 |
+
value=uidata.config.latest_preset,
|
| 210 |
+
help="Select a preset to load",
|
| 211 |
+
)
|
| 212 |
+
with gr.Row():
|
| 213 |
+
save_preset_btn = gr.Button(
|
| 214 |
+
f"{uidata.save_symbol} Save preset",
|
| 215 |
+
variant="primary",
|
| 216 |
+
elem_id="save_preset",
|
| 217 |
+
)
|
| 218 |
+
load_preset_btn = gr.Button(f"{uidata.download_symbol} Load preset", elem_id="load_preset")
|
| 219 |
+
with gr.Row():
|
| 220 |
+
gr.Markdown("### Engine Selection")
|
| 221 |
+
restorer_engine = DropdownPlus(
|
| 222 |
+
elem_id="restorer_engine",
|
| 223 |
+
label="Image Restore Engine",
|
| 224 |
+
choices=uidata.RESTORER_ENGINE_CHOICES,
|
| 225 |
+
value="SUPIR",
|
| 226 |
+
help="Select the image restoration engine to be used",
|
| 227 |
+
)
|
| 228 |
+
upscaler_engine = DropdownPlus(
|
| 229 |
+
elem_id="upscaler_engine",
|
| 230 |
+
label="Image Upscale Engine",
|
| 231 |
+
choices=uidata.UPSCALER_ENGINE_CHOICES,
|
| 232 |
+
value="None",
|
| 233 |
+
help="Select the image upscaling engine to be used",
|
| 234 |
+
)
|
| 235 |
+
gr.Markdown("### Model Selection")
|
| 236 |
+
restorer_model = DropdownPlus(
|
| 237 |
+
elem_id="restorer_model",
|
| 238 |
+
label="Image Restore Model",
|
| 239 |
+
choices=uidata.MODEL_CHOICES,
|
| 240 |
+
value=uidata.MODEL_CHOICES[0] if uidata.MODEL_CHOICES else None,
|
| 241 |
+
elem_classes=["custom-dropdown"],
|
| 242 |
+
help="Select the model to be used for the restoration engine",
|
| 243 |
+
)
|
| 244 |
+
upscaler_model = DropdownPlus(
|
| 245 |
+
elem_id="upscaler_model",
|
| 246 |
+
label="Image Upscale Model",
|
| 247 |
+
choices=uidata.MODEL_CHOICES,
|
| 248 |
+
value=uidata.MODEL_CHOICES[0] if uidata.MODEL_CHOICES else None,
|
| 249 |
+
elem_classes=["custom-dropdown"],
|
| 250 |
+
help="Select the model to be used for the upscaling engine",
|
| 251 |
+
)
|
| 252 |
+
vae_model = gr.Dropdown(
|
| 253 |
+
elem_id="vae_model",
|
| 254 |
+
label="VAE Model (Optional)",
|
| 255 |
+
choices=uidata.VAE_CHOICES,
|
| 256 |
+
value="Default",
|
| 257 |
+
)
|
| 258 |
+
gr.Markdown("### Sampler Selection")
|
| 259 |
+
with gr.Accordion(open=False, label="Create/Choose Sampler", elem_id="sampler-accordion"):
|
| 260 |
+
with gr.Group(elem_classes=["group"]):
|
| 261 |
+
with gr.Row(elem_classes=["fake-input-container", "no-border-dropdown"]):
|
| 262 |
+
restorer_sampler = DropdownPlus(
|
| 263 |
+
elem_id="restorer_sampler",
|
| 264 |
+
label="Image Restore Sampler",
|
| 265 |
+
choices=uidata.SAMPLER_SUPIR_LIST,
|
| 266 |
+
value="Euler",
|
| 267 |
+
help="Select the sampler to be used for the restoration engine",
|
| 268 |
+
)
|
| 269 |
+
restorer_sampler_ear_btn = ButtonPlus(
|
| 270 |
+
f"{uidata.engine_symbol}",
|
| 271 |
+
elem_id="restorer_sampler_ear_btn",
|
| 272 |
+
scale=1,
|
| 273 |
+
elem_classes=["integrated-ear-btn"],
|
| 274 |
+
help="Click to open advanced settings...",
|
| 275 |
+
)
|
| 276 |
+
with gr.Row(elem_classes=["fake-input-container", "no-border-dropdown"]):
|
| 277 |
+
upscaler_sampler = DropdownPlus(
|
| 278 |
+
elem_id="upscaler_sampler",
|
| 279 |
+
label="Image Upscale Sampler",
|
| 280 |
+
choices=uidata.SAMPLER_OTHERS_LIST,
|
| 281 |
+
value="Euler",
|
| 282 |
+
help="Select the sampler to be used for the upscaling engine",
|
| 283 |
+
)
|
| 284 |
+
upscaler_sampler_ear_btn = ButtonPlus(
|
| 285 |
+
f"{uidata.engine_symbol}",
|
| 286 |
+
elem_id="upscaler_sampler_ear_btn",
|
| 287 |
+
scale=1,
|
| 288 |
+
elem_classes=["integrated-ear-btn"],
|
| 289 |
+
help="Click to open advanced settings...",
|
| 290 |
+
)
|
| 291 |
+
with gr.Column(elem_id="flyout_property_sheet_panel", elem_classes=["flyout-source-hidden"]):
|
| 292 |
+
flyout_property_sheet_close_btn = gr.Button("×", elem_classes=["flyout-close-btn"])
|
| 293 |
+
flyout_sheet = PropertySheet(
|
| 294 |
+
elem_id="flyout_sheet",
|
| 295 |
+
visible=True,
|
| 296 |
+
container=False,
|
| 297 |
+
label="Settings",
|
| 298 |
+
show_group_name_only_one=False,
|
| 299 |
+
disable_accordion=True,
|
| 300 |
+
)
|
| 301 |
+
|
| 302 |
+
with gr.Tabs() as main_tabs:
|
| 303 |
+
with gr.TabItem(f"{uidata.process_symbol} Process", id="process-tab"):
|
| 304 |
+
with gr.Row():
|
| 305 |
+
with gr.Column(scale=3):
|
| 306 |
+
with gr.Row():
|
| 307 |
+
input_image = ImageMeta(
|
| 308 |
+
elem_id="input_image",
|
| 309 |
+
type="filepath",
|
| 310 |
+
label="Input Image",
|
| 311 |
+
height=500,
|
| 312 |
+
width=600,
|
| 313 |
+
popup_metadata_width=600,
|
| 314 |
+
popup_metadata_height=500,
|
| 315 |
+
only_custom_metadata=False,
|
| 316 |
+
)
|
| 317 |
+
result_slider = gr.ImageSlider(
|
| 318 |
+
elem_id="result_slider",
|
| 319 |
+
label="Result Comparison",
|
| 320 |
+
show_label=True,
|
| 321 |
+
height=500,
|
| 322 |
+
)
|
| 323 |
+
|
| 324 |
+
with gr.Accordion("Engine Configurations", open=True) as ec_accordion:
|
| 325 |
+
with gr.Tabs() as config_tabs:
|
| 326 |
+
with gr.TabItem("Restoration", id=0, visible=False, elem_id="res-tab") as restorer_tab:
|
| 327 |
+
res_prompt_method = gr.Dropdown(
|
| 328 |
+
elem_id="restorer_prompt_method",
|
| 329 |
+
label="Prompt Method",
|
| 330 |
+
choices=uidata.PROMPT_METHODS,
|
| 331 |
+
value=uidata.PROMPT_METHODS[1],
|
| 332 |
+
info="Method used to generate prompt embedding",
|
| 333 |
+
)
|
| 334 |
+
with gr.Group(elem_classes=["group"]):
|
| 335 |
+
with gr.Row(
|
| 336 |
+
elem_classes=[
|
| 337 |
+
"fake-input-container",
|
| 338 |
+
"no-border-dropdown",
|
| 339 |
+
"row-form-size-2",
|
| 340 |
+
]
|
| 341 |
+
):
|
| 342 |
+
res_prompt = gr.Textbox(
|
| 343 |
+
elem_id="restorer_prompt_1",
|
| 344 |
+
label="Prompt",
|
| 345 |
+
lines=3,
|
| 346 |
+
info="Your positive prompt",
|
| 347 |
+
value="Direct flash photography. Three (30-year-old men:1.1), (all black hair:1.2). Left man: (black t-shirt:1.1) with white text 'Road Kill Cafe' and in his right forearm has distinct (dark tribal tattoo:1.2).Their hands has clearly defined fingers and distinct outlines. A (plaster interior wall: 1.1) on the left.",
|
| 348 |
+
)
|
| 349 |
+
res_prompt_generate_btn = ButtonPlus(
|
| 350 |
+
value=f"{uidata.caption_symbol}",
|
| 351 |
+
elem_id="res_prompt_generate_btn",
|
| 352 |
+
elem_classes=["integrated-ear-btn"],
|
| 353 |
+
help="Click to auto generate the initial caption...",
|
| 354 |
+
visible=False
|
| 355 |
+
)
|
| 356 |
+
res_prompt_2 = gr.Textbox(
|
| 357 |
+
elem_id="restorer_prompt_2",
|
| 358 |
+
label="Prompt 2",
|
| 359 |
+
lines=2,
|
| 360 |
+
info="Your positive prompt complement",
|
| 361 |
+
value="Extremely detailed faces, flawless natural skin texture, skin pore detailing, 4k, 8k, clean image, no noise, shot on Fujifilm Superia 400, sharp focus, faithful colors.",
|
| 362 |
+
)
|
| 363 |
+
res_tokenizer_pos = TokenizerTextBox(
|
| 364 |
+
elem_id="restorer_tokenizer_prompt",
|
| 365 |
+
label="Positive Tokenizer",
|
| 366 |
+
hide_input=True,
|
| 367 |
+
model="Xenova/clip-vit-large-patch14",
|
| 368 |
+
)
|
| 369 |
+
res_negative_prompt = gr.Textbox(
|
| 370 |
+
elem_id="restorer_negative_prompt",
|
| 371 |
+
label="Negative Prompt",
|
| 372 |
+
lines=3,
|
| 373 |
+
info="Specify what you doesn't want to see",
|
| 374 |
+
value="low-res, disfigured, analog artifacts, smudged, animate, (out of focus:1.2), catchlights, over-smooth, extra eyes, worst quality, unreal engine, art, aberrations, surreal, pastel drawing, (tattoo patterns on walls:1.4), tatto patterns on skin, text on walls, green wall, grainy wall texture, harsh lighting, (tribal patterns on clothing text:1.3), tattoo on chest, dead eyes, deformed fingers, undistinct fingers outlines",
|
| 375 |
+
)
|
| 376 |
+
res_tokenizer_neg = TokenizerTextBox(
|
| 377 |
+
elem_id="restorer_tokenizer_negative_prompt",
|
| 378 |
+
label="Negative Tokenizer",
|
| 379 |
+
hide_input=True,
|
| 380 |
+
model="Xenova/clip-vit-large-patch14",
|
| 381 |
+
)
|
| 382 |
+
with gr.Accordion("Face Restoration", open=False, visible=False):
|
| 383 |
+
preview_restoration_mask_chk = gr.Checkbox(
|
| 384 |
+
elem_id="use_restoration_mask",
|
| 385 |
+
label="Enable Face Restoration",
|
| 386 |
+
value=False,
|
| 387 |
+
)
|
| 388 |
+
with gr.Row(
|
| 389 |
+
elem_classes=[
|
| 390 |
+
"fake-input-container",
|
| 391 |
+
"no-border-dropdown",
|
| 392 |
+
"row-form-size",
|
| 393 |
+
]
|
| 394 |
+
):
|
| 395 |
+
restoration_mask_prompt = gr.Textbox(
|
| 396 |
+
elem_id="restoration_mask_prompt",
|
| 397 |
+
label="Mask prompt",
|
| 398 |
+
placeholder="head",
|
| 399 |
+
value="head",
|
| 400 |
+
interactive=False,
|
| 401 |
+
)
|
| 402 |
+
preview_restoration_mask_btn = ButtonPlus(
|
| 403 |
+
value=f"{uidata.preview_symbol}",
|
| 404 |
+
elem_id="preview_restoration_mask_btn",
|
| 405 |
+
elem_classes=["integrated-ear-btn"],
|
| 406 |
+
interactive=False,
|
| 407 |
+
help="Click to open preview restoration mask...",
|
| 408 |
+
)
|
| 409 |
+
with gr.Column(
|
| 410 |
+
elem_id="flyout_restoration_mask_panel",
|
| 411 |
+
elem_classes=["flyout-source-hidden"],
|
| 412 |
+
):
|
| 413 |
+
flyout_restoration_image_close_btn = gr.Button("×", elem_classes=["flyout-close-btn"])
|
| 414 |
+
restoration_mask = gr.Image(
|
| 415 |
+
elem_id="flyout_restoration_mask",
|
| 416 |
+
label="Masking preview",
|
| 417 |
+
type="pil",
|
| 418 |
+
interactive=False,
|
| 419 |
+
show_download_button=False,
|
| 420 |
+
show_share_button=False,
|
| 421 |
+
height=300,
|
| 422 |
+
)
|
| 423 |
+
restorer_sheet = PropertySheet(
|
| 424 |
+
elem_id="restorer_settings",
|
| 425 |
+
label="Restoration Settings",
|
| 426 |
+
value=SUPIR_Config(),
|
| 427 |
+
)
|
| 428 |
+
restorer_sheet_supir_advanced = PropertySheet(
|
| 429 |
+
elem_id="restorer_supir_advanced_settings",
|
| 430 |
+
label="SFT Injection Settings (Experimental parameters for advanced users)",
|
| 431 |
+
open=False,
|
| 432 |
+
value=get_initial_supir_advanced_values(),
|
| 433 |
+
)
|
| 434 |
+
|
| 435 |
+
with gr.TabItem("Upscaling", id=1, visible=False, elem_id="ups-tab") as upscaler_tab:
|
| 436 |
+
ups_prompt_method = gr.Dropdown(
|
| 437 |
+
elem_id="upscaler_prompt_method",
|
| 438 |
+
label="Prompt Method",
|
| 439 |
+
choices=uidata.PROMPT_METHODS,
|
| 440 |
+
value=uidata.PROMPT_METHODS[1],
|
| 441 |
+
info="Method used to generate prompt embedding",
|
| 442 |
+
)
|
| 443 |
+
with gr.Group(elem_classes=["group"]):
|
| 444 |
+
with gr.Row(
|
| 445 |
+
elem_classes=[
|
| 446 |
+
"fake-input-container",
|
| 447 |
+
"no-border-dropdown",
|
| 448 |
+
"row-form-size-2",
|
| 449 |
+
]
|
| 450 |
+
):
|
| 451 |
+
ups_prompt = gr.Textbox(
|
| 452 |
+
elem_id="upscaler_prompt_1",
|
| 453 |
+
label="Prompt",
|
| 454 |
+
lines=3,
|
| 455 |
+
info="Your positive prompt",
|
| 456 |
+
)
|
| 457 |
+
ups_prompt_generate_btn = ButtonPlus(
|
| 458 |
+
value=f"{uidata.caption_symbol}",
|
| 459 |
+
elem_id="ups_prompt_generate_btn",
|
| 460 |
+
elem_classes=["integrated-ear-btn"],
|
| 461 |
+
help="Click to auto generate the initial caption...",
|
| 462 |
+
visible=False
|
| 463 |
+
)
|
| 464 |
+
ups_prompt_2 = gr.Textbox(
|
| 465 |
+
elem_id="upscaler_prompt_2",
|
| 466 |
+
label="Prompt 2",
|
| 467 |
+
lines=2,
|
| 468 |
+
info="Your positive prompt complement",
|
| 469 |
+
)
|
| 470 |
+
ups_tokenizer_pos = TokenizerTextBox(
|
| 471 |
+
elem_id="upscaler_tokenizer_prompt",
|
| 472 |
+
label="Positive Tokenizer",
|
| 473 |
+
hide_input=True,
|
| 474 |
+
model="Xenova/clip-vit-large-patch14",
|
| 475 |
+
)
|
| 476 |
+
ups_negative_prompt = gr.Textbox(
|
| 477 |
+
elem_id="upscaler_negative_prompt",
|
| 478 |
+
label="Negative Prompt",
|
| 479 |
+
lines=3,
|
| 480 |
+
info="Specify what you doesn't want to see",
|
| 481 |
+
)
|
| 482 |
+
ups_tokenizer_neg = TokenizerTextBox(
|
| 483 |
+
elem_id="upscaler_tokenizer_negative_prompt",
|
| 484 |
+
label="Negative Tokenizer",
|
| 485 |
+
hide_input=True,
|
| 486 |
+
model="Xenova/clip-vit-large-patch14",
|
| 487 |
+
)
|
| 488 |
+
upscaler_sheet = PropertySheet(
|
| 489 |
+
elem_id="upscaler_settings",
|
| 490 |
+
label="Upscaling Settings",
|
| 491 |
+
value=ControlNetTile_Config(),
|
| 492 |
+
)
|
| 493 |
+
upscaler_sheet_supir_advanced = PropertySheet(
|
| 494 |
+
elem_id="upscaler_supir_advanced_settings",
|
| 495 |
+
label="SFT Injection Settings (Experimental parameters for advanced users)",
|
| 496 |
+
open=False,
|
| 497 |
+
value=get_initial_supir_advanced_values(),
|
| 498 |
+
)
|
| 499 |
+
|
| 500 |
+
with gr.TabItem(f"{uidata.folder_symbol} Generated") as generated_tab:
|
| 501 |
+
with gr.Row(equal_height=True, elem_classes="media-gallery-row"):
|
| 502 |
+
with gr.Column(scale=0, min_width=300):
|
| 503 |
+
folder_explorer = FolderExplorer(
|
| 504 |
+
label="Select a Folder",
|
| 505 |
+
root_dir=uidata.config.output_dir,
|
| 506 |
+
value=uidata.config.output_dir,
|
| 507 |
+
)
|
| 508 |
+
with gr.Column(scale=2):
|
| 509 |
+
generated_image_viewer = MediaGallery(
|
| 510 |
+
label="Media in Folder",
|
| 511 |
+
columns=4,
|
| 512 |
+
height="auto",
|
| 513 |
+
preview=False,
|
| 514 |
+
show_download_button=False,
|
| 515 |
+
only_custom_metadata=False,
|
| 516 |
+
popup_metadata_width="40%",
|
| 517 |
+
)
|
| 518 |
+
|
| 519 |
+
with gr.Tab(f"{uidata.settings_symbol} Settings", interactive=False) as settings_tab:
|
| 520 |
+
settings_sheet = PropertySheet(elem_id="settings_sheet", label="General Setting", value=AppSettings())
|
| 521 |
+
with gr.Row(elem_id="settings-buttons-row"):
|
| 522 |
+
reset_settings_btn = gr.Button(
|
| 523 |
+
value=f"{uidata.refresh_symbol} Load defaults and restart",
|
| 524 |
+
elem_id="reset_settings_button",
|
| 525 |
+
)
|
| 526 |
+
save_settings_btn = gr.Button(
|
| 527 |
+
value=f"{uidata.save_symbol} Save and restart",
|
| 528 |
+
elem_id="save_settings_button",
|
| 529 |
+
)
|
| 530 |
+
|
| 531 |
+
with gr.Tab(f"{uidata.about_symbol} About") as about_tab:
|
| 532 |
+
CreditsPanel(
|
| 533 |
+
elem_classes="credit-panel",
|
| 534 |
+
height="auto",
|
| 535 |
+
credits=CREDIT_LIST,
|
| 536 |
+
licenses=LICENSE_PATHS,
|
| 537 |
+
effect="scroll",
|
| 538 |
+
speed=60.0,
|
| 539 |
+
base_font_size=1.5,
|
| 540 |
+
intro_title="SUP Toolbox",
|
| 541 |
+
intro_subtitle="Scaling-UP Application",
|
| 542 |
+
sidebar_position="right",
|
| 543 |
+
logo_path=None,
|
| 544 |
+
show_logo=True,
|
| 545 |
+
show_licenses=True,
|
| 546 |
+
show_credits=True,
|
| 547 |
+
logo_position="center",
|
| 548 |
+
logo_sizing="resize",
|
| 549 |
+
logo_width="200px",
|
| 550 |
+
logo_height="100px",
|
| 551 |
+
scroll_background_color="#000000",
|
| 552 |
+
scroll_title_color="#FFFFFF",
|
| 553 |
+
scroll_name_color="#FFFFFF",
|
| 554 |
+
scroll_section_title_color="#FFFFFF",
|
| 555 |
+
layout_style="two-column",
|
| 556 |
+
title_uppercase=True,
|
| 557 |
+
name_uppercase=True,
|
| 558 |
+
section_title_uppercase=True,
|
| 559 |
+
swap_font_sizes_on_two_column=True,
|
| 560 |
+
scroll_logo_path="assets/devaixp_logo-white.png",
|
| 561 |
+
scroll_logo_height="200px",
|
| 562 |
+
)
|
| 563 |
+
|
| 564 |
+
with gr.Sidebar(position="right", width=360):
|
| 565 |
+
gr.Markdown("### Helpers")
|
| 566 |
+
tag_helper_pos = TagGroupHelper(
|
| 567 |
+
elem_id="tag_helper_pos",
|
| 568 |
+
label="Positive Keywords",
|
| 569 |
+
value=dict(TAG_DATA_POSITIVE),
|
| 570 |
+
target_textbox_id="upscaler_prompt_1",
|
| 571 |
+
separator=", ",
|
| 572 |
+
width=290,
|
| 573 |
+
font_size_scale=90,
|
| 574 |
+
interactive=True,
|
| 575 |
+
open=False,
|
| 576 |
+
)
|
| 577 |
+
tag_helper_neg = TagGroupHelper(
|
| 578 |
+
elem_id="tag_helper_neg",
|
| 579 |
+
label="Negative Keywords",
|
| 580 |
+
value=dict(TAG_DATA_NEGATIVE),
|
| 581 |
+
target_textbox_id="upscaler_negative_prompt",
|
| 582 |
+
separator=", ",
|
| 583 |
+
width=290,
|
| 584 |
+
font_size_scale=90,
|
| 585 |
+
interactive=True,
|
| 586 |
+
open=False,
|
| 587 |
+
)
|
| 588 |
+
|
| 589 |
+
with BottomBar("Status", bring_to_front=False, height=340, open=False, rounded_borders=True) as bottom_bar:
|
| 590 |
+
livelog_viewer = LiveLog(label="Process output", height=250, autoscroll=True, line_numbers=False)
|
| 591 |
+
|
| 592 |
+
all_ui_components_for_presets = {
|
| 593 |
+
restorer_engine.elem_id: restorer_engine,
|
| 594 |
+
upscaler_engine.elem_id: upscaler_engine,
|
| 595 |
+
restorer_model.elem_id: restorer_model,
|
| 596 |
+
upscaler_model.elem_id: upscaler_model,
|
| 597 |
+
vae_model.elem_id: vae_model,
|
| 598 |
+
restorer_sampler.elem_id: restorer_sampler,
|
| 599 |
+
upscaler_sampler.elem_id: upscaler_sampler,
|
| 600 |
+
res_prompt_method.elem_id: res_prompt_method,
|
| 601 |
+
res_prompt.elem_id: res_prompt,
|
| 602 |
+
res_prompt_2.elem_id: res_prompt_2,
|
| 603 |
+
res_negative_prompt.elem_id: res_negative_prompt,
|
| 604 |
+
preview_restoration_mask_chk.elem_id: preview_restoration_mask_chk,
|
| 605 |
+
restoration_mask_prompt.elem_id: restoration_mask_prompt,
|
| 606 |
+
restorer_sheet.elem_id: restorer_sheet,
|
| 607 |
+
restorer_sheet_supir_advanced.elem_id: restorer_sheet_supir_advanced,
|
| 608 |
+
ups_prompt_method.elem_id: ups_prompt_method,
|
| 609 |
+
ups_prompt.elem_id: ups_prompt,
|
| 610 |
+
ups_prompt_2.elem_id: ups_prompt_2,
|
| 611 |
+
ups_negative_prompt.elem_id: ups_negative_prompt,
|
| 612 |
+
upscaler_sheet.elem_id: upscaler_sheet,
|
| 613 |
+
upscaler_sheet_supir_advanced.elem_id: upscaler_sheet_supir_advanced,
|
| 614 |
+
}
|
| 615 |
+
|
| 616 |
+
components_dict = {
|
| 617 |
+
"html_injector": html_injector,
|
| 618 |
+
"flyout_visible": flyout_visible,
|
| 619 |
+
"active_anchor_id": active_anchor_id,
|
| 620 |
+
"js_data_bridge": js_data_bridge,
|
| 621 |
+
"total_inference_steps": total_inference_steps,
|
| 622 |
+
"run_btn": run_btn,
|
| 623 |
+
"cancel_btn": cancel_btn,
|
| 624 |
+
"preset_name": preset_name,
|
| 625 |
+
"presets": presets,
|
| 626 |
+
"save_preset_btn": save_preset_btn,
|
| 627 |
+
"load_preset_btn": load_preset_btn,
|
| 628 |
+
"restorer_engine": restorer_engine,
|
| 629 |
+
"upscaler_engine": upscaler_engine,
|
| 630 |
+
"restorer_model": restorer_model,
|
| 631 |
+
"upscaler_model": upscaler_model,
|
| 632 |
+
"vae_model": vae_model,
|
| 633 |
+
"restorer_sampler": restorer_sampler,
|
| 634 |
+
"restorer_sampler_ear_btn": restorer_sampler_ear_btn,
|
| 635 |
+
"upscaler_sampler": upscaler_sampler,
|
| 636 |
+
"upscaler_sampler_ear_btn": upscaler_sampler_ear_btn,
|
| 637 |
+
"flyout_property_sheet_close_btn": flyout_property_sheet_close_btn,
|
| 638 |
+
"flyout_sheet": flyout_sheet,
|
| 639 |
+
"main_tabs": main_tabs,
|
| 640 |
+
"input_image": input_image,
|
| 641 |
+
"result_slider": result_slider,
|
| 642 |
+
"ec_accordion": ec_accordion,
|
| 643 |
+
"config_tabs": config_tabs,
|
| 644 |
+
"restorer_tab": restorer_tab,
|
| 645 |
+
"res_prompt_method": res_prompt_method,
|
| 646 |
+
"res_prompt": res_prompt,
|
| 647 |
+
"res_prompt_generate_btn": res_prompt_generate_btn,
|
| 648 |
+
"res_prompt_2": res_prompt_2,
|
| 649 |
+
"res_tokenizer_pos": res_tokenizer_pos,
|
| 650 |
+
"res_negative_prompt": res_negative_prompt,
|
| 651 |
+
"res_tokenizer_neg": res_tokenizer_neg,
|
| 652 |
+
"preview_restoration_mask_chk": preview_restoration_mask_chk,
|
| 653 |
+
"restoration_mask_prompt": restoration_mask_prompt,
|
| 654 |
+
"preview_restoration_mask_btn": preview_restoration_mask_btn,
|
| 655 |
+
"flyout_restoration_image_close_btn": flyout_restoration_image_close_btn,
|
| 656 |
+
"restoration_mask": restoration_mask,
|
| 657 |
+
"restorer_sheet": restorer_sheet,
|
| 658 |
+
"restorer_sheet_supir_advanced": restorer_sheet_supir_advanced,
|
| 659 |
+
"upscaler_tab": upscaler_tab,
|
| 660 |
+
"ups_prompt_method": ups_prompt_method,
|
| 661 |
+
"ups_prompt": ups_prompt,
|
| 662 |
+
"ups_prompt_generate_btn": ups_prompt_generate_btn,
|
| 663 |
+
"ups_prompt_2": ups_prompt_2,
|
| 664 |
+
"ups_tokenizer_pos": ups_tokenizer_pos,
|
| 665 |
+
"ups_negative_prompt": ups_negative_prompt,
|
| 666 |
+
"ups_tokenizer_neg": ups_tokenizer_neg,
|
| 667 |
+
"upscaler_sheet": upscaler_sheet,
|
| 668 |
+
"upscaler_sheet_supir_advanced": upscaler_sheet_supir_advanced,
|
| 669 |
+
"generated_tab": generated_tab,
|
| 670 |
+
"folder_explorer": folder_explorer,
|
| 671 |
+
"generated_image_viewer": generated_image_viewer,
|
| 672 |
+
"settings_tab": settings_tab,
|
| 673 |
+
"settings_sheet": settings_sheet,
|
| 674 |
+
"reset_settings_btn": reset_settings_btn,
|
| 675 |
+
"save_settings_btn": save_settings_btn,
|
| 676 |
+
"about_tab": about_tab,
|
| 677 |
+
"tag_helper_pos": tag_helper_pos,
|
| 678 |
+
"tag_helper_neg": tag_helper_neg,
|
| 679 |
+
"bottom_bar": bottom_bar,
|
| 680 |
+
"livelog_viewer": livelog_viewer,
|
| 681 |
+
"ALL_UI_COMPONENTS": all_ui_components_for_presets,
|
| 682 |
+
}
|
| 683 |
+
|
| 684 |
+
return components_dict
|
ui/ui_state.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2025 The DEVAIEXP Team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
from dataclasses import dataclass, field
|
| 16 |
+
from threading import Event
|
| 17 |
+
|
| 18 |
+
# Forward reference for type hint to avoid circular import
|
| 19 |
+
from typing import Any
|
| 20 |
+
|
| 21 |
+
from ui.ui_data import UIData
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
@dataclass
|
| 25 |
+
class AppState:
|
| 26 |
+
"""A container for the shared state of the Gradio application."""
|
| 27 |
+
|
| 28 |
+
uidata: UIData
|
| 29 |
+
cancel_event: Event = field(default_factory=Event)
|
| 30 |
+
|
| 31 |
+
# All other state variables that were previously global
|
| 32 |
+
input_image_path: str = None
|
| 33 |
+
restorer_config_class: Any = None
|
| 34 |
+
restorer_supir_advanced_config_class: Any = None
|
| 35 |
+
upscaler_config_class: Any = None
|
| 36 |
+
upscaler_supir_advanced_config_class: Any = None
|
| 37 |
+
restorer_engine_selected: str = "SUPIR" # Initial value
|
| 38 |
+
upscaler_engine_selected: str = "None" # Initial value
|
ui/util/dataclass_helpers.py
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2025 The DEVAIEXP Team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import dataclasses
|
| 16 |
+
import functools
|
| 17 |
+
from dataclasses import Field
|
| 18 |
+
from typing import Any, Dict, List, Tuple, Type
|
| 19 |
+
|
| 20 |
+
from typing_extensions import Literal
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
# General Helper Functions
|
| 24 |
+
def get_nested_attr(obj: Any, path: str, default: Any = None) -> Any:
|
| 25 |
+
"""
|
| 26 |
+
Accesses a nested attribute from an object using 'dot notation'.
|
| 27 |
+
|
| 28 |
+
Returns a default value if any attribute in the chain is missing or if
|
| 29 |
+
the object itself is None.
|
| 30 |
+
|
| 31 |
+
Args:
|
| 32 |
+
obj:
|
| 33 |
+
The root object.
|
| 34 |
+
path:
|
| 35 |
+
A string path like "parent.child.attribute".
|
| 36 |
+
default:
|
| 37 |
+
The value to return if the path is not found.
|
| 38 |
+
|
| 39 |
+
Returns:
|
| 40 |
+
The value of the nested attribute or the default value.
|
| 41 |
+
"""
|
| 42 |
+
try:
|
| 43 |
+
return functools.reduce(getattr, path.split("."), obj)
|
| 44 |
+
except AttributeError:
|
| 45 |
+
return default
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def _set_nested_dict_value(d: dict, path: str, value: Any):
|
| 49 |
+
"""
|
| 50 |
+
Sets a value in a nested dictionary using 'dot notation'.
|
| 51 |
+
|
| 52 |
+
Args:
|
| 53 |
+
d:
|
| 54 |
+
The dictionary to modify.
|
| 55 |
+
path:
|
| 56 |
+
A string path like "parent.child.key".
|
| 57 |
+
value:
|
| 58 |
+
The new value to set.
|
| 59 |
+
"""
|
| 60 |
+
keys = path.split(".")
|
| 61 |
+
for key in keys[:-1]:
|
| 62 |
+
d = d.setdefault(key, {})
|
| 63 |
+
d[keys[-1]] = value
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def get_nested_field(dc_type: Type, path: str) -> Field:
|
| 67 |
+
"""
|
| 68 |
+
Retrieves the dataclasses.Field object for a nested attribute.
|
| 69 |
+
|
| 70 |
+
Args:
|
| 71 |
+
dc_type:
|
| 72 |
+
The top-level dataclass type.
|
| 73 |
+
path:
|
| 74 |
+
A string path like "parent.child.field_name".
|
| 75 |
+
|
| 76 |
+
Returns:
|
| 77 |
+
The dataclasses.Field definition object.
|
| 78 |
+
"""
|
| 79 |
+
parts = path.split(".")
|
| 80 |
+
current_type = dc_type
|
| 81 |
+
for i, part in enumerate(parts):
|
| 82 |
+
field_info = current_type.__dataclass_fields__[part]
|
| 83 |
+
if i == len(parts) - 1:
|
| 84 |
+
return field_info
|
| 85 |
+
current_type = field_info.type
|
| 86 |
+
raise KeyError(f"Field path '{path}' could not be fully resolved in {dc_type}.")
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def get_nested_parent_and_field(obj: object, path: str) -> Tuple[object, str]:
|
| 90 |
+
"""
|
| 91 |
+
Navigates a nested object path and returns the parent object and the
|
| 92 |
+
final field name.
|
| 93 |
+
|
| 94 |
+
Example: for path "general.sampler.seed", returns (sampler_object, "seed").
|
| 95 |
+
|
| 96 |
+
Args:
|
| 97 |
+
obj:
|
| 98 |
+
The root object to navigate.
|
| 99 |
+
path:
|
| 100 |
+
A dot-separated string path.
|
| 101 |
+
|
| 102 |
+
Returns:
|
| 103 |
+
A tuple containing the direct parent object and the final field name.
|
| 104 |
+
"""
|
| 105 |
+
parts = path.split(".")
|
| 106 |
+
parent = obj
|
| 107 |
+
for part in parts[:-1]:
|
| 108 |
+
parent = getattr(parent, part)
|
| 109 |
+
return parent, parts[-1]
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
# Dataclass Instantiation Helper
|
| 113 |
+
def dataclass_from_dict(data_class: Type, data: dict) -> Any:
|
| 114 |
+
"""
|
| 115 |
+
Recursively constructs a dataclass instance from a dictionary.
|
| 116 |
+
|
| 117 |
+
This helper is essential for creating a dataclass instance from dictionary
|
| 118 |
+
data, especially when dealing with nested dataclasses. It correctly
|
| 119 |
+
handles nested structures by recursively calling itself.
|
| 120 |
+
|
| 121 |
+
Args:
|
| 122 |
+
data_class:
|
| 123 |
+
The dataclass type to instantiate (e.g., `AppSettings`).
|
| 124 |
+
data:
|
| 125 |
+
A dictionary containing the data to populate the instance.
|
| 126 |
+
Keys should correspond to field names in the dataclass.
|
| 127 |
+
|
| 128 |
+
Returns:
|
| 129 |
+
An instance of `data_class` populated with data from the dictionary.
|
| 130 |
+
"""
|
| 131 |
+
field_names = {f.name for f in dataclasses.fields(data_class)}
|
| 132 |
+
filtered_data = {k: v for k, v in data.items() if k in field_names}
|
| 133 |
+
|
| 134 |
+
for f in dataclasses.fields(data_class):
|
| 135 |
+
field_name = f.name
|
| 136 |
+
field_type = f.type
|
| 137 |
+
|
| 138 |
+
if field_name in filtered_data and dataclasses.is_dataclass(field_type):
|
| 139 |
+
if isinstance(filtered_data[field_name], dict):
|
| 140 |
+
nested_instance = dataclass_from_dict(field_type, filtered_data[field_name])
|
| 141 |
+
filtered_data[field_name] = nested_instance
|
| 142 |
+
|
| 143 |
+
return data_class(**filtered_data)
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
# The Core Engine for Dynamic Dataclass Reconstruction
|
| 147 |
+
def _rebuild_dataclass_recursively(dc_type: Type, path: str, new_field: Field) -> Type:
|
| 148 |
+
"""
|
| 149 |
+
Internal function to recursively rebuild a nested dataclass structure.
|
| 150 |
+
|
| 151 |
+
It replaces an entire target field definition, allowing for changes to
|
| 152 |
+
any part of the field (type, metadata, default value, etc.).
|
| 153 |
+
|
| 154 |
+
Args:
|
| 155 |
+
dc_type:
|
| 156 |
+
The current dataclass type to rebuild.
|
| 157 |
+
path:
|
| 158 |
+
The remaining dot-separated path to the target field.
|
| 159 |
+
new_field:
|
| 160 |
+
The complete new dataclasses.Field object to insert.
|
| 161 |
+
|
| 162 |
+
Returns:
|
| 163 |
+
A new, dynamically created dataclass type with the updated structure.
|
| 164 |
+
"""
|
| 165 |
+
parts = path.split(".")
|
| 166 |
+
field_to_change = parts[0]
|
| 167 |
+
|
| 168 |
+
if len(parts) == 1:
|
| 169 |
+
new_fields_spec = []
|
| 170 |
+
for f in dataclasses.fields(dc_type):
|
| 171 |
+
if f.name == field_to_change:
|
| 172 |
+
new_spec = (f.name, new_field.type, new_field)
|
| 173 |
+
new_fields_spec.append(new_spec)
|
| 174 |
+
else:
|
| 175 |
+
new_fields_spec.append((f.name, f.type, f))
|
| 176 |
+
class_name = f"Updated_{dc_type.__name__.rpartition('_')[-1]}"
|
| 177 |
+
return dataclasses.make_dataclass(
|
| 178 |
+
class_name,
|
| 179 |
+
new_fields_spec,
|
| 180 |
+
bases=dc_type.__bases__,
|
| 181 |
+
)
|
| 182 |
+
else:
|
| 183 |
+
nested_dc_type = dc_type.__dataclass_fields__[field_to_change].type
|
| 184 |
+
remaining_path = ".".join(parts[1:])
|
| 185 |
+
rebuilt_nested_type = _rebuild_dataclass_recursively(nested_dc_type, remaining_path, new_field)
|
| 186 |
+
|
| 187 |
+
new_parent_fields_spec = []
|
| 188 |
+
for f in dataclasses.fields(dc_type):
|
| 189 |
+
if f.name == field_to_change:
|
| 190 |
+
new_parent_nested_field = dataclasses.field(default_factory=f.default_factory, metadata=f.metadata, init=f.init)
|
| 191 |
+
new_spec = (f.name, rebuilt_nested_type, new_parent_nested_field)
|
| 192 |
+
new_parent_fields_spec.append(new_spec)
|
| 193 |
+
else:
|
| 194 |
+
new_parent_fields_spec.append((f.name, f.type, f))
|
| 195 |
+
class_name = f"Updated_{dc_type.__name__.rpartition('_')[-1]}"
|
| 196 |
+
return dataclasses.make_dataclass(class_name, new_parent_fields_spec, bases=dc_type.__bases__)
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def apply_dynamic_changes(dc_instance: object, rules: dict) -> object:
|
| 200 |
+
"""
|
| 201 |
+
Processes a dataclass instance against a set of UI rules.
|
| 202 |
+
|
| 203 |
+
This version ensures consistency by always rebuilding the dataclass
|
| 204 |
+
instance if any rule that modifies the class structure (like changing
|
| 205 |
+
options or visibility) is present. This prevents state inconsistencies
|
| 206 |
+
within the Gradio UI lifecycle.
|
| 207 |
+
|
| 208 |
+
Args:
|
| 209 |
+
dc_instance:
|
| 210 |
+
The source dataclass instance.
|
| 211 |
+
rules:
|
| 212 |
+
A dictionary containing the UI rules.
|
| 213 |
+
|
| 214 |
+
Returns:
|
| 215 |
+
A new dataclass instance of a dynamically created type, with all
|
| 216 |
+
structural rules applied.
|
| 217 |
+
"""
|
| 218 |
+
current_dc = dc_instance
|
| 219 |
+
actions_to_process: List[Dict] = []
|
| 220 |
+
|
| 221 |
+
# Part 1: Gather dynamic actions based on current field values
|
| 222 |
+
if "dynamic_dependencies" in rules:
|
| 223 |
+
for modifier_path, rule_group in rules["dynamic_dependencies"].items():
|
| 224 |
+
modifier_value = get_nested_attr(current_dc, modifier_path)
|
| 225 |
+
for action in rule_group.get("actions", []):
|
| 226 |
+
mapping = action.get("mapping", {})
|
| 227 |
+
if modifier_value in mapping:
|
| 228 |
+
outcome = mapping[modifier_value]
|
| 229 |
+
actions_to_process.append({**action, "outcome": outcome})
|
| 230 |
+
|
| 231 |
+
# Part 2: Gather unconditional "on_load" actions
|
| 232 |
+
if "on_load_actions" in rules:
|
| 233 |
+
actions_to_process.extend(rules["on_load_actions"])
|
| 234 |
+
|
| 235 |
+
# Part 3: Process all gathered actions
|
| 236 |
+
for action in actions_to_process:
|
| 237 |
+
action_type = action.get("type")
|
| 238 |
+
target_path = action.get("target_field_path")
|
| 239 |
+
outcome = action.get("outcome") # Will be None for on_load_actions
|
| 240 |
+
|
| 241 |
+
if not all([action_type, target_path]):
|
| 242 |
+
continue
|
| 243 |
+
|
| 244 |
+
target_field_info = get_nested_field(type(current_dc), target_path)
|
| 245 |
+
|
| 246 |
+
# Handle actions that require rebuilding the dataclass
|
| 247 |
+
if action_type in ["update_options", "update_visibility"]:
|
| 248 |
+
new_field_type = target_field_info.type
|
| 249 |
+
new_metadata = dict(target_field_info.metadata)
|
| 250 |
+
|
| 251 |
+
if action_type == "update_options":
|
| 252 |
+
new_options = outcome.get("options", [])
|
| 253 |
+
new_field_type = Literal[tuple(new_options)] if new_options else str
|
| 254 |
+
|
| 255 |
+
elif action_type == "update_visibility":
|
| 256 |
+
is_visible = action.get("visible")
|
| 257 |
+
if is_visible is not None:
|
| 258 |
+
new_metadata["visible"] = is_visible
|
| 259 |
+
|
| 260 |
+
# Create the new field definition based on the changes
|
| 261 |
+
new_field = dataclasses.field(
|
| 262 |
+
default=target_field_info.default,
|
| 263 |
+
default_factory=target_field_info.default_factory,
|
| 264 |
+
init=target_field_info.init,
|
| 265 |
+
repr=target_field_info.repr,
|
| 266 |
+
hash=target_field_info.hash,
|
| 267 |
+
compare=target_field_info.compare,
|
| 268 |
+
metadata=new_metadata,
|
| 269 |
+
)
|
| 270 |
+
new_field.type = new_field_type
|
| 271 |
+
new_field.name = target_field_info.name
|
| 272 |
+
|
| 273 |
+
# Rebuild the dataclass and replace the current instance
|
| 274 |
+
NewDcClass = _rebuild_dataclass_recursively(type(current_dc), target_path, new_field)
|
| 275 |
+
values_dict = dataclasses.asdict(current_dc)
|
| 276 |
+
|
| 277 |
+
# Correct the value if it became invalid after an `update_options` action
|
| 278 |
+
if action_type == "update_options":
|
| 279 |
+
current_value = get_nested_attr(current_dc, target_path)
|
| 280 |
+
new_options = outcome.get("options", [])
|
| 281 |
+
new_default = outcome.get("new_default")
|
| 282 |
+
should_update = current_value not in new_options or (new_default is not None and current_value != new_default)
|
| 283 |
+
if should_update:
|
| 284 |
+
value_to_set = new_default if new_default is not None else (new_options[0] if new_options else None)
|
| 285 |
+
if value_to_set is not None:
|
| 286 |
+
_set_nested_dict_value(values_dict, target_path, value_to_set)
|
| 287 |
+
|
| 288 |
+
# The `current_dc` instance is replaced. The next loop iteration
|
| 289 |
+
# will operate on this newly created instance.
|
| 290 |
+
current_dc = dataclass_from_dict(NewDcClass, values_dict)
|
| 291 |
+
|
| 292 |
+
# Handle actions that modify the instance directly
|
| 293 |
+
elif action_type == "set_value":
|
| 294 |
+
new_value = outcome
|
| 295 |
+
current_value = get_nested_attr(current_dc, target_path)
|
| 296 |
+
if new_value != current_value:
|
| 297 |
+
parent_obj, field_name = get_nested_parent_and_field(current_dc, target_path)
|
| 298 |
+
setattr(parent_obj, field_name, new_value)
|
| 299 |
+
|
| 300 |
+
return current_dc
|