Compare commits

...

1014 Commits

Author SHA1 Message Date
psychedelicious
434afdd21d chore(ui): lint 2024-08-26 22:57:56 +10:00
psychedelicious
ea267c0e09 chore: release v4.2.9.dev4
Canvas dev build.
2024-08-26 22:46:07 +10:00
psychedelicious
35b483b83a feat(ui): rough out undo/redo on canvas UNDO ME? 2024-08-26 22:45:11 +10:00
psychedelicious
217b2759c3 fix(ui): handle error from internal konva method
We are dipping into konva's private API for preview images and it appears to be unsafe (got an error once). Wrapped in a try/catch.
2024-08-26 22:44:47 +10:00
psychedelicious
7d528dc03d feat(ui): split out loras state from canvas rendering state 2024-08-26 22:29:28 +10:00
psychedelicious
bc40c1a99f feat(ui): split out session state from canvas rendering state 2024-08-26 22:20:04 +10:00
psychedelicious
a2b508016e feat(ui): split out settings state from canvas rendering state 2024-08-26 22:02:56 +10:00
psychedelicious
3ce8294379 feat(ui): split out tool state from canvas rendering state 2024-08-26 21:52:43 +10:00
psychedelicious
9c3da8de8e feat(ui): split out params/compositing state from canvas rendering state
First step to restoring undo/redo - the undoable state must be in its own slice. So params and settings must be isolated.
2024-08-26 21:41:47 +10:00
psychedelicious
ccba597e58 feat(ui): add CanvasModuleBase class to standardize canvas APIs
I did this ages ago but undid it for some reason, not sure why. Caught a few issues related to subscriptions.
2024-08-26 21:12:31 +10:00
psychedelicious
8028943cdd feat(ui): move selected tool and tool buffer out of redux
This ephemeral state can live in the canvas classes.
2024-08-26 19:59:06 +10:00
psychedelicious
cdd8b60fd0 feat(ui): move ephemeral state into canvas classes
Things like `$lastCursorPos` are now created within the canvas drawing classes. Consumers in react access them via `useCanvasManager`.

For example:
```tsx
const canvasManager = useCanvasManager();
const lastCursorPos = useStore(canvasManager.stateApi.$lastCursorPos);
```
2024-08-26 19:14:56 +10:00
psychedelicious
c624754404 feat(ui): normalize all actions to accept an entityIdentifier
Previously, canvas actions specific to an entity type only needed the id of that entity type. This allowed you to pass in the id of an entity of the wrong type.

All actions for a specific entity now take a full entity identifier, and the entity identifier type can be narrowed.

`selectEntity` and `selectEntityOrThrow` now need a full entity identifier, and narrow their return values to a specific entity type _if_ the entity identifier is narrowed.

The types for canvas entities are updated with optional type parameters for this purpose.

All reducers, actions and components have been updated.
2024-08-26 18:52:28 +10:00
psychedelicious
0505634296 feat(ui): move events into modules who care about them 2024-08-26 10:34:59 +10:00
psychedelicious
e06ea5d595 fix(ui): color picker resets brush opacity 2024-08-26 08:58:16 +10:00
psychedelicious
838a0574a5 fix(ui): scaled bbox loses sync 2024-08-26 08:55:33 +10:00
psychedelicious
ed3581c70c feat(ui): add context menu to entity list 2024-08-24 19:50:36 +10:00
psychedelicious
522cae6a42 chore(ui): bump @invoke-ai/ui-library 2024-08-24 19:49:43 +10:00
psychedelicious
a9032a34f2 fix(ui): missing vae precision in graph builders 2024-08-24 18:35:30 +10:00
psychedelicious
6af5a22d3a chore: release v4.2.9.dev3
Instead of using dates, just going to increment.
2024-08-24 15:03:43 +10:00
psychedelicious
8cf4321010 feat(ui): use new Result utils for enqueueing 2024-08-24 14:49:17 +10:00
psychedelicious
aa7f2b096a fix(ui): graph building issue w/ controlnet 2024-08-24 14:48:18 +10:00
psychedelicious
bf0824b56d feat(ui): add Result type & helpers
Wrappers to capture errors and turn into results:
- `withResult` wraps a sync function
- `withResultAsync` wraps an async function

Comments, tests.
2024-08-24 14:46:58 +10:00
psychedelicious
056c56d322 chore: release v4.2.9.dev20240824 2024-08-24 12:36:25 +10:00
psychedelicious
afc6f83d72 fix(ui): lint & fix issues with adding regional ip adapters 2024-08-24 12:32:38 +10:00
psychedelicious
c776ac3af2 feat(ui): add knipignore tag
I'm not ready to delete some things but still want to build the app.
2024-08-24 12:32:00 +10:00
psychedelicious
b7b3683bef feat(ui): duplicate entity 2024-08-24 12:20:35 +10:00
psychedelicious
fb26b6824a feat(ui): autocomplete on getPrefixeId 2024-08-24 12:20:26 +10:00
psychedelicious
63d8ad912f feat(ui): paste canvas gens back on source in generate mode 2024-08-24 11:56:24 +10:00
psychedelicious
bbd7d7fc17 chore(ui): typegen 2024-08-24 11:55:50 +10:00
psychedelicious
6507a78182 feat(nodes): CanvasV2MaskAndCropInvocation can paste generated image back on source
This is needed for `Generate` mode.
2024-08-24 11:55:43 +10:00
psychedelicious
22f46517f4 fix(ui): extraneous entity preview updates 2024-08-24 11:28:05 +10:00
psychedelicious
45596e1f94 fix(ui): newly-added entities are selected 2024-08-24 11:14:58 +10:00
psychedelicious
6de0dbe854 feat(ui): add crosshair to color picker 2024-08-24 10:51:34 +10:00
psychedelicious
011827fa29 fix(ui): color picker ignores alpha 2024-08-24 10:16:27 +10:00
psychedelicious
fc6d244071 fix(ui): calculate renderable entities correctly in tool module 2024-08-24 10:10:21 +10:00
psychedelicious
cd3da886d6 feat(ui): better color picker 2024-08-24 10:10:04 +10:00
psychedelicious
c013c55d92 feat(ui): colored mask preview image 2024-08-24 08:54:20 +10:00
psychedelicious
cd3dd7db0d fix(ui): new rectangles don't trigger rerender 2024-08-23 23:24:16 +10:00
psychedelicious
1fdcce9429 chore: bump version v4.2.9.dev20240823 2024-08-23 20:52:16 +10:00
psychedelicious
181e40926d feat(ui): disable most interaction while filtering 2024-08-23 20:32:49 +10:00
psychedelicious
c62ede5878 fix(ui): filter preview offset 2024-08-23 20:24:40 +10:00
psychedelicious
a2ad5f1a9a feat(ui): tweak layout of staging area toolbar 2024-08-23 19:55:02 +10:00
psychedelicious
ff74a5356f chore(ui): typegen 2024-08-23 19:52:37 +10:00
psychedelicious
92dc30dace tidy(app): clean up app changes for canvas v2 2024-08-23 19:52:04 +10:00
psychedelicious
3af577b210 feat(ui): use singleton for clear q confirm dialog 2024-08-23 19:47:51 +10:00
psychedelicious
d0464330f7 fix(ui): rip out broken recall logic, NO TS ERRORS 2024-08-23 19:47:51 +10:00
psychedelicious
dd3ef4a80f chore(ui): lint 2024-08-23 19:47:51 +10:00
psychedelicious
0ced891944 fix(ui): staging area interaction scopes 2024-08-23 19:47:51 +10:00
psychedelicious
10a5452df9 fix(ui): staging area actions 2024-08-23 19:47:51 +10:00
psychedelicious
cb97969bbc tidy(ui): more cleanup 2024-08-23 19:47:51 +10:00
psychedelicious
71e742e238 fix(ui): upscale tab graph 2024-08-23 19:47:51 +10:00
psychedelicious
fadd20fb8e fix(ui): sdxl graph builder 2024-08-23 19:47:51 +10:00
psychedelicious
01b9ca78e4 fix(ui): select next entity in the list when deleting 2024-08-23 19:47:51 +10:00
psychedelicious
2baf825f34 feat(ui): fix delete layer hotkey 2024-08-23 19:47:51 +10:00
psychedelicious
1fa8048509 tidy(ui): "eye dropper" -> "color picker" 2024-08-23 19:47:51 +10:00
psychedelicious
a000ad75f6 tidy(ui): regional guidance buttons 2024-08-23 19:47:51 +10:00
psychedelicious
aefb2339bb feat(ui): update entity list menu 2024-08-23 19:47:51 +10:00
psychedelicious
a4f8671f86 feat(ui): add log debug button 2024-08-23 19:47:51 +10:00
psychedelicious
73530ba54f chore(ui): lint 2024-08-23 19:47:51 +10:00
psychedelicious
685eb9927d chore(ui): prettier 2024-08-23 19:47:51 +10:00
psychedelicious
ee57302fc3 chore(ui): eslint 2024-08-23 19:47:51 +10:00
psychedelicious
c1fb9cdb93 tidy(ui): remove unused stuff 4 2024-08-23 19:47:36 +10:00
psychedelicious
aa6d441552 tidy(ui): remove unused stuff 3 2024-08-23 19:47:01 +10:00
psychedelicious
25d8d4c2e9 tidy(ui): remove unused pkg @chakra-ui/react-use-size 2024-08-23 19:47:01 +10:00
psychedelicious
427ea6da5c feat(ui): revise graph building for control layers, fix issues w/ invocation complete events 2024-08-23 19:47:01 +10:00
psychedelicious
d9f4266630 feat(ui): use unique id for metadata in Graph class 2024-08-23 19:47:01 +10:00
psychedelicious
96f6e9e683 tidy(ui): remove unused stuff 2 2024-08-23 19:47:01 +10:00
psychedelicious
f10248e3f5 tidy(ui): remove unused stuff 2024-08-23 19:47:01 +10:00
psychedelicious
6a21f5fde1 tidy(ui): reduce use of parseify util 2024-08-23 19:47:01 +10:00
psychedelicious
ff20dd509a feat(ui): refine canvas entity list items & menus 2024-08-23 19:47:01 +10:00
psychedelicious
78a59b5b78 feat(ui): canvas layer preview, revised reactivity for adapters 2024-08-23 19:47:01 +10:00
psychedelicious
46bfbbbc87 feat(ui): add SyncableMap
Can be used with useSyncExternal store to make a `Map` reactive.
2024-08-23 19:47:01 +10:00
psychedelicious
a6d73d0773 tidy(ui): removed unused transform methods from canvasmanager 2024-08-23 19:47:01 +10:00
psychedelicious
6578e8bef8 feat(ui): transform tool ux 2024-08-23 19:47:01 +10:00
psychedelicious
0596d25e07 feat(ui): rough out canvas mode 2024-08-23 19:47:01 +10:00
psychedelicious
86e8ce9139 feat(ui): add canvas autosave checkbox 2024-08-23 19:47:01 +10:00
psychedelicious
5aa2957da4 fix(ui): memory leak when getting image DTO
must unsubscribe!
2024-08-23 19:47:01 +10:00
psychedelicious
82f0cb2c8c feat(ui): rework settings menu 2024-08-23 19:47:01 +10:00
psychedelicious
fa48145cbc feat(ui): no entities fallback buttons 2024-08-23 19:47:01 +10:00
psychedelicious
6d1edc330d perf(ui): optimize gallery image delete button rendering 2024-08-23 19:47:01 +10:00
psychedelicious
97c0d3f6be feat(ui): remove "solid" background option 2024-08-23 19:47:01 +10:00
psychedelicious
a79a25ad63 tidy(ui): organise files and classes 2024-08-23 19:47:01 +10:00
psychedelicious
6a8ceef404 tidy(ui): abstract compositing logic to module 2024-08-23 19:47:01 +10:00
psychedelicious
3539670d93 fix(ui): fix canvas cache property access 2024-08-23 19:47:01 +10:00
psychedelicious
c54bc32ef6 tidy(ui): clean up CanvasFilter class 2024-08-23 19:47:01 +10:00
psychedelicious
fee293e289 tidy(ui): clean up a few bits and bobs 2024-08-23 19:47:01 +10:00
psychedelicious
747eef9ccc tidy(ui): abstract canvas rendering logic to module 2024-08-23 19:47:01 +10:00
psychedelicious
7d2df399ed tidy(ui): abstract caching logic to module 2024-08-23 19:47:01 +10:00
psychedelicious
68fad5cdcc tidy(ui): abstract worker logic to module 2024-08-23 19:47:01 +10:00
psychedelicious
b4d656c203 tidy(ui): abstract stage logic into module 2024-08-23 19:47:01 +10:00
psychedelicious
3136d89d52 feat(ui): add entity group hiding 2024-08-23 19:47:01 +10:00
psychedelicious
27e829b955 feat(ui): move all caching out of redux
While we lose the benefit of the caches persisting across reloads, this is a much simpler way to handle things. If we need a persistent cache, we can explore it in the future.
2024-08-23 19:47:01 +10:00
psychedelicious
e03e870d5b feat(ui): revised rasterization caching
- use `stable-hash` to generate stable, non-crypto hashes for cache entries, instead of using deep object comparisons
- use an object to store image name caches
2024-08-23 19:47:01 +10:00
psychedelicious
9465ff450b feat(ui): revise filter implementation 2024-08-23 19:47:01 +10:00
psychedelicious
92906a9575 fix(ui): add button to delete inpaint mask 2024-08-23 19:47:01 +10:00
psychedelicious
77f206abe4 feat(ui): add contexts/hooks to access entity adapters directly 2024-08-23 19:47:01 +10:00
psychedelicious
44a3f61580 feat(ui): add CanvasManagerProviderGate
This context waits to render its children its until the canvas manager is available. Then its children have access to the manager directly via hook.
2024-08-23 19:47:01 +10:00
psychedelicious
0a2afed08b feat(ui) do not set $canvasManager until ready 2024-08-23 19:47:01 +10:00
psychedelicious
9b3b961105 fix(ui): inpaint mask naming 2024-08-23 19:47:01 +10:00
psychedelicious
9b1828e1aa feat(ui): efficient canvas compositing
Also solves issue of exporting layers at different opacities than what is visible
2024-08-23 19:47:01 +10:00
psychedelicious
5101873f49 feat(ui): allow multiple inpaint masks
This is easier than making it a nullable singleton
2024-08-23 19:47:01 +10:00
psychedelicious
c612f18114 fix(ui): missing rasterization cache invalidations 2024-08-23 19:47:01 +10:00
psychedelicious
7e400d876f feat(ui): iterate on filter UI, flow 2024-08-23 19:47:01 +10:00
psychedelicious
677dddcfc9 fix(ui): rehydration data loss 2024-08-23 19:47:01 +10:00
psychedelicious
0792b9175e feat(ui): sort log namespaces 2024-08-23 19:47:01 +10:00
psychedelicious
e4829f80af fix(ui): do not merge arrays by index during rehydration 2024-08-23 19:47:01 +10:00
psychedelicious
bb760f3eb4 fix(ui): clone parsed data during state rehydration
Without this, the objects and arrays in `parsed` could be mutated, and the log statment would show the mutated data.
2024-08-23 19:47:01 +10:00
psychedelicious
388c65287b fix(ui): fix logger filter
was accidetnally replacing the filter instead of appending to it.
2024-08-23 19:47:01 +10:00
psychedelicious
12cd41e05c fix(ui): race condition queue status
Sequence of events causing the race condition:
- Enqueue batch
- Invalidate `SessionQueueStatus` tag
- Request updated queue status via HTTP - batch still processing at this point
- Batch completes
- Event emitted saying so
- Optimistically update the queue status cache, it is correct
- HTTP request makes it back and overwrites the optimistic update, indicating the batch is still in progress

FIxed by not invalidating the cache.
2024-08-23 19:47:01 +10:00
psychedelicious
7765c03949 fix(ui): handle opacity for masks 2024-08-23 19:47:01 +10:00
psychedelicious
3daa80c57f feat(ui): default background to checkerboard 2024-08-23 19:47:01 +10:00
psychedelicious
5dbbef4ebd feat(ui): clean up logging namespaces, allow skipping namespaces 2024-08-23 19:47:01 +10:00
psychedelicious
db33b3f7b5 chore(ui): bump ui library 2024-08-23 19:47:01 +10:00
psychedelicious
8ffcf2a6be fix(ui): do not allow drawing if layer disabled 2024-08-23 19:47:01 +10:00
psychedelicious
2e7ae6a07e fix(ui): stale state causing race conditions & extraneous renders 2024-08-23 19:47:01 +10:00
psychedelicious
fea1711f0c fix(ui): do not clear buffer when rendering "real" objects 2024-08-23 19:47:01 +10:00
psychedelicious
2a3546db97 tidy(ui): remove "filter" from CanvasImageState 2024-08-23 19:47:01 +10:00
psychedelicious
285c266612 feat(ui): better editable title 2024-08-23 19:47:01 +10:00
psychedelicious
426ad54c53 fix(ui): stroke eraserline 2024-08-23 19:47:01 +10:00
psychedelicious
fc75f7919f feat(ui): restore transparency effect for control layers 2024-08-23 19:47:01 +10:00
psychedelicious
6c6b1aaff6 feat(ui): use text cursor for entity title 2024-08-23 19:47:01 +10:00
psychedelicious
c319d653ac tidy(ui): remove extraneous logging in CanvasStateApi 2024-08-23 19:47:01 +10:00
psychedelicious
d887e474e7 feat(ui): better buffer commit logic 2024-08-23 19:47:01 +10:00
psychedelicious
da7b52d6ba feat(ui): render buffer separately from "real" objects 2024-08-23 19:47:01 +10:00
psychedelicious
b5aa308593 fix(ui): pixelRect should always be integer 2024-08-23 19:47:01 +10:00
psychedelicious
0b7ceb3bb6 fix(ui): only update stage attrs when stage itself is dragged 2024-08-23 19:47:01 +10:00
psychedelicious
3a70cefda2 feat(ui): add line simplification
This fixes some awkward issues where line segments stack up.
2024-08-23 19:47:01 +10:00
psychedelicious
4b609251e1 fix(ui): various things listening when they need not listen 2024-08-23 19:47:01 +10:00
psychedelicious
0839eac0f7 feat(ui): layer opacity via caching 2024-08-23 19:47:01 +10:00
psychedelicious
5f2a7feeee feat(ui): reset view fits all visible objects 2024-08-23 19:47:01 +10:00
psychedelicious
982535eb92 fix(ui): rerenders when changing canvas scale 2024-08-23 19:47:01 +10:00
psychedelicious
0c2b8edc8d fix(ui): do not render rasterized layer unless renderObjects=true 2024-08-23 19:47:01 +10:00
psychedelicious
f78f4ca25f feat(ui): revise app layout strategy, add interaction scopes for hotkeys 2024-08-23 19:47:01 +10:00
psychedelicious
d6b3e6c07d feat(ui): tweak mask patterns 2024-08-23 19:47:01 +10:00
psychedelicious
071ff8e74a fix(ui): dynamic prompts recalcs when presets are loaded 2024-08-23 19:47:01 +10:00
psychedelicious
1ea8aafca1 fix(ui): use style preset prompts correctly 2024-08-23 19:46:05 +10:00
psychedelicious
533dd221f8 fix(ui): discard selected staging image not all other images 2024-08-23 19:46:05 +10:00
psychedelicious
2b325c6683 fix(ui): respect image size in staging preview 2024-08-23 19:46:05 +10:00
psychedelicious
3845b1b3e6 tidy(ui): cleanup after events change 2024-08-23 19:46:05 +10:00
psychedelicious
cea7890a67 feat(ui): move socket event handling out of redux
Download events and invocation status events (including progress images) are very frequent. There's no real need for these to pass through redux. Handling them outside redux is a significant performance win - far fewer store subscription calls, far fewer trips through middleware.

All event handling is moved outside middleware. Cleanup of unused actions and listeners to follow.
2024-08-23 19:46:05 +10:00
psychedelicious
c38fe8025d fix(ui): rebase conflicts 2024-08-23 19:46:05 +10:00
psychedelicious
f1de95349c fix(ui): update compositing rect when fill changes 2024-08-23 19:46:05 +10:00
psychedelicious
2950775fa7 feat(ui): add canvas background style 2024-08-23 19:46:05 +10:00
psychedelicious
cb293fd7ac feat(ui): mask layers choose own opacity 2024-08-23 19:46:05 +10:00
psychedelicious
43b3fab6be feat(ui): mask fill patterns 2024-08-23 19:46:05 +10:00
psychedelicious
d4b0dbce49 build(ui): add vite types to tsconfig 2024-08-23 19:46:05 +10:00
psychedelicious
137b810669 fix(ui): do not smooth pixel data when using eyeDropper 2024-08-23 19:46:05 +10:00
psychedelicious
c172657324 tidy(ui): tool components & translations 2024-08-23 19:46:05 +10:00
psychedelicious
97c966b04f feat(ui): rough out eyedropper tool
It's a bit slow bc we are converting the stage to canvas on every mouse move. Also need to improve the visual but it works.
2024-08-23 19:46:05 +10:00
psychedelicious
7178fc6253 fix(ui): ip adapters work 2024-08-23 19:46:05 +10:00
psychedelicious
4adb2eabf5 feat(ui): rename layers 2024-08-23 19:46:05 +10:00
psychedelicious
9f2c815e13 feat(ui): revise entity menus 2024-08-23 19:46:05 +10:00
psychedelicious
1435557d1d feat(ui): split control layers from raster layers for UI and internal state, same rendering as raster layers 2024-08-23 19:46:05 +10:00
psychedelicious
96abf687f6 feat(ui): implement cache for image rasterization, rip out some old controladapters code 2024-08-23 19:46:05 +10:00
psychedelicious
636d9a7209 feat(ui, app): use layer as control (wip) 2024-08-23 19:46:05 +10:00
psychedelicious
3b36eb0223 feat(ui): add contextmenu for canvas entities 2024-08-23 19:46:05 +10:00
psychedelicious
388c97bff0 feat(ui): more better logging & naming 2024-08-23 19:46:05 +10:00
psychedelicious
b1cb018695 feat(ui): better logging w/ path 2024-08-23 19:46:05 +10:00
psychedelicious
df78dd7953 feat(ui): always show marks on canvas scale slider 2024-08-23 19:46:05 +10:00
psychedelicious
0dc344a22e fix(ui): do not import button from chakra 2024-08-23 19:46:05 +10:00
psychedelicious
350d7f6f14 fix(ui): scaled bbox preview 2024-08-23 19:46:05 +10:00
psychedelicious
11059ee2d4 feat(ui): tidy up atoms 2024-08-23 19:46:05 +10:00
psychedelicious
c90d3f3bb9 feat(ui): convert all my pubsubs to atoms
its the same but better
2024-08-23 19:46:05 +10:00
psychedelicious
7f6d439fd1 feat(ui): add trnalsation 2024-08-23 19:46:05 +10:00
psychedelicious
783a78f069 fix(ui): give up on thumbnail loading, causes flash during transformer 2024-08-23 19:46:05 +10:00
psychedelicious
0ff031950d fix(ui): depth anything v2 2024-08-23 19:46:05 +10:00
psychedelicious
d7e8f3d756 tidy(ui): remove unused code, comments 2024-08-23 19:46:05 +10:00
psychedelicious
4668ea449b fix(ui): staging area works 2024-08-23 19:46:05 +10:00
psychedelicious
30d318d021 feat(nodes): temp disable canvas output crop 2024-08-23 19:46:05 +10:00
psychedelicious
de96f97e5f fix(ui): max scale 1 when reset view 2024-08-23 19:46:05 +10:00
psychedelicious
57c0a2dfb1 feat(ui): better scale changer component, reset view functionality 2024-08-23 19:46:05 +10:00
psychedelicious
cd4e464bde fix(ui): img2img 2024-08-23 19:46:05 +10:00
psychedelicious
49e48c3eb7 feat(ui): add manual scale controls 2024-08-23 19:46:05 +10:00
psychedelicious
edd3b3bce9 fix(ui): do not await clearBuffer 2024-08-23 19:46:04 +10:00
psychedelicious
f8bfb66108 feat(ui): dnd image into layer 2024-08-23 19:46:04 +10:00
psychedelicious
3b6a76cbf3 fix(ui): do not await commitBuffer 2024-08-23 19:46:04 +10:00
psychedelicious
e0b60e4320 fix(ui): properly destroy entities in manager cleanup 2024-08-23 19:46:04 +10:00
psychedelicious
2159319035 tidy(ui): clearer component names for regional guidance 2024-08-23 19:46:04 +10:00
psychedelicious
b170fc232e tidy(ui): clearer component names for ip adapter 2024-08-23 19:46:04 +10:00
psychedelicious
594da60f2f tidy(ui): clearer component names for inpaint mask 2024-08-23 19:46:04 +10:00
psychedelicious
6a432f6518 tidy(ui): clearer component names for control adapters 2024-08-23 19:46:04 +10:00
psychedelicious
eb8eacfec6 feat(ui): simplify canvas list item headers 2024-08-23 19:46:04 +10:00
psychedelicious
c8d04d42e2 fix(ui): ip adapter list item 2024-08-23 19:46:04 +10:00
psychedelicious
d39c9de81e tidy(ui): clean up unused logic 2024-08-23 19:46:04 +10:00
psychedelicious
a27d39b9ff feat(ui): clean up state, add mutex for image loading, add thumbnail loading 2024-08-23 19:46:04 +10:00
psychedelicious
6b385614f0 chore(ui): add async-mutex dep 2024-08-23 19:46:04 +10:00
psychedelicious
3ae7250ef7 feat(ui): txt2img, img2img, inpaint & outpaint working 2024-08-23 19:46:04 +10:00
psychedelicious
a42d0ce1d2 feat(ui): no padding on transformer outlines 2024-08-23 19:46:04 +10:00
psychedelicious
d9131f7563 feat(ui): restore object count to layer titles 2024-08-23 19:46:04 +10:00
psychedelicious
bdce958f29 tidy(ui): "useIsEntitySelected" -> "useEntityIsSelected" 2024-08-23 19:46:04 +10:00
psychedelicious
3c86f1e979 tidy(ui): move transformer statics into class 2024-08-23 19:46:04 +10:00
psychedelicious
894b8a29b9 tidy(ui): massive cleanup
- create a context for entity identifiers, massively simplifying UI for each entity int he list
- consolidate common redux actions
- remove now-unused code
2024-08-23 19:46:04 +10:00
psychedelicious
8436a44973 perf(ui): do not add duplicate points to lines 2024-08-23 19:46:04 +10:00
psychedelicious
f9f9ec3688 feat(ui): up line tension to 0.3 2024-08-23 19:46:04 +10:00
psychedelicious
5a98d7a1f6 perf(ui): disable stroke, perfect draw on compositing rect 2024-08-23 19:46:04 +10:00
psychedelicious
f9bc96e497 tidy(ui): remove unused code, initial image 2024-08-23 19:46:04 +10:00
psychedelicious
56350ff5dc tidy(ui): remove unused state & actions 2024-08-23 19:46:04 +10:00
psychedelicious
6c1139340c feat(ui): region mask rendering 2024-08-23 19:46:04 +10:00
psychedelicious
641b1a7e6f feat(ui): esc cancels drawing buffer
maybe this is not wanted? we'll see
2024-08-23 19:46:04 +10:00
psychedelicious
674a3f462f fix(ui): render transformer over objects, fix issue w/ inpaint rect color 2024-08-23 19:46:04 +10:00
psychedelicious
2283186d3a fix(ui): brush preview fill for inpaint/region 2024-08-23 19:46:04 +10:00
psychedelicious
340af1fe50 fix(ui): no objects rendered until vis toggled 2024-08-23 19:46:04 +10:00
psychedelicious
9378656d78 feat(ui): inpaint mask transform 2024-08-23 19:46:04 +10:00
psychedelicious
5f0413e222 fix(ui): layer accidental early set isFirstRender=false 2024-08-23 19:46:04 +10:00
psychedelicious
c3e47515b1 fix(ui): inpaint mask rendering 2024-08-23 19:46:04 +10:00
psychedelicious
5dcef6fa0d feat(ui): wip inpaint mask uses new API 2024-08-23 19:46:04 +10:00
psychedelicious
31ac02cd93 feat(ui): move updatePosition to transformer 2024-08-23 19:46:04 +10:00
psychedelicious
ab16976084 feat(ui): move resetScale to transformer 2024-08-23 19:46:04 +10:00
psychedelicious
8e2b7845e1 tidy(ui): more imperative naming 2024-08-23 19:46:04 +10:00
psychedelicious
3973bce342 tidy(ui): use imperative names for setters in stateapi 2024-08-23 19:46:04 +10:00
psychedelicious
f63847a504 fix(ui): commit drawing buffer on tool change, fixing bbox not calculating 2024-08-23 19:46:04 +10:00
psychedelicious
07e3529948 fix(ui): sync transformer when requesting bbox calc 2024-08-23 19:46:04 +10:00
psychedelicious
03e1c60694 tidy(ui): rename union CanvasEntity -> CanvasEntityState 2024-08-23 19:46:04 +10:00
psychedelicious
d766ed71fc fix(ui): request rect calc immediately on transform, hiding rect 2024-08-23 19:46:04 +10:00
psychedelicious
ae68ef142a feat(ui): move bbox calculation to transformer 2024-08-23 19:46:04 +10:00
psychedelicious
20f55768c4 feat(ui): use set for transformer subscriptions 2024-08-23 19:46:04 +10:00
psychedelicious
c4fad4456e tidy(ui): clean up worker tasks when complete 2024-08-23 19:46:04 +10:00
psychedelicious
78f5ec44ad tidy(ui): remove unused code in CanvasTool 2024-08-23 19:46:04 +10:00
psychedelicious
e14ba86942 feat(ui): use pubsub for isTransforming on manager 2024-08-23 19:46:04 +10:00
psychedelicious
d4e7720f6b docs(ui): update transformer docstrings 2024-08-23 19:46:04 +10:00
psychedelicious
a3f0e7e1cb feat(ui): revised event pubsub, transformer logic split out 2024-08-23 19:46:04 +10:00
psychedelicious
30a696c476 feat(ui): add simple pubsub 2024-08-23 19:46:04 +10:00
psychedelicious
66d6c64e16 feat(ui): document & clean up object renderer 2024-08-23 19:46:04 +10:00
psychedelicious
d15be9b57c feat(ui): split out object renderer 2024-08-23 19:46:04 +10:00
psychedelicious
e5da902fd0 fix(ui): unable to hold shit while transforming to retain ratio 2024-08-23 19:46:04 +10:00
psychedelicious
fc558094c2 tidy(ui): rename canvas stuff 2024-08-23 19:46:04 +10:00
psychedelicious
ad9312e989 tidy(ui): consolidate getLoggingContext builders 2024-08-23 19:46:04 +10:00
psychedelicious
8e1a70b008 fix(ui): align all tools to 1px grid
- Offset brush tool by 0.5px when width is odd, ensuring each stroke edge is exactly on a pixel boundary
- Round the rect tool also
2024-08-23 19:46:04 +10:00
psychedelicious
17f88cd5ad feat(ui): disable image smoothing on layers 2024-08-23 19:46:04 +10:00
psychedelicious
298f1919fa fix(ui): round position when rasterizing layer 2024-08-23 19:46:04 +10:00
psychedelicious
4d20cc11d4 feat(ui): continue modularizing transform 2024-08-23 19:46:04 +10:00
psychedelicious
14f249a2f0 feat(ui): fix a few things that didn't unsubscribe correctly, add helper to manage subscriptions 2024-08-23 19:46:04 +10:00
psychedelicious
b9746a6c2c feat(ui): merge bbox outline into transformer 2024-08-23 19:46:04 +10:00
psychedelicious
94f298a6f4 fix(ui): update parent's pos not transformers 2024-08-23 19:46:04 +10:00
psychedelicious
8d3a8178da feat(ui): merge interaction rect into transformer class 2024-08-23 19:46:04 +10:00
psychedelicious
cad4212fe8 feat(ui): prepare staging area 2024-08-23 19:46:04 +10:00
psychedelicious
cff28dfaa9 feat(ui): typing for logging context 2024-08-23 19:46:04 +10:00
psychedelicious
70d7509fcc feat(ui): remove inheritance of CanvasObject
JS is terrible
2024-08-23 19:46:04 +10:00
psychedelicious
cf83af7a27 feat(ui): split & document transformer logic, iterate on class structures 2024-08-23 19:46:04 +10:00
psychedelicious
5c5a405c0f feat(ui): rotation snap to nearest 45deg when holding shift 2024-08-23 19:46:04 +10:00
psychedelicious
0208e4b232 feat(ui): expose subscribe method for nanostores 2024-08-23 19:46:04 +10:00
psychedelicious
e940754795 tidy(ui): remove layer scaling reducers 2024-08-23 19:46:04 +10:00
psychedelicious
dc9fa1a735 fix(ui): pixel-perfect transforms 2024-08-23 19:46:04 +10:00
psychedelicious
08591fbf6d fix(ui): layer visibility toggle 2024-08-23 19:46:04 +10:00
psychedelicious
74db71bb5d fix(nodes): fix canvas mask erode
it wasn't eroding enough and caused incorrect transparency in result images
2024-08-23 19:46:04 +10:00
psychedelicious
60dbe798a5 fix(ui): do not reset layer on first render 2024-08-23 19:46:04 +10:00
psychedelicious
0e676605fe feat(ui): revised logging and naming setup, fix staging area 2024-08-23 19:46:04 +10:00
psychedelicious
3f781016f6 feat(ui): add repr methods to layer and object classes 2024-08-23 19:46:04 +10:00
psychedelicious
17cd2f6b02 feat(ui): use nanoid(10) instead of uuidv4 for canvas
Shorter ids makes it much more readable
2024-08-23 19:46:04 +10:00
psychedelicious
99102a1b34 build(ui): add nanoid as explicit dep 2024-08-23 19:46:04 +10:00
psychedelicious
8d72e7d9e8 fix(ui): move CanvasImage's konva image to correct object 2024-08-23 19:46:04 +10:00
psychedelicious
0b6b6f97ad fix(ui): prevent flash when applying transform 2024-08-23 19:46:04 +10:00
psychedelicious
fb2f6382b1 build(ui): add eslint rules for async stuff 2024-08-23 19:46:04 +10:00
psychedelicious
1ddea87c35 feat(ui): trying to fix flicker after transform 2024-08-23 19:46:04 +10:00
psychedelicious
ea02323095 feat(ui): transform cleanup 2024-08-23 19:46:04 +10:00
psychedelicious
49733091c7 feat(ui): fix transform when rotated 2024-08-23 19:46:04 +10:00
psychedelicious
cf833fd6e2 fix(ui): use pixel bbox when image is in layer 2024-08-23 19:46:04 +10:00
psychedelicious
ba5cf07ab8 fix(ui): transforming when axes flipped 2024-08-23 19:46:04 +10:00
psychedelicious
d15321a373 feat(ui): hallelujah (???) 2024-08-23 19:46:04 +10:00
psychedelicious
de597a5eb4 feat(ui): add debug button 2024-08-23 19:46:04 +10:00
psychedelicious
e5f5cbdf5c fix(ui): transformer padding 2024-08-23 19:46:04 +10:00
psychedelicious
7d4342bbff feat(ui): wip transform mode 2 2024-08-23 19:46:04 +10:00
psychedelicious
7f8a1d8d20 feat(ui): wip transform mode 2024-08-23 19:46:04 +10:00
psychedelicious
65353ac1e1 feat(ui): wip transform mode 2024-08-23 19:46:04 +10:00
psychedelicious
7f9a31ca4a fix(ui): dnd to canvas broke 2024-08-23 19:46:04 +10:00
psychedelicious
592eb2886c fix(ui): conflicts after rebasing 2024-08-23 19:46:04 +10:00
psychedelicious
c220dd8987 fix(ui): imageDropped listener 2024-08-23 19:46:04 +10:00
psychedelicious
a263beb0d5 wip 2024-08-23 19:46:04 +10:00
psychedelicious
46b7c510eb fix(ui): transform tool seems to be working 2024-08-23 19:46:04 +10:00
psychedelicious
f405e472ea fix(ui): move tool fixes, add transform tool 2024-08-23 19:46:04 +10:00
psychedelicious
7bdfd3ef5f feat(ui): move tool now only moves 2024-08-23 19:46:04 +10:00
psychedelicious
778ee2c679 feat(ui): layer bbox calc in worker 2024-08-23 19:46:04 +10:00
psychedelicious
e70339ff3e feat(ui): tweaked entity & group selection styles 2024-08-23 19:46:04 +10:00
psychedelicious
88c57a9750 feat(ui): canvas entity list headers 2024-08-23 19:46:04 +10:00
psychedelicious
137252128b tidy(ui): CanvasRegion 2024-08-23 19:46:04 +10:00
psychedelicious
d4297b1345 tidy(ui): CanvasRect 2024-08-23 19:46:04 +10:00
psychedelicious
6059bc7b47 tidy(ui): CanvasLayer 2024-08-23 19:46:04 +10:00
psychedelicious
c3ff3eb51f tidy(ui): CanvasInpaintMask 2024-08-23 19:46:04 +10:00
psychedelicious
0b7751c413 tidy(ui): CanvasInitialImage 2024-08-23 19:46:04 +10:00
psychedelicious
d7f1c30624 tidy(ui): CanvasImage 2024-08-23 19:46:04 +10:00
psychedelicious
3f4d7dbeea tidy(ui): CanvasEraserLine 2024-08-23 19:46:04 +10:00
psychedelicious
19b6ae2907 tidy(ui): CanvasControlAdapter 2024-08-23 19:46:04 +10:00
psychedelicious
769f96ff9f tidy(ui): CanvasBrushLine 2024-08-23 19:46:04 +10:00
psychedelicious
fdaf75faa4 tidy(ui): CanvasBbox 2024-08-23 19:46:04 +10:00
psychedelicious
1380bb7ae6 tidy(ui): CanvasBackground 2024-08-23 19:46:04 +10:00
psychedelicious
9483c8cc29 tidy(ui): update canvas classes, organise location of konva nodes 2024-08-23 19:46:04 +10:00
psychedelicious
2ef8a8cf5a feat(ui): add names to all konva objects
Makes troubleshooting much simpler
2024-08-23 19:46:04 +10:00
psychedelicious
d296ec1932 fix(ui): do not await creating new canvas image
If you await this, it causes a race condition where multiple images are created.
2024-08-23 19:46:04 +10:00
psychedelicious
444ad3dae1 feat(ui): use position and dimensions instead of separate x,y,width,height attrs 2024-08-23 19:46:04 +10:00
psychedelicious
8cdcc71378 fix(ui): remove weird rtkq hook wrapper
I do not understand why I did that initially but it doesn't work with TS.
2024-08-23 19:46:04 +10:00
psychedelicious
e8bc06cfd3 feat(ui): rename types size and position to dimensions and coordinate 2024-08-23 19:46:04 +10:00
psychedelicious
67a0a024e9 tidy(ui): hide layer settings by default 2024-08-23 19:46:04 +10:00
psychedelicious
bd2c46c267 fix(ui): layer rendering when starting as disabled 2024-08-23 19:46:04 +10:00
psychedelicious
5acb27f350 feat(invocation): reduce canvas v2 mask & crop mask dilation 2024-08-23 19:46:04 +10:00
psychedelicious
7271b12d0f feat(ui): de-jank staging area and progress images 2024-08-23 19:46:04 +10:00
psychedelicious
4a79467a33 feat(ui): update staging handling to work w/ cropped mask 2024-08-23 19:46:04 +10:00
psychedelicious
5501bb87a3 chore(ui): typegen 2024-08-23 19:46:04 +10:00
psychedelicious
561610e296 feat(app): update CanvasV2MaskAndCropInvocation 2024-08-23 19:46:04 +10:00
psychedelicious
b76609ef18 feat(ui): use new canvas output node 2024-08-23 19:46:04 +10:00
psychedelicious
070b78501b chore(ui): typegen 2024-08-23 19:46:04 +10:00
psychedelicious
50df4f4ab6 feat(app): add CanvasV2MaskAndCropInvocation & CanvasV2MaskAndCropOutput
This handles some masking and cropping that the canvas needs.
2024-08-23 19:46:04 +10:00
psychedelicious
9bbf430125 fix(ui): restore nodes output tracking 2024-08-23 19:46:04 +10:00
psychedelicious
384a90958a feat(ui): rip out document size
barely knew ye
2024-08-23 19:46:04 +10:00
psychedelicious
0e4a25b029 feat(ui): convert initial image to layer when starting canvas session 2024-08-23 19:46:04 +10:00
psychedelicious
4a44e171fd fix(ui): fix layer transparency calculation 2024-08-23 19:46:04 +10:00
psychedelicious
9bc57a6f59 fix(ui): reset initial image when resetting canvas 2024-08-23 19:46:03 +10:00
psychedelicious
4341ed7ab4 fix(ui): reset node executions states when loading workflow 2024-08-23 19:46:03 +10:00
psychedelicious
97ce72c542 fix(ui): entity display list 2024-08-23 19:46:03 +10:00
psychedelicious
a2c78a57a7 feat(ui): img2img working 2024-08-23 19:46:03 +10:00
psychedelicious
044a713dc9 feat(ui): rough out img2img on canvas 2024-08-23 19:46:03 +10:00
psychedelicious
b8479c5fe2 UNDO ME WIP 2024-08-23 19:46:03 +10:00
psychedelicious
4e5d056824 feat(ui): log invocation source id on socket event 2024-08-23 19:46:03 +10:00
psychedelicious
118278b372 feat(ui): restore document size overlay renderer 2024-08-23 19:46:03 +10:00
psychedelicious
8e8c255f3f feat(ui): make documnet size a rect 2024-08-23 19:46:03 +10:00
psychedelicious
1575bee401 refactor(ui): remove modular imagesize components
This is no longer necessary with canvas v2 and added a ton of extraneous redux actions when changing the image size. Also renamed to document size
2024-08-23 19:46:03 +10:00
psychedelicious
249bbfc883 feat(ui): initialState is for generation mode 2024-08-23 19:46:03 +10:00
psychedelicious
3993ae410f feat(ui): split out canvas entity list component 2024-08-23 19:46:03 +10:00
psychedelicious
edf040e3d2 feat(ui): hide bbox button when no canvas session active 2024-08-23 19:46:03 +10:00
psychedelicious
66fd077ee7 tidy(ui): remove unused naming objects/utils
The canvas manager means we don't need to worry about konva node names as we never directly select konva nodes.
2024-08-23 19:46:03 +10:00
psychedelicious
b93462ebb6 feat(ui): split up tool chooser buttons
Prep for distinct toolbars for generation vs canvas modes
2024-08-23 19:46:03 +10:00
psychedelicious
aae60d0cdc feat(ui): add useAssertSingleton util hook
This simple hook asserts that it is only ever called once. Particularly useful for things like hotkeys hooks.
2024-08-23 19:46:03 +10:00
psychedelicious
d4da00e607 feat(ui): "stagingArea" -> "session" 2024-08-23 19:46:03 +10:00
psychedelicious
0c539ff00b feat(ui): add reset button to canvas 2024-08-23 19:46:03 +10:00
psychedelicious
5983cbf26c feat(ui): add snapToRect util 2024-08-23 19:46:03 +10:00
psychedelicious
c513d6e3af fix(ui): fiddle with control adapter filters
some jank still
2024-08-23 19:46:03 +10:00
psychedelicious
9d57c0e631 feat(ui): temp disable doc size overlay 2024-08-23 19:46:03 +10:00
psychedelicious
a1923a8966 feat(ui): no animation on layer selection
Felt sluggish
2024-08-23 19:46:03 +10:00
psychedelicious
d988e18731 feat(ui): use canvas as source for control images (wip) 2024-08-23 19:46:03 +10:00
psychedelicious
51008da2dd fix(ui): control adapter translate & scale 2024-08-23 19:46:03 +10:00
psychedelicious
6ccc1f5672 tidy(ui): removed unused state related to non-buffered drawing 2024-08-23 19:46:03 +10:00
psychedelicious
4a556f84e0 feat(ui): control adapter image rendering 2024-08-23 19:46:03 +10:00
psychedelicious
2f21a2220d fix(ui): do not floor bbox calc, it cuts off the last pixels 2024-08-23 19:46:03 +10:00
psychedelicious
91a420b13e feat(ui): fix issue where creating line needs 2 points 2024-08-23 19:46:03 +10:00
psychedelicious
c27da3581b fix(ui): edge cases when holding shift and drawing lines 2024-08-23 19:46:03 +10:00
psychedelicious
961dfbce93 fix(ui): set buffered rect color to full alpha 2024-08-23 19:46:03 +10:00
psychedelicious
df39c825ae fix(ui): handle mouseup correctly 2024-08-23 19:46:03 +10:00
psychedelicious
3f6ee1b7a4 feat(ui): buffered rect drawing 2024-08-23 19:46:03 +10:00
psychedelicious
908e504a6f fix(ui): buffered drawing edge cases 2024-08-23 19:46:03 +10:00
psychedelicious
f2fa41afc5 perf(ui): do not use stage.find 2024-08-23 19:46:03 +10:00
psychedelicious
440ff40ad5 perf(ui): object groups do not listen 2024-08-23 19:46:03 +10:00
psychedelicious
5c15458e15 perf(ui): buffered drawing (wip) 2024-08-23 19:46:03 +10:00
psychedelicious
be5b474f1e tidy(ui): organise files 2024-08-23 19:46:03 +10:00
psychedelicious
cee178c2b6 tidy(ui): organise files 2024-08-23 19:46:03 +10:00
psychedelicious
27657f8b7a tidy(ui): organise files 2024-08-23 19:46:03 +10:00
psychedelicious
e0cde3815a fix(ui): background rendering 2024-08-23 19:46:03 +10:00
psychedelicious
09d0421de4 pkg(ui): remove unused deps react-konva & use-image 2024-08-23 19:46:03 +10:00
psychedelicious
47b94d563c feat(ui): organize konva state and files 2024-08-23 19:46:03 +10:00
psychedelicious
0b5d20c9f0 fix(ui): merge conflicts in image deletion listener 2024-08-23 19:46:03 +10:00
psychedelicious
80e7e1293a fix(ui): region rendering 2024-08-23 19:46:03 +10:00
psychedelicious
3a82b0cbc1 fix(ui): inpaint mask rendering 2024-08-23 19:46:03 +10:00
psychedelicious
a27cbc13b6 fix(ui): staging area rendering 2024-08-23 19:46:03 +10:00
psychedelicious
a8f962eb3f fix(ui): stale selected entity 2024-08-23 19:46:03 +10:00
psychedelicious
7f40d23f19 fix(ui): staging area image offset 2024-08-23 19:46:03 +10:00
psychedelicious
918354cd9d feat(ui): tweak layer ui component 2024-08-23 19:46:03 +10:00
psychedelicious
eef9278ee6 fix(ui): resetting layer resets position 2024-08-23 19:46:03 +10:00
psychedelicious
2c32e2e5c1 feat(ui): updated layer list component styling 2024-08-23 19:46:03 +10:00
psychedelicious
6f05654db5 feat(ui): transformable layers 2024-08-23 19:46:03 +10:00
psychedelicious
1d31b6902f feat(ui): move tool icon is pointer like in other apps 2024-08-23 19:46:03 +10:00
psychedelicious
5a7d615e64 feat(ui): do not floor cursor position 2024-08-23 19:46:03 +10:00
psychedelicious
1dbf9e4ed4 feat(ui): disable gallery hotkeys while staging 2024-08-23 19:46:03 +10:00
psychedelicious
5dcc6ee203 feat(ui): revised canvas progress & staging image handling 2024-08-23 19:46:03 +10:00
psychedelicious
84a4e6ae3f feat(ui): show queue item origin in queue list 2024-08-23 19:46:03 +10:00
psychedelicious
f283bfd68f chore(ui): typegen 2024-08-23 19:46:03 +10:00
psychedelicious
6e5ff7b79c feat(app): add origin to session queue
The origin is an optional field indicating the queue item's origin. For example, "canvas" when the queue item originated from the canvas or "workflows" when the queue item originated from the workflows tab. If omitted, we assume the queue item originated from the API directly.

- Add migration to add the nullable column to the `session_queue` table.
- Update relevant event payloads with the new field.
- Add `cancel_by_origin` method to `session_queue` service and corresponding route. This is required for the canvas to bail out early when staging images.
- Add `origin` to both `SessionQueueItem` and `Batch` - it needs to be provided initially via the batch and then passed onto the queue item.
-
2024-08-23 19:46:03 +10:00
psychedelicious
7c3800d03f fix(ui): denoise start on outpainting 2024-08-23 19:46:03 +10:00
psychedelicious
941db90518 feat(ui): add redux events for queue cleared & batch enqueued socket events 2024-08-23 19:46:03 +10:00
psychedelicious
0d9ecf0f90 feat(ui): canvas staging area works 2024-08-23 19:46:03 +10:00
psychedelicious
9c77023a11 feat(ui): switch to view tool when staging 2024-08-23 19:46:03 +10:00
psychedelicious
b55378c63c tidy(ui): disable preview images on every enqueue 2024-08-23 19:46:03 +10:00
psychedelicious
946c2a49ab feat(ui): rough out save staging image 2024-08-23 19:46:03 +10:00
psychedelicious
b823c31ec6 feat(ui): staging area image visibility toggle 2024-08-23 19:46:03 +10:00
psychedelicious
ec6361e5cb fix(ui): batch building after removing canvas files 2024-08-23 19:46:03 +10:00
psychedelicious
0c26d28278 feat(ui): make Graph class's getMetadataNode public 2024-08-23 19:46:03 +10:00
psychedelicious
c5172d4c5a tidy(ui): remove old canvas graphs 2024-08-23 19:46:03 +10:00
psychedelicious
89de04775e fix(ui): do not select already-selected entity 2024-08-23 19:46:03 +10:00
psychedelicious
b4c3c940b5 tidy(ui): naming things 2024-08-23 19:46:03 +10:00
psychedelicious
aee2aad959 tidy(ui): file organisation 2024-08-23 19:46:03 +10:00
psychedelicious
5ca48a8a5f fix(ui): reset cursor pos when fitting document 2024-08-23 19:46:03 +10:00
psychedelicious
1806aa187b feat(ui): staging area works more better 2024-08-23 19:46:03 +10:00
psychedelicious
7824cb7a1a feat(ui): staging area barely works 2024-08-23 19:46:03 +10:00
psychedelicious
9807a896f4 feat(ui): consolidate konva API 2024-08-23 19:46:03 +10:00
psychedelicious
19866f057d feat(ui): consolidate konva API 2024-08-23 19:46:03 +10:00
psychedelicious
ec4eae3c9c feat(ui): staging area (rendering wip) 2024-08-23 19:46:03 +10:00
psychedelicious
bea0cba038 tidy(ui): type "Dimensions" -> "Size" 2024-08-23 19:46:03 +10:00
psychedelicious
48ee75af9c feat(ui): add updateNode to Graph 2024-08-23 19:46:03 +10:00
psychedelicious
929c593d2f feat(ui): sdxl graphs 2024-08-23 19:46:03 +10:00
psychedelicious
221f32eca7 feat(ui): sd1 outpaint graph 2024-08-23 19:46:03 +10:00
psychedelicious
c3acc15e8b tests(ui): add missing tests for Graph class 2024-08-23 19:46:03 +10:00
psychedelicious
1b653278fc feat(ui): add Graph.getid() util 2024-08-23 19:46:03 +10:00
psychedelicious
cc9062ee46 feat(ui): outpaint graph, organize builder a bit 2024-08-23 19:46:03 +10:00
psychedelicious
91c0feb0ad feat(ui): inpaint sd1 graph 2024-08-23 19:46:03 +10:00
psychedelicious
ae60292ac8 feat(ui): temp disable image caching while testing 2024-08-23 19:46:03 +10:00
psychedelicious
a6ca17b19d feat(ui): txt2img & img2img graphs 2024-08-23 19:46:03 +10:00
psychedelicious
6a4a5ece74 feat(ui): minor change to canvas bbox state type 2024-08-23 19:46:03 +10:00
psychedelicious
9b81860307 feat(ui): simplified konva node to blob/imagedata utils 2024-08-23 19:46:03 +10:00
psychedelicious
5f4a3928d2 feat(ui): node manager getter/setter 2024-08-23 19:46:03 +10:00
psychedelicious
b703884763 feat(ui): generation mode calculation, fudged graphs 2024-08-23 19:46:03 +10:00
psychedelicious
32da98ab8f feat(ui): add utils for getting images from canvas 2024-08-23 19:46:03 +10:00
psychedelicious
bd5a85bf70 feat(ui): even more simplified API - lean on the konva node manager to abstract imperative state API & rendering 2024-08-23 19:46:03 +10:00
psychedelicious
d045f24014 feat(ui): revised docstrings for renderers & simplified api 2024-08-23 19:46:03 +10:00
psychedelicious
2aad3f89c3 feat(ui): inpaint mask UI components 2024-08-23 19:46:03 +10:00
psychedelicious
dd54d19f00 feat(ui): inpaint mask rendering (wip) 2024-08-23 19:46:03 +10:00
psychedelicious
0ed6591d8c fix(ui): models loaded handler 2024-08-23 19:46:03 +10:00
psychedelicious
712e090134 feat(ui): internal state for inpaint mask 2024-08-23 19:46:03 +10:00
psychedelicious
8fc2a1d1cf refactor(ui): divvy up canvas state a bit 2024-08-23 19:46:03 +10:00
psychedelicious
cc15c1593e feat(ui): get region and base layer canvas to blob logic working 2024-08-23 19:46:03 +10:00
psychedelicious
9997d3abda refactor(ui): node manager handles more tedious annoying stuff 2024-08-23 19:46:03 +10:00
psychedelicious
031471e785 feat(ui): use node manager for addRegions 2024-08-23 19:46:03 +10:00
psychedelicious
2e860c6791 feat(ui): persist bbox 2024-08-23 19:46:03 +10:00
psychedelicious
d071a9e17d fix(ui): fix generation graphs 2024-08-23 19:46:03 +10:00
psychedelicious
ed53d33321 feat(ui): add toggle for clipToBbox 2024-08-23 19:46:03 +10:00
psychedelicious
382bc6d978 feat(ui): rename konva node manager 2024-08-23 19:46:03 +10:00
psychedelicious
dab42e258c refactor(ui): create classes to abstract mgmt of konva nodes 2024-08-23 19:46:03 +10:00
psychedelicious
81556410bb tidy(ui): organise renderers 2024-08-23 19:46:03 +10:00
psychedelicious
1f2dfd473c refactor(ui): create entity to konva node map abstraction (wip)
Instead of chaining konva `find` and `findOne` methods, all konva nodes are added to a mapping object. Finding and manipulating them is much simpler.

Done for regions and layers, wip for control adapters.
2024-08-23 19:46:03 +10:00
psychedelicious
8f0f51be2c perf(ui): fix lag w/ region rendering
Needed to memoize these selectors
2024-08-23 19:46:03 +10:00
psychedelicious
7179e250ed feat(ui): move canvas fill color picker to right 2024-08-23 19:46:03 +10:00
psychedelicious
5bec091fd6 refactor(ui): remove unused ellipse & polygon objects 2024-08-23 19:46:03 +10:00
psychedelicious
2c5896cb0c fix(ui): incorrect rect/brush/eraser positions 2024-08-23 19:46:03 +10:00
psychedelicious
93ff252dc0 refactor(ui): enable global debugging flag 2024-08-23 19:46:03 +10:00
psychedelicious
ac52224455 refactor(ui): disable the preview renderer for now 2024-08-23 19:46:03 +10:00
psychedelicious
4087cad23d tweak(ui): canvas editor layout 2024-08-23 19:46:03 +10:00
psychedelicious
e936b1ff8f perf(ui): memoize layeractionsmenu valid actions 2024-08-23 19:46:03 +10:00
psychedelicious
b7f9c5e221 refactor(ui): decouple konva renderer from react
Subscribe to redux store directly, skipping all the react overhead.

With react in dev mode, a typical frame while using the brush tool on almost-empty canvas is reduced from ~7.5ms to ~3.5ms. All things considered, this still feels slow, but it's a massive improvement.
2024-08-23 19:46:03 +10:00
psychedelicious
fc5467150e feat(ui): clip lines to bbox 2024-08-23 19:46:03 +10:00
psychedelicious
4dcab357a0 fix(ui): document fit positioning 2024-08-23 19:46:03 +10:00
psychedelicious
695e464255 feat(ui): document bounds overlay 2024-08-23 19:46:03 +10:00
psychedelicious
9999b60c3b tidy(ui): background layer 2024-08-23 19:46:03 +10:00
psychedelicious
e7df53e260 refactor(ui): use "entity" instead of "data" for canvas 2024-08-23 19:46:03 +10:00
psychedelicious
844590a571 feat(ui): brush size border radius = 1 2024-08-23 19:46:03 +10:00
psychedelicious
9622beaa0d fix(ui): canvas HUD doesn't interrupt tool 2024-08-23 19:46:03 +10:00
psychedelicious
007e2553a8 refactor(ui): split up canvas entity renderers, temp disable preview 2024-08-23 19:46:03 +10:00
psychedelicious
15ad4e3f5e fix(ui): delete all layers button 2024-08-23 19:46:03 +10:00
psychedelicious
be5094fcb4 fix(ui): ignore keyboard shortcuts in input/textarea elements 2024-08-23 19:46:03 +10:00
psychedelicious
a20a861680 fix(ui): canvas entity ids getting clobbered 2024-08-23 19:46:03 +10:00
psychedelicious
396d0a4bc0 fix(ui): move lora followup fixes 2024-08-23 19:46:03 +10:00
psychedelicious
ca9314e077 chore(ui): lint 2024-08-23 19:46:03 +10:00
psychedelicious
4b848798e7 refactor(ui): move loras to canvas slice 2024-08-23 19:46:03 +10:00
psychedelicious
083bcbc77d fix(ui): layer is selected when added 2024-08-23 19:46:03 +10:00
psychedelicious
e8cdc9ae62 feat(ui): r to center & fit stage on document 2024-08-23 19:46:03 +10:00
psychedelicious
8abfa759a4 feat(ui): better HUD 2024-08-23 19:46:03 +10:00
psychedelicious
f6a324b633 fix(ui): always use current brush width when making straight lines 2024-08-23 19:46:03 +10:00
psychedelicious
f083be9391 feat(ui): hold shift w/ brush to draw straight line 2024-08-23 19:46:03 +10:00
psychedelicious
091e2fb751 fix(ui): update bg on canvas resize 2024-08-23 19:46:03 +10:00
psychedelicious
d8539daf1f refactor(ui): better hud 2024-08-23 19:46:03 +10:00
psychedelicious
7ec059f5fa refactor(ui): scaled tool preview border 2024-08-23 19:46:03 +10:00
psychedelicious
4f2ecdefd2 refactor(ui): port remaining canvasV1 rendering logic to V2, remove old code 2024-08-23 19:46:03 +10:00
psychedelicious
e8891a1988 refactor(ui): fix more types 2024-08-23 19:46:03 +10:00
psychedelicious
37d2607f34 refactor(ui): metadata recall (wip)
just enough let the app run
2024-08-23 19:46:03 +10:00
psychedelicious
0e7b10d3d9 refactor(ui): undo/redo button temp fix 2024-08-23 19:46:03 +10:00
psychedelicious
1f85888638 refactor(ui): fix renderer stuff 2024-08-23 19:46:03 +10:00
psychedelicious
c1f9a129fa refactor(ui): fix misc types 2024-08-23 19:46:03 +10:00
psychedelicious
7ccc5ba398 refactor(ui): fix gallery stuff 2024-08-23 19:46:03 +10:00
psychedelicious
5e1a6ae334 refactor(ui): fix delete image stuff 2024-08-23 19:46:03 +10:00
psychedelicious
3f6cf638f9 refactor(ui): fix useIsReadyToEnqueue for new adapterType field 2024-08-23 19:46:03 +10:00
psychedelicious
46e062a828 refactor(ui): update generation tab graphs 2024-08-23 19:46:02 +10:00
psychedelicious
cc3a0b5d6c refactor(ui): add adapterType to ControlAdapterData 2024-08-23 19:46:02 +10:00
psychedelicious
775479ab7b refactor(ui): update components & logic to use new unified slice (again) 2024-08-23 19:46:02 +10:00
psychedelicious
6b9e0e6d63 refactor(ui): update components & logic to use new unified slice 2024-08-23 19:46:02 +10:00
psychedelicious
83a5c87f5e refactor(ui): merge compositing, params into canvasV2 slice 2024-08-23 19:46:02 +10:00
psychedelicious
84fde74331 refactor(ui): add scaled bbox state 2024-08-23 19:46:02 +10:00
psychedelicious
a517e29b39 refactor(ui): update dnd/image upload 2024-08-23 19:46:02 +10:00
psychedelicious
ccceba7565 refactor(ui): update size/prompts state 2024-08-23 19:46:02 +10:00
psychedelicious
5fc7a03669 refactor(ui): rip out old control adapter implementation 2024-08-23 19:46:02 +10:00
psychedelicious
8864ad1b50 refactor(ui): canvas v2 (wip)
fix entity count select
2024-08-23 19:46:02 +10:00
psychedelicious
f2989885fb refactor(ui): canvas v2 (wip)
delete unused file
2024-08-23 19:46:02 +10:00
psychedelicious
19c66e5c76 refactor(ui): canvas v2 (wip)
merge all canvas state reducers into one big slice (but with the logic split across files so it's not hell)
2024-08-23 19:46:02 +10:00
psychedelicious
8a6690a57c refactor(ui): canvas v2 (wip)
Fix a few more components
2024-08-23 19:46:02 +10:00
psychedelicious
2cc60f253a refactor(ui): canvas v2 (wip)
missed a spot
2024-08-23 19:46:02 +10:00
psychedelicious
cb69872dd3 refactor(ui): canvas v2 (wip)
Redo all UI components for different canvas entity types
2024-08-23 19:46:02 +10:00
psychedelicious
ba66d7c9a6 refactor(ui): canvas v2 (wip) 2024-08-23 19:46:02 +10:00
psychedelicious
9fe727c9f8 refactor(ui): canvas v2 (wip) 2024-08-23 19:46:02 +10:00
psychedelicious
58c656224f refactor(ui): canvas v2 (wip) 2024-08-23 19:46:02 +10:00
psychedelicious
c51253f5f6 refactor(ui): canvas v2 (wip) 2024-08-23 19:46:02 +10:00
psychedelicious
6c1d1588fc feat(ui): bbox tool 2024-08-23 19:46:02 +10:00
psychedelicious
95d6183a6c fix(ui): rect tool preview 2024-08-23 19:46:02 +10:00
psychedelicious
f4c9facdaf fix(ui): multiple stages 2024-08-23 19:46:02 +10:00
psychedelicious
a274e6f165 feat(ui): decouple konva logic from nanostores 2024-08-23 19:46:02 +10:00
psychedelicious
154e3e6f64 feat(ui): store all stage attrs in nanostores 2024-08-23 19:46:02 +10:00
psychedelicious
2c3ac972e5 feat(ui): round stage scale 2024-08-23 19:46:02 +10:00
psychedelicious
2e2e072b0b chore(ui): bump konva 2024-08-23 19:46:02 +10:00
psychedelicious
9d8dd2bf66 feat(ui): generation bbox transformation working
whew
2024-08-23 19:46:02 +10:00
psychedelicious
9047f6db30 feat(ui): wip generation bbox 2024-08-23 19:46:02 +10:00
psychedelicious
5ab345ee63 feat(ui): wip generation bbox 2024-08-23 19:46:02 +10:00
psychedelicious
d8a83acd3a feat(ui): CL zoom and pan, some rendering optimizations 2024-08-23 19:46:02 +10:00
psychedelicious
1f58e5756b Revert "feat(ui): add x,y,scaleX,scaleY,rotation to objects"
This reverts commit 53318b396c967c72326a7e4dea09667b2ab20bdd.
2024-08-23 19:46:02 +10:00
psychedelicious
744acb8f07 feat(ui): layers manage their own bbox 2024-08-23 19:46:02 +10:00
psychedelicious
ae7228d821 docs(ui): konva image object docstrings 2024-08-23 19:46:02 +10:00
psychedelicious
99d8b3a7bf feat(ui): add x,y,scaleX,scaleY,rotation to objects 2024-08-23 19:46:02 +10:00
psychedelicious
3fbe65bbcf fix(ui): show color picker when using rect tool 2024-08-23 19:46:02 +10:00
psychedelicious
f5d879d8e7 feat(ui): image loading fallback for raster layers 2024-08-23 19:46:02 +10:00
psychedelicious
cbc5a4f8e6 feat(ui): bbox calc for raster layers 2024-08-23 19:46:02 +10:00
psychedelicious
37ac7d8ed5 feat(ui): do not fill brush preview when drawing 2024-08-23 19:46:02 +10:00
psychedelicious
bee3fa339d fix(ui): brush spacing handling 2024-08-23 19:46:02 +10:00
psychedelicious
c171fe2b96 fix(ui): jank when starting a shape when not already focused on stage 2024-08-23 19:46:02 +10:00
psychedelicious
1fa8032fdb feat(ui): wip raster layers
I meant to split this up into smaller commits and undo some of it, but I committed afterwards and it's tedious to undo.
2024-08-23 19:46:02 +10:00
psychedelicious
5c0676bcc2 feat(ui): support image objects on raster layers
Just the UI and internal state, not rendering yet.
2024-08-23 19:46:02 +10:00
psychedelicious
cefd9a027c tidy(ui): clean up event handlers
Separate logic for each tool in preparation for ellipse and polygon tools.
2024-08-23 19:46:02 +10:00
psychedelicious
1bce156de1 feat(ui): raster layer reset, object group util 2024-08-23 19:46:02 +10:00
psychedelicious
cd4f63f2fd feat(ui): rect shape preview now has fill 2024-08-23 19:46:02 +10:00
psychedelicious
3c7140cbf3 feat(ui): cancel shape drawing on esc 2024-08-23 19:46:02 +10:00
psychedelicious
b71ba63b5a feat(ui): temp disable history on CL 2024-08-23 19:46:02 +10:00
psychedelicious
d540e2c0d3 feat(ui): raster layer logic
- Deduplicate shared logic
- Split up giant renderers file into separate cohesive files
- Tons of cleanup
- Progress on raster layer functionality
2024-08-23 19:46:02 +10:00
psychedelicious
d79fafc5f5 feat(ui): add raster layer rendering and interaction (WIP) 2024-08-23 19:46:02 +10:00
psychedelicious
9e93fa2092 feat(ui): scaffold out raster layers
Raster layers may have images, lines and shapes. These will replace initial image layers and provide sketching functionality like we have on canvas.
2024-08-23 19:46:02 +10:00
psychedelicious
392e9b4882 refactor(ui): revise types for line and rect objects
- Create separate object types for brush and eraser lines, instead of a single type that has a `tool` field.
- Create new object type for rect shapes.
- Add logic to schemas to migrate old object types to new.
- Update renderers & reducers.
2024-08-23 19:46:02 +10:00
psychedelicious
231e5ec94a chore: bump version v4.2.8post1 2024-08-23 06:55:30 +10:00
Mary Hipp
e5bb6f9693 lint fix 2024-08-23 06:46:19 +10:00
Mary Hipp
da7dee44c6 fix(ui): use empty string fallback if unable to parse prompts when creating style preset from existing image 2024-08-23 06:46:19 +10:00
Eugene Brodsky
83144f4fe3 fix(docs): follow-up docker readme fixes 2024-08-22 11:19:07 -04:00
psychedelicious
c451f52ea3 chore(ui): lint 2024-08-22 21:00:09 +10:00
psychedelicious
8a2c78f2e1 fix(ui): dynamic prompts not recalculating when deleting or updating a style preset
The root cause was the active style preset not being reset when it was deleted, or no longer present in the list of style presets.

- Add extra reducer to `stylePresetSlice` to reset the active preset if it is deleted or otherwise unavailable
- Update the dynamic prompts listener to trigger on delete/update/list of style presets
2024-08-22 21:00:09 +10:00
psychedelicious
bcc78bde9b chore: bump version to v4.2.8 2024-08-22 21:00:09 +10:00
Васянатор
054bb6fe0a translationBot(ui): update translation (Russian)
Currently translated at 100.0% (1367 of 1367 strings)

Co-authored-by: Васянатор <ilabulanov339@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/ru/
Translation: InvokeAI/Web UI
2024-08-22 13:09:56 +10:00
Riccardo Giovanetti
4f4aa6d92e translationBot(ui): update translation (Italian)
Currently translated at 98.4% (1346 of 1367 strings)

translationBot(ui): update translation (Italian)

Currently translated at 98.4% (1346 of 1367 strings)

Co-authored-by: Riccardo Giovanetti <riccardo.giovanetti@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/it/
Translation: InvokeAI/Web UI
2024-08-22 13:09:56 +10:00
Hosted Weblate
eac51ac6f5 translationBot(ui): update translation files
Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/
Translation: InvokeAI/Web UI
2024-08-22 13:09:56 +10:00
psychedelicious
9f349a7c0a fix(ui): do not constrain width of hide/show boards button
lets translations display fully
2024-08-22 11:36:07 +10:00
psychedelicious
918afa5b15 fix(ui): show more of current board name 2024-08-22 11:36:07 +10:00
psychedelicious
eb1113f95c feat(ui): add translation string for "Upscale" 2024-08-22 11:36:07 +10:00
psychedelicious
4f4ba7b462 tidy(ui): clean up ActiveStylePreset markup 2024-08-21 09:06:41 +10:00
Mary Hipp
2298be0e6b fix(ui): error handling if unable to convert image URL to blob 2024-08-21 09:06:41 +10:00
Mary Hipp
63494dfca7 remove extra slash in exports path 2024-08-21 09:06:41 +10:00
Mary Hipp
36a1d39454 fix(ui): handle badge styling when template name is long 2024-08-21 09:06:41 +10:00
Mary Hipp
a6f6d5c400 fix(ui): add loading state to button when creating or updating a style preset 2024-08-21 09:06:41 +10:00
Mary Hipp
e85f221aca fix(ui): clear prompt template when prompts are recalled 2024-08-21 09:04:35 +10:00
Mary Hipp
d4797e37dc fix(ui): properly unwrap delete style preset API request so that error is caught 2024-08-19 16:12:39 -04:00
Mary Hipp
3e7923d072 fix(api): allow updating of type for style preset 2024-08-19 16:12:39 -04:00
psychedelicious
a85d69ce3d tidy(ui): getViewModeChunks.tsx -> .ts 2024-08-19 08:25:39 +10:00
psychedelicious
96db006c99 fix(ui): edge case with getViewModeChunks 2024-08-19 08:25:39 +10:00
psychedelicious
8ca57d03d8 tests(ui): add tests for getViewModeChunks 2024-08-19 08:25:39 +10:00
psychedelicious
6c404ce5f8 fix(ui): prompt template preset preview out of order 2024-08-19 08:25:39 +10:00
psychedelicious
584e07182b fix(ui): use translations for style preset strings 2024-08-17 21:27:53 +10:00
psychedelicious
f787e9acf6 chore: bump version v4.2.8rc2 2024-08-16 21:47:06 +10:00
psychedelicious
5a24b89e54 fix(app): include style preset defaults in build 2024-08-16 21:47:06 +10:00
psychedelicious
9b482e2a4f chore: bump version to v4.2.8rc1 2024-08-16 10:53:19 +10:00
Max
df4dbe2d57 Fix invoke.sh not detecting symlinks
When invoke.sh is executed using a symlink with a working directory outside of InvokeAI's root directory, it will fail.

invoke.sh attempts to cd into the correct directory at the start of the script, but will cd into the directory of the symlink instead. This commit fixes that.
2024-08-16 10:40:59 +10:00
psychedelicious
713bd11177 feat(ui, api): prompt template export (#6745)
## Summary

Adds option to download all prompt templates to a CSV

## Related Issues / Discussions

<!--WHEN APPLICABLE: List any related issues or discussions on github or
discord. If this PR closes an issue, please use the "Closes #1234"
format, so that the issue will be automatically closed when the PR
merges.-->

## QA Instructions

<!--WHEN APPLICABLE: Describe how you have tested the changes in this
PR. Provide enough detail that a reviewer can reproduce your tests.-->

## Merge Plan

<!--WHEN APPLICABLE: Large PRs, or PRs that touch sensitive things like
DB schemas, may need some care when merging. For example, a careful
rebase by the change author, timing to not interfere with a pending
release, or a message to contributors on discord after merging.-->

## Checklist

- [ ] _The PR has a short but descriptive title, suitable for a
changelog_
- [ ] _Tests added / updated (if applicable)_
- [ ] _Documentation added / updated (if applicable)_
2024-08-16 10:38:50 +10:00
psychedelicious
182571df4b Merge branch 'main' into maryhipp/export-presets 2024-08-16 10:17:07 +10:00
psychedelicious
29bfe492b6 ui: translations update from weblate (#6746)
Translations update from [Hosted Weblate](https://hosted.weblate.org)
for [InvokeAI/Web
UI](https://hosted.weblate.org/projects/invokeai/web-ui/).



Current translation status:

![Weblate translation
status](https://hosted.weblate.org/widget/invokeai/web-ui/horizontal-auto.svg)
2024-08-16 10:16:51 +10:00
psychedelicious
3fb4e3050c feat(ui): focus in textarea after inserting placeholder 2024-08-16 10:14:25 +10:00
psychedelicious
39c7ec3cd9 feat(ui): per type fallbacks for templates 2024-08-16 10:11:43 +10:00
psychedelicious
26bfbdec7f feat(ui): use buttons instead of menu for preset import/export 2024-08-16 09:58:19 +10:00
psychedelicious
7a3eaa8da9 feat(api): save file as prompt_templates.csv 2024-08-16 09:51:46 +10:00
Mary Hipp
599db7296f export only user style presets 2024-08-15 16:07:32 -04:00
Riccardo Giovanetti
042aab4295 translationBot(ui): update translation (Italian)
Currently translated at 98.6% (1340 of 1359 strings)

Co-authored-by: Riccardo Giovanetti <riccardo.giovanetti@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/it/
Translation: InvokeAI/Web UI
2024-08-15 20:44:02 +02:00
Mary Hipp
24f298283f clean up, add context menu to import/download templates 2024-08-15 12:39:55 -04:00
Mary Hipp
68dac6349d Merge remote-tracking branch 'origin/main' into maryhipp/export-presets 2024-08-15 11:21:56 -04:00
chainchompa
b675fc19e8 feat: add base prop for selectedWorkflow to allow loading a workflow on launch (#6742)
## Summary
added a base prop for selectedWorkflow to allow loading a workflow on
launch

<!--A description of the changes in this PR. Include the kind of change
(fix, feature, docs, etc), the "why" and the "how". Screenshots or
videos are useful for frontend changes.-->

## Related Issues / Discussions

<!--WHEN APPLICABLE: List any related issues or discussions on github or
discord. If this PR closes an issue, please use the "Closes #1234"
format, so that the issue will be automatically closed when the PR
merges.-->

## QA Instructions
can test by loading InvokeAIUI with a selectedWorkflow prop of the
workflow ID
<!--WHEN APPLICABLE: Describe how you have tested the changes in this
PR. Provide enough detail that a reviewer can reproduce your tests.-->

## Merge Plan

<!--WHEN APPLICABLE: Large PRs, or PRs that touch sensitive things like
DB schemas, may need some care when merging. For example, a careful
rebase by the change author, timing to not interfere with a pending
release, or a message to contributors on discord after merging.-->

## Checklist

- [ ] _The PR has a short but descriptive title, suitable for a
changelog_
- [ ] _Tests added / updated (if applicable)_
- [ ] _Documentation added / updated (if applicable)_
2024-08-15 10:52:23 -04:00
chainchompa
659019cfd6 Merge branch 'main' into chainchompa/preselect-workflows 2024-08-15 10:40:44 -04:00
Mary Hipp
dcd61e1f82 pin ruff version in python check gha 2024-08-15 09:47:49 -04:00
Mary Hipp
f5c99b1488 exclude jupyter notebooks from ruff 2024-08-15 09:47:49 -04:00
Mary Hipp
810be3e1d4 update import directions to include JSON 2024-08-15 09:47:49 -04:00
psychedelicious
60d754d1df feat(api): tidy style presets import logic
- Extract parsing into utility function
- Log import errors
- Forbid extra properties on the imported data
2024-08-15 09:47:49 -04:00
psychedelicious
bd07c86db9 feat(ui): make style preset menu trigger look like button 2024-08-15 09:47:49 -04:00
psychedelicious
bcbf8b6bd8 feat(ui): revert to using {prompt} for prompt template placeholder 2024-08-15 09:47:49 -04:00
psychedelicious
356661459b feat(api): support JSON for preset imports
This allows us to support Fooocus format presets.
2024-08-15 09:47:49 -04:00
psychedelicious
deb917825e feat(api): use pydantic validation during style preset import
- Enforce name is present and not an empty string
- Provide empty string as default for positive and negative prompt
- Add `positive_prompt` as validation alias for `prompt` field
- Strip whitespace automatically
- Create `TypeAdapter` to validate the whole list in one go
2024-08-15 09:47:49 -04:00
psychedelicious
15415c6d85 feat(ui): use dropzone for style preset upload
Easier to accept multiple file types and supper drag and drop in the future.
2024-08-15 09:47:49 -04:00
Mary Hipp
76b0380b5f feat(ui): create component to upload CSV of style presets to import 2024-08-15 09:47:49 -04:00
Mary Hipp
2d58754789 feat(api): add endpoint to take a CSV, parse it, validate it, and create many style preset entries 2024-08-15 09:47:49 -04:00
chainchompa
9cdf1f599c Merge branch 'main' into chainchompa/preselect-workflows 2024-08-15 09:25:19 -04:00
chainchompa
268be97ba0 remove ref, make options optional for useGetLoadWorkflow 2024-08-15 09:18:41 -04:00
Mary Hipp
a9014673a0 wip export 2024-08-15 09:00:11 -04:00
psychedelicious
d36c43a10f ui: translations update from weblate (#6727)
Translations update from [Hosted Weblate](https://hosted.weblate.org)
for [InvokeAI/Web
UI](https://hosted.weblate.org/projects/invokeai/web-ui/).



Current translation status:

![Weblate translation
status](https://hosted.weblate.org/widget/invokeai/web-ui/horizontal-auto.svg)
2024-08-15 08:48:03 +10:00
Phrixus2023
54a5c4e482 translationBot(ui): update translation (Chinese (Simplified))
Currently translated at 98.1% (1296 of 1320 strings)

Co-authored-by: Phrixus2023 <920414016@qq.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/zh_Hans/
Translation: InvokeAI/Web UI
2024-08-15 00:46:01 +02:00
Riccardo Giovanetti
5e09a244e3 translationBot(ui): update translation (Italian)
Currently translated at 98.5% (1336 of 1355 strings)

translationBot(ui): update translation (Italian)

Currently translated at 98.5% (1302 of 1321 strings)

translationBot(ui): update translation (Italian)

Currently translated at 98.6% (1302 of 1320 strings)

Co-authored-by: Riccardo Giovanetti <riccardo.giovanetti@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/it/
Translation: InvokeAI/Web UI
2024-08-15 00:46:01 +02:00
chainchompa
88648dca1a change selectedWorkflow to selectedWorkflowId 2024-08-14 11:22:37 -04:00
chainchompa
8840df2b00 Merge branch 'main' into chainchompa/preselect-workflows 2024-08-14 09:02:12 -04:00
chainchompa
af159acbdf cleanup 2024-08-14 08:58:38 -04:00
chainchompa
471719bbbe add base prop for selectedWorkflow to allow loading a workflow on launch 2024-08-14 08:47:02 -04:00
psychedelicious
b126f2ffd5 feat(ui, api): prompt templates (#6729)
## Summary

Adds prompt templates to the UI. Demo video is attached.
* added default prompt templates to seed database on startup (these
cannot be edited or deleted by users via the UI)
* can create fresh prompt template, create from an image in gallery that
has prompt metadata, or copy an existing prompt template and modify
* if a template is active, can view what your prompt will be invoked as
by switching to "view mode"



https://github.com/user-attachments/assets/32d84e0c-b04c-48da-bae5-aa6eb685d209



## Related Issues / Discussions

<!--WHEN APPLICABLE: List any related issues or discussions on github or
discord. If this PR closes an issue, please use the "Closes #1234"
format, so that the issue will be automatically closed when the PR
merges.-->

## QA Instructions

<!--WHEN APPLICABLE: Describe how you have tested the changes in this
PR. Provide enough detail that a reviewer can reproduce your tests.-->

## Merge Plan

<!--WHEN APPLICABLE: Large PRs, or PRs that touch sensitive things like
DB schemas, may need some care when merging. For example, a careful
rebase by the change author, timing to not interfere with a pending
release, or a message to contributors on discord after merging.-->

## Checklist

- [ ] _The PR has a short but descriptive title, suitable for a
changelog_
- [ ] _Tests added / updated (if applicable)_
- [ ] _Documentation added / updated (if applicable)_
2024-08-14 12:49:31 +10:00
psychedelicious
9938f12ef0 Merge branch 'main' into maryhipp/style-presets 2024-08-14 12:33:30 +10:00
psychedelicious
982c266073 tidy: remove extra characters in prompt templates 2024-08-14 12:31:57 +10:00
psychedelicious
5c37391883 fix(ui): do not show [prompt] in preset preview 2024-08-14 12:29:05 +10:00
psychedelicious
ddeafc6833 fix(ui): minimize layout shift when overlaying preset prompt preview 2024-08-14 12:24:57 +10:00
psychedelicious
41b2d5d013 fix(ui): prompt preview not working preset starts with [prompt] 2024-08-14 12:21:38 +10:00
psychedelicious
29d6f48901 fix(ui): prompt shows thru prompt label text 2024-08-14 12:01:49 +10:00
psychedelicious
d5c9f4e47f chore(ui): revert framer-motion upgrade
`framer-motion` 11 breaks a lot of stuff in profoundly unintuitive ways, holy crap. UI lib rolled back its dep, pulling in latest version of that
2024-08-14 06:12:00 +10:00
psychedelicious
24d73387d8 build(ui): fix chakra deps
We had multiple versions of @emotion/react, stemming from an extraneous dependency on @chakra-ui/react. Removed the extraneosu dep
2024-08-14 06:12:00 +10:00
Mary Hipp
e0d3927265 feat: add flag for allowPrivateStylePresets that shows a type field when creating a style preset 2024-08-13 14:08:54 -04:00
Mary Hipp
e5f7c2a9b7 add type safety / validation to form data payloads and allow type to be passed through api 2024-08-13 13:00:31 -04:00
Mary Hipp
b0760710d5 add the rest of default style presets, update image service to return default images correctly by name, add tooltip popover to images in UI 2024-08-13 11:33:15 -04:00
Mary Hipp
764accc921 update config docstring 2024-08-12 15:17:40 -04:00
Mary Hipp
6a01fce9c1 fix payloads for stringified data 2024-08-12 15:16:22 -04:00
Mary Hipp
9c732ac3b1 Merge remote-tracking branch 'origin/main' into maryhipp/style-presets 2024-08-12 14:53:45 -04:00
Mary Hipp
b70891c661 update descriptoin of placeholder in modal 2024-08-12 13:37:04 -04:00
Mary Hipp
4dbf851741 ui: add labels to prompt boxes 2024-08-12 13:33:39 -04:00
Mary Hipp
6c927a9fd4 move mdoal state into nanostore 2024-08-12 12:46:02 -04:00
Mary Hipp
096f001634 ui: add ability to copy template 2024-08-12 12:32:31 -04:00
Mary Hipp
4837e578b2 api: update dir path for style preset images, update payload for create/update formdata 2024-08-12 12:00:14 -04:00
Mary Hipp
1e547ef912 UI more pr feedback 2024-08-12 11:59:25 -04:00
psychedelicious
f6b8970bd1 fix(app): create reference to events task to prevent accidental GC
This wasn't a problem, but it's advised in the official docs so I've done it.
2024-08-12 07:49:58 +10:00
psychedelicious
29325a7214 fix(app): use asyncio queue and existing event loop for events
Around the time we (I) implemented pydantic events, I noticed a short pause between progress images every 4 or 5 steps when generating with SDXL. It didn't happen with SD1.5, but I did notice that with SD1.5, we'd get 4 or 5 progress events simultaneously. I'd expect one event every ~25ms, matching my it/s with SD1.5. Mysterious!

Digging in, I found an issue is related to our use of a synchronous queue for events. When the event queue is empty, we must call `asyncio.sleep` before checking again. We were sleeping for 100ms.

Said another way, every time we clear the event queue, we have to wait 100ms before another event can be dispatched, even if it is put on the queue immediately after we start waiting. In practice, this means our events get buffered into batches, dispatched once every 100ms.

This explains why I was getting batches of 4 or 5 SD1.5 progress events at once, but not the intermittent SDXL delay.

But this 100ms wait has another effect when the events are put on the queue in intervals that don't perfectly line up with the 100ms wait. This is most noticeable when the time between events is >100ms, and can add up to 100ms delay before the event is dispatched.

For example, say the queue is empty and we start a 100ms wait. Then, immediately after - like 0.01ms later - we push an event on to the queue. We still need to wait another 99.9ms before that event will be dispatched. That's the SDXL delay.

The easy fix is to reduce the sleep to something like 0.01 seconds, but this feels kinda dirty. Can't we just wait on the queue and dispatch every event immediately? Not with the normal synchronous queue - but we can with `asyncio.Queue`.

I switched the events queue to use `asyncio.Queue` (as seen in this commit), which lets us asynchronous wait on the queue in a loop.

Unfortunately, I ran into another issue - events now felt like their timing was inconsistent, but in a different way than with the 100ms sleep. The time between pushing events on the queue and dispatching them was not consistently ~0ms as I'd expect - it was highly variable from ~0ms up to ~100ms.

This is resolved by passing the asyncio loop directly into the events service and using its methods to create the task and interact with the queue. I don't fully understand why this resolved the issue, because either way we are interacting with the same event loop (as shown by `asyncio.get_running_loop()`). I suppose there's some scheduling magic happening.
2024-08-12 07:49:58 +10:00
psychedelicious
8ecf72838d fix(api): image downloads with correct filename
Closes #6730
2024-08-10 09:53:56 -04:00
psychedelicious
c3ab8a6aa8 chore(ui): bump rest of deps 2024-08-10 07:45:23 -04:00
psychedelicious
1931aa3e70 chore(ui): typegen 2024-08-10 07:45:23 -04:00
psychedelicious
d3d8055055 feat(ui): update typegen script 2024-08-10 07:45:23 -04:00
psychedelicious
476b0a0403 chore(ui): bump openapi-typescript 2024-08-10 07:45:23 -04:00
psychedelicious
f66584713c fix(api): sort OpenAPI schema properties for InvocationOutputMap
This makes the schema output deterministic!
2024-08-10 07:45:23 -04:00
psychedelicious
33624fc2fa fix(api): duplicate operation id for get_image_full
There's a FastAPI bug that results in the OpenAPI spec outputting the same operation id for each operation when specifying multiple HTTP methods.

- Discussion: https://github.com/tiangolo/fastapi/discussions/8449
- Pending PR to fix: https://github.com/tiangolo/fastapi/pull/10694

In our case, we have a `get_image_full` endpoint that handles GET and HEAD.

This results in an invalid OpenAPI schema. A workaround is to use two route decorators for the operation handler. This works as expected - HEAD requests get the header, and GET requests get the resource. And the OpenAPI schema is valid.
2024-08-10 07:45:23 -04:00
Mary Hipp
41c3e73a3c fix tests 2024-08-09 16:31:42 -04:00
Mary Hipp
97553a7de2 API/DB updates per PR feedback 2024-08-09 16:27:37 -04:00
Mary Hipp
12ba15bfa9 UI updates per PR feedback 2024-08-09 16:00:13 -04:00
Mary Hipp
09d1e190e7 show warning for maxUpscaleDimension if model tab is disabled 2024-08-09 14:07:55 -04:00
Mary Hipp
8eb5d08499 missed translation 2024-08-08 16:01:16 -04:00
Mary Hipp
9be6acde7d require name to submit style preset 2024-08-08 15:53:21 -04:00
Mary Hipp
5f83bb0069 update config docstring 2024-08-08 15:20:43 -04:00
Mary Hipp
b138882abc fix tests? 2024-08-08 15:18:32 -04:00
Mary Hipp
0cd7cdb52e remove send2trash 2024-08-08 15:13:36 -04:00
Mary Hipp
1d8b7e2bcf ruff 2024-08-08 15:08:45 -04:00
Mary Hipp
6461f4758d lint fix 2024-08-08 15:07:58 -04:00
Mary Hipp
3189ab6863 get dynamic prompts working 2024-08-08 15:07:23 -04:00
Mary Hipp
3f9a674d4b seed default presets and handle them in UI 2024-08-08 15:02:41 -04:00
Mary Hipp
587f59b25b focus on prompt textarea when exiting view mode by clicking 2024-08-08 14:38:50 -04:00
Mary Hipp
4952eada87 ruff format 2024-08-08 14:22:40 -04:00
Mary Hipp
581029ebaa ruff 2024-08-08 14:21:37 -04:00
Mary Hipp
42d68780de lint 2024-08-08 14:19:33 -04:00
Mary Hipp
28032a2f80 more cleanup 2024-08-08 14:18:05 -04:00
Mary Hipp
e381e021e9 knip lint 2024-08-08 14:00:17 -04:00
Mary Hipp
641af64f93 regnerate schema 2024-08-08 13:58:25 -04:00
Mary Hipp
a7b83c8b5b Merge remote-tracking branch 'origin/main' into maryhipp/style-presets 2024-08-08 13:56:59 -04:00
Mary Hipp
4cc41e0188 translations and lint fix 2024-08-08 13:56:37 -04:00
Mary Hipp
442fc02429 resize images to 100x100 for style preset images 2024-08-08 12:56:55 -04:00
Mary Hipp
9a4d075074 fix path for style_preset_images, fix png type when converting blobs to files, built view mode components 2024-08-08 12:31:20 -04:00
Sergey Borisov
17ff8196cb Remove tmp code 2024-08-07 22:06:05 -04:00
Sergey Borisov
68f993998a Add support for norm layer 2024-08-07 22:06:05 -04:00
Sergey Borisov
7da6120b39 Fix LoKR refactor bug 2024-08-07 22:06:05 -04:00
blessedcoolant
6cd40965c4 Depth Anything V2 (#6674)
- Updated the previous DepthAnything manual implementation to use the
`transformers` implementation instead. So we can get upstream features.
- Plugged in the DepthAnything models to be handled by Invoke's Model
Manager.
- `small_v2` model will use DepthAnythingV2. This has been added as a
new model option and is now also the default in the Linear UI.


![opera_TxRhmbFole](https://github.com/user-attachments/assets/2a25abe3-ba0b-4f97-b75a-2ce5fd6246e6)


# Merge

Review and merge.
2024-08-07 20:26:58 +05:30
Kent Keirsey
408a1d6dbb Merge branch 'main' into depth_anything_v2 2024-08-07 10:45:56 -04:00
Mary Hipp
0b0abfbe8f clean up image implementation 2024-08-07 10:36:38 -04:00
Mary Hipp
cc96dcf0ed style preset images 2024-08-07 09:58:27 -04:00
Mary Hipp
2604fd9fde a whole bunch of stuff 2024-08-06 15:31:13 -04:00
Hosted Weblate
140670d00e translationBot(ui): update translation files
Updated by "Cleanup translation files" hook in Weblate.

translationBot(ui): update translation files

Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/
Translation: InvokeAI/Web UI
2024-08-06 17:54:47 +10:00
Phrixus2023
70233fae5d translationBot(ui): update translation (Chinese (Simplified))
Currently translated at 98.1% (1296 of 1321 strings)

Co-authored-by: Phrixus2023 <920414016@qq.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/zh_Hans/
Translation: InvokeAI/Web UI
2024-08-06 17:54:47 +10:00
Alexander Eichhorn
6f457a6c4c translationBot(ui): update translation (German)
Currently translated at 65.1% (860 of 1321 strings)

Co-authored-by: Alexander Eichhorn <pfannkuchensack@einfach-doof.de>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/de/
Translation: InvokeAI/Web UI
2024-08-06 17:54:47 +10:00
B N
5c319f5356 translationBot(ui): update translation (German)
Currently translated at 64.8% (857 of 1321 strings)

Co-authored-by: B N <berndnieschalk@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/de/
Translation: InvokeAI/Web UI
2024-08-06 17:54:47 +10:00
Riccardo Giovanetti
991a04f090 translationBot(ui): update translation (Italian)
Currently translated at 98.6% (1303 of 1321 strings)

translationBot(ui): update translation (Italian)

Currently translated at 98.6% (1302 of 1320 strings)

translationBot(ui): update translation (Italian)

Currently translated at 98.6% (1294 of 1312 strings)

Co-authored-by: Riccardo Giovanetti <riccardo.giovanetti@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/it/
Translation: InvokeAI/Web UI
2024-08-06 17:54:47 +10:00
psychedelicious
c39fa75113 docs(ui): add comment in useIsTooLargeToUpscale 2024-08-06 11:49:35 +10:00
psychedelicious
f7863e17ce docs(ui): add docstring for maxUpscaleDimension 2024-08-06 11:49:35 +10:00
psychedelicious
7c526390ed fix(ui): compare upscaledPixels vs square of max dimension 2024-08-06 11:49:35 +10:00
Mary Hipp
2cff20f87a update translations, change config value to be dimension instead of total pixels 2024-08-06 11:49:35 +10:00
Mary Hipp
90ec757802 lint 2024-08-06 11:49:35 +10:00
Mary Hipp
4b85dfcefe (ui): restore optioanl limit on upcsale output resolution 2024-08-06 11:49:35 +10:00
Mary Hipp
21deefdc41 (ui): add image resolution badge to initial upscale image 2024-08-06 11:49:35 +10:00
Mary Hipp
857d74bbfe wip apply and calculate prompt with interpolation 2024-08-05 19:11:48 -04:00
Mary Hipp
fd7a635777 (ui) the most basic crud ui: view list of presets, create a new preset, edit/delete existing presets 2024-08-05 15:48:23 -04:00
Mary Hipp
af9110e964 fix prompt concat logic 2024-08-05 13:42:28 -04:00
Mary Hipp
a61209206b remove custom SDXL prompts component 2024-08-05 13:40:46 -04:00
Mary Hipp
e05cc62e5f add style presets API layer to UI 2024-08-05 13:37:07 -04:00
psychedelicious
4d4f921a4e build: exclude matplotlib 3.9.1
There was a problem w/ this release on windows and the builds were pulled from pypi. When installing invoke on windows, pip attempts to build from source, but most (all?) systems won't have the prerequisites for this and installs fail.

This also affects GH actions.

The simple fix is to exclude version 3.9.1 from our deps.

For more information, see https://github.com/matplotlib/matplotlib/issues/28551
2024-08-05 08:38:44 +10:00
psychedelicious
98db8f395b feat(app): clean up DiskImageStorage types 2024-08-04 09:43:20 +10:00
psychedelicious
f465a956a3 feat(ui): remove "images can be restored" messages 2024-08-04 09:43:20 +10:00
psychedelicious
9edb02d7ef build: remove send2trash dependency 2024-08-04 09:43:20 +10:00
psychedelicious
6c4cf58a31 feat(app): delete model_images instead of using send2trash 2024-08-04 09:43:20 +10:00
psychedelicious
08993c0d29 feat(app): delete images instead of using send2trash
Closes #6709
2024-08-04 09:43:20 +10:00
blessedcoolant
4f8a4b0f22 Merge branch 'main' into depth_anything_v2 2024-08-03 00:38:57 +05:30
blessedcoolant
a743f3c9b5 fix: implement model to func for depth anything 2024-08-03 00:37:17 +05:30
Mary Hipp
217fe40d99 feat(api): add style_presets router, make sure all CRUD is working, add is_default 2024-08-02 12:29:54 -04:00
Mary Hipp
b76bf50b93 feat(db,api): create new table for style presets, build out record storage service for style presets 2024-08-01 22:20:11 -04:00
Mary Hipp
571ba87e13 fix(ui): include upscale metadata for SDXL multidiffusion 2024-08-01 21:30:42 -04:00
Ryan Dick
f27b6e2b44 Add Grounded SAM support (text prompt image segmentation) (#6701)
## Summary

This PR enables Grounded SAM workflows
(https://arxiv.org/pdf/2401.14159) via the following:
- `GroundingDinoInvocation` for running a Grounding DINO model.
- `SegmentAnythingModelInvocation` for running a SAM model.
- `MaskTensorToImageInvocation` for convenient visualization.

Other notes:
- Uses the transformers implementation of Grounding DINO and SAM.
- The new models are treated as 'utility models' meaning that they are
not visible in the Models tab, and are downloaded automatically the
first time that they are used.

<img width="874" alt="image"
src="https://github.com/user-attachments/assets/1cbaa97d-0e27-4943-86b1-dc7327ba8675">

## Example

Input image

![be10ec0c-20a8-4ac7-840e-d1a05fffdb6a](https://github.com/user-attachments/assets/bf21572c-635d-4703-b4ab-7aba658a9671)

Prompt: "wheels", all other configs default
Result:

![2221c44e-64e6-4b18-b4cb-610514b7a554](https://github.com/user-attachments/assets/344b91f4-7f4a-4b70-8e2e-3b4a0e55176d)

## Related Issues / Discussions

Thanks to @blessedcoolant for the initial draft here:
https://github.com/invoke-ai/InvokeAI/pull/6678

## QA Instructions

Manual tests:
- [ ] Test that default settings work well.
- [ ] Test with / without apply_polygon_refinement
- [ ] Test mask_filter options
- [ ] Test detection_threshold values
- [ ] Test RGB input image
- [ ] Test RGBA input image
- [ ] Test grayscale input image
- [ ] Smoke test that an empty mask is returned when 0 objects are
detected
- [ ] Test on CPU
- [ ] Test on MPS (Works on Mac OS, but had to force both models to run
on CPU instead of MPS)

Performance:
- Peak GPU memory utilization with both Grounding DINO and SAM models
loaded is ~4.5GB. (The models do not need to be loaded at the same time,
so could be offloaded by the MM if needed.)
- On an RTX4090, with the models already cached, node execution takes
~0.6 secs.
- On my CPU, with the models cached, node execution takes ~10secs.

## Merge Plan

No special instructions.

## Checklist

- [x] _The PR has a short but descriptive title, suitable for a
changelog_
- [ ] _Tests added / updated (if applicable)_
- [x] _Documentation added / updated (if applicable)_
2024-08-01 20:40:18 +02:00
Ryan Dick
981475a624 Merge branch 'main' into ryan/grounded-sam 2024-08-01 20:30:35 +02:00
Ryan Dick
27ac61a4fb Expose all model options in the GroundingDinoInvocation and the SegmentAnythingInvocation. 2024-08-01 14:23:32 -04:00
Ryan Dick
675ffc2757 Remove BoundingBoxInvocation field name overrides. 2024-08-01 14:05:44 -04:00
Ryan Dick
44b21f10f1 Add a pydantic model_validator to BoundingBoxField to check the validity of the coords. 2024-08-01 14:00:57 -04:00
Ryan Dick
c6d49e8b1f Shorten SegmentAnythingInvocation and GroundingDinoInvocatino docstrings, since they are used as the invocation descriptions in the UI. 2024-08-01 10:17:42 -04:00
Ryan Dick
e6a512aa86 (minor) Tweak order of mask operations. 2024-08-01 10:12:24 -04:00
Ryan Dick
c3a6a6fb22 Rename SegmentAnythingModelInvocation -> SegmentAnythingInvocation. 2024-08-01 10:00:36 -04:00
Ryan Dick
b9dc3460ba Rename SegmentAnythingModel -> SegmentAnythingPipeline. 2024-08-01 09:57:47 -04:00
Ryan Dick
63581ec980 (minor) Add None check to fix static type checking error. 2024-08-01 09:51:53 -04:00
chainchompa
08b1feeed7 add base prop for destination to direct users to different tabs on initial load (#6706)
## Summary
- we want a way to load the studio while being directed to a specific
tab, introduced a destination prop to achieve that
<!--A description of the changes in this PR. Include the kind of change
(fix, feature, docs, etc), the "why" and the "how". Screenshots or
videos are useful for frontend changes.-->

## Related Issues / Discussions

<!--WHEN APPLICABLE: List any related issues or discussions on github or
discord. If this PR closes an issue, please use the "Closes #1234"
format, so that the issue will be automatically closed when the PR
merges.-->

## QA Instructions

<!--WHEN APPLICABLE: Describe how you have tested the changes in this
PR. Provide enough detail that a reviewer can reproduce your tests.-->

## Merge Plan

<!--WHEN APPLICABLE: Large PRs, or PRs that touch sensitive things like
DB schemas, may need some care when merging. For example, a careful
rebase by the change author, timing to not interfere with a pending
release, or a message to contributors on discord after merging.-->

## Checklist

- [ ] _The PR has a short but descriptive title, suitable for a
changelog_
- [ ] _Tests added / updated (if applicable)_
- [ ] _Documentation added / updated (if applicable)_
2024-07-31 19:25:36 -04:00
blessedcoolant
f5cfdcf32d feat: Add BoundingBox Primitive Node 2024-08-01 04:09:08 +05:30
chainchompa
e78fb428f0 simplify destination prop handling 2024-07-31 18:06:22 -04:00
chainchompa
31e270e32c add base prop for destination to direct users to different tabs 2024-07-31 17:20:51 -04:00
Ryan Dick
b5832768dc Return a MaskOutput from SegmentAnythingModelInvocation. And add a MaskTensorToImageInvocation. 2024-07-31 17:16:14 -04:00
Ryan Dick
4ce64b69cb Modular backend - LoRA/LyCORIS (#6667)
## Summary

Code for lora patching from #6577.
Additionally made it the way, that lora can patch not only `weight`, but
also `bias`, because saw some loras which doing it.

## Related Issues / Discussions

#6606 

https://invokeai.notion.site/Modular-Stable-Diffusion-Backend-Design-Document-e8952daab5d5472faecdc4a72d377b0d

## QA Instructions

Run with and without set `USE_MODULAR_DENOISE` environment.

## Merge Plan

Replace old lora patcher with new after review done.
If you think that there should be some kind of tests - feel free to add.

## Checklist

- [x] _The PR has a short but descriptive title, suitable for a
changelog_
- [ ] _Tests added / updated (if applicable)_
- [ ] _Documentation added / updated (if applicable)_
2024-07-31 21:31:31 +02:00
Ryan Dick
5a9173f766 Merge branch 'main' into stalker-modular_lora 2024-07-31 15:13:22 -04:00
Ryan Dick
0bb7ed44f6 Add some docs to OriginalWeightsStorage and fix type hints. 2024-07-31 15:08:24 -04:00
blessedcoolant
332bc9da5b fix: Update depth anything node default to v2 2024-07-31 23:52:29 +05:30
blessedcoolant
08def3da95 fix: Update canvas depth anything processor default to v2 2024-07-31 23:50:13 +05:30
blessedcoolant
daf899f9c4 fix: Move the manual image resizing out of the depth anything pipeline 2024-07-31 23:38:12 +05:30
blessedcoolant
13fb2d1f49 fix: Add Depth Anything V2 as a new option
It is also now the default in the UI replacing Depth Anything V1 small
2024-07-31 23:29:43 +05:30
blessedcoolant
95dde802ea fix: assert the return depth map to be a PIL image 2024-07-31 23:22:01 +05:30
Ryan Dick
fca119773b Split invokeai/backend/image_util/segment_anything/ dir into grounding_dino/ and segment_anything/ 2024-07-31 12:28:47 -04:00
Ryan Dick
0193267a53 Split GroundedSamInvocation into GroundingDinoInvocation and SegmentAnythingModelInvocation. 2024-07-31 12:20:23 -04:00
blessedcoolant
b4cf78a95d fix: make DA Pipeline a subclass of RawModel 2024-07-31 21:14:49 +05:30
Ryan Dick
73386826d6 Make GroundingDinoPipeline and SegmentAnythingModel subclasses of RawModel for type checking purposes. 2024-07-31 10:25:34 -04:00
Ryan Dick
9f448fecb7 Move invokeai/backend/grounded_sam -> invokeai/backend/image_util/grounded_sam 2024-07-31 10:00:30 -04:00
Ryan Dick
bcd1483a14 Re-order GroundedSAMInvocation._to_numpy_masks(...) to do slightly more work on the GPU. 2024-07-31 09:51:14 -04:00
Ryan Dick
e206890e25 Use staticmethods rather than inner functions for the Grounding DINO and SAM model loaders. 2024-07-31 09:28:52 -04:00
Ryan Dick
0a7048f650 (minor) Simplify GroundedSAMInvocation._merge_masks(...). 2024-07-31 08:58:51 -04:00
Ryan Dick
e8ecf5e155 (minor) Move apply_polygon_refinement condition up a layer. 2024-07-31 08:50:56 -04:00
Ryan Dick
33e8604b57 Make Grounding DINO DetectionResult a Pydantic model. 2024-07-31 08:47:00 -04:00
Ryan Dick
cec7399366 (minor) Use a new variable name to satisfy type checks. 2024-07-31 08:27:01 -04:00
Ryan Dick
bdae81e429 (minor) Simplify GroundedSAMInvocation._filter_detections() 2024-07-31 08:25:19 -04:00
Ryan Dick
67c32f3d6c Fix typo: zip(..., strict=True) 2024-07-31 08:15:28 -04:00
blessedcoolant
94d64b8a78 Fix gradient mask values range (#6688)
## Summary

Gradient mask node outputs mask tensor with values in range [-1, 1],
which unexpected range for mask.
It handled in denoise node the way it translates to [0, 2] mask, which
looks even more wrongly)
From discussion with @dunkeroni I understand him as he thought that
negative values will be treated same as 0, so clamping values not change
intended node logic.

## Related Issues / Discussions

#6643 

## QA Instructions

\-

## Merge Plan

\-

## Checklist

- [x] _The PR has a short but descriptive title, suitable for a
changelog_
- [ ] _Tests added / updated (if applicable)_
- [ ] _Documentation added / updated (if applicable)_
2024-07-31 06:37:32 +05:30
blessedcoolant
fa3c0c81b3 Merge branch 'main' into stalker7779/fix_gradient_mask 2024-07-31 06:30:44 +05:30
blessedcoolant
66547b99c1 Add more karras schedulers (#6695)
## Summary

Add karras variants of `deis`, `unipc`, `kdpm2` and `kdpm_2_a`
schedulers.
Also added `dpmpp_3` schedulers, but `dpmpp_3s` currently bugged, so
added only 3m:
https://github.com/huggingface/diffusers/issues/9007

## Related Issues / Discussions

\-

## QA Instructions

\-

## Merge Plan

~@psychedelicious We need to decide what to do with schedulers order, as
it looks a bit broken:~

![image](https://github.com/user-attachments/assets/e41674af-d87c-4432-8014-c90bd86965a6)

## Checklist

- [x] _The PR has a short but descriptive title, suitable for a
changelog_
- [ ] _Tests added / updated (if applicable)_
- [ ] _Documentation added / updated (if applicable)_
2024-07-31 06:09:26 +05:30
blessedcoolant
328e58be4c Merge branch 'main' into stalker7779/new_karras_schedulers 2024-07-31 05:56:13 +05:30
blessedcoolant
18f89ed5ed fix: Make DepthAnything work with Invoke's Model Management 2024-07-31 03:57:54 +05:30
Ryan Dick
5701c79fab Prevent Grounding DINO and Segment Anything from being moved to MPS - they don't work on MPS devices. 2024-07-30 23:04:15 +02:00
Ryan Dick
2da9f913f3 Add detection_result.py - was forgotten in a prior commit 2024-07-30 16:04:29 -04:00
Ryan Dick
6b10b59abe Make GroundedSAMInvocation work with any input image mode (RGB, RGBA, grayscale). 2024-07-30 15:55:57 -04:00
Ryan Dick
918f77bce0 Move some logic from GroundedSAMInvocation to the backend classes. 2024-07-30 15:34:33 -04:00
blessedcoolant
f170697ebe Merge branch 'main' into depth_anything_v2 2024-07-31 00:53:32 +05:30
blessedcoolant
556c6a1d84 fix: Update DepthAnything to use the transformers implementation 2024-07-31 00:51:55 +05:30
Ryan Dick
aca2a2fa13 Add mask_filter and detection_threshold options to the GroundedSAMInvocation. 2024-07-30 14:22:40 -04:00
Ryan Dick
ff6398f7d8 Add a GroundedSamInvocation for image segmentation from a text prompt (Grounding DINO + Segment Anything Model). 2024-07-30 11:12:26 -04:00
Sergey Borisov
cf996472b9 Suggested changes
Co-Authored-By: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
2024-07-30 04:50:56 +03:00
Sergey Borisov
156d14c349 Run api regen 2024-07-30 04:05:21 +03:00
Sergey Borisov
86f705bf48 Optimize weights handling 2024-07-30 03:39:01 +03:00
Sergey Borisov
1fd9631f2d Comments fix
Co-Authored-By: Ryan Dick <14897797+RyanJDick@users.noreply.github.com>
2024-07-30 00:39:50 +03:00
Sergey Borisov
2227a2357f Suggested changes + simplify weights logic in patching
Co-Authored-By: Ryan Dick <14897797+RyanJDick@users.noreply.github.com>
2024-07-30 00:34:37 +03:00
Sergey Borisov
58e7ab157d Ruff format 2024-07-29 22:59:17 +03:00
Sergey Borisov
8d16fa6a49 Remove dpmpp_3s schedulers as it bugged now 2024-07-29 22:55:45 +03:00
Sergey Borisov
55e810efa3 Add dpmpp_3 schedulers 2024-07-29 22:52:15 +03:00
chainchompa
2755316021 update delete board modal to be more descriptive (#6690)
## Summary

<!--A description of the changes in this PR. Include the kind of change
(fix, feature, docs, etc), the "why" and the "how". Screenshots or
videos are useful for frontend changes.-->

## Related Issues / Discussions

<!--WHEN APPLICABLE: List any related issues or discussions on github or
discord. If this PR closes an issue, please use the "Closes #1234"
format, so that the issue will be automatically closed when the PR
merges.-->

## QA Instructions

<!--WHEN APPLICABLE: Describe how you have tested the changes in this
PR. Provide enough detail that a reviewer can reproduce your tests.-->

## Merge Plan

<!--WHEN APPLICABLE: Large PRs, or PRs that touch sensitive things like
DB schemas, may need some care when merging. For example, a careful
rebase by the change author, timing to not interfere with a pending
release, or a message to contributors on discord after merging.-->

## Checklist

- [ ] _The PR has a short but descriptive title, suitable for a
changelog_
- [ ] _Tests added / updated (if applicable)_
- [ ] _Documentation added / updated (if applicable)_
2024-07-29 13:43:17 -04:00
chainchompa
6525f18610 Merge branch 'main' into chainchompa/board-delete-info 2024-07-29 12:52:36 -04:00
Ryan Dick
2ad13ac7eb Modular backend - inpaint (#6643)
## Summary

Code for inpainting and inpaint models handling from
https://github.com/invoke-ai/InvokeAI/pull/6577.
Separated in 2 extensions as discussed briefly before, so wait for
discussion about such implementation.

## Related Issues / Discussions

#6606

https://invokeai.notion.site/Modular-Stable-Diffusion-Backend-Design-Document-e8952daab5d5472faecdc4a72d377b0d

## QA Instructions

Run with and without set `USE_MODULAR_DENOISE` environment.
Try and compare outputs between backends in cases:
- Normal generation on inpaint model
- Inpainting on inpaint model
- Inpainting on normal model

## Merge Plan

Nope.
If you think that there should be some kind of tests - feel free to add.

## Checklist

- [x] _The PR has a short but descriptive title, suitable for a
changelog_
- [ ] _Tests added / updated (if applicable)_
- [ ] _Documentation added / updated (if applicable)_
2024-07-29 10:27:25 -04:00
Ryan Dick
693a3eaff5 Merge branch 'main' into stalker-modular_inpaint-2 2024-07-29 10:14:45 -04:00
chainchompa
ffca792d5b edited copy for deleted boards message 2024-07-29 09:46:08 -04:00
Sergey Borisov
86a92bb6b5 Add more karras schedulers 2024-07-29 15:14:34 +03:00
psychedelicious
171a4e6d80 fix(ui): race condition when deleting a board and resetting selected/auto-add
We were checking the selected and auto-add board ids against the query cache to see if they still exist. If not, we reset.

This only works if the query cache is updated by the time we do the check - race condition!

We already have the board id from the query args, so there's no need to check the query cache - just compare the deleted board ID directly.

Previously this file's several listeners were all in a single one and I had adapted/split its logic up a bit wonkily, introducing these problems.
2024-07-29 11:36:03 +10:00
psychedelicious
e3a75a8adf fix(ui): fix logic to reset selected/auto-add boards when toggling show archived boards
The logic was incorrect in two ways:
1. We only ran the logic if we _enable_ showing archived boards. It should be run we we _disable_ showing archived boards.
2. If we couldn't find the selected board in the query cache, we didn't do the reset. This is wrong - if the board isn't in the query cache, we _should_ do the reset. This inverted logic makes more sense before the fix for issue 1.
2024-07-29 11:36:03 +10:00
Ryan Dick
ee7503ce13 Modular backend - T2I Adapter (#6662)
## Summary

T2I Adapter code from #6577.

## Related Issues / Discussions

#6606 

https://invokeai.notion.site/Modular-Stable-Diffusion-Backend-Design-Document-e8952daab5d5472faecdc4a72d377b0d

## QA Instructions

Run with and without set `USE_MODULAR_DENOISE` environment.

## Merge Plan

Nope.
If you think that there should be some kind of tests - feel free to add.

## Checklist

- [x] _The PR has a short but descriptive title, suitable for a
changelog_
- [ ] _Tests added / updated (if applicable)_
- [ ] _Documentation added / updated (if applicable)_
2024-07-28 15:52:04 -04:00
Sergey Borisov
8500bac3ca Use logger for warning 2024-07-28 22:51:52 +03:00
Ryan Dick
310719eb4c Merge branch 'main' into stalker-modular_t2i_adapter 2024-07-28 15:30:00 -04:00
Ryan Dick
e8e24822ec Modular backend - Seamless (#6651)
## Summary

Seamless code from #6577.

## Related Issues / Discussions

#6606 

https://invokeai.notion.site/Modular-Stable-Diffusion-Backend-Design-Document-e8952daab5d5472faecdc4a72d377b0d

## QA Instructions

Run with and without set `USE_MODULAR_DENOISE` environment.

## Merge Plan

Nope.
If you think that there should be some kind of tests - feel free to add.

## Checklist

- [x] _The PR has a short but descriptive title, suitable for a
changelog_
- [ ] _Tests added / updated (if applicable)_
- [ ] _Documentation added / updated (if applicable)_
2024-07-28 13:57:38 -04:00
Ryan Dick
c57a7afb87 Merge branch 'main' into stalker7779/modular_seamless 2024-07-28 13:49:43 -04:00
Sergey Borisov
84d028898c Revert wrong comment copy 2024-07-27 13:20:58 +03:00
Sergey Borisov
ed0174fbc6 Suggested changes
Co-Authored-By: Ryan Dick <14897797+RyanJDick@users.noreply.github.com>
2024-07-27 13:18:28 +03:00
Sergey Borisov
9e582563eb Suggested changes
Co-Authored-By: Ryan Dick <14897797+RyanJDick@users.noreply.github.com>
2024-07-27 04:25:15 +03:00
Sergey Borisov
faa88f72bf Make lora as separate extensions 2024-07-27 02:39:53 +03:00
chainchompa
0d69a31df0 Merge branch 'main' into chainchompa/board-delete-info 2024-07-26 14:03:18 -04:00
brandonrising
daa5a88eb2 Update docker image to use pnpm version 8 2024-07-26 13:57:33 -04:00
Sergey Borisov
5b84e117b2 Suggested changes
Co-Authored-By: Ryan Dick <14897797+RyanJDick@users.noreply.github.com>
2024-07-26 20:51:12 +03:00
chainchompa
eb257d2d28 update delete board modal to be more descriptive 2024-07-26 13:34:25 -04:00
Sergey Borisov
5810cee6c9 Suggested changes
Co-Authored-By: Ryan Dick <14897797+RyanJDick@users.noreply.github.com>
2024-07-26 19:47:28 +03:00
Sergey Borisov
eef88d1f83 Update gradient mask node version 2024-07-26 19:33:41 +03:00
Sergey Borisov
78f6850fc0 Fix gradient mask values range 2024-07-26 19:28:00 +03:00
Sergey Borisov
bd8890be11 Revert "Fix create gradient mask node output"
This reverts commit 9d1fcba415.
2024-07-26 19:24:46 +03:00
Sergey Borisov
adf1a977ea Suggested changes
Co-Authored-By: Ryan Dick <14897797+RyanJDick@users.noreply.github.com>
2024-07-26 19:22:26 +03:00
Mary Hipp
e1509bcb45 bump version to 4.2.7 2024-07-26 09:11:17 -07:00
psychedelicious
edcaf8287d feat(app): remove beta from multidiffusion workflows 2024-07-26 13:47:51 +10:00
psychedelicious
39bd30f2a0 feat(app): update default workflows
- Update `MultiDiffusion SDXL (Beta)`
- Add `MultiDiffusion SD1.5 (Beta)`
2024-07-26 13:47:51 +10:00
psychedelicious
102b47190f feat(ui): update qr code cnet starter model
- For SD1.5, use the new V2 version
- Add the SDXL version
2024-07-26 13:34:32 +10:00
Mary Hipp
269fe2e3bb track accordions in tabs separately so open/close state isnt shared 2024-07-26 08:20:24 +10:00
Mary Hipp
b32aa1c77f fix missing quote in translation 2024-07-26 08:20:24 +10:00
Mary Hipp Rogers
6656544ed5 tooltip copy updates
Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
2024-07-26 08:20:24 +10:00
Mary Hipp
4c75b93410 feat(ui): add informational popovers for upscale params 2024-07-26 08:20:24 +10:00
Mary Hipp
5be0de967d feat(ui): close generation and advanced accordions when switching to upscale tab 2024-07-26 08:20:24 +10:00
psychedelicious
f8e27b837b fix(ui): memoize model manager components 2024-07-26 07:52:10 +10:00
psychedelicious
47414be1e6 fix(ui): dropped model config cache breaking model edit UI
The model edit UI's composition allows for the model edit form to be instantiated before the model's config has been received. This results in the form having no values - all the fields are blank instead of populated by the model config.

Part of the fix is to pass the model config around directly instead of relying on _all_ components to fetch the model directly.

I also fixed a crapload of performance issues related to improper use of redux selectors.
2024-07-26 07:52:10 +10:00
psychedelicious
74cef38bcf fix(backend): add refiner to single-file load_classes
Fixes single-file refiner loading.
2024-07-26 05:08:01 +10:00
psychedelicious
bb876b8d4e fix(ui): copied edges must have new ids set
Problems this was causing:
- Deleting an edge was a copy of another edge deletes both edges
- Deleting a node that was a copy-with-edges of another node deletes its edges and it's original edges, leaving what I will call "ghost noodles" behind
2024-07-26 04:54:33 +10:00
psychedelicious
ba747373db feat(ui): add button to disable info popovers from info popover 2024-07-25 08:06:41 -04:00
psychedelicious
95661c8b21 feat(ui): enable info popovers by default 2024-07-25 08:06:41 -04:00
blessedcoolant
e5d9ca013e fix: use v1 models for large and base versions 2024-07-25 17:24:12 +05:30
blessedcoolant
4166c756ce wip: depth_anything_v2 init lint fixes 2024-07-25 14:41:22 +05:30
blessedcoolant
4f0dfbd34d wip: depth_anything_v2 initial implementation 2024-07-25 13:53:06 +05:30
psychedelicious
b70ac88684 perf(ui): throttle page changes
Previously you could spam the next/prev buttons and really thrash the server. Throttled to 500ms, which feels like a happy medium between responsive and not-thrash-y.
2024-07-25 11:57:54 +10:00
psychedelicious
24609da6ab feat(ui): tweak pagination styles 2024-07-25 11:57:54 +10:00
psychedelicious
524647b1f1 fix(ui): jumpto interactions
- Autofocus on popover open
- Autoselect number on popover open
- Enter works to change page when input is focused
- Esc works to close popover when input is focused
2024-07-25 11:57:54 +10:00
Mary Hipp
cf1af94f53 feat(ui): make jump to page a popover 2024-07-25 11:57:54 +10:00
Mary Hipp
2a9fdc6314 feat(ui): add jump to option for gallery pagination 2024-07-25 11:57:54 +10:00
Sergey Borisov
46c632e7cc Change layer detection keys according to LyCORIS repository 2024-07-25 02:10:47 +03:00
Sergey Borisov
653f63ae71 Add layer keys check 2024-07-25 02:03:08 +03:00
Sergey Borisov
8a9e2f57a4 Handle bias in full/diff lora layer 2024-07-25 02:02:37 +03:00
Sergey Borisov
31949ed2f2 Refactor code a bit 2024-07-25 02:00:30 +03:00
psychedelicious
3657285b1b chore: bump version v4.2.7rc1 2024-07-25 06:23:50 +10:00
Hosted Weblate
e4b5975305 translationBot(ui): update translation files
Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/
Translation: InvokeAI/Web UI
2024-07-25 06:09:04 +10:00
gallegonovato
b59825edc0 translationBot(ui): update translation (Spanish)
Currently translated at 34.4% (448 of 1300 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/es/
Translation: InvokeAI/Web UI
2024-07-25 06:09:04 +10:00
Riccardo Giovanetti
25788f6869 translationBot(ui): update translation (Italian)
Currently translated at 98.6% (1289 of 1307 strings)

translationBot(ui): update translation (Italian)

Currently translated at 98.5% (1277 of 1296 strings)

Co-authored-by: Riccardo Giovanetti <riccardo.giovanetti@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/it/
Translation: InvokeAI/Web UI
2024-07-25 06:09:04 +10:00
Sergey Borisov
0ccb304b8b Ruff format 2024-07-24 16:01:29 +03:00
psychedelicious
ca5a4ee59d fix(ui): few cases where board totals don't updated when moving 2024-07-24 22:30:44 +10:00
psychedelicious
4fdefe58c7 feat(ui): clear gallery search on esc key 2024-07-24 14:10:16 +10:00
psychedelicious
9870f5a96f fix(ui): race condition with gallery search
It was possible to clear the search term while a debounced setSearchTerm is still pending. This resulted in the gallery getting out of sync w/ the search term.

To fix this, we need to lift the state up a bit and  cancel any pending debounced setSearchTerm calls when closing the search or clearing the search term box.
2024-07-24 14:10:16 +10:00
psychedelicious
c296ae8cfe feat(ui): add useAssertSingleton hook
Use this to enforce singleton components and hooks.
2024-07-24 14:10:16 +10:00
psychedelicious
17493f4ae0 fix(ui): close boards search when toggling panel 2024-07-24 14:10:16 +10:00
psychedelicious
2503dca813 fix(ui): show boards panel when opening board search 2024-07-24 14:10:16 +10:00
psychedelicious
cb61ef9bb1 feat(ui): use color instead of super tiny icon change to indicate board search toggle state
You can't even see the icon, no point in changing it. Blue = active/open, Grey = closed.
2024-07-24 14:10:16 +10:00
psychedelicious
1831ed620f fix(ui): gallery tabs layout 2024-07-24 14:10:16 +10:00
psychedelicious
c385e76356 fix(ui): DeleteBoardModal must be a singleton 2024-07-24 14:10:16 +10:00
psychedelicious
ff1972fbb3 fix(ui): spacing issue w/ boards search 2024-07-24 14:10:16 +10:00
psychedelicious
c4b3405bfa fix(ui): make uncategorized and board components same height 2024-07-24 14:10:16 +10:00
psychedelicious
ab2548c0cd feat(ui): minor padding tweaks in boardslist 2024-07-24 14:10:16 +10:00
psychedelicious
dc2a3363b0 feat(ui): layout shift when using a collapse w/ flex gap
the gap isn't handled smoothly, there's always a jump. cannot use gap in the collapsible's container
2024-07-24 14:10:16 +10:00
psychedelicious
d7a5fe2805 feat(ui): make arrow icon rotate on boards list 2024-07-24 14:10:16 +10:00
psychedelicious
4e49689d46 feat(ui): make isPrivate required on BoardsList 2024-07-24 14:10:16 +10:00
psychedelicious
ca8441a32f fix(ui): alignment & overflow on gallery header 2024-07-24 14:10:16 +10:00
psychedelicious
44284d671c feat(ui): tweak padding for boards in list 2024-07-24 14:10:16 +10:00
psychedelicious
e89de1d5b7 feat(ui): tweak board tooltip styles
When the totals were high enough, the image looked really off. Also fixed some inconsistent padding.
2024-07-24 14:10:16 +10:00
psychedelicious
6db63349f8 fix(ui): missing key on list element 2024-07-24 14:10:16 +10:00
Mary Hipp
7f6f892533 fix circular dep 2024-07-24 14:10:16 +10:00
Mary Hipp
d1bbd0cf80 cleanup 2024-07-24 14:10:16 +10:00
Mary Hipp
bd73b6b2af reorganize the gallery - move board name to top of image grid, add hide/view boards button for toggle 2024-07-24 14:10:16 +10:00
Mary Hipp
0d40a7d865 exclude uncategorized from search and make sure list is always correct 2024-07-24 14:10:16 +10:00
Mary Hipp
c2f6b80246 move Uncategorized back to private board list 2024-07-24 14:10:16 +10:00
Mary Hipp
80f5f8210a increase font size of Move for boards 2024-07-24 14:10:16 +10:00
Mary Hipp
b7383cc0e5 board UI updates: always show search for boards and images if a term is entered, clear search when view is toggled off 2024-07-24 14:10:16 +10:00
Mary Hipp
2172e4d292 board UI updates: font tweaks, add cover image to tooltip, move uncategorized out of board list, allow collapsible board list if private enabled 2024-07-24 14:10:16 +10:00
Sergey Borisov
ab0bfa709a Handle loras in modular denoise 2024-07-24 05:07:29 +03:00
Sergey Borisov
6af659b1da Handle t2i adapter in modular denoise 2024-07-24 02:55:33 +03:00
psychedelicious
db664afc49 fix(ui): model select overflowing when model names are too long 2024-07-24 09:35:32 +10:00
psychedelicious
b99a53e64e tidy(ui): organise postprocessing listeners 2024-07-24 08:22:46 +10:00
psychedelicious
5f4ce6fda3 tidy(ui): organise postprocessing files 2024-07-24 08:22:46 +10:00
psychedelicious
93e95ce53f chore(ui): lint 2024-07-24 08:22:46 +10:00
psychedelicious
2997f0a1f8 fix(ui): ts issue 2024-07-24 08:22:46 +10:00
psychedelicious
40b262bcc2 tidy(ui): "simpleUpscale" -> "postProcessing" 2024-07-24 08:22:46 +10:00
psychedelicious
a26f050cbb feat(ui): rename ad-hoc upscale stuff to post-processing 2024-07-24 08:22:46 +10:00
psychedelicious
94b5b2a467 feat(ui): improve starter model search for spandrel models 2024-07-24 08:22:46 +10:00
psychedelicious
b4519ea61f tidy(ui): remove unused maxUpscalePixels config 2024-07-24 08:22:46 +10:00
psychedelicious
7f7ce291b5 feat(ui): revised simple upscale warning UI 2024-07-24 08:22:46 +10:00
psychedelicious
aeb53563ff feat(ui): use graph util for ad-hoc upscale graph 2024-07-24 08:22:46 +10:00
psychedelicious
e8d2e2330e fix(ui): set board in ad-hoc upscale graph 2024-07-24 08:22:46 +10:00
psychedelicious
4c6b9ce7c9 fix(ui): use spandrel autoscale node in upscaling tab 2024-07-24 08:22:46 +10:00
psychedelicious
87a2221d72 chore(ui): typegen 2024-07-24 08:22:46 +10:00
psychedelicious
76aa6bdf05 feat(nodes): split spandrel node
`spandrel_image_to_image` now just runs the model with no changes.

`spandrel_image_to_image_autoscale` runs the model repeatedly until the desired scale is reached. previously, `spandrel_image_to_image` did this.
2024-07-24 08:22:46 +10:00
Sergey Borisov
416d29fb83 Ruff format 2024-07-24 01:17:28 +03:00
psychedelicious
0c1994d682 fix(ui): restore pnpm-lock.yaml
#6645 inadvertently removed the lockfile
2024-07-24 08:07:32 +10:00
Sergey Borisov
19c00241c6 Use non-inverted mask generally(except inpaint model handling) 2024-07-24 00:59:13 +03:00
Lincoln Stein
633bbb4e85 [MM2] Use typed ModelRecordChanges for model_install() rather than untyped dict (#6645)
* [MM2] replace untyped config dict passed to install_model with typed ModelRecordChanges

- adjusted frontend to work with new schema
- used this facility to assign "starter model" names and descriptions to the installed
  models.

* documentation fix

* [MM2] replace untyped config dict passed to install_model with typed ModelRecordChanges

- adjusted frontend to work with new schema
- used this facility to assign "starter model" names and descriptions to the installed
  models.

* documentation fix

* remove v9 pnpm lockfile

* [MM2] replace untyped config dict passed to install_model with typed ModelRecordChanges

- adjusted frontend to work with new schema
- used this facility to assign "starter model" names and descriptions to the installed
  models.

* [MM2] replace untyped config dict passed to install_model with typed ModelRecordChanges

- adjusted frontend to work with new schema
- used this facility to assign "starter model" names and descriptions to the installed
  models.

* remove v9 pnpm lockfile

* regenerate schema.ts

* prettified

---------

Co-authored-by: Lincoln Stein <lstein@gmail.com>
2024-07-23 21:41:00 +00:00
psychedelicious
a221ab2fb6 fix(ui): upsell menuitem styling 2024-07-24 06:58:27 +10:00
psychedelicious
0279a27f66 fix(ui): render settingsmenu in portal, no zindex 2024-07-24 06:58:27 +10:00
chainchompa
54aef4959c cleanup 2024-07-24 06:56:02 +10:00
chainchompa
4017609b91 clean up useIsAllowedToUpscale since its no longer necessary 2024-07-24 06:56:02 +10:00
chainchompa
cb0bffedd5 fix board handling for simple upscale 2024-07-24 06:56:02 +10:00
chainchompa
1fd2a91ccd only show warning for simple upscale if no simple upscale model is available 2024-07-24 06:56:02 +10:00
Sergey Borisov
c323a760a5 Suggested changes
Co-Authored-By: Ryan Dick <14897797+RyanJDick@users.noreply.github.com>
2024-07-23 23:34:28 +03:00
Sergey Borisov
9d1fcba415 Fix create gradient mask node output 2024-07-23 23:29:28 +03:00
chainchompa
075e0405f9 Update Simple Upscale Button to work with spandrel models (#6649)
## Summary
Update Simple Upscale Button to work with spandrel models, add
UpscaleWarning when models aren't available, clean up ESRGAN logic
<!--A description of the changes in this PR. Include the kind of change
(fix, feature, docs, etc), the "why" and the "how". Screenshots or
videos are useful for frontend changes.-->

## Related Issues / Discussions

<!--WHEN APPLICABLE: List any related issues or discussions on github or
discord. If this PR closes an issue, please use the "Closes #1234"
format, so that the issue will be automatically closed when the PR
merges.-->

## QA Instructions

<!--WHEN APPLICABLE: Describe how you have tested the changes in this
PR. Provide enough detail that a reviewer can reproduce your tests.-->

## Merge Plan

<!--WHEN APPLICABLE: Large PRs, or PRs that touch sensitive things like
DB schemas, may need some care when merging. For example, a careful
rebase by the change author, timing to not interfere with a pending
release, or a message to contributors on discord after merging.-->

## Checklist

- [ ] _The PR has a short but descriptive title, suitable for a
changelog_
- [ ] _Tests added / updated (if applicable)_
- [ ] _Documentation added / updated (if applicable)_
2024-07-23 13:33:01 -04:00
chainchompa
bf6066d834 Merge branch 'main' into chainchompa/simple-upscale-updates 2024-07-23 13:27:48 -04:00
Mary Hipp
5635f65ee9 feat(ui): add upsells for pro edition to settings menu 2024-07-23 13:27:00 -04:00
chainchompa
6317cf8ef9 move handleSimpleUpscaleModels logic into handleSpandrelImageToImageModels listener 2024-07-23 13:13:21 -04:00
chainchompa
9e1daf06f7 Merge branch 'main' into chainchompa/simple-upscale-updates 2024-07-23 12:16:44 -04:00
chainchompa
e1a718b512 cleanup 2024-07-23 12:16:35 -04:00
chainchompa
cbce89162b update simple upscale metadata to match upscale metadata 2024-07-23 12:15:26 -04:00
chainchompa
b46b20210d handle simple upscale models on modelsLoaded 2024-07-23 11:53:43 -04:00
chainchompa
8e89157a83 reuse ParamSpandrelModel for simple upscale 2024-07-23 11:36:46 -04:00
Sergey Borisov
ca21996a97 Remove old seamless class 2024-07-23 18:04:33 +03:00
Sergey Borisov
62aa064e56 Handle seamless in modular denoise 2024-07-23 18:03:59 +03:00
Ryan Dick
7c975f0d00 Modular backend - add ControlNet (#6642)
## Summary

ControlNet code from #6577.

## Related Issues / Discussions

#6606

https://invokeai.notion.site/Modular-Stable-Diffusion-Backend-Design-Document-e8952daab5d5472faecdc4a72d377b0d

## QA Instructions

Run with and without set `USE_MODULAR_DENOISE` environment.

## Merge Plan

Merge #6641 firstly, to be able see output difference properly.
If you think that there should be some kind of tests - feel free to add.

## Checklist

- [x] _The PR has a short but descriptive title, suitable for a
changelog_
- [ ] _Tests added / updated (if applicable)_
- [ ] _Documentation added / updated (if applicable)_
2024-07-23 10:37:25 -04:00
chainchompa
8107884c8d Merge branch 'main' into chainchompa/simple-upscale-updates 2024-07-23 10:28:11 -04:00
chainchompa
a2f49ef7c1 cleanup esrgan frontend code 2024-07-23 10:22:38 -04:00
Ryan Dick
e2e47fd606 Merge branch 'main' into stalker-modular_controlnet 2024-07-23 10:19:12 -04:00
chainchompa
c098edc6b2 updated simple upscale to use spandrel node and list of available spandrel models 2024-07-23 10:15:31 -04:00
chainchompa
bc1d9748ce updated upscale warning to work for simple upscale 2024-07-23 10:04:31 -04:00
Ryan Dick
7b8e25f525 Modular backend - add FreeU (#6641)
## Summary

FreeU code from https://github.com/invoke-ai/InvokeAI/pull/6577.
Also fix issue with sometimes slightly different output.

## Related Issues / Discussions

#6606 

https://invokeai.notion.site/Modular-Stable-Diffusion-Backend-Design-Document-e8952daab5d5472faecdc4a72d377b0d

## QA Instructions

Run with and without set `USE_MODULAR_DENOISE` environment.

## Merge Plan

Nope.
If you think that there should be some kind of tests - feel free to add.

## Checklist

- [x] _The PR has a short but descriptive title, suitable for a
changelog_
- [ ] _Tests added / updated (if applicable)_
- [ ] _Documentation added / updated (if applicable)_
2024-07-23 10:02:56 -04:00
Ryan Dick
db52f5606f Merge branch 'main' into stalker-modular_freeu 2024-07-23 09:53:32 -04:00
Ryan Dick
de39c5ed21 Modular backend - add rescale cfg (#6640)
## Summary

Rescale CFG code from #6577.

## Related Issues / Discussions

#6606 

https://invokeai.notion.site/Modular-Stable-Diffusion-Backend-Design-Document-e8952daab5d5472faecdc4a72d377b0d

## QA Instructions

Run with and without set `USE_MODULAR_DENOISE` environment.
~~Note: for some reasons there slightly different output from run to
run, but I able sometimes to get same output on main and this branch.~~
Fix presented in #6641.

## Merge Plan

~~Nope.~~ Merge #6641 firstly, to be able see output difference
properly.
If you think that there should be some kind of tests - feel free to add.

## Checklist

- [x] _The PR has a short but descriptive title, suitable for a
changelog_
- [ ] _Tests added / updated (if applicable)_
- [ ] _Documentation added / updated (if applicable)_
2024-07-23 09:45:30 -04:00
Ryan Dick
d014dc94fd Merge branch 'main' into stalker7779/modular_rescale_cfg 2024-07-23 09:34:22 -04:00
Ryan Dick
39e804d0f8 Use consistent param names in patch_extension(...) functions: context -> ctx. 2024-07-23 09:18:04 -04:00
psychedelicious
154e8f6e78 chore(ui): lint 2024-07-23 15:42:16 +10:00
psychedelicious
2d31b82e60 feat(ui): tweak layout for warning message 2024-07-23 15:42:16 +10:00
psychedelicious
8f934747f3 feat(ui): updated upscale tab warnings 2024-07-23 15:42:16 +10:00
psychedelicious
4352341a00 feat(ui): starter models filter matches spandrel models to "upscale" search term 2024-07-23 15:42:16 +10:00
psychedelicious
d7e0ec52ff feat(ui): make model install tab controlled
This lets us navigate directly to eg the Starter Models tab
2024-07-23 15:42:16 +10:00
psychedelicious
1072b74c0e fix(ui): edge cases in starter models search 2024-07-23 15:42:16 +10:00
psychedelicious
46dc8c6641 chore(ui): lint 2024-07-23 15:42:16 +10:00
psychedelicious
a8bc6ab5b1 fix(ui): typos 2024-07-23 15:42:16 +10:00
Kent Keirsey
bd91bd4a84 Math Updates 2024-07-23 15:42:16 +10:00
psychedelicious
8756a6b8c3 fix(ui): remove sharpness param 2024-07-23 10:55:54 +10:00
psychedelicious
2e0cebb571 fix(ui): bug where viewer would disappear on upscaling tab 2024-07-23 10:55:54 +10:00
psychedelicious
c3a8184431 feat(ui): add number input to scale slider 2024-07-23 10:55:54 +10:00
psychedelicious
ffa39d74b3 feat(ui): remove first unsharp from upscale graph 2024-07-23 10:55:54 +10:00
psychedelicious
f9d3966ea2 feat(ui): add scale param to upscaling tab 2024-07-23 10:55:54 +10:00
psychedelicious
7cee4e42a7 feat(ui): add addEdgeToMetadata graph helper 2024-07-23 10:55:54 +10:00
psychedelicious
071c7c7c7e chore(ui): typegen 2024-07-23 10:55:54 +10:00
psychedelicious
818045f678 tidy(ui): use × instead of translation string 2024-07-23 10:55:54 +10:00
psychedelicious
7edefbefff feat(ui): add translation for upscaling tab 2024-07-23 10:55:54 +10:00
psychedelicious
29efab70b7 feat(nodes): spandrel_image_to_image.scale defaults to 4.0 2024-07-23 10:55:54 +10:00
psychedelicious
ac6adc392a feat(nodes): add scale and fit_to_multiple_of_8 to spandrel node 2024-07-23 10:55:54 +10:00
psychedelicious
a2ef5d56ee feat(nodes): split out spandrel node upscale logic into utils 2024-07-23 10:55:54 +10:00
Mary Hipp
13f3560e55 more lint fixes 2024-07-23 10:55:54 +10:00
Mary Hipp
c4bd60e00f knip fix 2024-07-23 10:55:54 +10:00
Mary Hipp
54eda9163c remove tiledVAE option and make it true 2024-07-23 10:55:54 +10:00
Mary Hipp
582f384fff lint fix 2024-07-23 10:55:54 +10:00
Mary Hipp
a43211e650 math updates for controlnet tiles 2024-07-23 10:55:54 +10:00
Mary Hipp
6cb0581b0d add description to upscale model dropdown tooltip 2024-07-23 10:55:54 +10:00
Mary Hipp
845d77916e lint fix 2024-07-23 10:55:54 +10:00
Mary Hipp
f18431a999 use fn to get width/height of output image 2024-07-23 10:55:54 +10:00
Mary Hipp
5060bf2f62 lint fix 2024-07-23 10:55:54 +10:00
Mary Hipp
7854d913b2 add upscaling data to metadata 2024-07-23 10:55:54 +10:00
Mary Hipp
890a3ce32a add limited metadata 2024-07-23 10:55:54 +10:00
Mary Hipp
fb4b3f3350 fix creativity/sharpness/structure scales, move where loras are added, get scale const working 2024-07-23 10:55:54 +10:00
Mary Hipp
d166b08b6a restore scale but hardcode it to 2 regardless of upscale model 2024-07-23 10:55:54 +10:00
psychedelicious
5266e9e682 fix(ui): remove unused scale param 2024-07-23 10:55:54 +10:00
psychedelicious
d0265e21b0 fix(ui): use spandrel node for upscaling 2024-07-23 10:55:54 +10:00
psychedelicious
3126e8e49a chore(ui): typegen 2024-07-23 10:55:54 +10:00
Mary Hipp
9e3412d776 translations and lint fix 2024-07-23 10:55:54 +10:00
Mary Hipp
4a09cc57be use the tile conttrolnet in graph instad of marys hardcoded key 2024-07-23 10:55:54 +10:00
Mary Hipp
5ab36e0433 add warning if no upscale model or no tile controlnet for base model 2024-07-23 10:55:54 +10:00
Mary Hipp
d2bf3629bf base scale off of upscale model selected 2024-07-23 10:55:54 +10:00
Mary Hipp
d9b217d908 hook up sharpness, structure, and creativity 2024-07-23 10:55:54 +10:00
Mary Hipp
2847f1b5ac add vae toggle, lint fix 2024-07-23 10:55:54 +10:00
Mary Hipp
bc30850f3a hardcode marys tile cnet key 2024-07-23 10:55:54 +10:00
Mary Hipp
7668dc68a0 cleanup, add loras 2024-07-23 10:55:54 +10:00
Mary Hipp
ea449f5a0a upscale graph built, no multidiffusion yet 2024-07-23 10:55:54 +10:00
Mary Hipp
5a1ed99ca1 restore adhoc upscale button 2024-07-23 10:55:54 +10:00
Mary Hipp
3a2707ac02 disable invoke button properly for upscaling tab 2024-07-23 10:55:54 +10:00
Mary Hipp
ce5b1103ed add send to upscale to context menu 2024-07-23 10:55:54 +10:00
Mary Hipp
fd91b83d86 build out the rest of the accordions 2024-07-23 10:55:54 +10:00
Mary Hipp
a0a54348e8 removed upscale button, created spandrel model dropdown, created upscale initial image that works with dnd 2024-07-23 10:55:54 +10:00
Mary Hipp
43b3e242b0 tidy(ui): refactor parameters panel components to be 1:1 with tabs 2024-07-23 10:55:54 +10:00
Sergey Borisov
4e8dcb7a1a Suggested changes
Co-Authored-By: Ryan Dick <14897797+RyanJDick@users.noreply.github.com>
2024-07-23 01:46:29 +03:00
Sergey Borisov
3cb13d6288 Rename as suggested in other PRs
Co-Authored-By: Ryan Dick <14897797+RyanJDick@users.noreply.github.com>
2024-07-23 01:01:18 +03:00
chainchompa
4f01c0f2d3 fix: update uncategorized board totals when deleting and moving images (#6646)
## Summary
- currently the total for uncategorized images is not updating when
moving and deleting images, this will update that count when making
those actions
<!--A description of the changes in this PR. Include the kind of change
(fix, feature, docs, etc), the "why" and the "how". Screenshots or
videos are useful for frontend changes.-->

## Related Issues / Discussions

<!--WHEN APPLICABLE: List any related issues or discussions on github or
discord. If this PR closes an issue, please use the "Closes #1234"
format, so that the issue will be automatically closed when the PR
merges.-->

## QA Instructions

<!--WHEN APPLICABLE: Describe how you have tested the changes in this
PR. Provide enough detail that a reviewer can reproduce your tests.-->

## Merge Plan

<!--WHEN APPLICABLE: Large PRs, or PRs that touch sensitive things like
DB schemas, may need some care when merging. For example, a careful
rebase by the change author, timing to not interfere with a pending
release, or a message to contributors on discord after merging.-->

## Checklist

- [ ] _The PR has a short but descriptive title, suitable for a
changelog_
- [ ] _Tests added / updated (if applicable)_
- [ ] _Documentation added / updated (if applicable)_
2024-07-22 17:10:52 -04:00
Sergey Borisov
87eb018380 Revert debug change 2024-07-22 23:49:20 +03:00
Sergey Borisov
5003e5d763 Same changes as in other PRs, add check for running inpainting on inpaint model without source image
Co-Authored-By: Ryan Dick <14897797+RyanJDick@users.noreply.github.com>
2024-07-22 23:47:39 +03:00
chainchompa
e92af52fb8 fix moving items to uncategorized updating 2024-07-22 16:11:36 -04:00
Sergey Borisov
5f0fe3c8a9 Suggested changes
Co-Authored-By: Ryan Dick <14897797+RyanJDick@users.noreply.github.com>
2024-07-22 23:09:11 +03:00
chainchompa
339dddd018 update uncategorized board totals when deleting and moving images 2024-07-22 16:03:01 -04:00
Sergey Borisov
1b359b55cb Suggested changes
Co-Authored-By: Ryan Dick <14897797+RyanJDick@users.noreply.github.com>
2024-07-22 22:17:29 +03:00
Eugene Brodsky
d0435575c1 chore(deps): bump fastapi-events to the next minor version 2024-07-22 04:15:36 -07:00
Sergey Borisov
58f3072b91 Handle inpainting on normal models 2024-07-21 22:17:29 +03:00
Sergey Borisov
9e7b470189 Handle inpaint models 2024-07-21 20:45:55 +03:00
Sergey Borisov
42356ec866 Add ControlNet support to denoise 2024-07-21 20:01:30 +03:00
Sergey Borisov
1748848b7b Ruff fixes 2024-07-21 18:37:20 +03:00
Sergey Borisov
5772965f09 Fix slightly different output with old backend 2024-07-21 18:31:30 +03:00
Sergey Borisov
e046e60e1c Add FreeU support to denoise 2024-07-21 18:31:10 +03:00
Sergey Borisov
9a1420280e Add rescale cfg support to denoise 2024-07-21 17:33:43 +03:00
Ryan Dick
f9c61f1b6c Fix function call that we forgot to update in #6606 (#6636)
## Summary

Fix function call that we forgot to update in #6606

## QA Instructions

Run a TiledMultiDiffusionDenoiseLatents invocation and make sure it
doesn't crash.

## Checklist

- [x] _The PR has a short but descriptive title, suitable for a
changelog_
- [x] _Tests added / updated (if applicable)_
- [x] _Documentation added / updated (if applicable)_
2024-07-19 17:19:32 -04:00
Ryan Dick
a8cc5caf96 Fix function call that we forgot to update in https://github.com/invoke-ai/InvokeAI/pull/6606 2024-07-19 17:07:52 -04:00
Mary Hipp
930ff559e4 add sdxl tile to starter models 2024-07-19 16:49:33 -04:00
Ryan Dick
473f4cc1c3 Base of modular backend (#6606)
## Summary

Base code of new modular backend from #6577.
Contains normal generation and regional prompts support.
Also preview extension included to test if extensions logic works.

## Related Issues / Discussions


https://invokeai.notion.site/Modular-Stable-Diffusion-Backend-Design-Document-e8952daab5d5472faecdc4a72d377b0d

## QA Instructions

Run with and without set `USE_MODULAR_DENOISE` environment.
Currently only normal and regional conditionings supported, so just
generate some images and compare with main output.

## Merge Plan

Discuss a bit more about injection point names?
As if for example in future unet will be overridable, current
`pre_unet`/`post_unet` assumes to name override as `unet` what feels a
bit odd.
Also `apply_cfg` - future implementation could ignore/not use cfg, so in
this case `combine_noise_predictions`/`combine_noise` seems more
suitable.

## Checklist

- [x] _The PR has a short but descriptive title, suitable for a
changelog_
- [x] _Tests added / updated (if applicable)_
- [ ] _Documentation added / updated (if applicable)_
2024-07-19 16:37:57 -04:00
Ryan Dick
78d2b1b650 Merge branch 'main' into stalker-backend_base 2024-07-19 16:25:20 -04:00
Sergey Borisov
39e10d894c Add invocation cancellation logic to patchers 2024-07-19 23:17:01 +03:00
Ryan Dick
e16faa6370 Add gradient blending to tile seams in MultiDiffusion. 2024-07-19 13:05:50 -07:00
Ryan Dick
83a86abce2 Add unit tests for ExtensionsManager and ExtensionBase. 2024-07-19 14:15:46 -04:00
Sergey Borisov
0c56d4a581 Ryan's suggested changes to extension manager/extensions
Co-Authored-By: Ryan Dick <14897797+RyanJDick@users.noreply.github.com>
2024-07-18 23:49:44 +03:00
Lincoln Stein
97a7f51721 don't use cpu state_dict for model unpatching when executing on cpu (#6631)
Co-authored-by: Lincoln Stein <lstein@gmail.com>
2024-07-18 15:34:01 -04:00
StAlKeR7779
710dc6b487 Merge branch 'main' into stalker7779/backend_base 2024-07-18 01:08:04 +03:00
Sergey Borisov
2ef3b49a79 Add run cancelling logic to extension manager 2024-07-17 04:39:15 +03:00
Sergey Borisov
3f79467f7b Ruff format 2024-07-17 04:24:45 +03:00
Sergey Borisov
2c2ec8f0bc Comments, a bit refactor 2024-07-17 04:20:31 +03:00
Sergey Borisov
79e35bd0d3 Minor fixes 2024-07-17 03:48:37 +03:00
Sergey Borisov
137202b77c Remove patch_unet logic for now 2024-07-17 03:40:27 +03:00
Sergey Borisov
03e22c257b Convert conditioning_mode to enum 2024-07-17 03:37:11 +03:00
Sergey Borisov
ae6d4fbc78 Move out _concat_conditionings_for_batch submethods 2024-07-17 03:31:26 +03:00
Sergey Borisov
cd1bc1595a Rename sequential as private variable 2024-07-17 03:24:11 +03:00
Ryan Dick
0583101c1c Add Spandrel upscale starter models (#6605)
## Summary

This PR adds some spandrel upscale models to the starter model list.

In the future we may also want to add:
- Some DAT models
(https://drive.google.com/drive/folders/1iBdf_-LVZuz_PAbFtuxSKd_11RL1YKxM)

## QA Instructions

I installed the starter models via the model manager UI, and tested that
I could use them in a workflow.

## Merge Plan

- [ ] Merge the preceding Spandrel PRs first, then change the target
branch to `main`.

## Checklist

- [x] _The PR has a short but descriptive title, suitable for a
changelog_
- [x] _Tests added / updated (if applicable)_
- [x] _Documentation added / updated (if applicable)_
2024-07-16 16:04:52 -04:00
Ryan Dick
f866b49255 Add some ESRGAN and SwinIR upscale models to the starter models list. 2024-07-16 15:55:10 -04:00
Sergey Borisov
b7c6c63005 Added some comments 2024-07-16 22:52:44 +03:00
Ryan Dick
95e9f5323b Add tiling to SpandrelImageToImageInvocation (#6594)
## Summary

Add tiling to the `SpandrelImageToImageInvocation` node so that it can
process large images.

Tiling enables this node to run on effectively any input image
dimension. Of course, the computation time increases quadratically with
the image dimension.

Some profiling results on an RTX4090:
- Input 1024x1024, 4x upscale, 4x UltraSharp ESRGAN: `13 secs`, `<4 GB
VRAM`
- Input 4096x4096, 4x upscale, 4x UltraSharop ESRGAN: `46 secs`, `<4 GB
VRAM`
- Input 4096x4096, 2x upscale, SwinIR: `165 secs`, `<5 GB VRAM`

A lot of the time is spent PNG encoding the final image:
- PNG encoding of a 16384x16384 image takes `83secs @
pil_compress_level=7`, `24secs @ pil_compress_level=1`

Callout: If we want to start building workflows that pass large images
between nodes, we are going to have to find a way to avoid the PNG
encode/decode roundtrip that we are currently doing. As is, we will be
incurring a huge penalty for every node that receives/produces a large
image.

## QA Instructions

- [x] Tested with tiling up to 4096x4096 -> 16384x16384.
- [x] Test on images with an alpha channel (the alpha channel is
dropped).
- [x] Test on images with odd dimension.
- [x] Test no tiling (`tile_size=0`)

## Merge Plan

- [x] Merge #6556 first, and change the target branch to `main`.

## Checklist

- [x] _The PR has a short but descriptive title, suitable for a
changelog_
- [ ] _Tests added / updated (if applicable)_
- [x] _Documentation added / updated (if applicable)_
2024-07-16 15:51:15 -04:00
Ryan Dick
6b0ca88177 Merge branch 'main' into ryan/spandrel-upscale-tiling 2024-07-16 15:40:14 -04:00
Ryan Dick
7ad32dcad2 Add support for Spandrel Image-to-Image models (e.g. ESRGAN, Real-ESRGAN, Swin-IR, DAT, etc.) (#6556)
## Summary

- Add support for all
[spandrel](https://github.com/chaiNNer-org/spandrel) image-to-image
models - this is a collection of many popular super-resolution models
(e.g. ESRGAN, Real-ESRGAN, SwinIR, DAT, etc.)

Examples of supported models:

- DAT:
https://drive.google.com/drive/folders/1iBdf_-LVZuz_PAbFtuxSKd_11RL1YKxM
- SwinIR: https://github.com/JingyunLiang/SwinIR/releases
- Any ESRGAN / Real-ESRGAN model

## Related Issues

Closes #6394 

## QA Instructions

- [x] Test that unsupported models still fail the probe (i.e. no false
positive spandrel models)
- [x] Test adding a few non-spandrel model types
- [x] Test adding a handful of spandrel model types: ESRGAN,
Real-ESRGAN, SwinIR, DAT
- [x] Verify model size estimation for the model cache
- [x] Test using the spandrel models in a practical image upscaling
workflow

## Merge Plan

- [x] Get approval from @brandonrising and @maryhipp before merging -
this PR has commercial implications.
- [x] Merge #6571 and change the target branch to `main`

## Checklist

- [x] _The PR has a short but descriptive title, suitable for a
changelog_
- [x] _Tests added / updated (if applicable)_
- [x] _Documentation added / updated (if applicable)_
2024-07-16 15:37:20 -04:00
Ryan Dick
81991e072b Merge branch 'main' into ryan/spandrel-upscale 2024-07-16 15:14:08 -04:00
Sergey Borisov
cec345cb5c Change attention processor apply logic 2024-07-16 20:03:29 +03:00
Sergey Borisov
608cbe3f5c Separate inputs in denoise context 2024-07-16 19:30:29 +03:00
psychedelicious
7905a46ca4 chore: bump version to 4.2.6post1 2024-07-16 09:09:04 +10:00
psychedelicious
38343917f8 fix(backend): revert non-blocking device transfer
In #6490 we enabled non-blocking torch device transfers throughout the model manager's memory management code. When using this torch feature, torch attempts to wait until the tensor transfer has completed before allowing any access to the tensor. Theoretically, that should make this a safe feature to use.

This provides a small performance improvement but causes race conditions in some situations. Specific platforms/systems are affected, and complicated data dependencies can make this unsafe.

- Intermittent black images on MPS devices - reported on discord and #6545, fixed with special handling in #6549.
- Intermittent OOMs and black images on a P4000 GPU on Windows - reported in #6613, fixed in this commit.

On my system, I haven't experience any issues with generation, but targeted testing of non-blocking ops did expose a race condition when moving tensors from CUDA to CPU.

One workaround is to use torch streams with manual sync points. Our application logic is complicated enough that this would be a lot of work and feels ripe for edge cases and missed spots.

Much safer is to fully revert non-locking - which is what this change does.
2024-07-16 08:59:42 +10:00
Sergey Borisov
9f088d1bf5 Multiple small fixes 2024-07-16 00:51:25 +03:00
Sergey Borisov
fd8d1c12d4 Remove 'del' operator overload 2024-07-16 00:43:32 +03:00
Sergey Borisov
d623bd429b Fix condtionings logic 2024-07-16 00:31:56 +03:00
psychedelicious
5a0c99816c chore: bump version to v4.2.6 2024-07-15 14:16:31 +10:00
psychedelicious
24bf1ea65a fix(ui): boards cut off when search open 2024-07-15 14:07:20 +10:00
psychedelicious
28e79c4c5e chore: ruff
Looks like an upstream change to ruff resulted in this file being a violation.
2024-07-15 14:05:04 +10:00
psychedelicious
d7d59d704b chore: update default workflows
- Update all existing defaults
- Add Tiled MultiDiffusion workflow
2024-07-15 14:05:04 +10:00
Riccardo Giovanetti
8539c601e6 translationBot(ui): update translation (Italian)
Currently translated at 98.4% (1262 of 1282 strings)

Co-authored-by: Riccardo Giovanetti <riccardo.giovanetti@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/it/
Translation: InvokeAI/Web UI
2024-07-15 11:54:45 +10:00
psychedelicious
5cbe9fafb2 fix(ui): clear selection when deleting last image in board 2024-07-15 08:57:13 +10:00
psychedelicious
3ecd14f394 chore: bump version to 4.2.6rc1 2024-07-13 14:55:21 +10:00
psychedelicious@windows
7c0dfd74a5 fix(api): deleting large images fails
This issue is caused by a race condition. When a large image is served to the client, it is done using a streaming `FileResponse`. This concurrently serves the image straight from disk. The file is kept open by FastAPI until the image is fully served.

When a user deletes an image before the file is done serving, the delete fails because the file is still held by FastAPI.

To reproduce the issue:
- Create a very large image (8k reliably creates the issue).
- Create a smaller image, so that the first image in the gallery is not the large image.
- Refresh the app. The small image should be selected.
- Select the large image and immediately delete it. You have to be fast, to delete it before it finishes loading.
- In the terminal, we expect to see an error saying `Failed to delete image file`, and the image does not disappear from the UI.
- After a short wait, once the image has fully loaded, try deleting it again. We expect this to work.

The workaround is to instead serve the image from memory.

Loading the image to memory is very fast, so there is only a tiny window in which we could create the race condition, but it technically could still occur, because FastAPI is asynchronous and handles requests concurrently.

Once we load the image into memory, deletions of that image will work. Then we return a normal `Response` object with the image bytes. This is essentially what `FileResponse` does - except it uses `anyio.open_file`, which is async.

The tradeoff is that the server thread is blocked while opening the file. I think this is a fair tradeoff.

A future enhancement could be to implement soft deletion of images (db is already set up for this), and then clean up deleted image files on startup/shutdown. We could move back to using the async `FileResponse` for best responsiveness in the server without any risk of race conditions.
2024-07-13 14:46:41 +10:00
psychedelicious@windows
2c1a91241e fix(app): windows indefinite hang while finding port
For some reason, I started getting this indefinite hang when the app checks if port 9090 is available. After some fiddling around, I found that adding a timeout resolves the issue.

I confirmed that the util still works by starting the app on 9090, then starting a second instance. The second instance correctly saw 9090 in use and moved to 9091.
2024-07-13 14:46:41 +10:00
Riccardo Giovanetti
84f136e737 translationBot(ui): update translation (Italian)
Currently translated at 98.4% (1262 of 1282 strings)

Co-authored-by: Riccardo Giovanetti <riccardo.giovanetti@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/it/
Translation: InvokeAI/Web UI
2024-07-13 08:38:22 +10:00
Sergey Borisov
499e4d4fde Add preview extension to check logic 2024-07-13 00:45:04 +03:00
Sergey Borisov
e961dd1dec Remove remains of priority logic 2024-07-13 00:44:21 +03:00
Sergey Borisov
7e00526999 Remove overrides logic for now 2024-07-13 00:28:56 +03:00
Sergey Borisov
3a9dda9177 Renames 2024-07-12 22:44:00 +03:00
Sergey Borisov
bd8ae5d896 Simplify guidance modes 2024-07-12 22:01:37 +03:00
Sergey Borisov
87e96e1be2 Rename modifiers to callbacks, convert order to int, a bit unify injection points 2024-07-12 22:01:05 +03:00
Sergey Borisov
0bc60378d3 A bit rework conditioning convert to unet kwargs 2024-07-12 20:43:32 +03:00
Sergey Borisov
9cc852cf7f Base code from draft PR 2024-07-12 20:31:26 +03:00
psychedelicious
712cf00a82 fix(app): vae tile size field description 2024-07-12 06:30:27 -07:00
psychedelicious
fb1130c644 fix(ui): do not invalidate image dto cache when deleting image 2024-07-12 14:25:38 +10:00
psychedelicious
0f65a12cf3 fix(ui): handle archived boards like other boards when they are visible, do not reset board selection when autoadd board is hidden 2024-07-12 14:25:38 +10:00
psychedelicious
84abdc5780 fix(ui): prevent cutoff of last board 2024-07-12 14:25:38 +10:00
Ryan Dick
2320701929 Do not crash if there are invalid model configs in the DB (#6593)
## Summary

This PR changes the handling of invalid model configs in the DB to log a
warning rather than crashing the app.

This change is being made in preparation for some upcoming new model
additions. Previously, if a user rolled back from an app version that
added a new model type, the app would not launch until the DB was fixed.
This PR changes this behaviour to allow rollbacks of this type (with
warnings).

**Keep in mind that this change is only helpful to users _rolling back
to a version that has this fix_. I.e. it offers no help in the first
version that includes it.**

## QA Instructions

1. Run the Spandrel model branch, which adds a new model type
https://github.com/invoke-ai/InvokeAI/pull/6556.
2. Add a spandrel model via the model manager.
3. Rollback to main. The app will crash on launch due to the invalid
spandrel model config.
4. Checkout this branch. The app should now run with warnings about the
invalid model config.


## Checklist

- [x] _The PR has a short but descriptive title, suitable for a
changelog_
- [ ] _Tests added / updated (if applicable)_
- [x] _Documentation added / updated (if applicable)_
2024-07-11 21:15:51 -04:00
Ryan Dick
69af099532 Warn on invalid model configs in the DB rather than crashing. 2024-07-11 21:05:55 -04:00
Ryan Dick
0428ce73a9 Add early cancellation to SpandrelImageToImageInvocation. 2024-07-11 15:42:33 -04:00
Alexander Eichhorn
5795617f86 translationBot(ui): update translation (German)
Currently translated at 67.0% (859 of 1282 strings)

Co-authored-by: Alexander Eichhorn <pfannkuchensack@einfach-doof.de>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/de/
Translation: InvokeAI/Web UI
2024-07-11 19:23:28 +10:00
Nathan
b533bc072e translationBot(ui): update translation (French)
Currently translated at 25.2% (322 of 1275 strings)

Co-authored-by: Nathan <bonnemainsnathan@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/fr/
Translation: InvokeAI/Web UI
2024-07-11 19:23:28 +10:00
Васянатор
d7199c7ca6 translationBot(ui): update translation (Russian)
Currently translated at 100.0% (1282 of 1282 strings)

translationBot(ui): update translation (Russian)

Currently translated at 100.0% (1280 of 1280 strings)

translationBot(ui): update translation (Russian)

Currently translated at 100.0% (1275 of 1275 strings)

translationBot(ui): update translation (Russian)

Currently translated at 100.0% (1273 of 1273 strings)

Co-authored-by: Васянатор <ilabulanov339@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/ru/
Translation: InvokeAI/Web UI
2024-07-11 19:23:28 +10:00
Riccardo Giovanetti
a69284367b translationBot(ui): update translation (Italian)
Currently translated at 98.2% (1260 of 1282 strings)

translationBot(ui): update translation (Italian)

Currently translated at 98.4% (1260 of 1280 strings)

translationBot(ui): update translation (Italian)

Currently translated at 98.4% (1255 of 1275 strings)

translationBot(ui): update translation (Italian)

Currently translated at 98.4% (1253 of 1273 strings)

translationBot(ui): update translation (Italian)

Currently translated at 98.4% (1245 of 1265 strings)

Co-authored-by: Riccardo Giovanetti <riccardo.giovanetti@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/it/
Translation: InvokeAI/Web UI
2024-07-11 19:23:28 +10:00
Phrixus2023
c4d2fe9c65 translationBot(ui): update translation (Chinese (Simplified))
Currently translated at 76.5% (968 of 1265 strings)

Co-authored-by: Phrixus2023 <920414016@qq.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/zh_Hans/
Translation: InvokeAI/Web UI
2024-07-11 19:23:28 +10:00
Hosted Weblate
fe0d56de5c translationBot(ui): update translation files
Updated by "Remove blank strings" hook in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/
Translation: InvokeAI/Web UI
2024-07-11 19:23:28 +10:00
HAL
7aec5624f7 translationBot(ui): update translation (Japanese)
Currently translated at 50.4% (636 of 1261 strings)

Co-authored-by: HAL <HALQME@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/ja/
Translation: InvokeAI/Web UI
2024-07-11 19:23:28 +10:00
B N
2f3ec41f94 translationBot(ui): update translation (German)
Currently translated at 67.3% (849 of 1261 strings)

Co-authored-by: B N <berndnieschalk@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/de/
Translation: InvokeAI/Web UI
2024-07-11 19:23:28 +10:00
Ryan Dick
d0d2955992 Reduce peak VRAM utilization of SpandrelImageToImageInvocation. 2024-07-10 14:25:19 -04:00
Ryan Dick
d868d5d584 Make SpandrelImageToImage tiling much faster. 2024-07-10 14:25:19 -04:00
Ryan Dick
ab775726b7 Add tiling support to the SpoandrelImageToImage node. 2024-07-10 14:25:19 -04:00
Ryan Dick
650902dc29 Fix broken unit test caused by non-existent model path. 2024-07-10 13:59:17 -04:00
Ryan Dick
7b5d4935b4 Merge branch 'main' into ryan/spandrel-upscale 2024-07-09 13:47:11 -04:00
Ryan Dick
ecbff2aa44 Whoops... forgot to commit this file. 2024-07-05 14:57:05 -04:00
Ryan Dick
0ce6ec634d Do not assign the result of SpandrelImageToImageModel.load_from_file(...) during probe to ensure that the model is immediately gc'd. 2024-07-05 14:05:12 -04:00
Ryan Dick
d09999736c Rename spandrel models to 'Image-to-Image Model' throughout the UI. 2024-07-05 14:04:08 -04:00
Ryan Dick
a405f14ea2 Fix SpandrelImageToImageModel size calculation for the model cache. 2024-07-03 16:38:16 -04:00
Ryan Dick
9d3739244f Prettier formatting. 2024-07-03 16:28:21 -04:00
Ryan Dick
534528b85a Re-generate schema.ts 2024-07-03 16:28:21 -04:00
Ryan Dick
114320ee69 (minor) typo 2024-07-03 16:28:21 -04:00
Ryan Dick
6161aa73af Move pil_to_tensor() and tensor_to_pil() utilities to the SpandrelImageToImage class. 2024-07-03 16:28:21 -04:00
Ryan Dick
1ab20f43c8 Tidy spandrel model probe logic, and document the reasons behind the current implementation. 2024-07-03 16:28:21 -04:00
Ryan Dick
9328c17ded Add Spandrel models to the list of models in the Model Manager tab. 2024-07-03 16:28:21 -04:00
Ryan Dick
c1c8e55e8e Fix static check errors. 2024-07-03 16:28:21 -04:00
Ryan Dick
504a42fe61 typo: fix UIType on Spandrel Upscaling node. 2024-07-03 16:28:21 -04:00
Ryan Dick
29c8ddfb88 WIP - A bunch of boilerplate to support Spandrel Image-to-Image models throughout the model manager and the frontend. 2024-07-03 16:28:21 -04:00
Ryan Dick
95079dc7d4 Use a ModelIdentifierField to identify the spandrel model in the UpscaleSpandrelInvocation. 2024-07-03 16:28:21 -04:00
Ryan Dick
2a1514272f Set the dtype correctly for SpandrelImageToImageModels when they are loaded. 2024-07-03 16:28:21 -04:00
Ryan Dick
59ce9cf41c WIP - Begin to integrate SpandreImageToImageModel type into the model manager. 2024-07-03 16:28:21 -04:00
Ryan Dick
e6abea7bc5 (minor) Remove redundant else clause on a for-loop with no break statement. 2024-07-03 16:28:21 -04:00
Ryan Dick
c335f92345 (minor) simplify startswith(...) syntax. 2024-07-03 16:28:21 -04:00
Ryan Dick
c1afe35704 Add prototype invocation for running upscaling models with spandrel. 2024-07-03 16:28:21 -04:00
Ryan Dick
e4813f800a Update calc_model_size_by_data(...) to handle all expected model types, and to log an error if an unexpected model type is received. 2024-07-02 21:51:45 -04:00
952 changed files with 55933 additions and 52315 deletions

View File

@@ -62,7 +62,7 @@ jobs:
- name: install ruff
if: ${{ steps.changed-files.outputs.python_any_changed == 'true' || inputs.always_run == true }}
run: pip install ruff
run: pip install ruff==0.6.0
shell: bash
- name: ruff check

View File

@@ -55,6 +55,7 @@ RUN --mount=type=cache,target=/root/.cache/pip \
FROM node:20-slim AS web-builder
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack use pnpm@8.x
RUN corepack enable
WORKDIR /build

View File

@@ -1,20 +1,22 @@
# Invoke in Docker
- Ensure that Docker can use the GPU on your system
- This documentation assumes Linux, but should work similarly under Windows with WSL2
First things first:
- Ensure that Docker can use your [NVIDIA][nvidia docker docs] or [AMD][amd docker docs] GPU.
- This document assumes a Linux system, but should work similarly under Windows with WSL2.
- We don't recommend running Invoke in Docker on macOS at this time. It works, but very slowly.
## Quickstart :lightning:
## Quickstart
No `docker compose`, no persistence, just a simple one-liner using the official images:
No `docker compose`, no persistence, single command, using the official images:
**CUDA:**
**CUDA (NVIDIA GPU):**
```bash
docker run --runtime=nvidia --gpus=all --publish 9090:9090 ghcr.io/invoke-ai/invokeai
```
**ROCm:**
**ROCm (AMD GPU):**
```bash
docker run --device /dev/kfd --device /dev/dri --publish 9090:9090 ghcr.io/invoke-ai/invokeai:main-rocm
@@ -22,12 +24,20 @@ docker run --device /dev/kfd --device /dev/dri --publish 9090:9090 ghcr.io/invok
Open `http://localhost:9090` in your browser once the container finishes booting, install some models, and generate away!
> [!TIP]
> To persist your data (including downloaded models) outside of the container, add a `--volume/-v` flag to the above command, e.g.: `docker run --volume /some/local/path:/invokeai <...the rest of the command>`
### Data persistence
To persist your generated images and downloaded models outside of the container, add a `--volume/-v` flag to the above command, e.g.:
```bash
docker run --volume /some/local/path:/invokeai {...etc...}
```
`/some/local/path/invokeai` will contain all your data.
It can *usually* be reused between different installs of Invoke. Tread with caution and read the release notes!
## Customize the container
We ship the `run.sh` script, which is a convenient wrapper around `docker compose` for cases where custom image build args are needed. Alternatively, the familiar `docker compose` commands work just as well.
The included `run.sh` script is a convenience wrapper around `docker compose`. It can be helpful for passing additional build arguments to `docker compose`. Alternatively, the familiar `docker compose` commands work just as well.
```bash
cd docker
@@ -38,11 +48,14 @@ cp .env.sample .env
It will take a few minutes to build the image the first time. Once the application starts up, open `http://localhost:9090` in your browser to invoke!
>[!TIP]
>When using the `run.sh` script, the container will continue running after Ctrl+C. To shut it down, use the `docker compose down` command.
## Docker setup in detail
#### Linux
1. Ensure builkit is enabled in the Docker daemon settings (`/etc/docker/daemon.json`)
1. Ensure buildkit is enabled in the Docker daemon settings (`/etc/docker/daemon.json`)
2. Install the `docker compose` plugin using your package manager, or follow a [tutorial](https://docs.docker.com/compose/install/linux/#install-using-the-repository).
- The deprecated `docker-compose` (hyphenated) CLI probably won't work. Update to a recent version.
3. Ensure docker daemon is able to access the GPU.
@@ -98,25 +111,7 @@ GPU_DRIVER=cuda
Any environment variables supported by InvokeAI can be set here. See the [Configuration docs](https://invoke-ai.github.io/InvokeAI/features/CONFIGURATION/) for further detail.
## Even More Customizing!
---
See the `docker-compose.yml` file. The `command` instruction can be uncommented and used to run arbitrary startup commands. Some examples below.
### Reconfigure the runtime directory
Can be used to download additional models from the supported model list
In conjunction with `INVOKEAI_ROOT` can be also used to initialize a runtime directory
```yaml
command:
- invokeai-configure
- --yes
```
Or install models:
```yaml
command:
- invokeai-model-install
```
[nvidia docker docs]: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
[amd docker docs]: https://rocm.docs.amd.com/projects/install-on-linux/en/latest/how-to/docker.html

View File

@@ -408,7 +408,7 @@ config = get_config()
logger = InvokeAILogger.get_logger(config=config)
db = SqliteDatabase(config.db_path, logger)
record_store = ModelRecordServiceSQL(db)
record_store = ModelRecordServiceSQL(db, logger)
queue = DownloadQueueService()
queue.start()

View File

@@ -17,7 +17,7 @@
set -eu
# Ensure we're in the correct folder in case user's CWD is somewhere else
scriptdir=$(dirname "$0")
scriptdir=$(dirname $(readlink -f "$0"))
cd "$scriptdir"
. .venv/bin/activate

View File

@@ -1,5 +1,6 @@
# Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654)
import asyncio
from logging import Logger
import torch
@@ -31,6 +32,8 @@ from invokeai.app.services.session_processor.session_processor_default import (
)
from invokeai.app.services.session_queue.session_queue_sqlite import SqliteSessionQueue
from invokeai.app.services.shared.sqlite.sqlite_util import init_db
from invokeai.app.services.style_preset_images.style_preset_images_disk import StylePresetImageFileStorageDisk
from invokeai.app.services.style_preset_records.style_preset_records_sqlite import SqliteStylePresetRecordsStorage
from invokeai.app.services.urls.urls_default import LocalUrlService
from invokeai.app.services.workflow_records.workflow_records_sqlite import SqliteWorkflowRecordsStorage
from invokeai.backend.stable_diffusion.diffusion.conditioning_data import ConditioningFieldData
@@ -63,7 +66,12 @@ class ApiDependencies:
invoker: Invoker
@staticmethod
def initialize(config: InvokeAIAppConfig, event_handler_id: int, logger: Logger = logger) -> None:
def initialize(
config: InvokeAIAppConfig,
event_handler_id: int,
loop: asyncio.AbstractEventLoop,
logger: Logger = logger,
) -> None:
logger.info(f"InvokeAI version {__version__}")
logger.info(f"Root directory = {str(config.root_path)}")
@@ -74,6 +82,7 @@ class ApiDependencies:
image_files = DiskImageFileStorage(f"{output_folder}/images")
model_images_folder = config.models_path
style_presets_folder = config.style_presets_path
db = init_db(config=config, logger=logger, image_files=image_files)
@@ -84,7 +93,7 @@ class ApiDependencies:
board_images = BoardImagesService()
board_records = SqliteBoardRecordStorage(db=db)
boards = BoardService()
events = FastAPIEventService(event_handler_id)
events = FastAPIEventService(event_handler_id, loop=loop)
bulk_download = BulkDownloadService()
image_records = SqliteImageRecordStorage(db=db)
images = ImageService()
@@ -99,7 +108,7 @@ class ApiDependencies:
model_images_service = ModelImageFileStorageDisk(model_images_folder / "model_images")
model_manager = ModelManagerService.build_model_manager(
app_config=configuration,
model_record_service=ModelRecordServiceSQL(db=db),
model_record_service=ModelRecordServiceSQL(db=db, logger=logger),
download_queue=download_queue_service,
events=events,
)
@@ -109,6 +118,8 @@ class ApiDependencies:
session_queue = SqliteSessionQueue(db=db)
urls = LocalUrlService()
workflow_records = SqliteWorkflowRecordsStorage(db=db)
style_preset_records = SqliteStylePresetRecordsStorage(db=db)
style_preset_image_files = StylePresetImageFileStorageDisk(style_presets_folder / "images")
services = InvocationServices(
board_image_records=board_image_records,
@@ -134,6 +145,8 @@ class ApiDependencies:
workflow_records=workflow_records,
tensors=tensors,
conditioning=conditioning,
style_preset_records=style_preset_records,
style_preset_image_files=style_preset_image_files,
)
ApiDependencies.invoker = Invoker(services)

View File

@@ -218,9 +218,8 @@ async def get_image_workflow(
raise HTTPException(status_code=404)
@images_router.api_route(
@images_router.get(
"/i/{image_name}/full",
methods=["GET", "HEAD"],
operation_id="get_image_full",
response_class=Response,
responses={
@@ -231,24 +230,30 @@ async def get_image_workflow(
404: {"description": "Image not found"},
},
)
@images_router.head(
"/i/{image_name}/full",
operation_id="get_image_full_head",
response_class=Response,
responses={
200: {
"description": "Return the full-resolution image",
"content": {"image/png": {}},
},
404: {"description": "Image not found"},
},
)
async def get_image_full(
image_name: str = Path(description="The name of full-resolution image file to get"),
) -> FileResponse:
) -> Response:
"""Gets a full-resolution image file"""
try:
path = ApiDependencies.invoker.services.images.get_path(image_name)
if not ApiDependencies.invoker.services.images.validate_path(path):
raise HTTPException(status_code=404)
response = FileResponse(
path,
media_type="image/png",
filename=image_name,
content_disposition_type="inline",
)
with open(path, "rb") as f:
content = f.read()
response = Response(content, media_type="image/png")
response.headers["Cache-Control"] = f"max-age={IMAGE_MAX_AGE}"
response.headers["Content-Disposition"] = f'inline; filename="{image_name}"'
return response
except Exception:
raise HTTPException(status_code=404)
@@ -268,15 +273,14 @@ async def get_image_full(
)
async def get_image_thumbnail(
image_name: str = Path(description="The name of thumbnail image file to get"),
) -> FileResponse:
) -> Response:
"""Gets a thumbnail image file"""
try:
path = ApiDependencies.invoker.services.images.get_path(image_name, thumbnail=True)
if not ApiDependencies.invoker.services.images.validate_path(path):
raise HTTPException(status_code=404)
response = FileResponse(path, media_type="image/webp", content_disposition_type="inline")
with open(path, "rb") as f:
content = f.read()
response = Response(content, media_type="image/webp")
response.headers["Cache-Control"] = f"max-age={IMAGE_MAX_AGE}"
return response
except Exception:

View File

@@ -6,7 +6,7 @@ import pathlib
import traceback
from copy import deepcopy
from tempfile import TemporaryDirectory
from typing import Any, Dict, List, Optional, Type
from typing import List, Optional, Type
from fastapi import Body, Path, Query, Response, UploadFile
from fastapi.responses import FileResponse, HTMLResponse
@@ -430,13 +430,11 @@ async def delete_model_image(
async def install_model(
source: str = Query(description="Model source to install, can be a local path, repo_id, or remote URL"),
inplace: Optional[bool] = Query(description="Whether or not to install a local model in place", default=False),
# TODO(MM2): Can we type this?
config: Optional[Dict[str, Any]] = Body(
description="Dict of fields that override auto-probed values in the model config record, such as name, description and prediction_type ",
default=None,
access_token: Optional[str] = Query(description="access token for the remote resource", default=None),
config: ModelRecordChanges = Body(
description="Object containing fields that override auto-probed values in the model config record, such as name, description and prediction_type ",
example={"name": "string", "description": "string"},
),
access_token: Optional[str] = None,
) -> ModelInstallJob:
"""Install a model using a string identifier.
@@ -451,8 +449,9 @@ async def install_model(
- model/name:fp16:path/to/model.safetensors
- model/name::path/to/model.safetensors
`config` is an optional dict containing model configuration values that will override
the ones that are probed automatically.
`config` is a ModelRecordChanges object. Fields in this object will override
the ones that are probed automatically. Pass an empty object to accept
all the defaults.
`access_token` is an optional access token for use with Urls that require
authentication.
@@ -737,7 +736,7 @@ async def convert_model(
# write the converted file to the convert path
raw_model = converted_model.model
assert hasattr(raw_model, "save_pretrained")
raw_model.save_pretrained(convert_path)
raw_model.save_pretrained(convert_path) # type: ignore
assert convert_path.exists()
# temporarily rename the original safetensors file so that there is no naming conflict
@@ -750,12 +749,12 @@ async def convert_model(
try:
new_key = installer.install_path(
convert_path,
config={
"name": original_name,
"description": model_config.description,
"hash": model_config.hash,
"source": model_config.source,
},
config=ModelRecordChanges(
name=original_name,
description=model_config.description,
hash=model_config.hash,
source=model_config.source,
),
)
except Exception as e:
logger.error(str(e))

View File

@@ -11,6 +11,7 @@ from invokeai.app.services.session_queue.session_queue_common import (
Batch,
BatchStatus,
CancelByBatchIDsResult,
CancelByOriginResult,
ClearResult,
EnqueueBatchResult,
PruneResult,
@@ -105,6 +106,19 @@ async def cancel_by_batch_ids(
return ApiDependencies.invoker.services.session_queue.cancel_by_batch_ids(queue_id=queue_id, batch_ids=batch_ids)
@session_queue_router.put(
"/{queue_id}/cancel_by_origin",
operation_id="cancel_by_origin",
responses={200: {"model": CancelByBatchIDsResult}},
)
async def cancel_by_origin(
queue_id: str = Path(description="The queue id to perform this operation on"),
origin: str = Query(description="The origin to cancel all queue items for"),
) -> CancelByOriginResult:
"""Immediately cancels all queue items with the given origin"""
return ApiDependencies.invoker.services.session_queue.cancel_by_origin(queue_id=queue_id, origin=origin)
@session_queue_router.put(
"/{queue_id}/clear",
operation_id="clear",

View File

@@ -0,0 +1,274 @@
import csv
import io
import json
import traceback
from typing import Optional
import pydantic
from fastapi import APIRouter, File, Form, HTTPException, Path, Response, UploadFile
from fastapi.responses import FileResponse
from PIL import Image
from pydantic import BaseModel, Field
from invokeai.app.api.dependencies import ApiDependencies
from invokeai.app.api.routers.model_manager import IMAGE_MAX_AGE
from invokeai.app.services.style_preset_images.style_preset_images_common import StylePresetImageFileNotFoundException
from invokeai.app.services.style_preset_records.style_preset_records_common import (
InvalidPresetImportDataError,
PresetData,
PresetType,
StylePresetChanges,
StylePresetNotFoundError,
StylePresetRecordWithImage,
StylePresetWithoutId,
UnsupportedFileTypeError,
parse_presets_from_file,
)
class StylePresetFormData(BaseModel):
name: str = Field(description="Preset name")
positive_prompt: str = Field(description="Positive prompt")
negative_prompt: str = Field(description="Negative prompt")
type: PresetType = Field(description="Preset type")
style_presets_router = APIRouter(prefix="/v1/style_presets", tags=["style_presets"])
@style_presets_router.get(
"/i/{style_preset_id}",
operation_id="get_style_preset",
responses={
200: {"model": StylePresetRecordWithImage},
},
)
async def get_style_preset(
style_preset_id: str = Path(description="The style preset to get"),
) -> StylePresetRecordWithImage:
"""Gets a style preset"""
try:
image = ApiDependencies.invoker.services.style_preset_image_files.get_url(style_preset_id)
style_preset = ApiDependencies.invoker.services.style_preset_records.get(style_preset_id)
return StylePresetRecordWithImage(image=image, **style_preset.model_dump())
except StylePresetNotFoundError:
raise HTTPException(status_code=404, detail="Style preset not found")
@style_presets_router.patch(
"/i/{style_preset_id}",
operation_id="update_style_preset",
responses={
200: {"model": StylePresetRecordWithImage},
},
)
async def update_style_preset(
image: Optional[UploadFile] = File(description="The image file to upload", default=None),
style_preset_id: str = Path(description="The id of the style preset to update"),
data: str = Form(description="The data of the style preset to update"),
) -> StylePresetRecordWithImage:
"""Updates a style preset"""
if image is not None:
if not image.content_type or not image.content_type.startswith("image"):
raise HTTPException(status_code=415, detail="Not an image")
contents = await image.read()
try:
pil_image = Image.open(io.BytesIO(contents))
except Exception:
ApiDependencies.invoker.services.logger.error(traceback.format_exc())
raise HTTPException(status_code=415, detail="Failed to read image")
try:
ApiDependencies.invoker.services.style_preset_image_files.save(style_preset_id, pil_image)
except ValueError as e:
raise HTTPException(status_code=409, detail=str(e))
else:
try:
ApiDependencies.invoker.services.style_preset_image_files.delete(style_preset_id)
except StylePresetImageFileNotFoundException:
pass
try:
parsed_data = json.loads(data)
validated_data = StylePresetFormData(**parsed_data)
name = validated_data.name
type = validated_data.type
positive_prompt = validated_data.positive_prompt
negative_prompt = validated_data.negative_prompt
except pydantic.ValidationError:
raise HTTPException(status_code=400, detail="Invalid preset data")
preset_data = PresetData(positive_prompt=positive_prompt, negative_prompt=negative_prompt)
changes = StylePresetChanges(name=name, preset_data=preset_data, type=type)
style_preset_image = ApiDependencies.invoker.services.style_preset_image_files.get_url(style_preset_id)
style_preset = ApiDependencies.invoker.services.style_preset_records.update(
style_preset_id=style_preset_id, changes=changes
)
return StylePresetRecordWithImage(image=style_preset_image, **style_preset.model_dump())
@style_presets_router.delete(
"/i/{style_preset_id}",
operation_id="delete_style_preset",
)
async def delete_style_preset(
style_preset_id: str = Path(description="The style preset to delete"),
) -> None:
"""Deletes a style preset"""
try:
ApiDependencies.invoker.services.style_preset_image_files.delete(style_preset_id)
except StylePresetImageFileNotFoundException:
pass
ApiDependencies.invoker.services.style_preset_records.delete(style_preset_id)
@style_presets_router.post(
"/",
operation_id="create_style_preset",
responses={
200: {"model": StylePresetRecordWithImage},
},
)
async def create_style_preset(
image: Optional[UploadFile] = File(description="The image file to upload", default=None),
data: str = Form(description="The data of the style preset to create"),
) -> StylePresetRecordWithImage:
"""Creates a style preset"""
try:
parsed_data = json.loads(data)
validated_data = StylePresetFormData(**parsed_data)
name = validated_data.name
type = validated_data.type
positive_prompt = validated_data.positive_prompt
negative_prompt = validated_data.negative_prompt
except pydantic.ValidationError:
raise HTTPException(status_code=400, detail="Invalid preset data")
preset_data = PresetData(positive_prompt=positive_prompt, negative_prompt=negative_prompt)
style_preset = StylePresetWithoutId(name=name, preset_data=preset_data, type=type)
new_style_preset = ApiDependencies.invoker.services.style_preset_records.create(style_preset=style_preset)
if image is not None:
if not image.content_type or not image.content_type.startswith("image"):
raise HTTPException(status_code=415, detail="Not an image")
contents = await image.read()
try:
pil_image = Image.open(io.BytesIO(contents))
except Exception:
ApiDependencies.invoker.services.logger.error(traceback.format_exc())
raise HTTPException(status_code=415, detail="Failed to read image")
try:
ApiDependencies.invoker.services.style_preset_image_files.save(new_style_preset.id, pil_image)
except ValueError as e:
raise HTTPException(status_code=409, detail=str(e))
preset_image = ApiDependencies.invoker.services.style_preset_image_files.get_url(new_style_preset.id)
return StylePresetRecordWithImage(image=preset_image, **new_style_preset.model_dump())
@style_presets_router.get(
"/",
operation_id="list_style_presets",
responses={
200: {"model": list[StylePresetRecordWithImage]},
},
)
async def list_style_presets() -> list[StylePresetRecordWithImage]:
"""Gets a page of style presets"""
style_presets_with_image: list[StylePresetRecordWithImage] = []
style_presets = ApiDependencies.invoker.services.style_preset_records.get_many()
for preset in style_presets:
image = ApiDependencies.invoker.services.style_preset_image_files.get_url(preset.id)
style_preset_with_image = StylePresetRecordWithImage(image=image, **preset.model_dump())
style_presets_with_image.append(style_preset_with_image)
return style_presets_with_image
@style_presets_router.get(
"/i/{style_preset_id}/image",
operation_id="get_style_preset_image",
responses={
200: {
"description": "The style preset image was fetched successfully",
},
400: {"description": "Bad request"},
404: {"description": "The style preset image could not be found"},
},
status_code=200,
)
async def get_style_preset_image(
style_preset_id: str = Path(description="The id of the style preset image to get"),
) -> FileResponse:
"""Gets an image file that previews the model"""
try:
path = ApiDependencies.invoker.services.style_preset_image_files.get_path(style_preset_id)
response = FileResponse(
path,
media_type="image/png",
filename=style_preset_id + ".png",
content_disposition_type="inline",
)
response.headers["Cache-Control"] = f"max-age={IMAGE_MAX_AGE}"
return response
except Exception:
raise HTTPException(status_code=404)
@style_presets_router.get(
"/export",
operation_id="export_style_presets",
responses={200: {"content": {"text/csv": {}}, "description": "A CSV file with the requested data."}},
status_code=200,
)
async def export_style_presets():
# Create an in-memory stream to store the CSV data
output = io.StringIO()
writer = csv.writer(output)
# Write the header
writer.writerow(["name", "prompt", "negative_prompt"])
style_presets = ApiDependencies.invoker.services.style_preset_records.get_many(type=PresetType.User)
for preset in style_presets:
writer.writerow([preset.name, preset.preset_data.positive_prompt, preset.preset_data.negative_prompt])
csv_data = output.getvalue()
output.close()
return Response(
content=csv_data,
media_type="text/csv",
headers={"Content-Disposition": "attachment; filename=prompt_templates.csv"},
)
@style_presets_router.post(
"/import",
operation_id="import_style_presets",
)
async def import_style_presets(file: UploadFile = File(description="The file to import")):
try:
style_presets = await parse_presets_from_file(file)
ApiDependencies.invoker.services.style_preset_records.create_many(style_presets)
except InvalidPresetImportDataError as e:
ApiDependencies.invoker.services.logger.error(traceback.format_exc())
raise HTTPException(status_code=400, detail=str(e))
except UnsupportedFileTypeError as e:
ApiDependencies.invoker.services.logger.error(traceback.format_exc())
raise HTTPException(status_code=415, detail=str(e))

View File

@@ -30,6 +30,7 @@ from invokeai.app.api.routers import (
images,
model_manager,
session_queue,
style_presets,
utilities,
workflows,
)
@@ -55,11 +56,13 @@ mimetypes.add_type("text/css", ".css")
torch_device_name = TorchDevice.get_torch_device_name()
logger.info(f"Using torch device: {torch_device_name}")
loop = asyncio.new_event_loop()
@asynccontextmanager
async def lifespan(app: FastAPI):
# Add startup event to load dependencies
ApiDependencies.initialize(config=app_config, event_handler_id=event_handler_id, logger=logger)
ApiDependencies.initialize(config=app_config, event_handler_id=event_handler_id, loop=loop, logger=logger)
yield
# Shut down threads
ApiDependencies.shutdown()
@@ -106,6 +109,7 @@ app.include_router(board_images.board_images_router, prefix="/api")
app.include_router(app_info.app_router, prefix="/api")
app.include_router(session_queue.session_queue_router, prefix="/api")
app.include_router(workflows.workflows_router, prefix="/api")
app.include_router(style_presets.style_presets_router, prefix="/api")
app.openapi = get_openapi_func(app)
@@ -161,6 +165,7 @@ def invoke_api() -> None:
# Taken from https://waylonwalker.com/python-find-available-port/, thanks Waylon!
# https://github.com/WaylonWalker
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(1)
if s.connect_ex(("localhost", port)) == 0:
return find_port(port=port + 1)
else:
@@ -183,8 +188,6 @@ def invoke_api() -> None:
check_cudnn(logger)
# Start our own event loop for eventing usage
loop = asyncio.new_event_loop()
config = uvicorn.Config(
app=app,
host=app_config.host,

View File

@@ -80,12 +80,12 @@ class CompelInvocation(BaseInvocation):
with (
# apply all patches while the model is on the target device
text_encoder_info.model_on_device() as (model_state_dict, text_encoder),
text_encoder_info.model_on_device() as (cached_weights, text_encoder),
tokenizer_info as tokenizer,
ModelPatcher.apply_lora_text_encoder(
text_encoder,
loras=_lora_loader(),
model_state_dict=model_state_dict,
cached_weights=cached_weights,
),
# Apply CLIP Skip after LoRA to prevent LoRA application from failing on skipped layers.
ModelPatcher.apply_clip_skip(text_encoder, self.clip.skipped_layers),
@@ -175,13 +175,13 @@ class SDXLPromptInvocationBase:
with (
# apply all patches while the model is on the target device
text_encoder_info.model_on_device() as (state_dict, text_encoder),
text_encoder_info.model_on_device() as (cached_weights, text_encoder),
tokenizer_info as tokenizer,
ModelPatcher.apply_lora(
text_encoder,
loras=_lora_loader(),
prefix=lora_prefix,
model_state_dict=state_dict,
cached_weights=cached_weights,
),
# Apply CLIP Skip after LoRA to prevent LoRA application from failing on skipped layers.
ModelPatcher.apply_clip_skip(text_encoder, clip_field.skipped_layers),

View File

@@ -21,6 +21,8 @@ from controlnet_aux import (
from controlnet_aux.util import HWC3, ade_palette
from PIL import Image
from pydantic import BaseModel, Field, field_validator, model_validator
from transformers import pipeline
from transformers.pipelines import DepthEstimationPipeline
from invokeai.app.invocations.baseinvocation import (
BaseInvocation,
@@ -44,13 +46,12 @@ from invokeai.app.invocations.util import validate_begin_end_step, validate_weig
from invokeai.app.services.shared.invocation_context import InvocationContext
from invokeai.app.util.controlnet_utils import CONTROLNET_MODE_VALUES, CONTROLNET_RESIZE_VALUES, heuristic_resize
from invokeai.backend.image_util.canny import get_canny_edges
from invokeai.backend.image_util.depth_anything import DEPTH_ANYTHING_MODELS, DepthAnythingDetector
from invokeai.backend.image_util.depth_anything.depth_anything_pipeline import DepthAnythingPipeline
from invokeai.backend.image_util.dw_openpose import DWPOSE_MODELS, DWOpenposeDetector
from invokeai.backend.image_util.hed import HEDProcessor
from invokeai.backend.image_util.lineart import LineartProcessor
from invokeai.backend.image_util.lineart_anime import LineartAnimeProcessor
from invokeai.backend.image_util.util import np_to_pil, pil_to_np
from invokeai.backend.util.devices import TorchDevice
class ControlField(BaseModel):
@@ -592,7 +593,14 @@ class ColorMapImageProcessorInvocation(ImageProcessorInvocation):
return color_map
DEPTH_ANYTHING_MODEL_SIZES = Literal["large", "base", "small"]
DEPTH_ANYTHING_MODEL_SIZES = Literal["large", "base", "small", "small_v2"]
# DepthAnything V2 Small model is licensed under Apache 2.0 but not the base and large models.
DEPTH_ANYTHING_MODELS = {
"large": "LiheYoung/depth-anything-large-hf",
"base": "LiheYoung/depth-anything-base-hf",
"small": "LiheYoung/depth-anything-small-hf",
"small_v2": "depth-anything/Depth-Anything-V2-Small-hf",
}
@invocation(
@@ -600,28 +608,33 @@ DEPTH_ANYTHING_MODEL_SIZES = Literal["large", "base", "small"]
title="Depth Anything Processor",
tags=["controlnet", "depth", "depth anything"],
category="controlnet",
version="1.1.2",
version="1.1.3",
)
class DepthAnythingImageProcessorInvocation(ImageProcessorInvocation):
"""Generates a depth map based on the Depth Anything algorithm"""
model_size: DEPTH_ANYTHING_MODEL_SIZES = InputField(
default="small", description="The size of the depth model to use"
default="small_v2", description="The size of the depth model to use"
)
resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.image_res)
def run_processor(self, image: Image.Image) -> Image.Image:
def loader(model_path: Path):
return DepthAnythingDetector.load_model(
model_path, model_size=self.model_size, device=TorchDevice.choose_torch_device()
)
def load_depth_anything(model_path: Path):
depth_anything_pipeline = pipeline(model=str(model_path), task="depth-estimation", local_files_only=True)
assert isinstance(depth_anything_pipeline, DepthEstimationPipeline)
return DepthAnythingPipeline(depth_anything_pipeline)
with self._context.models.load_remote_model(
source=DEPTH_ANYTHING_MODELS[self.model_size], loader=loader
) as model:
depth_anything_detector = DepthAnythingDetector(model, TorchDevice.choose_torch_device())
processed_image = depth_anything_detector(image=image, resolution=self.resolution)
return processed_image
source=DEPTH_ANYTHING_MODELS[self.model_size], loader=load_depth_anything
) as depth_anything_detector:
assert isinstance(depth_anything_detector, DepthAnythingPipeline)
depth_map = depth_anything_detector.generate_depth(image)
# Resizing to user target specified size
new_height = int(image.size[1] * (self.resolution / image.size[0]))
depth_map = depth_map.resize((self.resolution, new_height))
return depth_map
@invocation(

View File

@@ -39,7 +39,7 @@ class GradientMaskOutput(BaseInvocationOutput):
title="Create Gradient Mask",
tags=["mask", "denoise"],
category="latents",
version="1.1.0",
version="1.2.0",
)
class CreateGradientMaskInvocation(BaseInvocation):
"""Creates mask for denoising model run."""
@@ -93,6 +93,7 @@ class CreateGradientMaskInvocation(BaseInvocation):
# redistribute blur so that the original edges are 0 and blur outwards to 1
blur_tensor = (blur_tensor - 0.5) * 2
blur_tensor[blur_tensor < 0] = 0.0
threshold = 1 - self.minimum_denoise

View File

@@ -1,5 +1,6 @@
# Copyright (c) 2023 Kyle Schouviller (https://github.com/kyle0654)
import inspect
import os
from contextlib import ExitStack
from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
@@ -36,9 +37,10 @@ from invokeai.app.services.shared.invocation_context import InvocationContext
from invokeai.app.util.controlnet_utils import prepare_control_image
from invokeai.backend.ip_adapter.ip_adapter import IPAdapter
from invokeai.backend.lora import LoRAModelRaw
from invokeai.backend.model_manager import BaseModelType
from invokeai.backend.model_manager import BaseModelType, ModelVariantType
from invokeai.backend.model_patcher import ModelPatcher
from invokeai.backend.stable_diffusion import PipelineIntermediateState, set_seamless
from invokeai.backend.stable_diffusion import PipelineIntermediateState
from invokeai.backend.stable_diffusion.denoise_context import DenoiseContext, DenoiseInputs
from invokeai.backend.stable_diffusion.diffusers_pipeline import (
ControlNetData,
StableDiffusionGeneratorPipeline,
@@ -53,6 +55,19 @@ from invokeai.backend.stable_diffusion.diffusion.conditioning_data import (
TextConditioningData,
TextConditioningRegions,
)
from invokeai.backend.stable_diffusion.diffusion.custom_atttention import CustomAttnProcessor2_0
from invokeai.backend.stable_diffusion.diffusion_backend import StableDiffusionBackend
from invokeai.backend.stable_diffusion.extension_callback_type import ExtensionCallbackType
from invokeai.backend.stable_diffusion.extensions.controlnet import ControlNetExt
from invokeai.backend.stable_diffusion.extensions.freeu import FreeUExt
from invokeai.backend.stable_diffusion.extensions.inpaint import InpaintExt
from invokeai.backend.stable_diffusion.extensions.inpaint_model import InpaintModelExt
from invokeai.backend.stable_diffusion.extensions.lora import LoRAExt
from invokeai.backend.stable_diffusion.extensions.preview import PreviewExt
from invokeai.backend.stable_diffusion.extensions.rescale_cfg import RescaleCFGExt
from invokeai.backend.stable_diffusion.extensions.seamless import SeamlessExt
from invokeai.backend.stable_diffusion.extensions.t2i_adapter import T2IAdapterExt
from invokeai.backend.stable_diffusion.extensions_manager import ExtensionsManager
from invokeai.backend.stable_diffusion.schedulers import SCHEDULER_MAP
from invokeai.backend.stable_diffusion.schedulers.schedulers import SCHEDULER_NAME_VALUES
from invokeai.backend.util.devices import TorchDevice
@@ -314,9 +329,10 @@ class DenoiseLatentsInvocation(BaseInvocation):
context: InvocationContext,
positive_conditioning_field: Union[ConditioningField, list[ConditioningField]],
negative_conditioning_field: Union[ConditioningField, list[ConditioningField]],
unet: UNet2DConditionModel,
latent_height: int,
latent_width: int,
device: torch.device,
dtype: torch.dtype,
cfg_scale: float | list[float],
steps: int,
cfg_rescale_multiplier: float,
@@ -330,10 +346,10 @@ class DenoiseLatentsInvocation(BaseInvocation):
uncond_list = [uncond_list]
cond_text_embeddings, cond_text_embedding_masks = DenoiseLatentsInvocation._get_text_embeddings_and_masks(
cond_list, context, unet.device, unet.dtype
cond_list, context, device, dtype
)
uncond_text_embeddings, uncond_text_embedding_masks = DenoiseLatentsInvocation._get_text_embeddings_and_masks(
uncond_list, context, unet.device, unet.dtype
uncond_list, context, device, dtype
)
cond_text_embedding, cond_regions = DenoiseLatentsInvocation._concat_regional_text_embeddings(
@@ -341,14 +357,14 @@ class DenoiseLatentsInvocation(BaseInvocation):
masks=cond_text_embedding_masks,
latent_height=latent_height,
latent_width=latent_width,
dtype=unet.dtype,
dtype=dtype,
)
uncond_text_embedding, uncond_regions = DenoiseLatentsInvocation._concat_regional_text_embeddings(
text_conditionings=uncond_text_embeddings,
masks=uncond_text_embedding_masks,
latent_height=latent_height,
latent_width=latent_width,
dtype=unet.dtype,
dtype=dtype,
)
if isinstance(cfg_scale, list):
@@ -455,6 +471,65 @@ class DenoiseLatentsInvocation(BaseInvocation):
return controlnet_data
@staticmethod
def parse_controlnet_field(
exit_stack: ExitStack,
context: InvocationContext,
control_input: ControlField | list[ControlField] | None,
ext_manager: ExtensionsManager,
) -> None:
# Normalize control_input to a list.
control_list: list[ControlField]
if isinstance(control_input, ControlField):
control_list = [control_input]
elif isinstance(control_input, list):
control_list = control_input
elif control_input is None:
control_list = []
else:
raise ValueError(f"Unexpected control_input type: {type(control_input)}")
for control_info in control_list:
model = exit_stack.enter_context(context.models.load(control_info.control_model))
ext_manager.add_extension(
ControlNetExt(
model=model,
image=context.images.get_pil(control_info.image.image_name),
weight=control_info.control_weight,
begin_step_percent=control_info.begin_step_percent,
end_step_percent=control_info.end_step_percent,
control_mode=control_info.control_mode,
resize_mode=control_info.resize_mode,
)
)
@staticmethod
def parse_t2i_adapter_field(
exit_stack: ExitStack,
context: InvocationContext,
t2i_adapters: Optional[Union[T2IAdapterField, list[T2IAdapterField]]],
ext_manager: ExtensionsManager,
) -> None:
if t2i_adapters is None:
return
# Handle the possibility that t2i_adapters could be a list or a single T2IAdapterField.
if isinstance(t2i_adapters, T2IAdapterField):
t2i_adapters = [t2i_adapters]
for t2i_adapter_field in t2i_adapters:
ext_manager.add_extension(
T2IAdapterExt(
node_context=context,
model_id=t2i_adapter_field.t2i_adapter_model,
image=context.images.get_pil(t2i_adapter_field.image.image_name),
weight=t2i_adapter_field.weight,
begin_step_percent=t2i_adapter_field.begin_step_percent,
end_step_percent=t2i_adapter_field.end_step_percent,
resize_mode=t2i_adapter_field.resize_mode,
)
)
def prep_ip_adapter_image_prompts(
self,
context: InvocationContext,
@@ -664,7 +739,7 @@ class DenoiseLatentsInvocation(BaseInvocation):
else:
masked_latents = torch.where(mask < 0.5, 0.0, latents)
return 1 - mask, masked_latents, self.denoise_mask.gradient
return mask, masked_latents, self.denoise_mask.gradient
@staticmethod
def prepare_noise_and_latents(
@@ -707,12 +782,157 @@ class DenoiseLatentsInvocation(BaseInvocation):
return seed, noise, latents
def invoke(self, context: InvocationContext) -> LatentsOutput:
if os.environ.get("USE_MODULAR_DENOISE", False):
return self._new_invoke(context)
else:
return self._old_invoke(context)
@torch.no_grad()
@SilenceWarnings() # This quenches the NSFW nag from diffusers.
def invoke(self, context: InvocationContext) -> LatentsOutput:
def _new_invoke(self, context: InvocationContext) -> LatentsOutput:
ext_manager = ExtensionsManager(is_canceled=context.util.is_canceled)
device = TorchDevice.choose_torch_device()
dtype = TorchDevice.choose_torch_dtype()
seed, noise, latents = self.prepare_noise_and_latents(context, self.noise, self.latents)
_, _, latent_height, latent_width = latents.shape
conditioning_data = self.get_conditioning_data(
context=context,
positive_conditioning_field=self.positive_conditioning,
negative_conditioning_field=self.negative_conditioning,
cfg_scale=self.cfg_scale,
steps=self.steps,
latent_height=latent_height,
latent_width=latent_width,
device=device,
dtype=dtype,
# TODO: old backend, remove
cfg_rescale_multiplier=self.cfg_rescale_multiplier,
)
scheduler = get_scheduler(
context=context,
scheduler_info=self.unet.scheduler,
scheduler_name=self.scheduler,
seed=seed,
)
timesteps, init_timestep, scheduler_step_kwargs = self.init_scheduler(
scheduler,
seed=seed,
device=device,
steps=self.steps,
denoising_start=self.denoising_start,
denoising_end=self.denoising_end,
)
# get the unet's config so that we can pass the base to sd_step_callback()
unet_config = context.models.get_config(self.unet.unet.key)
### preview
def step_callback(state: PipelineIntermediateState) -> None:
context.util.sd_step_callback(state, unet_config.base)
ext_manager.add_extension(PreviewExt(step_callback))
### cfg rescale
if self.cfg_rescale_multiplier > 0:
ext_manager.add_extension(RescaleCFGExt(self.cfg_rescale_multiplier))
### freeu
if self.unet.freeu_config:
ext_manager.add_extension(FreeUExt(self.unet.freeu_config))
### lora
if self.unet.loras:
for lora_field in self.unet.loras:
ext_manager.add_extension(
LoRAExt(
node_context=context,
model_id=lora_field.lora,
weight=lora_field.weight,
)
)
### seamless
if self.unet.seamless_axes:
ext_manager.add_extension(SeamlessExt(self.unet.seamless_axes))
### inpaint
mask, masked_latents, is_gradient_mask = self.prep_inpaint_mask(context, latents)
# NOTE: We used to identify inpainting models by inpecting the shape of the loaded UNet model weights. Now we
# use the ModelVariantType config. During testing, there was a report of a user with models that had an
# incorrect ModelVariantType value. Re-installing the model fixed the issue. If this issue turns out to be
# prevalent, we will have to revisit how we initialize the inpainting extensions.
if unet_config.variant == ModelVariantType.Inpaint:
ext_manager.add_extension(InpaintModelExt(mask, masked_latents, is_gradient_mask))
elif mask is not None:
ext_manager.add_extension(InpaintExt(mask, is_gradient_mask))
# Initialize context for modular denoise
latents = latents.to(device=device, dtype=dtype)
if noise is not None:
noise = noise.to(device=device, dtype=dtype)
denoise_ctx = DenoiseContext(
inputs=DenoiseInputs(
orig_latents=latents,
timesteps=timesteps,
init_timestep=init_timestep,
noise=noise,
seed=seed,
scheduler_step_kwargs=scheduler_step_kwargs,
conditioning_data=conditioning_data,
attention_processor_cls=CustomAttnProcessor2_0,
),
unet=None,
scheduler=scheduler,
)
# context for loading additional models
with ExitStack() as exit_stack:
# later should be smth like:
# for extension_field in self.extensions:
# ext = extension_field.to_extension(exit_stack, context, ext_manager)
# ext_manager.add_extension(ext)
self.parse_controlnet_field(exit_stack, context, self.control, ext_manager)
self.parse_t2i_adapter_field(exit_stack, context, self.t2i_adapter, ext_manager)
# ext: t2i/ip adapter
ext_manager.run_callback(ExtensionCallbackType.SETUP, denoise_ctx)
unet_info = context.models.load(self.unet.unet)
assert isinstance(unet_info.model, UNet2DConditionModel)
with (
unet_info.model_on_device() as (cached_weights, unet),
ModelPatcher.patch_unet_attention_processor(unet, denoise_ctx.inputs.attention_processor_cls),
# ext: controlnet
ext_manager.patch_extensions(denoise_ctx),
# ext: freeu, seamless, ip adapter, lora
ext_manager.patch_unet(unet, cached_weights),
):
sd_backend = StableDiffusionBackend(unet, scheduler)
denoise_ctx.unet = unet
result_latents = sd_backend.latents_from_embeddings(denoise_ctx, ext_manager)
# https://discuss.huggingface.co/t/memory-usage-by-later-pipeline-stages/23699
result_latents = result_latents.detach().to("cpu")
TorchDevice.empty_cache()
name = context.tensors.save(tensor=result_latents)
return LatentsOutput.build(latents_name=name, latents=result_latents, seed=None)
@torch.no_grad()
@SilenceWarnings() # This quenches the NSFW nag from diffusers.
def _old_invoke(self, context: InvocationContext) -> LatentsOutput:
seed, noise, latents = self.prepare_noise_and_latents(context, self.noise, self.latents)
mask, masked_latents, gradient_mask = self.prep_inpaint_mask(context, latents)
# At this point, the mask ranges from 0 (leave unchanged) to 1 (inpaint).
# We invert the mask here for compatibility with the old backend implementation.
if mask is not None:
mask = 1 - mask
# TODO(ryand): I have hard-coded `do_classifier_free_guidance=True` to mirror the behaviour of ControlNets,
# below. Investigate whether this is appropriate.
@@ -755,14 +975,14 @@ class DenoiseLatentsInvocation(BaseInvocation):
assert isinstance(unet_info.model, UNet2DConditionModel)
with (
ExitStack() as exit_stack,
unet_info.model_on_device() as (model_state_dict, unet),
unet_info.model_on_device() as (cached_weights, unet),
ModelPatcher.apply_freeu(unet, self.unet.freeu_config),
set_seamless(unet, self.unet.seamless_axes), # FIXME
SeamlessExt.static_patch_model(unet, self.unet.seamless_axes), # FIXME
# Apply the LoRA after unet has been moved to its target device for faster patching.
ModelPatcher.apply_lora_unet(
unet,
loras=_lora_loader(),
model_state_dict=model_state_dict,
cached_weights=cached_weights,
),
):
assert isinstance(unet, UNet2DConditionModel)
@@ -788,7 +1008,8 @@ class DenoiseLatentsInvocation(BaseInvocation):
context=context,
positive_conditioning_field=self.positive_conditioning,
negative_conditioning_field=self.negative_conditioning,
unet=unet,
device=unet.device,
dtype=unet.dtype,
latent_height=latent_height,
latent_width=latent_width,
cfg_scale=self.cfg_scale,

View File

@@ -1,7 +1,7 @@
from enum import Enum
from typing import Any, Callable, Optional, Tuple
from pydantic import BaseModel, ConfigDict, Field, RootModel, TypeAdapter
from pydantic import BaseModel, ConfigDict, Field, RootModel, TypeAdapter, model_validator
from pydantic.fields import _Unset
from pydantic_core import PydanticUndefined
@@ -48,6 +48,7 @@ class UIType(str, Enum, metaclass=MetaEnum):
ControlNetModel = "ControlNetModelField"
IPAdapterModel = "IPAdapterModelField"
T2IAdapterModel = "T2IAdapterModelField"
SpandrelImageToImageModel = "SpandrelImageToImageModelField"
# endregion
# region Misc Field Types
@@ -134,6 +135,7 @@ class FieldDescriptions:
sdxl_main_model = "SDXL Main model (UNet, VAE, CLIP1, CLIP2) to load"
sdxl_refiner_model = "SDXL Refiner Main Modde (UNet, VAE, CLIP2) to load"
onnx_main_model = "ONNX Main model (UNet, VAE, CLIP) to load"
spandrel_image_to_image_model = "Image-to-Image model"
lora_weight = "The weight at which the LoRA is applied to each model"
compel_prompt = "Prompt to be parsed by Compel to create a conditioning tensor"
raw_prompt = "Raw prompt text (no parsing)"
@@ -160,8 +162,7 @@ class FieldDescriptions:
fp32 = "Whether or not to use full float32 precision"
precision = "Precision to use"
tiled = "Processing using overlapping tiles (reduce memory consumption)"
vae_tile_size = "The tile size for VAE tiling in pixels (image space). If set to 0, the default tile size for the "
"model will be used. Larger tile sizes generally produce better results at the cost of higher memory usage."
vae_tile_size = "The tile size for VAE tiling in pixels (image space). If set to 0, the default tile size for the model will be used. Larger tile sizes generally produce better results at the cost of higher memory usage."
detect_res = "Pixel resolution for detection"
image_res = "Pixel resolution for output image"
safe_mode = "Whether or not to use safe mode"
@@ -241,6 +242,31 @@ class ConditioningField(BaseModel):
)
class BoundingBoxField(BaseModel):
"""A bounding box primitive value."""
x_min: int = Field(ge=0, description="The minimum x-coordinate of the bounding box (inclusive).")
x_max: int = Field(ge=0, description="The maximum x-coordinate of the bounding box (exclusive).")
y_min: int = Field(ge=0, description="The minimum y-coordinate of the bounding box (inclusive).")
y_max: int = Field(ge=0, description="The maximum y-coordinate of the bounding box (exclusive).")
score: Optional[float] = Field(
default=None,
ge=0.0,
le=1.0,
description="The score associated with the bounding box. In the range [0, 1]. This value is typically set "
"when the bounding box was produced by a detector and has an associated confidence score.",
)
@model_validator(mode="after")
def check_coords(self):
if self.x_min > self.x_max:
raise ValueError(f"x_min ({self.x_min}) is greater than x_max ({self.x_max}).")
if self.y_min > self.y_max:
raise ValueError(f"y_min ({self.y_min}) is greater than y_max ({self.y_max}).")
return self
class MetadataField(RootModel[dict[str, Any]]):
"""
Pydantic model for metadata with custom root of type dict[str, Any].

View File

@@ -0,0 +1,100 @@
from pathlib import Path
from typing import Literal
import torch
from PIL import Image
from transformers import pipeline
from transformers.pipelines import ZeroShotObjectDetectionPipeline
from invokeai.app.invocations.baseinvocation import BaseInvocation, invocation
from invokeai.app.invocations.fields import BoundingBoxField, ImageField, InputField
from invokeai.app.invocations.primitives import BoundingBoxCollectionOutput
from invokeai.app.services.shared.invocation_context import InvocationContext
from invokeai.backend.image_util.grounding_dino.detection_result import DetectionResult
from invokeai.backend.image_util.grounding_dino.grounding_dino_pipeline import GroundingDinoPipeline
GroundingDinoModelKey = Literal["grounding-dino-tiny", "grounding-dino-base"]
GROUNDING_DINO_MODEL_IDS: dict[GroundingDinoModelKey, str] = {
"grounding-dino-tiny": "IDEA-Research/grounding-dino-tiny",
"grounding-dino-base": "IDEA-Research/grounding-dino-base",
}
@invocation(
"grounding_dino",
title="Grounding DINO (Text Prompt Object Detection)",
tags=["prompt", "object detection"],
category="image",
version="1.0.0",
)
class GroundingDinoInvocation(BaseInvocation):
"""Runs a Grounding DINO model. Performs zero-shot bounding-box object detection from a text prompt."""
# Reference:
# - https://arxiv.org/pdf/2303.05499
# - https://huggingface.co/docs/transformers/v4.43.3/en/model_doc/grounding-dino#grounded-sam
# - https://github.com/NielsRogge/Transformers-Tutorials/blob/a39f33ac1557b02ebfb191ea7753e332b5ca933f/Grounding%20DINO/GroundingDINO_with_Segment_Anything.ipynb
model: GroundingDinoModelKey = InputField(description="The Grounding DINO model to use.")
prompt: str = InputField(description="The prompt describing the object to segment.")
image: ImageField = InputField(description="The image to segment.")
detection_threshold: float = InputField(
description="The detection threshold for the Grounding DINO model. All detected bounding boxes with scores above this threshold will be returned.",
ge=0.0,
le=1.0,
default=0.3,
)
@torch.no_grad()
def invoke(self, context: InvocationContext) -> BoundingBoxCollectionOutput:
# The model expects a 3-channel RGB image.
image_pil = context.images.get_pil(self.image.image_name, mode="RGB")
detections = self._detect(
context=context, image=image_pil, labels=[self.prompt], threshold=self.detection_threshold
)
# Convert detections to BoundingBoxCollectionOutput.
bounding_boxes: list[BoundingBoxField] = []
for detection in detections:
bounding_boxes.append(
BoundingBoxField(
x_min=detection.box.xmin,
x_max=detection.box.xmax,
y_min=detection.box.ymin,
y_max=detection.box.ymax,
score=detection.score,
)
)
return BoundingBoxCollectionOutput(collection=bounding_boxes)
@staticmethod
def _load_grounding_dino(model_path: Path):
grounding_dino_pipeline = pipeline(
model=str(model_path),
task="zero-shot-object-detection",
local_files_only=True,
# TODO(ryand): Setting the torch_dtype here doesn't work. Investigate whether fp16 is supported by the
# model, and figure out how to make it work in the pipeline.
# torch_dtype=TorchDevice.choose_torch_dtype(),
)
assert isinstance(grounding_dino_pipeline, ZeroShotObjectDetectionPipeline)
return GroundingDinoPipeline(grounding_dino_pipeline)
def _detect(
self,
context: InvocationContext,
image: Image.Image,
labels: list[str],
threshold: float = 0.3,
) -> list[DetectionResult]:
"""Use Grounding DINO to detect bounding boxes for a set of labels in an image."""
# TODO(ryand): I copied this "."-handling logic from the transformers example code. Test it and see if it
# actually makes a difference.
labels = [label if label.endswith(".") else label + "." for label in labels]
with context.models.load_remote_model(
source=GROUNDING_DINO_MODEL_IDS[self.model], loader=GroundingDinoInvocation._load_grounding_dino
) as detector:
assert isinstance(detector, GroundingDinoPipeline)
return detector.detect(image=image, candidate_labels=labels, threshold=threshold)

View File

@@ -6,13 +6,19 @@ import cv2
import numpy
from PIL import Image, ImageChops, ImageFilter, ImageOps
from invokeai.app.invocations.baseinvocation import BaseInvocation, Classification, invocation
from invokeai.app.invocations.baseinvocation import (
BaseInvocation,
Classification,
invocation,
invocation_output,
)
from invokeai.app.invocations.constants import IMAGE_MODES
from invokeai.app.invocations.fields import (
ColorField,
FieldDescriptions,
ImageField,
InputField,
OutputField,
WithBoard,
WithMetadata,
)
@@ -1007,3 +1013,62 @@ class MaskFromIDInvocation(BaseInvocation, WithMetadata, WithBoard):
image_dto = context.images.save(image=mask, image_category=ImageCategory.MASK)
return ImageOutput.build(image_dto)
@invocation_output("canvas_v2_mask_and_crop_output")
class CanvasV2MaskAndCropOutput(ImageOutput):
offset_x: int = OutputField(description="The x offset of the image, after cropping")
offset_y: int = OutputField(description="The y offset of the image, after cropping")
@invocation(
"canvas_v2_mask_and_crop",
title="Canvas V2 Mask and Crop",
tags=["image", "mask", "id"],
category="image",
version="1.0.0",
classification=Classification.Prototype,
)
class CanvasV2MaskAndCropInvocation(BaseInvocation, WithMetadata, WithBoard):
"""Handles Canvas V2 image output masking and cropping"""
source_image: ImageField | None = InputField(
default=None,
description="The source image onto which the masked generated image is pasted. If omitted, the masked generated image is returned with transparency.",
)
generated_image: ImageField = InputField(description="The image to apply the mask to")
mask: ImageField = InputField(description="The mask to apply")
mask_blur: int = InputField(default=0, ge=0, description="The amount to blur the mask by")
def _prepare_mask(self, mask: Image.Image) -> Image.Image:
mask_array = numpy.array(mask)
kernel = numpy.ones((self.mask_blur, self.mask_blur), numpy.uint8)
dilated_mask_array = cv2.erode(mask_array, kernel, iterations=3)
dilated_mask = Image.fromarray(dilated_mask_array)
if self.mask_blur > 0:
mask = dilated_mask.filter(ImageFilter.GaussianBlur(self.mask_blur))
return ImageOps.invert(mask.convert("L"))
def invoke(self, context: InvocationContext) -> CanvasV2MaskAndCropOutput:
mask = self._prepare_mask(context.images.get_pil(self.mask.image_name))
if self.source_image:
generated_image = context.images.get_pil(self.generated_image.image_name)
source_image = context.images.get_pil(self.source_image.image_name)
source_image.paste(generated_image, (0, 0), mask)
image_dto = context.images.save(image=source_image)
else:
generated_image = context.images.get_pil(self.generated_image.image_name)
generated_image.putalpha(mask)
image_dto = context.images.save(image=generated_image)
# bbox = image.getbbox()
# image = image.crop(bbox)
return CanvasV2MaskAndCropOutput(
image=ImageField(image_name=image_dto.image_name),
offset_x=0,
offset_y=0,
width=image_dto.width,
height=image_dto.height,
)

View File

@@ -24,7 +24,7 @@ from invokeai.app.invocations.fields import (
from invokeai.app.invocations.model import VAEField
from invokeai.app.invocations.primitives import ImageOutput
from invokeai.app.services.shared.invocation_context import InvocationContext
from invokeai.backend.stable_diffusion import set_seamless
from invokeai.backend.stable_diffusion.extensions.seamless import SeamlessExt
from invokeai.backend.stable_diffusion.vae_tiling import patch_vae_tiling_params
from invokeai.backend.util.devices import TorchDevice
@@ -59,7 +59,7 @@ class LatentsToImageInvocation(BaseInvocation, WithMetadata, WithBoard):
vae_info = context.models.load(self.vae.vae)
assert isinstance(vae_info.model, (AutoencoderKL, AutoencoderTiny))
with set_seamless(vae_info.model, self.vae.seamless_axes), vae_info as vae:
with SeamlessExt.static_patch_model(vae_info.model, self.vae.seamless_axes), vae_info as vae:
assert isinstance(vae, (AutoencoderKL, AutoencoderTiny))
latents = latents.to(vae.device)
if self.fp32:

View File

@@ -1,9 +1,10 @@
import numpy as np
import torch
from PIL import Image
from invokeai.app.invocations.baseinvocation import BaseInvocation, Classification, InvocationContext, invocation
from invokeai.app.invocations.fields import ImageField, InputField, TensorField, WithMetadata
from invokeai.app.invocations.primitives import MaskOutput
from invokeai.app.invocations.fields import ImageField, InputField, TensorField, WithBoard, WithMetadata
from invokeai.app.invocations.primitives import ImageOutput, MaskOutput
@invocation(
@@ -118,3 +119,27 @@ class ImageMaskToTensorInvocation(BaseInvocation, WithMetadata):
height=mask.shape[1],
width=mask.shape[2],
)
@invocation(
"tensor_mask_to_image",
title="Tensor Mask to Image",
tags=["mask"],
category="mask",
version="1.0.0",
)
class MaskTensorToImageInvocation(BaseInvocation, WithMetadata, WithBoard):
"""Convert a mask tensor to an image."""
mask: TensorField = InputField(description="The mask tensor to convert.")
def invoke(self, context: InvocationContext) -> ImageOutput:
mask = context.tensors.load(self.mask.tensor_name)
# Ensure that the mask is binary.
if mask.dtype != torch.bool:
mask = mask > 0.5
mask_np = (mask.float() * 255).byte().cpu().numpy()
mask_pil = Image.fromarray(mask_np, mode="L")
image_dto = context.images.save(image=mask_pil)
return ImageOutput.build(image_dto)

View File

@@ -7,6 +7,7 @@ import torch
from invokeai.app.invocations.baseinvocation import BaseInvocation, BaseInvocationOutput, invocation, invocation_output
from invokeai.app.invocations.constants import LATENT_SCALE_FACTOR
from invokeai.app.invocations.fields import (
BoundingBoxField,
ColorField,
ConditioningField,
DenoiseMaskField,
@@ -469,3 +470,42 @@ class ConditioningCollectionInvocation(BaseInvocation):
# endregion
# region BoundingBox
@invocation_output("bounding_box_output")
class BoundingBoxOutput(BaseInvocationOutput):
"""Base class for nodes that output a single bounding box"""
bounding_box: BoundingBoxField = OutputField(description="The output bounding box.")
@invocation_output("bounding_box_collection_output")
class BoundingBoxCollectionOutput(BaseInvocationOutput):
"""Base class for nodes that output a collection of bounding boxes"""
collection: list[BoundingBoxField] = OutputField(description="The output bounding boxes.", title="Bounding Boxes")
@invocation(
"bounding_box",
title="Bounding Box",
tags=["primitives", "segmentation", "collection", "bounding box"],
category="primitives",
version="1.0.0",
)
class BoundingBoxInvocation(BaseInvocation):
"""Create a bounding box manually by supplying box coordinates"""
x_min: int = InputField(default=0, description="x-coordinate of the bounding box's top left vertex")
y_min: int = InputField(default=0, description="y-coordinate of the bounding box's top left vertex")
x_max: int = InputField(default=0, description="x-coordinate of the bounding box's bottom right vertex")
y_max: int = InputField(default=0, description="y-coordinate of the bounding box's bottom right vertex")
def invoke(self, context: InvocationContext) -> BoundingBoxOutput:
bounding_box = BoundingBoxField(x_min=self.x_min, y_min=self.y_min, x_max=self.x_max, y_max=self.y_max)
return BoundingBoxOutput(bounding_box=bounding_box)
# endregion

View File

@@ -0,0 +1,161 @@
from pathlib import Path
from typing import Literal
import numpy as np
import torch
from PIL import Image
from transformers import AutoModelForMaskGeneration, AutoProcessor
from transformers.models.sam import SamModel
from transformers.models.sam.processing_sam import SamProcessor
from invokeai.app.invocations.baseinvocation import BaseInvocation, invocation
from invokeai.app.invocations.fields import BoundingBoxField, ImageField, InputField, TensorField
from invokeai.app.invocations.primitives import MaskOutput
from invokeai.app.services.shared.invocation_context import InvocationContext
from invokeai.backend.image_util.segment_anything.mask_refinement import mask_to_polygon, polygon_to_mask
from invokeai.backend.image_util.segment_anything.segment_anything_pipeline import SegmentAnythingPipeline
SegmentAnythingModelKey = Literal["segment-anything-base", "segment-anything-large", "segment-anything-huge"]
SEGMENT_ANYTHING_MODEL_IDS: dict[SegmentAnythingModelKey, str] = {
"segment-anything-base": "facebook/sam-vit-base",
"segment-anything-large": "facebook/sam-vit-large",
"segment-anything-huge": "facebook/sam-vit-huge",
}
@invocation(
"segment_anything",
title="Segment Anything",
tags=["prompt", "segmentation"],
category="segmentation",
version="1.0.0",
)
class SegmentAnythingInvocation(BaseInvocation):
"""Runs a Segment Anything Model."""
# Reference:
# - https://arxiv.org/pdf/2304.02643
# - https://huggingface.co/docs/transformers/v4.43.3/en/model_doc/grounding-dino#grounded-sam
# - https://github.com/NielsRogge/Transformers-Tutorials/blob/a39f33ac1557b02ebfb191ea7753e332b5ca933f/Grounding%20DINO/GroundingDINO_with_Segment_Anything.ipynb
model: SegmentAnythingModelKey = InputField(description="The Segment Anything model to use.")
image: ImageField = InputField(description="The image to segment.")
bounding_boxes: list[BoundingBoxField] = InputField(description="The bounding boxes to prompt the SAM model with.")
apply_polygon_refinement: bool = InputField(
description="Whether to apply polygon refinement to the masks. This will smooth the edges of the masks slightly and ensure that each mask consists of a single closed polygon (before merging).",
default=True,
)
mask_filter: Literal["all", "largest", "highest_box_score"] = InputField(
description="The filtering to apply to the detected masks before merging them into a final output.",
default="all",
)
@torch.no_grad()
def invoke(self, context: InvocationContext) -> MaskOutput:
# The models expect a 3-channel RGB image.
image_pil = context.images.get_pil(self.image.image_name, mode="RGB")
if len(self.bounding_boxes) == 0:
combined_mask = torch.zeros(image_pil.size[::-1], dtype=torch.bool)
else:
masks = self._segment(context=context, image=image_pil)
masks = self._filter_masks(masks=masks, bounding_boxes=self.bounding_boxes)
# masks contains bool values, so we merge them via max-reduce.
combined_mask, _ = torch.stack(masks).max(dim=0)
mask_tensor_name = context.tensors.save(combined_mask)
height, width = combined_mask.shape
return MaskOutput(mask=TensorField(tensor_name=mask_tensor_name), width=width, height=height)
@staticmethod
def _load_sam_model(model_path: Path):
sam_model = AutoModelForMaskGeneration.from_pretrained(
model_path,
local_files_only=True,
# TODO(ryand): Setting the torch_dtype here doesn't work. Investigate whether fp16 is supported by the
# model, and figure out how to make it work in the pipeline.
# torch_dtype=TorchDevice.choose_torch_dtype(),
)
assert isinstance(sam_model, SamModel)
sam_processor = AutoProcessor.from_pretrained(model_path, local_files_only=True)
assert isinstance(sam_processor, SamProcessor)
return SegmentAnythingPipeline(sam_model=sam_model, sam_processor=sam_processor)
def _segment(
self,
context: InvocationContext,
image: Image.Image,
) -> list[torch.Tensor]:
"""Use Segment Anything (SAM) to generate masks given an image + a set of bounding boxes."""
# Convert the bounding boxes to the SAM input format.
sam_bounding_boxes = [[bb.x_min, bb.y_min, bb.x_max, bb.y_max] for bb in self.bounding_boxes]
with (
context.models.load_remote_model(
source=SEGMENT_ANYTHING_MODEL_IDS[self.model], loader=SegmentAnythingInvocation._load_sam_model
) as sam_pipeline,
):
assert isinstance(sam_pipeline, SegmentAnythingPipeline)
masks = sam_pipeline.segment(image=image, bounding_boxes=sam_bounding_boxes)
masks = self._process_masks(masks)
if self.apply_polygon_refinement:
masks = self._apply_polygon_refinement(masks)
return masks
def _process_masks(self, masks: torch.Tensor) -> list[torch.Tensor]:
"""Convert the tensor output from the Segment Anything model from a tensor of shape
[num_masks, channels, height, width] to a list of tensors of shape [height, width].
"""
assert masks.dtype == torch.bool
# [num_masks, channels, height, width] -> [num_masks, height, width]
masks, _ = masks.max(dim=1)
# Split the first dimension into a list of masks.
return list(masks.cpu().unbind(dim=0))
def _apply_polygon_refinement(self, masks: list[torch.Tensor]) -> list[torch.Tensor]:
"""Apply polygon refinement to the masks.
Convert each mask to a polygon, then back to a mask. This has the following effect:
- Smooth the edges of the mask slightly.
- Ensure that each mask consists of a single closed polygon
- Removes small mask pieces.
- Removes holes from the mask.
"""
# Convert tensor masks to np masks.
np_masks = [mask.cpu().numpy().astype(np.uint8) for mask in masks]
# Apply polygon refinement.
for idx, mask in enumerate(np_masks):
shape = mask.shape
assert len(shape) == 2 # Assert length to satisfy type checker.
polygon = mask_to_polygon(mask)
mask = polygon_to_mask(polygon, shape)
np_masks[idx] = mask
# Convert np masks back to tensor masks.
masks = [torch.tensor(mask, dtype=torch.bool) for mask in np_masks]
return masks
def _filter_masks(self, masks: list[torch.Tensor], bounding_boxes: list[BoundingBoxField]) -> list[torch.Tensor]:
"""Filter the detected masks based on the specified mask filter."""
assert len(masks) == len(bounding_boxes)
if self.mask_filter == "all":
return masks
elif self.mask_filter == "largest":
# Find the largest mask.
return [max(masks, key=lambda x: float(x.sum()))]
elif self.mask_filter == "highest_box_score":
# Find the index of the bounding box with the highest score.
# Note that we fallback to -1.0 if the score is None. This is mainly to satisfy the type checker. In most
# cases the scores should all be non-None when using this filtering mode. That being said, -1.0 is a
# reasonable fallback since the expected score range is [0.0, 1.0].
max_score_idx = max(range(len(bounding_boxes)), key=lambda i: bounding_boxes[i].score or -1.0)
return [masks[max_score_idx]]
else:
raise ValueError(f"Invalid mask filter: {self.mask_filter}")

View File

@@ -0,0 +1,253 @@
from typing import Callable
import numpy as np
import torch
from PIL import Image
from tqdm import tqdm
from invokeai.app.invocations.baseinvocation import BaseInvocation, invocation
from invokeai.app.invocations.fields import (
FieldDescriptions,
ImageField,
InputField,
UIType,
WithBoard,
WithMetadata,
)
from invokeai.app.invocations.model import ModelIdentifierField
from invokeai.app.invocations.primitives import ImageOutput
from invokeai.app.services.session_processor.session_processor_common import CanceledException
from invokeai.app.services.shared.invocation_context import InvocationContext
from invokeai.backend.spandrel_image_to_image_model import SpandrelImageToImageModel
from invokeai.backend.tiles.tiles import calc_tiles_min_overlap
from invokeai.backend.tiles.utils import TBLR, Tile
@invocation("spandrel_image_to_image", title="Image-to-Image", tags=["upscale"], category="upscale", version="1.3.0")
class SpandrelImageToImageInvocation(BaseInvocation, WithMetadata, WithBoard):
"""Run any spandrel image-to-image model (https://github.com/chaiNNer-org/spandrel)."""
image: ImageField = InputField(description="The input image")
image_to_image_model: ModelIdentifierField = InputField(
title="Image-to-Image Model",
description=FieldDescriptions.spandrel_image_to_image_model,
ui_type=UIType.SpandrelImageToImageModel,
)
tile_size: int = InputField(
default=512, description="The tile size for tiled image-to-image. Set to 0 to disable tiling."
)
@classmethod
def scale_tile(cls, tile: Tile, scale: int) -> Tile:
return Tile(
coords=TBLR(
top=tile.coords.top * scale,
bottom=tile.coords.bottom * scale,
left=tile.coords.left * scale,
right=tile.coords.right * scale,
),
overlap=TBLR(
top=tile.overlap.top * scale,
bottom=tile.overlap.bottom * scale,
left=tile.overlap.left * scale,
right=tile.overlap.right * scale,
),
)
@classmethod
def upscale_image(
cls,
image: Image.Image,
tile_size: int,
spandrel_model: SpandrelImageToImageModel,
is_canceled: Callable[[], bool],
) -> Image.Image:
# Compute the image tiles.
if tile_size > 0:
min_overlap = 20
tiles = calc_tiles_min_overlap(
image_height=image.height,
image_width=image.width,
tile_height=tile_size,
tile_width=tile_size,
min_overlap=min_overlap,
)
else:
# No tiling. Generate a single tile that covers the entire image.
min_overlap = 0
tiles = [
Tile(
coords=TBLR(top=0, bottom=image.height, left=0, right=image.width),
overlap=TBLR(top=0, bottom=0, left=0, right=0),
)
]
# Sort tiles first by left x coordinate, then by top y coordinate. During tile processing, we want to iterate
# over tiles left-to-right, top-to-bottom.
tiles = sorted(tiles, key=lambda x: x.coords.left)
tiles = sorted(tiles, key=lambda x: x.coords.top)
# Prepare input image for inference.
image_tensor = SpandrelImageToImageModel.pil_to_tensor(image)
# Scale the tiles for re-assembling the final image.
scale = spandrel_model.scale
scaled_tiles = [cls.scale_tile(tile, scale=scale) for tile in tiles]
# Prepare the output tensor.
_, channels, height, width = image_tensor.shape
output_tensor = torch.zeros(
(height * scale, width * scale, channels), dtype=torch.uint8, device=torch.device("cpu")
)
image_tensor = image_tensor.to(device=spandrel_model.device, dtype=spandrel_model.dtype)
# Run the model on each tile.
for tile, scaled_tile in tqdm(list(zip(tiles, scaled_tiles, strict=True)), desc="Upscaling Tiles"):
# Exit early if the invocation has been canceled.
if is_canceled():
raise CanceledException
# Extract the current tile from the input tensor.
input_tile = image_tensor[
:, :, tile.coords.top : tile.coords.bottom, tile.coords.left : tile.coords.right
].to(device=spandrel_model.device, dtype=spandrel_model.dtype)
# Run the model on the tile.
output_tile = spandrel_model.run(input_tile)
# Convert the output tile into the output tensor's format.
# (N, C, H, W) -> (C, H, W)
output_tile = output_tile.squeeze(0)
# (C, H, W) -> (H, W, C)
output_tile = output_tile.permute(1, 2, 0)
output_tile = output_tile.clamp(0, 1)
output_tile = (output_tile * 255).to(dtype=torch.uint8, device=torch.device("cpu"))
# Merge the output tile into the output tensor.
# We only keep half of the overlap on the top and left side of the tile. We do this in case there are
# edge artifacts. We don't bother with any 'blending' in the current implementation - for most upscalers
# it seems unnecessary, but we may find a need in the future.
top_overlap = scaled_tile.overlap.top // 2
left_overlap = scaled_tile.overlap.left // 2
output_tensor[
scaled_tile.coords.top + top_overlap : scaled_tile.coords.bottom,
scaled_tile.coords.left + left_overlap : scaled_tile.coords.right,
:,
] = output_tile[top_overlap:, left_overlap:, :]
# Convert the output tensor to a PIL image.
np_image = output_tensor.detach().numpy().astype(np.uint8)
pil_image = Image.fromarray(np_image)
return pil_image
@torch.inference_mode()
def invoke(self, context: InvocationContext) -> ImageOutput:
# Images are converted to RGB, because most models don't support an alpha channel. In the future, we may want to
# revisit this.
image = context.images.get_pil(self.image.image_name, mode="RGB")
# Load the model.
spandrel_model_info = context.models.load(self.image_to_image_model)
# Do the upscaling.
with spandrel_model_info as spandrel_model:
assert isinstance(spandrel_model, SpandrelImageToImageModel)
# Upscale the image
pil_image = self.upscale_image(image, self.tile_size, spandrel_model, context.util.is_canceled)
image_dto = context.images.save(image=pil_image)
return ImageOutput.build(image_dto)
@invocation(
"spandrel_image_to_image_autoscale",
title="Image-to-Image (Autoscale)",
tags=["upscale"],
category="upscale",
version="1.0.0",
)
class SpandrelImageToImageAutoscaleInvocation(SpandrelImageToImageInvocation):
"""Run any spandrel image-to-image model (https://github.com/chaiNNer-org/spandrel) until the target scale is reached."""
scale: float = InputField(
default=4.0,
gt=0.0,
le=16.0,
description="The final scale of the output image. If the model does not upscale the image, this will be ignored.",
)
fit_to_multiple_of_8: bool = InputField(
default=False,
description="If true, the output image will be resized to the nearest multiple of 8 in both dimensions.",
)
@torch.inference_mode()
def invoke(self, context: InvocationContext) -> ImageOutput:
# Images are converted to RGB, because most models don't support an alpha channel. In the future, we may want to
# revisit this.
image = context.images.get_pil(self.image.image_name, mode="RGB")
# Load the model.
spandrel_model_info = context.models.load(self.image_to_image_model)
# The target size of the image, determined by the provided scale. We'll run the upscaler until we hit this size.
# Later, we may mutate this value if the model doesn't upscale the image or if the user requested a multiple of 8.
target_width = int(image.width * self.scale)
target_height = int(image.height * self.scale)
# Do the upscaling.
with spandrel_model_info as spandrel_model:
assert isinstance(spandrel_model, SpandrelImageToImageModel)
# First pass of upscaling. Note: `pil_image` will be mutated.
pil_image = self.upscale_image(image, self.tile_size, spandrel_model, context.util.is_canceled)
# Some models don't upscale the image, but we have no way to know this in advance. We'll check if the model
# upscaled the image and run the loop below if it did. We'll require the model to upscale both dimensions
# to be considered an upscale model.
is_upscale_model = pil_image.width > image.width and pil_image.height > image.height
if is_upscale_model:
# This is an upscale model, so we should keep upscaling until we reach the target size.
iterations = 1
while pil_image.width < target_width or pil_image.height < target_height:
pil_image = self.upscale_image(pil_image, self.tile_size, spandrel_model, context.util.is_canceled)
iterations += 1
# Sanity check to prevent excessive or infinite loops. All known upscaling models are at least 2x.
# Our max scale is 16x, so with a 2x model, we should never exceed 16x == 2^4 -> 4 iterations.
# We'll allow one extra iteration "just in case" and bail at 5 upscaling iterations. In practice,
# we should never reach this limit.
if iterations >= 5:
context.logger.warning(
"Upscale loop reached maximum iteration count of 5, stopping upscaling early."
)
break
else:
# This model doesn't upscale the image. We should ignore the scale parameter, modifying the output size
# to be the same as the processed image size.
# The output size is now the size of the processed image.
target_width = pil_image.width
target_height = pil_image.height
# Warn the user if they requested a scale greater than 1.
if self.scale > 1:
context.logger.warning(
"Model does not increase the size of the image, but a greater scale than 1 was requested. Image will not be scaled."
)
# We may need to resize the image to a multiple of 8. Use floor division to ensure we don't scale the image up
# in the final resize
if self.fit_to_multiple_of_8:
target_width = int(target_width // 8 * 8)
target_height = int(target_height // 8 * 8)
# Final resize. Per PIL documentation, Lanczos provides the best quality for both upscale and downscale.
# See: https://pillow.readthedocs.io/en/stable/handbook/concepts.html#filters-comparison-table
pil_image = pil_image.resize((target_width, target_height), resample=Image.Resampling.LANCZOS)
image_dto = context.images.save(image=pil_image)
return ImageOutput.build(image_dto)

View File

@@ -175,6 +175,10 @@ class TiledMultiDiffusionDenoiseLatents(BaseInvocation):
_, _, latent_height, latent_width = latents.shape
# Calculate the tile locations to cover the latent-space image.
# TODO(ryand): In the future, we may want to revisit the tile overlap strategy. Things to consider:
# - How much overlap 'context' to provide for each denoising step.
# - How much overlap to use during merging/blending.
# - Should we 'jitter' the tile locations in each step so that the seams are in different places?
tiles = calc_tiles_min_overlap(
image_height=latent_height,
image_width=latent_width,
@@ -218,7 +222,8 @@ class TiledMultiDiffusionDenoiseLatents(BaseInvocation):
context=context,
positive_conditioning_field=self.positive_conditioning,
negative_conditioning_field=self.negative_conditioning,
unet=unet,
device=unet.device,
dtype=unet.dtype,
latent_height=latent_tile_height,
latent_width=latent_tile_width,
cfg_scale=self.cfg_scale,

View File

@@ -91,6 +91,7 @@ class InvokeAIAppConfig(BaseSettings):
db_dir: Path to InvokeAI databases directory.
outputs_dir: Path to directory for outputs.
custom_nodes_dir: Path to directory for custom nodes.
style_presets_dir: Path to directory for style presets.
log_handlers: Log handler. Valid options are "console", "file=<path>", "syslog=path|address:host:port", "http=<url>".
log_format: Log format. Use "plain" for text-only, "color" for colorized output, "legacy" for 2.3-style logging and "syslog" for syslog-style.<br>Valid values: `plain`, `color`, `syslog`, `legacy`
log_level: Emit logging messages at this level or higher.<br>Valid values: `debug`, `info`, `warning`, `error`, `critical`
@@ -153,6 +154,7 @@ class InvokeAIAppConfig(BaseSettings):
db_dir: Path = Field(default=Path("databases"), description="Path to InvokeAI databases directory.")
outputs_dir: Path = Field(default=Path("outputs"), description="Path to directory for outputs.")
custom_nodes_dir: Path = Field(default=Path("nodes"), description="Path to directory for custom nodes.")
style_presets_dir: Path = Field(default=Path("style_presets"), description="Path to directory for style presets.")
# LOGGING
log_handlers: list[str] = Field(default=["console"], description='Log handler. Valid options are "console", "file=<path>", "syslog=path|address:host:port", "http=<url>".')
@@ -300,6 +302,11 @@ class InvokeAIAppConfig(BaseSettings):
"""Path to the models directory, resolved to an absolute path.."""
return self._resolve(self.models_dir)
@property
def style_presets_path(self) -> Path:
"""Path to the style presets directory, resolved to an absolute path.."""
return self._resolve(self.style_presets_dir)
@property
def convert_cache_path(self) -> Path:
"""Path to the converted cache models directory, resolved to an absolute path.."""

View File

@@ -88,6 +88,7 @@ class QueueItemEventBase(QueueEventBase):
item_id: int = Field(description="The ID of the queue item")
batch_id: str = Field(description="The ID of the queue batch")
origin: str | None = Field(default=None, description="The origin of the batch")
class InvocationEventBase(QueueItemEventBase):
@@ -95,8 +96,6 @@ class InvocationEventBase(QueueItemEventBase):
session_id: str = Field(description="The ID of the session (aka graph execution state)")
queue_id: str = Field(description="The ID of the queue")
item_id: int = Field(description="The ID of the queue item")
batch_id: str = Field(description="The ID of the queue batch")
session_id: str = Field(description="The ID of the session (aka graph execution state)")
invocation: AnyInvocation = Field(description="The ID of the invocation")
invocation_source_id: str = Field(description="The ID of the prepared invocation's source node")
@@ -114,6 +113,7 @@ class InvocationStartedEvent(InvocationEventBase):
queue_id=queue_item.queue_id,
item_id=queue_item.item_id,
batch_id=queue_item.batch_id,
origin=queue_item.origin,
session_id=queue_item.session_id,
invocation=invocation,
invocation_source_id=queue_item.session.prepared_source_mapping[invocation.id],
@@ -147,6 +147,7 @@ class InvocationDenoiseProgressEvent(InvocationEventBase):
queue_id=queue_item.queue_id,
item_id=queue_item.item_id,
batch_id=queue_item.batch_id,
origin=queue_item.origin,
session_id=queue_item.session_id,
invocation=invocation,
invocation_source_id=queue_item.session.prepared_source_mapping[invocation.id],
@@ -184,6 +185,7 @@ class InvocationCompleteEvent(InvocationEventBase):
queue_id=queue_item.queue_id,
item_id=queue_item.item_id,
batch_id=queue_item.batch_id,
origin=queue_item.origin,
session_id=queue_item.session_id,
invocation=invocation,
invocation_source_id=queue_item.session.prepared_source_mapping[invocation.id],
@@ -216,6 +218,7 @@ class InvocationErrorEvent(InvocationEventBase):
queue_id=queue_item.queue_id,
item_id=queue_item.item_id,
batch_id=queue_item.batch_id,
origin=queue_item.origin,
session_id=queue_item.session_id,
invocation=invocation,
invocation_source_id=queue_item.session.prepared_source_mapping[invocation.id],
@@ -253,6 +256,7 @@ class QueueItemStatusChangedEvent(QueueItemEventBase):
queue_id=queue_item.queue_id,
item_id=queue_item.item_id,
batch_id=queue_item.batch_id,
origin=queue_item.origin,
session_id=queue_item.session_id,
status=queue_item.status,
error_type=queue_item.error_type,
@@ -279,12 +283,14 @@ class BatchEnqueuedEvent(QueueEventBase):
description="The number of invocations initially requested to be enqueued (may be less than enqueued if queue was full)"
)
priority: int = Field(description="The priority of the batch")
origin: str | None = Field(default=None, description="The origin of the batch")
@classmethod
def build(cls, enqueue_result: EnqueueBatchResult) -> "BatchEnqueuedEvent":
return cls(
queue_id=enqueue_result.queue_id,
batch_id=enqueue_result.batch.batch_id,
origin=enqueue_result.batch.origin,
enqueued=enqueue_result.enqueued,
requested=enqueue_result.requested,
priority=enqueue_result.priority,

View File

@@ -1,46 +1,44 @@
# Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654)
import asyncio
import threading
from queue import Empty, Queue
from fastapi_events.dispatcher import dispatch
from invokeai.app.services.events.events_base import EventServiceBase
from invokeai.app.services.events.events_common import (
EventBase,
)
from invokeai.app.services.events.events_common import EventBase
class FastAPIEventService(EventServiceBase):
def __init__(self, event_handler_id: int) -> None:
def __init__(self, event_handler_id: int, loop: asyncio.AbstractEventLoop) -> None:
self.event_handler_id = event_handler_id
self._queue = Queue[EventBase | None]()
self._queue = asyncio.Queue[EventBase | None]()
self._stop_event = threading.Event()
asyncio.create_task(self._dispatch_from_queue(stop_event=self._stop_event))
self._loop = loop
# We need to store a reference to the task so it doesn't get GC'd
# See: https://docs.python.org/3/library/asyncio-task.html#creating-tasks
self._background_tasks: set[asyncio.Task[None]] = set()
task = self._loop.create_task(self._dispatch_from_queue(stop_event=self._stop_event))
self._background_tasks.add(task)
task.add_done_callback(self._background_tasks.remove)
super().__init__()
def stop(self, *args, **kwargs):
self._stop_event.set()
self._queue.put(None)
self._loop.call_soon_threadsafe(self._queue.put_nowait, None)
def dispatch(self, event: EventBase) -> None:
self._queue.put(event)
self._loop.call_soon_threadsafe(self._queue.put_nowait, event)
async def _dispatch_from_queue(self, stop_event: threading.Event):
"""Get events on from the queue and dispatch them, from the correct thread"""
while not stop_event.is_set():
try:
event = self._queue.get(block=False)
event = await self._queue.get()
if not event: # Probably stopping
continue
# Leave the payloads as live pydantic models
dispatch(event, middleware_id=self.event_handler_id, payload_schema_dump=False)
except Empty:
await asyncio.sleep(0.1)
pass
except asyncio.CancelledError as e:
raise e # Raise a proper error

View File

@@ -1,11 +1,10 @@
# Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654) and the InvokeAI Team
from pathlib import Path
from queue import Queue
from typing import Dict, Optional, Union
from typing import Optional, Union
from PIL import Image, PngImagePlugin
from PIL.Image import Image as PILImageType
from send2trash import send2trash
from invokeai.app.services.image_files.image_files_base import ImageFileStorageBase
from invokeai.app.services.image_files.image_files_common import (
@@ -20,18 +19,12 @@ from invokeai.app.util.thumbnails import get_thumbnail_name, make_thumbnail
class DiskImageFileStorage(ImageFileStorageBase):
"""Stores images on disk"""
__output_folder: Path
__cache_ids: Queue # TODO: this is an incredibly naive cache
__cache: Dict[Path, PILImageType]
__max_cache_size: int
__invoker: Invoker
def __init__(self, output_folder: Union[str, Path]):
self.__cache = {}
self.__cache_ids = Queue()
self.__cache: dict[Path, PILImageType] = {}
self.__cache_ids = Queue[Path]()
self.__max_cache_size = 10 # TODO: get this from config
self.__output_folder: Path = output_folder if isinstance(output_folder, Path) else Path(output_folder)
self.__output_folder = output_folder if isinstance(output_folder, Path) else Path(output_folder)
self.__thumbnails_folder = self.__output_folder / "thumbnails"
# Validate required output folders at launch
self.__validate_storage_folders()
@@ -103,7 +96,7 @@ class DiskImageFileStorage(ImageFileStorageBase):
image_path = self.get_path(image_name)
if image_path.exists():
send2trash(image_path)
image_path.unlink()
if image_path in self.__cache:
del self.__cache[image_path]
@@ -111,7 +104,7 @@ class DiskImageFileStorage(ImageFileStorageBase):
thumbnail_path = self.get_path(thumbnail_name, True)
if thumbnail_path.exists():
send2trash(thumbnail_path)
thumbnail_path.unlink()
if thumbnail_path in self.__cache:
del self.__cache[thumbnail_path]
except Exception as e:

View File

@@ -4,6 +4,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
from invokeai.app.services.object_serializer.object_serializer_base import ObjectSerializerBase
from invokeai.app.services.style_preset_images.style_preset_images_base import StylePresetImageFileStorageBase
from invokeai.app.services.style_preset_records.style_preset_records_base import StylePresetRecordsStorageBase
if TYPE_CHECKING:
from logging import Logger
@@ -61,6 +63,8 @@ class InvocationServices:
workflow_records: "WorkflowRecordsStorageBase",
tensors: "ObjectSerializerBase[torch.Tensor]",
conditioning: "ObjectSerializerBase[ConditioningFieldData]",
style_preset_records: "StylePresetRecordsStorageBase",
style_preset_image_files: "StylePresetImageFileStorageBase",
):
self.board_images = board_images
self.board_image_records = board_image_records
@@ -85,3 +89,5 @@ class InvocationServices:
self.workflow_records = workflow_records
self.tensors = tensors
self.conditioning = conditioning
self.style_preset_records = style_preset_records
self.style_preset_image_files = style_preset_image_files

View File

@@ -2,7 +2,6 @@ from pathlib import Path
from PIL import Image
from PIL.Image import Image as PILImageType
from send2trash import send2trash
from invokeai.app.services.invoker import Invoker
from invokeai.app.services.model_images.model_images_base import ModelImageFileStorageBase
@@ -70,7 +69,7 @@ class ModelImageFileStorageDisk(ModelImageFileStorageBase):
if not self._validate_path(path):
raise ModelImageFileNotFoundException
send2trash(path)
path.unlink()
except Exception as e:
raise ModelImageFileDeleteException from e

View File

@@ -3,7 +3,7 @@
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Any, Dict, List, Optional, Union
from typing import List, Optional, Union
from pydantic.networks import AnyHttpUrl
@@ -12,7 +12,7 @@ from invokeai.app.services.download import DownloadQueueServiceBase
from invokeai.app.services.events.events_base import EventServiceBase
from invokeai.app.services.invoker import Invoker
from invokeai.app.services.model_install.model_install_common import ModelInstallJob, ModelSource
from invokeai.app.services.model_records import ModelRecordServiceBase
from invokeai.app.services.model_records import ModelRecordChanges, ModelRecordServiceBase
from invokeai.backend.model_manager import AnyModelConfig
@@ -64,7 +64,7 @@ class ModelInstallServiceBase(ABC):
def register_path(
self,
model_path: Union[Path, str],
config: Optional[Dict[str, Any]] = None,
config: Optional[ModelRecordChanges] = None,
) -> str:
"""
Probe and register the model at model_path.
@@ -72,7 +72,7 @@ class ModelInstallServiceBase(ABC):
This keeps the model in its current location.
:param model_path: Filesystem Path to the model.
:param config: Dict of attributes that will override autoassigned values.
:param config: ModelRecordChanges object that will override autoassigned model record values.
:returns id: The string ID of the registered model.
"""
@@ -92,7 +92,7 @@ class ModelInstallServiceBase(ABC):
def install_path(
self,
model_path: Union[Path, str],
config: Optional[Dict[str, Any]] = None,
config: Optional[ModelRecordChanges] = None,
) -> str:
"""
Probe, register and install the model in the models directory.
@@ -101,7 +101,7 @@ class ModelInstallServiceBase(ABC):
the models directory handled by InvokeAI.
:param model_path: Filesystem Path to the model.
:param config: Dict of attributes that will override autoassigned values.
:param config: ModelRecordChanges object that will override autoassigned model record values.
:returns id: The string ID of the registered model.
"""
@@ -109,14 +109,14 @@ class ModelInstallServiceBase(ABC):
def heuristic_import(
self,
source: str,
config: Optional[Dict[str, Any]] = None,
config: Optional[ModelRecordChanges] = None,
access_token: Optional[str] = None,
inplace: Optional[bool] = False,
) -> ModelInstallJob:
r"""Install the indicated model using heuristics to interpret user intentions.
:param source: String source
:param config: Optional dict. Any fields in this dict
:param config: Optional ModelRecordChanges object. Any fields in this object
will override corresponding autoassigned probe fields in the
model's config record as described in `import_model()`.
:param access_token: Optional access token for remote sources.
@@ -147,7 +147,7 @@ class ModelInstallServiceBase(ABC):
def import_model(
self,
source: ModelSource,
config: Optional[Dict[str, Any]] = None,
config: Optional[ModelRecordChanges] = None,
) -> ModelInstallJob:
"""Install the indicated model.

View File

@@ -2,13 +2,14 @@ import re
import traceback
from enum import Enum
from pathlib import Path
from typing import Any, Dict, Literal, Optional, Set, Union
from typing import Literal, Optional, Set, Union
from pydantic import BaseModel, Field, PrivateAttr, field_validator
from pydantic.networks import AnyHttpUrl
from typing_extensions import Annotated
from invokeai.app.services.download import DownloadJob, MultiFileDownloadJob
from invokeai.app.services.model_records import ModelRecordChanges
from invokeai.backend.model_manager import AnyModelConfig, ModelRepoVariant
from invokeai.backend.model_manager.config import ModelSourceType
from invokeai.backend.model_manager.metadata import AnyModelRepoMetadata
@@ -133,8 +134,9 @@ class ModelInstallJob(BaseModel):
id: int = Field(description="Unique ID for this job")
status: InstallStatus = Field(default=InstallStatus.WAITING, description="Current status of install process")
error_reason: Optional[str] = Field(default=None, description="Information about why the job failed")
config_in: Dict[str, Any] = Field(
default_factory=dict, description="Configuration information (e.g. 'description') to apply to model."
config_in: ModelRecordChanges = Field(
default_factory=ModelRecordChanges,
description="Configuration information (e.g. 'description') to apply to model.",
)
config_out: Optional[AnyModelConfig] = Field(
default=None, description="After successful installation, this will hold the configuration object."

View File

@@ -163,26 +163,27 @@ class ModelInstallService(ModelInstallServiceBase):
def register_path(
self,
model_path: Union[Path, str],
config: Optional[Dict[str, Any]] = None,
config: Optional[ModelRecordChanges] = None,
) -> str: # noqa D102
model_path = Path(model_path)
config = config or {}
if not config.get("source"):
config["source"] = model_path.resolve().as_posix()
config["source_type"] = ModelSourceType.Path
config = config or ModelRecordChanges()
if not config.source:
config.source = model_path.resolve().as_posix()
config.source_type = ModelSourceType.Path
return self._register(model_path, config)
def install_path(
self,
model_path: Union[Path, str],
config: Optional[Dict[str, Any]] = None,
config: Optional[ModelRecordChanges] = None,
) -> str: # noqa D102
model_path = Path(model_path)
config = config or {}
config = config or ModelRecordChanges()
info: AnyModelConfig = ModelProbe.probe(
Path(model_path), config.model_dump(), hash_algo=self._app_config.hashing_algorithm
) # type: ignore
info: AnyModelConfig = ModelProbe.probe(Path(model_path), config, hash_algo=self._app_config.hashing_algorithm)
if preferred_name := config.get("name"):
if preferred_name := config.name:
preferred_name = Path(preferred_name).with_suffix(model_path.suffix)
dest_path = (
@@ -204,7 +205,7 @@ class ModelInstallService(ModelInstallServiceBase):
def heuristic_import(
self,
source: str,
config: Optional[Dict[str, Any]] = None,
config: Optional[ModelRecordChanges] = None,
access_token: Optional[str] = None,
inplace: Optional[bool] = False,
) -> ModelInstallJob:
@@ -216,7 +217,7 @@ class ModelInstallService(ModelInstallServiceBase):
source_obj.access_token = access_token
return self.import_model(source_obj, config)
def import_model(self, source: ModelSource, config: Optional[Dict[str, Any]] = None) -> ModelInstallJob: # noqa D102
def import_model(self, source: ModelSource, config: Optional[ModelRecordChanges] = None) -> ModelInstallJob: # noqa D102
similar_jobs = [x for x in self.list_jobs() if x.source == source and not x.in_terminal_state]
if similar_jobs:
self._logger.warning(f"There is already an active install job for {source}. Not enqueuing.")
@@ -318,16 +319,17 @@ class ModelInstallService(ModelInstallServiceBase):
model_path = self._app_config.models_path / model_path
model_path = model_path.resolve()
config: dict[str, Any] = {}
config["name"] = model_name
config["description"] = stanza.get("description")
config = ModelRecordChanges(
name=model_name,
description=stanza.get("description"),
)
legacy_config_path = stanza.get("config")
if legacy_config_path:
# In v3, these paths were relative to the root. Migrate them to be relative to the legacy_conf_dir.
legacy_config_path = self._app_config.root_path / legacy_config_path
if legacy_config_path.is_relative_to(self._app_config.legacy_conf_path):
legacy_config_path = legacy_config_path.relative_to(self._app_config.legacy_conf_path)
config["config_path"] = str(legacy_config_path)
config.config_path = str(legacy_config_path)
try:
id = self.register_path(model_path=model_path, config=config)
self._logger.info(f"Migrated {model_name} with id {id}")
@@ -500,11 +502,11 @@ class ModelInstallService(ModelInstallServiceBase):
job.total_bytes = self._stat_size(job.local_path)
job.bytes = job.total_bytes
self._signal_job_running(job)
job.config_in["source"] = str(job.source)
job.config_in["source_type"] = MODEL_SOURCE_TO_TYPE_MAP[job.source.__class__]
job.config_in.source = str(job.source)
job.config_in.source_type = MODEL_SOURCE_TO_TYPE_MAP[job.source.__class__]
# enter the metadata, if there is any
if isinstance(job.source_metadata, (HuggingFaceMetadata)):
job.config_in["source_api_response"] = job.source_metadata.api_response
job.config_in.source_api_response = job.source_metadata.api_response
if job.inplace:
key = self.register_path(job.local_path, job.config_in)
@@ -639,11 +641,11 @@ class ModelInstallService(ModelInstallServiceBase):
return new_path
def _register(
self, model_path: Path, config: Optional[Dict[str, Any]] = None, info: Optional[AnyModelConfig] = None
self, model_path: Path, config: Optional[ModelRecordChanges] = None, info: Optional[AnyModelConfig] = None
) -> str:
config = config or {}
config = config or ModelRecordChanges()
info = info or ModelProbe.probe(model_path, config, hash_algo=self._app_config.hashing_algorithm)
info = info or ModelProbe.probe(model_path, config.model_dump(), hash_algo=self._app_config.hashing_algorithm) # type: ignore
model_path = model_path.resolve()
@@ -674,11 +676,13 @@ class ModelInstallService(ModelInstallServiceBase):
precision = TorchDevice.choose_torch_dtype()
return ModelRepoVariant.FP16 if precision == torch.float16 else None
def _import_local_model(self, source: LocalModelSource, config: Optional[Dict[str, Any]]) -> ModelInstallJob:
def _import_local_model(
self, source: LocalModelSource, config: Optional[ModelRecordChanges] = None
) -> ModelInstallJob:
return ModelInstallJob(
id=self._next_id(),
source=source,
config_in=config or {},
config_in=config or ModelRecordChanges(),
local_path=Path(source.path),
inplace=source.inplace or False,
)
@@ -686,7 +690,7 @@ class ModelInstallService(ModelInstallServiceBase):
def _import_from_hf(
self,
source: HFModelSource,
config: Optional[Dict[str, Any]] = None,
config: Optional[ModelRecordChanges] = None,
) -> ModelInstallJob:
# Add user's cached access token to HuggingFace requests
if source.access_token is None:
@@ -702,7 +706,7 @@ class ModelInstallService(ModelInstallServiceBase):
def _import_from_url(
self,
source: URLModelSource,
config: Optional[Dict[str, Any]],
config: Optional[ModelRecordChanges] = None,
) -> ModelInstallJob:
remote_files, metadata = self._remote_files_from_source(source)
return self._import_remote_model(
@@ -717,7 +721,7 @@ class ModelInstallService(ModelInstallServiceBase):
source: HFModelSource | URLModelSource,
remote_files: List[RemoteModelFile],
metadata: Optional[AnyModelRepoMetadata],
config: Optional[Dict[str, Any]],
config: Optional[ModelRecordChanges],
) -> ModelInstallJob:
if len(remote_files) == 0:
raise ValueError(f"{source}: No downloadable files found")
@@ -730,7 +734,7 @@ class ModelInstallService(ModelInstallServiceBase):
install_job = ModelInstallJob(
id=self._next_id(),
source=source,
config_in=config or {},
config_in=config or ModelRecordChanges(),
source_metadata=metadata,
local_path=destdir, # local path may change once the download has started due to content-disposition handling
bytes=0,

View File

@@ -18,6 +18,7 @@ from invokeai.backend.model_manager.config import (
ControlAdapterDefaultSettings,
MainModelDefaultSettings,
ModelFormat,
ModelSourceType,
ModelType,
ModelVariantType,
SchedulerPredictionType,
@@ -66,10 +67,16 @@ class ModelRecordChanges(BaseModelExcludeNull):
"""A set of changes to apply to a model."""
# Changes applicable to all models
source: Optional[str] = Field(description="original source of the model", default=None)
source_type: Optional[ModelSourceType] = Field(description="type of model source", default=None)
source_api_response: Optional[str] = Field(description="metadata from remote source", default=None)
name: Optional[str] = Field(description="Name of the model.", default=None)
path: Optional[str] = Field(description="Path to the model.", default=None)
description: Optional[str] = Field(description="Model description", default=None)
base: Optional[BaseModelType] = Field(description="The base model.", default=None)
type: Optional[ModelType] = Field(description="Type of model", default=None)
key: Optional[str] = Field(description="Database ID for this model", default=None)
hash: Optional[str] = Field(description="hash of model file", default=None)
trigger_phrases: Optional[set[str]] = Field(description="Set of trigger phrases for this model", default=None)
default_settings: Optional[MainModelDefaultSettings | ControlAdapterDefaultSettings] = Field(
description="Default settings for this model", default=None

View File

@@ -40,11 +40,14 @@ Typical usage:
"""
import json
import logging
import sqlite3
from math import ceil
from pathlib import Path
from typing import List, Optional, Union
import pydantic
from invokeai.app.services.model_records.model_records_base import (
DuplicateModelException,
ModelRecordChanges,
@@ -67,7 +70,7 @@ from invokeai.backend.model_manager.config import (
class ModelRecordServiceSQL(ModelRecordServiceBase):
"""Implementation of the ModelConfigStore ABC using a SQL database."""
def __init__(self, db: SqliteDatabase):
def __init__(self, db: SqliteDatabase, logger: logging.Logger):
"""
Initialize a new object from preexisting sqlite3 connection and threading lock objects.
@@ -76,6 +79,7 @@ class ModelRecordServiceSQL(ModelRecordServiceBase):
super().__init__()
self._db = db
self._cursor = db.conn.cursor()
self._logger = logger
@property
def db(self) -> SqliteDatabase:
@@ -291,7 +295,20 @@ class ModelRecordServiceSQL(ModelRecordServiceBase):
tuple(bindings),
)
result = self._cursor.fetchall()
results = [ModelConfigFactory.make_config(json.loads(x[0]), timestamp=x[1]) for x in result]
# Parse the model configs.
results: list[AnyModelConfig] = []
for row in result:
try:
model_config = ModelConfigFactory.make_config(json.loads(row[0]), timestamp=row[1])
except pydantic.ValidationError:
# We catch this error so that the app can still run if there are invalid model configs in the database.
# One reason that an invalid model config might be in the database is if someone had to rollback from a
# newer version of the app that added a new model type.
self._logger.warning(f"Found an invalid model config in the database. Ignoring this model. ({row[0]})")
else:
results.append(model_config)
return results
def search_by_path(self, path: Union[str, Path]) -> List[AnyModelConfig]:

View File

@@ -6,6 +6,7 @@ from invokeai.app.services.session_queue.session_queue_common import (
Batch,
BatchStatus,
CancelByBatchIDsResult,
CancelByOriginResult,
CancelByQueueIDResult,
ClearResult,
EnqueueBatchResult,
@@ -95,6 +96,11 @@ class SessionQueueBase(ABC):
"""Cancels all queue items with matching batch IDs"""
pass
@abstractmethod
def cancel_by_origin(self, queue_id: str, origin: str) -> CancelByOriginResult:
"""Cancels all queue items with the given batch origin"""
pass
@abstractmethod
def cancel_by_queue_id(self, queue_id: str) -> CancelByQueueIDResult:
"""Cancels all queue items with matching queue ID"""

View File

@@ -77,6 +77,7 @@ BatchDataCollection: TypeAlias = list[list[BatchDatum]]
class Batch(BaseModel):
batch_id: str = Field(default_factory=uuid_string, description="The ID of the batch")
origin: str | None = Field(default=None, description="The origin of this batch.")
data: Optional[BatchDataCollection] = Field(default=None, description="The batch data collection.")
graph: Graph = Field(description="The graph to initialize the session with")
workflow: Optional[WorkflowWithoutID] = Field(
@@ -195,6 +196,7 @@ class SessionQueueItemWithoutGraph(BaseModel):
status: QUEUE_ITEM_STATUS = Field(default="pending", description="The status of this queue item")
priority: int = Field(default=0, description="The priority of this queue item")
batch_id: str = Field(description="The ID of the batch associated with this queue item")
origin: str | None = Field(default=None, description="The origin of this queue item. ")
session_id: str = Field(
description="The ID of the session associated with this queue item. The session doesn't exist in graph_executions until the queue item is executed."
)
@@ -294,6 +296,7 @@ class SessionQueueStatus(BaseModel):
class BatchStatus(BaseModel):
queue_id: str = Field(..., description="The ID of the queue")
batch_id: str = Field(..., description="The ID of the batch")
origin: str | None = Field(..., description="The origin of the batch")
pending: int = Field(..., description="Number of queue items with status 'pending'")
in_progress: int = Field(..., description="Number of queue items with status 'in_progress'")
completed: int = Field(..., description="Number of queue items with status 'complete'")
@@ -328,6 +331,12 @@ class CancelByBatchIDsResult(BaseModel):
canceled: int = Field(..., description="Number of queue items canceled")
class CancelByOriginResult(BaseModel):
"""Result of canceling by list of batch ids"""
canceled: int = Field(..., description="Number of queue items canceled")
class CancelByQueueIDResult(CancelByBatchIDsResult):
"""Result of canceling by queue id"""
@@ -433,6 +442,7 @@ class SessionQueueValueToInsert(NamedTuple):
field_values: Optional[str] # field_values json
priority: int # priority
workflow: Optional[str] # workflow json
origin: str | None
ValuesToInsert: TypeAlias = list[SessionQueueValueToInsert]
@@ -453,6 +463,7 @@ def prepare_values_to_insert(queue_id: str, batch: Batch, priority: int, max_new
json.dumps(field_values, default=to_jsonable_python) if field_values else None, # field_values (json)
priority, # priority
json.dumps(workflow, default=to_jsonable_python) if workflow else None, # workflow (json)
batch.origin, # origin
)
)
return values_to_insert

View File

@@ -10,6 +10,7 @@ from invokeai.app.services.session_queue.session_queue_common import (
Batch,
BatchStatus,
CancelByBatchIDsResult,
CancelByOriginResult,
CancelByQueueIDResult,
ClearResult,
EnqueueBatchResult,
@@ -127,8 +128,8 @@ class SqliteSessionQueue(SessionQueueBase):
self.__cursor.executemany(
"""--sql
INSERT INTO session_queue (queue_id, session, session_id, batch_id, field_values, priority, workflow)
VALUES (?, ?, ?, ?, ?, ?, ?)
INSERT INTO session_queue (queue_id, session, session_id, batch_id, field_values, priority, workflow, origin)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""",
values_to_insert,
)
@@ -417,11 +418,7 @@ class SqliteSessionQueue(SessionQueueBase):
)
self.__conn.commit()
if current_queue_item is not None and current_queue_item.batch_id in batch_ids:
batch_status = self.get_batch_status(queue_id=queue_id, batch_id=current_queue_item.batch_id)
queue_status = self.get_queue_status(queue_id=queue_id)
self.__invoker.services.events.emit_queue_item_status_changed(
current_queue_item, batch_status, queue_status
)
self._set_queue_item_status(current_queue_item.item_id, "canceled")
except Exception:
self.__conn.rollback()
raise
@@ -429,6 +426,46 @@ class SqliteSessionQueue(SessionQueueBase):
self.__lock.release()
return CancelByBatchIDsResult(canceled=count)
def cancel_by_origin(self, queue_id: str, origin: str) -> CancelByOriginResult:
try:
current_queue_item = self.get_current(queue_id)
self.__lock.acquire()
where = """--sql
WHERE
queue_id == ?
AND origin == ?
AND status != 'canceled'
AND status != 'completed'
AND status != 'failed'
"""
params = (queue_id, origin)
self.__cursor.execute(
f"""--sql
SELECT COUNT(*)
FROM session_queue
{where};
""",
params,
)
count = self.__cursor.fetchone()[0]
self.__cursor.execute(
f"""--sql
UPDATE session_queue
SET status = 'canceled'
{where};
""",
params,
)
self.__conn.commit()
if current_queue_item is not None and current_queue_item.origin == origin:
self._set_queue_item_status(current_queue_item.item_id, "canceled")
except Exception:
self.__conn.rollback()
raise
finally:
self.__lock.release()
return CancelByOriginResult(canceled=count)
def cancel_by_queue_id(self, queue_id: str) -> CancelByQueueIDResult:
try:
current_queue_item = self.get_current(queue_id)
@@ -541,7 +578,8 @@ class SqliteSessionQueue(SessionQueueBase):
started_at,
session_id,
batch_id,
queue_id
queue_id,
origin
FROM session_queue
WHERE queue_id = ?
"""
@@ -621,7 +659,7 @@ class SqliteSessionQueue(SessionQueueBase):
self.__lock.acquire()
self.__cursor.execute(
"""--sql
SELECT status, count(*)
SELECT status, count(*), origin
FROM session_queue
WHERE
queue_id = ?
@@ -633,6 +671,7 @@ class SqliteSessionQueue(SessionQueueBase):
result = cast(list[sqlite3.Row], self.__cursor.fetchall())
total = sum(row[1] for row in result)
counts: dict[str, int] = {row[0]: row[1] for row in result}
origin = result[0]["origin"] if result else None
except Exception:
self.__conn.rollback()
raise
@@ -641,6 +680,7 @@ class SqliteSessionQueue(SessionQueueBase):
return BatchStatus(
batch_id=batch_id,
origin=origin,
queue_id=queue_id,
pending=counts.get("pending", 0),
in_progress=counts.get("in_progress", 0),

View File

@@ -16,6 +16,8 @@ from invokeai.app.services.shared.sqlite_migrator.migrations.migration_10 import
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_11 import build_migration_11
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_12 import build_migration_12
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_13 import build_migration_13
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_14 import build_migration_14
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_15 import build_migration_15
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_impl import SqliteMigrator
@@ -49,6 +51,8 @@ def init_db(config: InvokeAIAppConfig, logger: Logger, image_files: ImageFileSto
migrator.register_migration(build_migration_11(app_config=config, logger=logger))
migrator.register_migration(build_migration_12(app_config=config))
migrator.register_migration(build_migration_13())
migrator.register_migration(build_migration_14())
migrator.register_migration(build_migration_15())
migrator.run_migrations()
return db

View File

@@ -0,0 +1,61 @@
import sqlite3
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
class Migration14Callback:
def __call__(self, cursor: sqlite3.Cursor) -> None:
self._create_style_presets(cursor)
def _create_style_presets(self, cursor: sqlite3.Cursor) -> None:
"""Create the table used to store style presets."""
tables = [
"""--sql
CREATE TABLE IF NOT EXISTS style_presets (
id TEXT NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
preset_data TEXT NOT NULL,
type TEXT NOT NULL DEFAULT "user",
created_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')),
-- Updated via trigger
updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW'))
);
"""
]
# Add trigger for `updated_at`.
triggers = [
"""--sql
CREATE TRIGGER IF NOT EXISTS style_presets
AFTER UPDATE
ON style_presets FOR EACH ROW
BEGIN
UPDATE style_presets SET updated_at = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')
WHERE id = old.id;
END;
"""
]
# Add indexes for searchable fields
indices = [
"CREATE INDEX IF NOT EXISTS idx_style_presets_name ON style_presets(name);",
]
for stmt in tables + indices + triggers:
cursor.execute(stmt)
def build_migration_14() -> Migration:
"""
Build the migration from database version 13 to 14..
This migration does the following:
- Create the table used to store style presets.
"""
migration_14 = Migration(
from_version=13,
to_version=14,
callback=Migration14Callback(),
)
return migration_14

View File

@@ -0,0 +1,31 @@
import sqlite3
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
class Migration15Callback:
def __call__(self, cursor: sqlite3.Cursor) -> None:
self._add_origin_col(cursor)
def _add_origin_col(self, cursor: sqlite3.Cursor) -> None:
"""
- Adds `origin` column to the session queue table.
"""
cursor.execute("ALTER TABLE session_queue ADD COLUMN origin TEXT;")
def build_migration_15() -> Migration:
"""
Build the migration from database version 14 to 15.
This migration does the following:
- Adds `origin` column to the session queue table.
"""
migration_15 = Migration(
from_version=14,
to_version=15,
callback=Migration15Callback(),
)
return migration_15

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

View File

@@ -0,0 +1,33 @@
from abc import ABC, abstractmethod
from pathlib import Path
from PIL.Image import Image as PILImageType
class StylePresetImageFileStorageBase(ABC):
"""Low-level service responsible for storing and retrieving image files."""
@abstractmethod
def get(self, style_preset_id: str) -> PILImageType:
"""Retrieves a style preset image as PIL Image."""
pass
@abstractmethod
def get_path(self, style_preset_id: str) -> Path:
"""Gets the internal path to a style preset image."""
pass
@abstractmethod
def get_url(self, style_preset_id: str) -> str | None:
"""Gets the URL to fetch a style preset image."""
pass
@abstractmethod
def save(self, style_preset_id: str, image: PILImageType) -> None:
"""Saves a style preset image."""
pass
@abstractmethod
def delete(self, style_preset_id: str) -> None:
"""Deletes a style preset image."""
pass

View File

@@ -0,0 +1,19 @@
class StylePresetImageFileNotFoundException(Exception):
"""Raised when an image file is not found in storage."""
def __init__(self, message: str = "Style preset image file not found"):
super().__init__(message)
class StylePresetImageFileSaveException(Exception):
"""Raised when an image cannot be saved."""
def __init__(self, message: str = "Style preset image file not saved"):
super().__init__(message)
class StylePresetImageFileDeleteException(Exception):
"""Raised when an image cannot be deleted."""
def __init__(self, message: str = "Style preset image file not deleted"):
super().__init__(message)

View File

@@ -0,0 +1,88 @@
from pathlib import Path
from PIL import Image
from PIL.Image import Image as PILImageType
from invokeai.app.services.invoker import Invoker
from invokeai.app.services.style_preset_images.style_preset_images_base import StylePresetImageFileStorageBase
from invokeai.app.services.style_preset_images.style_preset_images_common import (
StylePresetImageFileDeleteException,
StylePresetImageFileNotFoundException,
StylePresetImageFileSaveException,
)
from invokeai.app.services.style_preset_records.style_preset_records_common import PresetType
from invokeai.app.util.misc import uuid_string
from invokeai.app.util.thumbnails import make_thumbnail
class StylePresetImageFileStorageDisk(StylePresetImageFileStorageBase):
"""Stores images on disk"""
def __init__(self, style_preset_images_folder: Path):
self._style_preset_images_folder = style_preset_images_folder
self._validate_storage_folders()
def start(self, invoker: Invoker) -> None:
self._invoker = invoker
def get(self, style_preset_id: str) -> PILImageType:
try:
path = self.get_path(style_preset_id)
return Image.open(path)
except FileNotFoundError as e:
raise StylePresetImageFileNotFoundException from e
def save(self, style_preset_id: str, image: PILImageType) -> None:
try:
self._validate_storage_folders()
image_path = self._style_preset_images_folder / (style_preset_id + ".webp")
thumbnail = make_thumbnail(image, 256)
thumbnail.save(image_path, format="webp")
except Exception as e:
raise StylePresetImageFileSaveException from e
def get_path(self, style_preset_id: str) -> Path:
style_preset = self._invoker.services.style_preset_records.get(style_preset_id)
if style_preset.type is PresetType.Default:
default_images_dir = Path(__file__).parent / Path("default_style_preset_images")
path = default_images_dir / (style_preset.name + ".png")
else:
path = self._style_preset_images_folder / (style_preset_id + ".webp")
return path
def get_url(self, style_preset_id: str) -> str | None:
path = self.get_path(style_preset_id)
if not self._validate_path(path):
return
url = self._invoker.services.urls.get_style_preset_image_url(style_preset_id)
# The image URL never changes, so we must add random query string to it to prevent caching
url += f"?{uuid_string()}"
return url
def delete(self, style_preset_id: str) -> None:
try:
path = self.get_path(style_preset_id)
if not self._validate_path(path):
raise StylePresetImageFileNotFoundException
path.unlink()
except StylePresetImageFileNotFoundException as e:
raise StylePresetImageFileNotFoundException from e
except Exception as e:
raise StylePresetImageFileDeleteException from e
def _validate_path(self, path: Path) -> bool:
"""Validates the path given for an image."""
return path.exists()
def _validate_storage_folders(self) -> None:
"""Checks if the required folders exist and create them if they don't"""
self._style_preset_images_folder.mkdir(parents=True, exist_ok=True)

View File

@@ -0,0 +1,146 @@
[
{
"name": "Photography (General)",
"type": "default",
"preset_data": {
"positive_prompt": "{prompt}. photography. f/2.8 macro photo, bokeh, photorealism",
"negative_prompt": "painting, digital art. sketch, blurry"
}
},
{
"name": "Photography (Studio Lighting)",
"type": "default",
"preset_data": {
"positive_prompt": "{prompt}, photography. f/8 photo. centered subject, studio lighting.",
"negative_prompt": "painting, digital art. sketch, blurry"
}
},
{
"name": "Photography (Landscape)",
"type": "default",
"preset_data": {
"positive_prompt": "{prompt}, landscape photograph, f/12, lifelike, highly detailed.",
"negative_prompt": "painting, digital art. sketch, blurry"
}
},
{
"name": "Photography (Portrait)",
"type": "default",
"preset_data": {
"positive_prompt": "{prompt}. photography. portraiture. catch light in eyes. one flash. rembrandt lighting. Soft box. dark shadows. High contrast. 80mm lens. F2.8.",
"negative_prompt": "painting, digital art. sketch, blurry"
}
},
{
"name": "Photography (Black and White)",
"type": "default",
"preset_data": {
"positive_prompt": "{prompt} photography. natural light. 80mm lens. F1.4. strong contrast, hard light. dark contrast. blurred background. black and white",
"negative_prompt": "painting, digital art. sketch, colour+"
}
},
{
"name": "Architectural Visualization",
"type": "default",
"preset_data": {
"positive_prompt": "{prompt}. architectural photography, f/12, luxury, aesthetically pleasing form and function.",
"negative_prompt": "painting, digital art. sketch, blurry"
}
},
{
"name": "Concept Art (Fantasy)",
"type": "default",
"preset_data": {
"positive_prompt": "concept artwork of a {prompt}. (digital painterly art style)++, mythological, (textured 2d dry media brushpack)++, glazed brushstrokes, otherworldly. painting+, illustration+",
"negative_prompt": "photo. distorted, blurry, out of focus. sketch. (cgi, 3d.)++"
}
},
{
"name": "Concept Art (Sci-Fi)",
"type": "default",
"preset_data": {
"positive_prompt": "(concept art)++, {prompt}, (sleek futurism)++, (textured 2d dry media)++, metallic highlights, digital painting style",
"negative_prompt": "photo. distorted, blurry, out of focus. sketch. (cgi, 3d.)++"
}
},
{
"name": "Concept Art (Character)",
"type": "default",
"preset_data": {
"positive_prompt": "(character concept art)++, stylized painterly digital painting of {prompt}, (painterly, impasto. Dry brush.)++",
"negative_prompt": "photo. distorted, blurry, out of focus. sketch. (cgi, 3d.)++"
}
},
{
"name": "Concept Art (Painterly)",
"type": "default",
"preset_data": {
"positive_prompt": "{prompt} oil painting. high contrast. impasto. sfumato. chiaroscuro. Palette knife.",
"negative_prompt": "photo. smooth. border. frame"
}
},
{
"name": "Environment Art",
"type": "default",
"preset_data": {
"positive_prompt": "{prompt} environment artwork, hyper-realistic digital painting style with cinematic composition, atmospheric, depth and detail, voluminous. textured dry brush 2d media",
"negative_prompt": "photo, distorted, blurry, out of focus. sketch."
}
},
{
"name": "Interior Design (Visualization)",
"type": "default",
"preset_data": {
"positive_prompt": "{prompt} interior design photo, gentle shadows, light mid-tones, dimension, mix of smooth and textured surfaces, focus on negative space and clean lines, focus",
"negative_prompt": "photo, distorted. sketch."
}
},
{
"name": "Product Rendering",
"type": "default",
"preset_data": {
"positive_prompt": "{prompt} high quality product photography, 3d rendering with key lighting, shallow depth of field, simple plain background, studio lighting.",
"negative_prompt": "blurry, sketch, messy, dirty. unfinished."
}
},
{
"name": "Sketch",
"type": "default",
"preset_data": {
"positive_prompt": "{prompt} black and white pencil drawing, off-center composition, cross-hatching for shadows, bold strokes, textured paper. sketch+++",
"negative_prompt": "blurry, photo, painting, color. messy, dirty. unfinished. frame, borders."
}
},
{
"name": "Line Art",
"type": "default",
"preset_data": {
"positive_prompt": "{prompt} Line art. bold outline. simplistic. white background. 2d",
"negative_prompt": "photo. digital art. greyscale. solid black. painting"
}
},
{
"name": "Anime",
"type": "default",
"preset_data": {
"positive_prompt": "{prompt} anime++, bold outline, cel-shaded coloring, shounen, seinen",
"negative_prompt": "(photo)+++. greyscale. solid black. painting"
}
},
{
"name": "Illustration",
"type": "default",
"preset_data": {
"positive_prompt": "{prompt} illustration, bold linework, illustrative details, vector art style, flat coloring",
"negative_prompt": "(photo)+++. greyscale. painting, black and white."
}
},
{
"name": "Vehicles",
"type": "default",
"preset_data": {
"positive_prompt": "A weird futuristic normal auto, {prompt} elegant design, nice color, nice wheels",
"negative_prompt": "sketch. digital art. greyscale. painting"
}
}
]

View File

@@ -0,0 +1,42 @@
from abc import ABC, abstractmethod
from invokeai.app.services.style_preset_records.style_preset_records_common import (
PresetType,
StylePresetChanges,
StylePresetRecordDTO,
StylePresetWithoutId,
)
class StylePresetRecordsStorageBase(ABC):
"""Base class for style preset storage services."""
@abstractmethod
def get(self, style_preset_id: str) -> StylePresetRecordDTO:
"""Get style preset by id."""
pass
@abstractmethod
def create(self, style_preset: StylePresetWithoutId) -> StylePresetRecordDTO:
"""Creates a style preset."""
pass
@abstractmethod
def create_many(self, style_presets: list[StylePresetWithoutId]) -> None:
"""Creates many style presets."""
pass
@abstractmethod
def update(self, style_preset_id: str, changes: StylePresetChanges) -> StylePresetRecordDTO:
"""Updates a style preset."""
pass
@abstractmethod
def delete(self, style_preset_id: str) -> None:
"""Deletes a style preset."""
pass
@abstractmethod
def get_many(self, type: PresetType | None = None) -> list[StylePresetRecordDTO]:
"""Gets many workflows."""
pass

View File

@@ -0,0 +1,139 @@
import codecs
import csv
import json
from enum import Enum
from typing import Any, Optional
import pydantic
from fastapi import UploadFile
from pydantic import AliasChoices, BaseModel, ConfigDict, Field, TypeAdapter
from invokeai.app.util.metaenum import MetaEnum
class StylePresetNotFoundError(Exception):
"""Raised when a style preset is not found"""
class PresetData(BaseModel, extra="forbid"):
positive_prompt: str = Field(description="Positive prompt")
negative_prompt: str = Field(description="Negative prompt")
PresetDataValidator = TypeAdapter(PresetData)
class PresetType(str, Enum, metaclass=MetaEnum):
User = "user"
Default = "default"
Project = "project"
class StylePresetChanges(BaseModel, extra="forbid"):
name: Optional[str] = Field(default=None, description="The style preset's new name.")
preset_data: Optional[PresetData] = Field(default=None, description="The updated data for style preset.")
type: Optional[PresetType] = Field(description="The updated type of the style preset")
class StylePresetWithoutId(BaseModel):
name: str = Field(description="The name of the style preset.")
preset_data: PresetData = Field(description="The preset data")
type: PresetType = Field(description="The type of style preset")
class StylePresetRecordDTO(StylePresetWithoutId):
id: str = Field(description="The style preset ID.")
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "StylePresetRecordDTO":
data["preset_data"] = PresetDataValidator.validate_json(data.get("preset_data", ""))
return StylePresetRecordDTOValidator.validate_python(data)
StylePresetRecordDTOValidator = TypeAdapter(StylePresetRecordDTO)
class StylePresetRecordWithImage(StylePresetRecordDTO):
image: Optional[str] = Field(description="The path for image")
class StylePresetImportRow(BaseModel):
name: str = Field(min_length=1, description="The name of the preset.")
positive_prompt: str = Field(
default="",
description="The positive prompt for the preset.",
validation_alias=AliasChoices("positive_prompt", "prompt"),
)
negative_prompt: str = Field(default="", description="The negative prompt for the preset.")
model_config = ConfigDict(str_strip_whitespace=True, extra="forbid")
StylePresetImportList = list[StylePresetImportRow]
StylePresetImportListTypeAdapter = TypeAdapter(StylePresetImportList)
class UnsupportedFileTypeError(ValueError):
"""Raised when an unsupported file type is encountered"""
pass
class InvalidPresetImportDataError(ValueError):
"""Raised when invalid preset import data is encountered"""
pass
async def parse_presets_from_file(file: UploadFile) -> list[StylePresetWithoutId]:
"""Parses style presets from a file. The file must be a CSV or JSON file.
If CSV, the file must have the following columns:
- name
- prompt (or positive_prompt)
- negative_prompt
If JSON, the file must be a list of objects with the following keys:
- name
- prompt (or positive_prompt)
- negative_prompt
Args:
file (UploadFile): The file to parse.
Returns:
list[StylePresetWithoutId]: The parsed style presets.
Raises:
UnsupportedFileTypeError: If the file type is not supported.
InvalidPresetImportDataError: If the data in the file is invalid.
"""
if file.content_type not in ["text/csv", "application/json"]:
raise UnsupportedFileTypeError()
if file.content_type == "text/csv":
csv_reader = csv.DictReader(codecs.iterdecode(file.file, "utf-8"))
data = list(csv_reader)
else: # file.content_type == "application/json":
json_data = await file.read()
data = json.loads(json_data)
try:
imported_presets = StylePresetImportListTypeAdapter.validate_python(data)
style_presets: list[StylePresetWithoutId] = []
for imported in imported_presets:
preset_data = PresetData(positive_prompt=imported.positive_prompt, negative_prompt=imported.negative_prompt)
style_preset = StylePresetWithoutId(name=imported.name, preset_data=preset_data, type=PresetType.User)
style_presets.append(style_preset)
except pydantic.ValidationError as e:
if file.content_type == "text/csv":
msg = "Invalid CSV format: must include columns 'name', 'prompt', and 'negative_prompt' and name cannot be blank"
else: # file.content_type == "application/json":
msg = "Invalid JSON format: must be a list of objects with keys 'name', 'prompt', and 'negative_prompt' and name cannot be blank"
raise InvalidPresetImportDataError(msg) from e
finally:
file.file.close()
return style_presets

View File

@@ -0,0 +1,215 @@
import json
from pathlib import Path
from invokeai.app.services.invoker import Invoker
from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase
from invokeai.app.services.style_preset_records.style_preset_records_base import StylePresetRecordsStorageBase
from invokeai.app.services.style_preset_records.style_preset_records_common import (
PresetType,
StylePresetChanges,
StylePresetNotFoundError,
StylePresetRecordDTO,
StylePresetWithoutId,
)
from invokeai.app.util.misc import uuid_string
class SqliteStylePresetRecordsStorage(StylePresetRecordsStorageBase):
def __init__(self, db: SqliteDatabase) -> None:
super().__init__()
self._lock = db.lock
self._conn = db.conn
self._cursor = self._conn.cursor()
def start(self, invoker: Invoker) -> None:
self._invoker = invoker
self._sync_default_style_presets()
def get(self, style_preset_id: str) -> StylePresetRecordDTO:
"""Gets a style preset by ID."""
try:
self._lock.acquire()
self._cursor.execute(
"""--sql
SELECT *
FROM style_presets
WHERE id = ?;
""",
(style_preset_id,),
)
row = self._cursor.fetchone()
if row is None:
raise StylePresetNotFoundError(f"Style preset with id {style_preset_id} not found")
return StylePresetRecordDTO.from_dict(dict(row))
except Exception:
self._conn.rollback()
raise
finally:
self._lock.release()
def create(self, style_preset: StylePresetWithoutId) -> StylePresetRecordDTO:
style_preset_id = uuid_string()
try:
self._lock.acquire()
self._cursor.execute(
"""--sql
INSERT OR IGNORE INTO style_presets (
id,
name,
preset_data,
type
)
VALUES (?, ?, ?, ?);
""",
(
style_preset_id,
style_preset.name,
style_preset.preset_data.model_dump_json(),
style_preset.type,
),
)
self._conn.commit()
except Exception:
self._conn.rollback()
raise
finally:
self._lock.release()
return self.get(style_preset_id)
def create_many(self, style_presets: list[StylePresetWithoutId]) -> None:
style_preset_ids = []
try:
self._lock.acquire()
for style_preset in style_presets:
style_preset_id = uuid_string()
style_preset_ids.append(style_preset_id)
self._cursor.execute(
"""--sql
INSERT OR IGNORE INTO style_presets (
id,
name,
preset_data,
type
)
VALUES (?, ?, ?, ?);
""",
(
style_preset_id,
style_preset.name,
style_preset.preset_data.model_dump_json(),
style_preset.type,
),
)
self._conn.commit()
except Exception:
self._conn.rollback()
raise
finally:
self._lock.release()
return None
def update(self, style_preset_id: str, changes: StylePresetChanges) -> StylePresetRecordDTO:
try:
self._lock.acquire()
# Change the name of a style preset
if changes.name is not None:
self._cursor.execute(
"""--sql
UPDATE style_presets
SET name = ?
WHERE id = ?;
""",
(changes.name, style_preset_id),
)
# Change the preset data for a style preset
if changes.preset_data is not None:
self._cursor.execute(
"""--sql
UPDATE style_presets
SET preset_data = ?
WHERE id = ?;
""",
(changes.preset_data.model_dump_json(), style_preset_id),
)
self._conn.commit()
except Exception:
self._conn.rollback()
raise
finally:
self._lock.release()
return self.get(style_preset_id)
def delete(self, style_preset_id: str) -> None:
try:
self._lock.acquire()
self._cursor.execute(
"""--sql
DELETE from style_presets
WHERE id = ?;
""",
(style_preset_id,),
)
self._conn.commit()
except Exception:
self._conn.rollback()
raise
finally:
self._lock.release()
return None
def get_many(self, type: PresetType | None = None) -> list[StylePresetRecordDTO]:
try:
self._lock.acquire()
main_query = """
SELECT
*
FROM style_presets
"""
if type is not None:
main_query += "WHERE type = ? "
main_query += "ORDER BY LOWER(name) ASC"
if type is not None:
self._cursor.execute(main_query, (type,))
else:
self._cursor.execute(main_query)
rows = self._cursor.fetchall()
style_presets = [StylePresetRecordDTO.from_dict(dict(row)) for row in rows]
return style_presets
except Exception:
self._conn.rollback()
raise
finally:
self._lock.release()
def _sync_default_style_presets(self) -> None:
"""Syncs default style presets to the database. Internal use only."""
# First delete all existing default style presets
try:
self._lock.acquire()
self._cursor.execute(
"""--sql
DELETE FROM style_presets
WHERE type = "default";
"""
)
self._conn.commit()
except Exception:
self._conn.rollback()
raise
finally:
self._lock.release()
# Next, parse and create the default style presets
with self._lock, open(Path(__file__).parent / Path("default_style_presets.json"), "r") as file:
presets = json.load(file)
for preset in presets:
style_preset = StylePresetWithoutId.model_validate(preset)
self.create(style_preset)

View File

@@ -13,3 +13,8 @@ class UrlServiceBase(ABC):
def get_model_image_url(self, model_key: str) -> str:
"""Gets the URL for a model image"""
pass
@abstractmethod
def get_style_preset_image_url(self, style_preset_id: str) -> str:
"""Gets the URL for a style preset image"""
pass

View File

@@ -19,3 +19,6 @@ class LocalUrlService(UrlServiceBase):
def get_model_image_url(self, model_key: str) -> str:
return f"{self._base_url_v2}/models/i/{model_key}/image"
def get_style_preset_image_url(self, style_preset_id: str) -> str:
return f"{self._base_url}/style_presets/i/{style_preset_id}/image"

View File

@@ -2,7 +2,7 @@
"name": "ESRGAN Upscaling with Canny ControlNet",
"author": "InvokeAI",
"description": "Sample workflow for using Upscaling with ControlNet with SD1.5",
"version": "2.0.0",
"version": "2.1.0",
"contact": "invoke@invoke.ai",
"tags": "upscale, controlnet, default",
"notes": "",
@@ -36,14 +36,13 @@
"version": "3.0.0",
"category": "default"
},
"id": "0e71a27e-a22b-4a9b-b20a-6d789abff2bc",
"nodes": [
{
"id": "e8bf67fe-67de-4227-87eb-79e86afdfc74",
"id": "63b6ab7e-5b05-4d1b-a3b1-42d8e53ce16b",
"type": "invocation",
"data": {
"id": "e8bf67fe-67de-4227-87eb-79e86afdfc74",
"version": "1.1.1",
"id": "63b6ab7e-5b05-4d1b-a3b1-42d8e53ce16b",
"version": "1.2.0",
"nodePack": "invokeai",
"label": "",
"notes": "",
@@ -57,6 +56,10 @@
"clip": {
"name": "clip",
"label": ""
},
"mask": {
"name": "mask",
"label": ""
}
},
"isOpen": true,
@@ -65,122 +68,63 @@
},
"position": {
"x": 1250,
"y": 1500
"y": 1200
}
},
{
"id": "d8ace142-c05f-4f1d-8982-88dc7473958d",
"id": "5ca498a4-c8c8-4580-a396-0c984317205d",
"type": "invocation",
"data": {
"id": "d8ace142-c05f-4f1d-8982-88dc7473958d",
"version": "1.0.2",
"id": "5ca498a4-c8c8-4580-a396-0c984317205d",
"version": "1.1.0",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "main_model_loader",
"type": "i2l",
"inputs": {
"model": {
"name": "model",
"image": {
"name": "image",
"label": ""
},
"vae": {
"name": "vae",
"label": ""
},
"tiled": {
"name": "tiled",
"label": "",
"value": {
"key": "5cd43ca0-dd0a-418d-9f7e-35b2b9d5e106",
"hash": "blake3:6987f323017f597213cc3264250edf57056d21a40a0a85d83a1a33a7d44dc41a",
"name": "Deliberate_v5",
"base": "sd-1",
"type": "main"
}
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 700,
"y": 1375
}
},
{
"id": "771bdf6a-0813-4099-a5d8-921a138754d4",
"type": "invocation",
"data": {
"id": "771bdf6a-0813-4099-a5d8-921a138754d4",
"version": "1.0.2",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "image",
"inputs": {
"image": {
"name": "image",
"label": "Image To Upscale",
"value": {
"image_name": "d2e42ba6-d420-496b-82db-91c9b75956c1.png"
}
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 344.5593065887157,
"y": 1698.161491368619
}
},
{
"id": "f7564dd2-9539-47f2-ac13-190804461f4e",
"type": "invocation",
"data": {
"id": "f7564dd2-9539-47f2-ac13-190804461f4e",
"version": "1.3.2",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "esrgan",
"inputs": {
"board": {
"name": "board",
"label": ""
},
"metadata": {
"name": "metadata",
"label": ""
},
"image": {
"name": "image",
"label": ""
},
"model_name": {
"name": "model_name",
"label": "Upscaler Model",
"value": "RealESRGAN_x2plus.pth"
"value": false
},
"tile_size": {
"name": "tile_size",
"label": "",
"value": 400
"value": 0
},
"fp32": {
"name": "fp32",
"label": "",
"value": false
}
},
"isOpen": true,
"isOpen": false,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 717.3863693661265,
"y": 1721.9215053134815
"x": 1650,
"y": 1675
}
},
{
"id": "1d887701-df21-4966-ae6e-a7d82307d7bd",
"id": "3ed9b2ef-f4ec-40a7-94db-92e63b583ec0",
"type": "invocation",
"data": {
"id": "1d887701-df21-4966-ae6e-a7d82307d7bd",
"version": "1.3.2",
"id": "3ed9b2ef-f4ec-40a7-94db-92e63b583ec0",
"version": "1.3.0",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "canny_image_processor",
"type": "l2i",
"inputs": {
"board": {
"name": "board",
@@ -190,38 +134,37 @@
"name": "metadata",
"label": ""
},
"image": {
"name": "image",
"latents": {
"name": "latents",
"label": ""
},
"detect_resolution": {
"name": "detect_resolution",
"label": "",
"value": 512
"vae": {
"name": "vae",
"label": ""
},
"image_resolution": {
"name": "image_resolution",
"tiled": {
"name": "tiled",
"label": "",
"value": 512
"value": false
},
"low_threshold": {
"name": "low_threshold",
"tile_size": {
"name": "tile_size",
"label": "",
"value": 100
"value": 0
},
"high_threshold": {
"name": "high_threshold",
"fp32": {
"name": "fp32",
"label": "",
"value": 200
"value": false
}
},
"isOpen": true,
"isIntermediate": true,
"isIntermediate": false,
"useCache": true
},
"position": {
"x": 1200,
"y": 1900
"x": 2559.4751127537957,
"y": 1246.6000376741406
}
},
{
@@ -229,7 +172,7 @@
"type": "invocation",
"data": {
"id": "ca1d020c-89a8-4958-880a-016d28775cfa",
"version": "1.1.1",
"version": "1.1.2",
"nodePack": "invokeai",
"label": "",
"notes": "",
@@ -285,6 +228,193 @@
"y": 1902.9649340196056
}
},
{
"id": "1d887701-df21-4966-ae6e-a7d82307d7bd",
"type": "invocation",
"data": {
"id": "1d887701-df21-4966-ae6e-a7d82307d7bd",
"version": "1.3.3",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "canny_image_processor",
"inputs": {
"board": {
"name": "board",
"label": ""
},
"metadata": {
"name": "metadata",
"label": ""
},
"image": {
"name": "image",
"label": ""
},
"detect_resolution": {
"name": "detect_resolution",
"label": "",
"value": 512
},
"image_resolution": {
"name": "image_resolution",
"label": "",
"value": 512
},
"low_threshold": {
"name": "low_threshold",
"label": "",
"value": 100
},
"high_threshold": {
"name": "high_threshold",
"label": "",
"value": 200
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 1200,
"y": 1900
}
},
{
"id": "d8ace142-c05f-4f1d-8982-88dc7473958d",
"type": "invocation",
"data": {
"id": "d8ace142-c05f-4f1d-8982-88dc7473958d",
"version": "1.0.3",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "main_model_loader",
"inputs": {
"model": {
"name": "model",
"label": "",
"value": {
"key": "5cd43ca0-dd0a-418d-9f7e-35b2b9d5e106",
"hash": "blake3:6987f323017f597213cc3264250edf57056d21a40a0a85d83a1a33a7d44dc41a",
"name": "Deliberate_v5",
"base": "sd-1",
"type": "main"
}
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 700,
"y": 1375
}
},
{
"id": "e8bf67fe-67de-4227-87eb-79e86afdfc74",
"type": "invocation",
"data": {
"id": "e8bf67fe-67de-4227-87eb-79e86afdfc74",
"version": "1.2.0",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "compel",
"inputs": {
"prompt": {
"name": "prompt",
"label": "",
"value": ""
},
"clip": {
"name": "clip",
"label": ""
},
"mask": {
"name": "mask",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 1250,
"y": 1500
}
},
{
"id": "771bdf6a-0813-4099-a5d8-921a138754d4",
"type": "invocation",
"data": {
"id": "771bdf6a-0813-4099-a5d8-921a138754d4",
"version": "1.0.2",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "image",
"inputs": {
"image": {
"name": "image",
"label": "Image To Upscale"
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 344.5593065887157,
"y": 1698.161491368619
}
},
{
"id": "f7564dd2-9539-47f2-ac13-190804461f4e",
"type": "invocation",
"data": {
"id": "f7564dd2-9539-47f2-ac13-190804461f4e",
"version": "1.3.2",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "esrgan",
"inputs": {
"board": {
"name": "board",
"label": ""
},
"metadata": {
"name": "metadata",
"label": ""
},
"image": {
"name": "image",
"label": ""
},
"model_name": {
"name": "model_name",
"label": "Upscaler Model",
"value": "RealESRGAN_x2plus.pth"
},
"tile_size": {
"name": "tile_size",
"label": "",
"value": 400
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 717.3863693661265,
"y": 1721.9215053134815
}
},
{
"id": "f50624ce-82bf-41d0-bdf7-8aab11a80d48",
"type": "invocation",
@@ -413,122 +543,6 @@
"y": 1232.6219060454753
}
},
{
"id": "3ed9b2ef-f4ec-40a7-94db-92e63b583ec0",
"type": "invocation",
"data": {
"id": "3ed9b2ef-f4ec-40a7-94db-92e63b583ec0",
"version": "1.2.2",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "l2i",
"inputs": {
"board": {
"name": "board",
"label": ""
},
"metadata": {
"name": "metadata",
"label": ""
},
"latents": {
"name": "latents",
"label": ""
},
"vae": {
"name": "vae",
"label": ""
},
"tiled": {
"name": "tiled",
"label": "",
"value": false
},
"fp32": {
"name": "fp32",
"label": "",
"value": false
}
},
"isOpen": true,
"isIntermediate": false,
"useCache": true
},
"position": {
"x": 2559.4751127537957,
"y": 1246.6000376741406
}
},
{
"id": "5ca498a4-c8c8-4580-a396-0c984317205d",
"type": "invocation",
"data": {
"id": "5ca498a4-c8c8-4580-a396-0c984317205d",
"version": "1.0.2",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "i2l",
"inputs": {
"image": {
"name": "image",
"label": ""
},
"vae": {
"name": "vae",
"label": ""
},
"tiled": {
"name": "tiled",
"label": "",
"value": false
},
"fp32": {
"name": "fp32",
"label": "",
"value": false
}
},
"isOpen": false,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 1650,
"y": 1675
}
},
{
"id": "63b6ab7e-5b05-4d1b-a3b1-42d8e53ce16b",
"type": "invocation",
"data": {
"id": "63b6ab7e-5b05-4d1b-a3b1-42d8e53ce16b",
"version": "1.1.1",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "compel",
"inputs": {
"prompt": {
"name": "prompt",
"label": "",
"value": ""
},
"clip": {
"name": "clip",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 1250,
"y": 1200
}
},
{
"id": "eb8f6f8a-c7b1-4914-806e-045ee2717a35",
"type": "invocation",

View File

@@ -2,7 +2,7 @@
"name": "Face Detailer with IP-Adapter & Canny (See Note in Details)",
"author": "kosmoskatten",
"description": "A workflow to add detail to and improve faces. This workflow is most effective when used with a model that creates realistic outputs. ",
"version": "2.0.0",
"version": "2.1.0",
"contact": "invoke@invoke.ai",
"tags": "face detailer, IP-Adapter, Canny",
"notes": "Set this image as the blur mask: https://i.imgur.com/Gxi61zP.png",
@@ -37,16 +37,219 @@
}
],
"meta": {
"category": "default",
"version": "3.0.0"
"version": "3.0.0",
"category": "default"
},
"nodes": [
{
"id": "44f2c190-eb03-460d-8d11-a94d13b33f19",
"id": "c6359181-6479-40ec-bf3a-b7e8451683b8",
"type": "invocation",
"data": {
"id": "44f2c190-eb03-460d-8d11-a94d13b33f19",
"version": "1.1.1",
"id": "c6359181-6479-40ec-bf3a-b7e8451683b8",
"version": "1.0.3",
"label": "",
"notes": "",
"type": "main_model_loader",
"inputs": {
"model": {
"name": "model",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 2031.5518710051792,
"y": -492.1742944307074
}
},
{
"id": "8fe598c6-d447-44fa-a165-4975af77d080",
"type": "invocation",
"data": {
"id": "8fe598c6-d447-44fa-a165-4975af77d080",
"version": "1.3.3",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "canny_image_processor",
"inputs": {
"board": {
"name": "board",
"label": ""
},
"metadata": {
"name": "metadata",
"label": ""
},
"image": {
"name": "image",
"label": ""
},
"detect_resolution": {
"name": "detect_resolution",
"label": "",
"value": 512
},
"image_resolution": {
"name": "image_resolution",
"label": "",
"value": 512
},
"low_threshold": {
"name": "low_threshold",
"label": "",
"value": 100
},
"high_threshold": {
"name": "high_threshold",
"label": "",
"value": 200
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 3519.4131037388597,
"y": 576.7946795840575
}
},
{
"id": "f60b6161-8f26-42f6-89ff-545e6011e501",
"type": "invocation",
"data": {
"id": "f60b6161-8f26-42f6-89ff-545e6011e501",
"version": "1.1.2",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "controlnet",
"inputs": {
"image": {
"name": "image",
"label": ""
},
"control_model": {
"name": "control_model",
"label": "Control Model (select canny)",
"value": {
"key": "5bdaacf7-a7a3-4fb8-b394-cc0ffbb8941d",
"hash": "blake3:260c7f8e10aefea9868cfc68d89970e91033bd37132b14b903e70ee05ebf530e",
"name": "sd-controlnet-canny",
"base": "sd-1",
"type": "controlnet"
}
},
"control_weight": {
"name": "control_weight",
"label": "",
"value": 0.5
},
"begin_step_percent": {
"name": "begin_step_percent",
"label": "",
"value": 0
},
"end_step_percent": {
"name": "end_step_percent",
"label": "",
"value": 0.5
},
"control_mode": {
"name": "control_mode",
"label": "",
"value": "balanced"
},
"resize_mode": {
"name": "resize_mode",
"label": "",
"value": "just_resize"
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 3950,
"y": 150
}
},
{
"id": "22b750db-b85e-486b-b278-ac983e329813",
"type": "invocation",
"data": {
"id": "22b750db-b85e-486b-b278-ac983e329813",
"version": "1.4.1",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "ip_adapter",
"inputs": {
"image": {
"name": "image",
"label": ""
},
"ip_adapter_model": {
"name": "ip_adapter_model",
"label": "IP-Adapter Model (select IP Adapter Face)",
"value": {
"key": "1cc210bb-4d0a-4312-b36c-b5d46c43768e",
"hash": "blake3:3d669dffa7471b357b4df088b99ffb6bf4d4383d5e0ef1de5ec1c89728a3d5a5",
"name": "ip_adapter_sd15",
"base": "sd-1",
"type": "ip_adapter"
}
},
"clip_vision_model": {
"name": "clip_vision_model",
"label": "",
"value": "ViT-H"
},
"weight": {
"name": "weight",
"label": "",
"value": 0.5
},
"method": {
"name": "method",
"label": "",
"value": "full"
},
"begin_step_percent": {
"name": "begin_step_percent",
"label": "",
"value": 0
},
"end_step_percent": {
"name": "end_step_percent",
"label": "",
"value": 0.8
},
"mask": {
"name": "mask",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 3575,
"y": -200
}
},
{
"id": "f4d15b64-c4a6-42a5-90fc-e4ed07a0ca65",
"type": "invocation",
"data": {
"id": "f4d15b64-c4a6-42a5-90fc-e4ed07a0ca65",
"version": "1.2.0",
"nodePack": "invokeai",
"label": "",
"notes": "",
@@ -60,6 +263,140 @@
"clip": {
"name": "clip",
"label": ""
},
"mask": {
"name": "mask",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 2550,
"y": -525
}
},
{
"id": "2224ed72-2453-4252-bd89-3085240e0b6f",
"type": "invocation",
"data": {
"id": "2224ed72-2453-4252-bd89-3085240e0b6f",
"version": "1.3.0",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "l2i",
"inputs": {
"board": {
"name": "board",
"label": ""
},
"metadata": {
"name": "metadata",
"label": ""
},
"latents": {
"name": "latents",
"label": ""
},
"vae": {
"name": "vae",
"label": ""
},
"tiled": {
"name": "tiled",
"label": "",
"value": false
},
"tile_size": {
"name": "tile_size",
"label": "",
"value": 0
},
"fp32": {
"name": "fp32",
"label": "",
"value": true
}
},
"isOpen": true,
"isIntermediate": false,
"useCache": true
},
"position": {
"x": 4980.1395106966565,
"y": -255.9158921745602
}
},
{
"id": "de8b1a48-a2e4-42ca-90bb-66058bffd534",
"type": "invocation",
"data": {
"id": "de8b1a48-a2e4-42ca-90bb-66058bffd534",
"version": "1.1.0",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "i2l",
"inputs": {
"image": {
"name": "image",
"label": ""
},
"vae": {
"name": "vae",
"label": ""
},
"tiled": {
"name": "tiled",
"label": "",
"value": false
},
"tile_size": {
"name": "tile_size",
"label": "",
"value": 0
},
"fp32": {
"name": "fp32",
"label": "",
"value": true
}
},
"isOpen": false,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 3100,
"y": -275
}
},
{
"id": "44f2c190-eb03-460d-8d11-a94d13b33f19",
"type": "invocation",
"data": {
"id": "44f2c190-eb03-460d-8d11-a94d13b33f19",
"version": "1.2.0",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "compel",
"inputs": {
"prompt": {
"name": "prompt",
"label": "",
"value": ""
},
"clip": {
"name": "clip",
"label": ""
},
"mask": {
"name": "mask",
"label": ""
}
},
"isOpen": true,
@@ -251,45 +588,6 @@
"y": 0
}
},
{
"id": "de8b1a48-a2e4-42ca-90bb-66058bffd534",
"type": "invocation",
"data": {
"id": "de8b1a48-a2e4-42ca-90bb-66058bffd534",
"version": "1.0.2",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "i2l",
"inputs": {
"image": {
"name": "image",
"label": ""
},
"vae": {
"name": "vae",
"label": ""
},
"tiled": {
"name": "tiled",
"label": "",
"value": false
},
"fp32": {
"name": "fp32",
"label": "",
"value": true
}
},
"isOpen": false,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 3100,
"y": -275
}
},
{
"id": "bd06261d-a74a-4d1f-8374-745ed6194bc2",
"type": "invocation",
@@ -418,53 +716,6 @@
"y": -175
}
},
{
"id": "2224ed72-2453-4252-bd89-3085240e0b6f",
"type": "invocation",
"data": {
"id": "2224ed72-2453-4252-bd89-3085240e0b6f",
"version": "1.2.2",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "l2i",
"inputs": {
"board": {
"name": "board",
"label": ""
},
"metadata": {
"name": "metadata",
"label": ""
},
"latents": {
"name": "latents",
"label": ""
},
"vae": {
"name": "vae",
"label": ""
},
"tiled": {
"name": "tiled",
"label": "",
"value": false
},
"fp32": {
"name": "fp32",
"label": "",
"value": true
}
},
"isOpen": true,
"isIntermediate": false,
"useCache": true
},
"position": {
"x": 4980.1395106966565,
"y": -255.9158921745602
}
},
{
"id": "2974e5b3-3d41-4b6f-9953-cd21e8f3a323",
"type": "invocation",
@@ -692,201 +943,6 @@
"y": -275
}
},
{
"id": "f4d15b64-c4a6-42a5-90fc-e4ed07a0ca65",
"type": "invocation",
"data": {
"id": "f4d15b64-c4a6-42a5-90fc-e4ed07a0ca65",
"version": "1.1.1",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "compel",
"inputs": {
"prompt": {
"name": "prompt",
"label": "",
"value": ""
},
"clip": {
"name": "clip",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 2550,
"y": -525
}
},
{
"id": "22b750db-b85e-486b-b278-ac983e329813",
"type": "invocation",
"data": {
"id": "22b750db-b85e-486b-b278-ac983e329813",
"version": "1.2.2",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "ip_adapter",
"inputs": {
"image": {
"name": "image",
"label": ""
},
"ip_adapter_model": {
"name": "ip_adapter_model",
"label": "IP-Adapter Model (select IP Adapter Face)",
"value": {
"key": "1cc210bb-4d0a-4312-b36c-b5d46c43768e",
"hash": "blake3:3d669dffa7471b357b4df088b99ffb6bf4d4383d5e0ef1de5ec1c89728a3d5a5",
"name": "ip_adapter_sd15",
"base": "sd-1",
"type": "ip_adapter"
}
},
"weight": {
"name": "weight",
"label": "",
"value": 0.5
},
"begin_step_percent": {
"name": "begin_step_percent",
"label": "",
"value": 0
},
"end_step_percent": {
"name": "end_step_percent",
"label": "",
"value": 0.8
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 3575,
"y": -200
}
},
{
"id": "f60b6161-8f26-42f6-89ff-545e6011e501",
"type": "invocation",
"data": {
"id": "f60b6161-8f26-42f6-89ff-545e6011e501",
"version": "1.1.1",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "controlnet",
"inputs": {
"image": {
"name": "image",
"label": ""
},
"control_model": {
"name": "control_model",
"label": "Control Model (select canny)",
"value": {
"key": "5bdaacf7-a7a3-4fb8-b394-cc0ffbb8941d",
"hash": "blake3:260c7f8e10aefea9868cfc68d89970e91033bd37132b14b903e70ee05ebf530e",
"name": "sd-controlnet-canny",
"base": "sd-1",
"type": "controlnet"
}
},
"control_weight": {
"name": "control_weight",
"label": "",
"value": 0.5
},
"begin_step_percent": {
"name": "begin_step_percent",
"label": "",
"value": 0
},
"end_step_percent": {
"name": "end_step_percent",
"label": "",
"value": 0.5
},
"control_mode": {
"name": "control_mode",
"label": "",
"value": "balanced"
},
"resize_mode": {
"name": "resize_mode",
"label": "",
"value": "just_resize"
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 3950,
"y": 150
}
},
{
"id": "8fe598c6-d447-44fa-a165-4975af77d080",
"type": "invocation",
"data": {
"id": "8fe598c6-d447-44fa-a165-4975af77d080",
"version": "1.3.2",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "canny_image_processor",
"inputs": {
"board": {
"name": "board",
"label": ""
},
"metadata": {
"name": "metadata",
"label": ""
},
"image": {
"name": "image",
"label": ""
},
"detect_resolution": {
"name": "detect_resolution",
"label": "",
"value": 512
},
"image_resolution": {
"name": "image_resolution",
"label": "",
"value": 512
},
"low_threshold": {
"name": "low_threshold",
"label": "",
"value": 100
},
"high_threshold": {
"name": "high_threshold",
"label": "",
"value": 200
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 3519.4131037388597,
"y": 576.7946795840575
}
},
{
"id": "4bd4ae80-567f-4366-b8c6-3bb06f4fb46a",
"type": "invocation",
@@ -1035,30 +1091,6 @@
"x": 2578.2364832140506,
"y": 78.7948456497351
}
},
{
"id": "c6359181-6479-40ec-bf3a-b7e8451683b8",
"type": "invocation",
"data": {
"id": "c6359181-6479-40ec-bf3a-b7e8451683b8",
"version": "1.0.2",
"label": "",
"notes": "",
"type": "main_model_loader",
"inputs": {
"model": {
"name": "model",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 2031.5518710051792,
"y": -492.1742944307074
}
}
],
"edges": [

View File

@@ -2,7 +2,7 @@
"name": "Multi ControlNet (Canny & Depth)",
"author": "InvokeAI",
"description": "A sample workflow using canny & depth ControlNets to guide the generation process. ",
"version": "2.0.0",
"version": "2.1.0",
"contact": "invoke@invoke.ai",
"tags": "ControlNet, canny, depth",
"notes": "",
@@ -37,140 +37,104 @@
}
],
"meta": {
"category": "default",
"version": "3.0.0"
"version": "3.0.0",
"category": "default"
},
"nodes": [
{
"id": "8e860e51-5045-456e-bf04-9a62a2a5c49e",
"id": "9db25398-c869-4a63-8815-c6559341ef12",
"type": "invocation",
"data": {
"id": "8e860e51-5045-456e-bf04-9a62a2a5c49e",
"version": "1.0.2",
"id": "9db25398-c869-4a63-8815-c6559341ef12",
"version": "1.3.0",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "image",
"type": "l2i",
"inputs": {
"image": {
"name": "image",
"label": "Depth Input Image"
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 3666.135718057363,
"y": 186.66887319822808
}
},
{
"id": "a33199c2-8340-401e-b8a2-42ffa875fc1c",
"type": "invocation",
"data": {
"id": "a33199c2-8340-401e-b8a2-42ffa875fc1c",
"version": "1.1.1",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "controlnet",
"inputs": {
"image": {
"name": "image",
"board": {
"name": "board",
"label": ""
},
"control_model": {
"name": "control_model",
"label": "Control Model (select depth)",
"value": {
"key": "87e8855c-671f-4c9e-bbbb-8ed47ccb4aac",
"hash": "blake3:2550bf22a53942dfa28ab2fed9d10d80851112531f44d977168992edf9d0534c",
"name": "control_v11f1p_sd15_depth",
"base": "sd-1",
"type": "controlnet"
}
"metadata": {
"name": "metadata",
"label": ""
},
"control_weight": {
"name": "control_weight",
"latents": {
"name": "latents",
"label": ""
},
"vae": {
"name": "vae",
"label": ""
},
"tiled": {
"name": "tiled",
"label": "",
"value": 1
"value": false
},
"begin_step_percent": {
"name": "begin_step_percent",
"tile_size": {
"name": "tile_size",
"label": "",
"value": 0
},
"end_step_percent": {
"name": "end_step_percent",
"fp32": {
"name": "fp32",
"label": "",
"value": 1
},
"control_mode": {
"name": "control_mode",
"label": "",
"value": "balanced"
},
"resize_mode": {
"name": "resize_mode",
"label": "",
"value": "just_resize"
"value": false
}
},
"isOpen": true,
"isIntermediate": true,
"isIntermediate": false,
"useCache": true
},
"position": {
"x": 4477.604342844504,
"y": -49.39005411272677
}
},
{
"id": "273e3f96-49ea-4dc5-9d5b-9660390f14e1",
"type": "invocation",
"data": {
"id": "273e3f96-49ea-4dc5-9d5b-9660390f14e1",
"version": "1.1.1",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "compel",
"inputs": {
"prompt": {
"name": "prompt",
"label": "Negative Prompt",
"value": ""
},
"clip": {
"name": "clip",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 4075,
"x": 5675,
"y": -825
}
},
{
"id": "54486974-835b-4d81-8f82-05f9f32ce9e9",
"id": "c826ba5e-9676-4475-b260-07b85e88753c",
"type": "invocation",
"data": {
"id": "54486974-835b-4d81-8f82-05f9f32ce9e9",
"version": "1.0.2",
"id": "c826ba5e-9676-4475-b260-07b85e88753c",
"version": "1.3.3",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "main_model_loader",
"type": "canny_image_processor",
"inputs": {
"model": {
"name": "model",
"board": {
"name": "board",
"label": ""
},
"metadata": {
"name": "metadata",
"label": ""
},
"image": {
"name": "image",
"label": ""
},
"detect_resolution": {
"name": "detect_resolution",
"label": "",
"value": 512
},
"image_resolution": {
"name": "image_resolution",
"label": "",
"value": 512
},
"low_threshold": {
"name": "low_threshold",
"label": "",
"value": 100
},
"high_threshold": {
"name": "high_threshold",
"label": "",
"value": 200
}
},
"isOpen": true,
@@ -178,29 +142,52 @@
"useCache": true
},
"position": {
"x": 3600,
"y": -1000
"x": 4095.757337055795,
"y": -455.63440891935863
}
},
{
"id": "7ce68934-3419-42d4-ac70-82cfc9397306",
"id": "018b1214-c2af-43a7-9910-fb687c6726d7",
"type": "invocation",
"data": {
"id": "7ce68934-3419-42d4-ac70-82cfc9397306",
"version": "1.1.1",
"id": "018b1214-c2af-43a7-9910-fb687c6726d7",
"version": "1.2.4",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "compel",
"type": "midas_depth_image_processor",
"inputs": {
"prompt": {
"name": "prompt",
"label": "Positive Prompt",
"value": ""
},
"clip": {
"name": "clip",
"board": {
"name": "board",
"label": ""
},
"metadata": {
"name": "metadata",
"label": ""
},
"image": {
"name": "image",
"label": ""
},
"a_mult": {
"name": "a_mult",
"label": "",
"value": 2
},
"bg_th": {
"name": "bg_th",
"label": "",
"value": 0.1
},
"detect_resolution": {
"name": "detect_resolution",
"label": "",
"value": 512
},
"image_resolution": {
"name": "image_resolution",
"label": "",
"value": 512
}
},
"isOpen": true,
@@ -208,8 +195,8 @@
"useCache": true
},
"position": {
"x": 4075,
"y": -1125
"x": 4082.783145980783,
"y": 0.01629251229994111
}
},
{
@@ -217,7 +204,7 @@
"type": "invocation",
"data": {
"id": "d204d184-f209-4fae-a0a1-d152800844e1",
"version": "1.1.1",
"version": "1.1.2",
"nodePack": "invokeai",
"label": "",
"notes": "",
@@ -273,6 +260,185 @@
"y": -618.4221638099414
}
},
{
"id": "7ce68934-3419-42d4-ac70-82cfc9397306",
"type": "invocation",
"data": {
"id": "7ce68934-3419-42d4-ac70-82cfc9397306",
"version": "1.2.0",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "compel",
"inputs": {
"prompt": {
"name": "prompt",
"label": "Positive Prompt",
"value": ""
},
"clip": {
"name": "clip",
"label": ""
},
"mask": {
"name": "mask",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 4075,
"y": -1125
}
},
{
"id": "54486974-835b-4d81-8f82-05f9f32ce9e9",
"type": "invocation",
"data": {
"id": "54486974-835b-4d81-8f82-05f9f32ce9e9",
"version": "1.0.3",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "main_model_loader",
"inputs": {
"model": {
"name": "model",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 3600,
"y": -1000
}
},
{
"id": "273e3f96-49ea-4dc5-9d5b-9660390f14e1",
"type": "invocation",
"data": {
"id": "273e3f96-49ea-4dc5-9d5b-9660390f14e1",
"version": "1.2.0",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "compel",
"inputs": {
"prompt": {
"name": "prompt",
"label": "Negative Prompt",
"value": ""
},
"clip": {
"name": "clip",
"label": ""
},
"mask": {
"name": "mask",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 4075,
"y": -825
}
},
{
"id": "a33199c2-8340-401e-b8a2-42ffa875fc1c",
"type": "invocation",
"data": {
"id": "a33199c2-8340-401e-b8a2-42ffa875fc1c",
"version": "1.1.2",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "controlnet",
"inputs": {
"image": {
"name": "image",
"label": ""
},
"control_model": {
"name": "control_model",
"label": "Control Model (select depth)",
"value": {
"key": "87e8855c-671f-4c9e-bbbb-8ed47ccb4aac",
"hash": "blake3:2550bf22a53942dfa28ab2fed9d10d80851112531f44d977168992edf9d0534c",
"name": "control_v11f1p_sd15_depth",
"base": "sd-1",
"type": "controlnet"
}
},
"control_weight": {
"name": "control_weight",
"label": "",
"value": 1
},
"begin_step_percent": {
"name": "begin_step_percent",
"label": "",
"value": 0
},
"end_step_percent": {
"name": "end_step_percent",
"label": "",
"value": 1
},
"control_mode": {
"name": "control_mode",
"label": "",
"value": "balanced"
},
"resize_mode": {
"name": "resize_mode",
"label": "",
"value": "just_resize"
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 4477.604342844504,
"y": -49.39005411272677
}
},
{
"id": "8e860e51-5045-456e-bf04-9a62a2a5c49e",
"type": "invocation",
"data": {
"id": "8e860e51-5045-456e-bf04-9a62a2a5c49e",
"version": "1.0.2",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "image",
"inputs": {
"image": {
"name": "image",
"label": "Depth Input Image"
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 3666.135718057363,
"y": 186.66887319822808
}
},
{
"id": "c4b23e64-7986-40c4-9cad-46327b12e204",
"type": "invocation",
@@ -322,159 +488,6 @@
"y": -575
}
},
{
"id": "018b1214-c2af-43a7-9910-fb687c6726d7",
"type": "invocation",
"data": {
"id": "018b1214-c2af-43a7-9910-fb687c6726d7",
"version": "1.2.3",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "midas_depth_image_processor",
"inputs": {
"board": {
"name": "board",
"label": ""
},
"metadata": {
"name": "metadata",
"label": ""
},
"image": {
"name": "image",
"label": ""
},
"a_mult": {
"name": "a_mult",
"label": "",
"value": 2
},
"bg_th": {
"name": "bg_th",
"label": "",
"value": 0.1
},
"detect_resolution": {
"name": "detect_resolution",
"label": "",
"value": 512
},
"image_resolution": {
"name": "image_resolution",
"label": "",
"value": 512
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 4082.783145980783,
"y": 0.01629251229994111
}
},
{
"id": "c826ba5e-9676-4475-b260-07b85e88753c",
"type": "invocation",
"data": {
"id": "c826ba5e-9676-4475-b260-07b85e88753c",
"version": "1.3.2",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "canny_image_processor",
"inputs": {
"board": {
"name": "board",
"label": ""
},
"metadata": {
"name": "metadata",
"label": ""
},
"image": {
"name": "image",
"label": ""
},
"detect_resolution": {
"name": "detect_resolution",
"label": "",
"value": 512
},
"image_resolution": {
"name": "image_resolution",
"label": "",
"value": 512
},
"low_threshold": {
"name": "low_threshold",
"label": "",
"value": 100
},
"high_threshold": {
"name": "high_threshold",
"label": "",
"value": 200
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 4095.757337055795,
"y": -455.63440891935863
}
},
{
"id": "9db25398-c869-4a63-8815-c6559341ef12",
"type": "invocation",
"data": {
"id": "9db25398-c869-4a63-8815-c6559341ef12",
"version": "1.2.2",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "l2i",
"inputs": {
"board": {
"name": "board",
"label": ""
},
"metadata": {
"name": "metadata",
"label": ""
},
"latents": {
"name": "latents",
"label": ""
},
"vae": {
"name": "vae",
"label": ""
},
"tiled": {
"name": "tiled",
"label": "",
"value": false
},
"fp32": {
"name": "fp32",
"label": "",
"value": false
}
},
"isOpen": true,
"isIntermediate": false,
"useCache": true
},
"position": {
"x": 5675,
"y": -825
}
},
{
"id": "ac481b7f-08bf-4a9d-9e0c-3a82ea5243ce",
"type": "invocation",

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "Prompt from File",
"author": "InvokeAI",
"description": "Sample workflow using Prompt from File node",
"version": "2.0.0",
"version": "2.1.0",
"contact": "invoke@invoke.ai",
"tags": "text2image, prompt from file, default",
"notes": "",
@@ -37,16 +37,68 @@
}
],
"meta": {
"category": "default",
"version": "3.0.0"
"version": "3.0.0",
"category": "default"
},
"nodes": [
{
"id": "c2eaf1ba-5708-4679-9e15-945b8b432692",
"id": "491ec988-3c77-4c37-af8a-39a0c4e7a2a1",
"type": "invocation",
"data": {
"id": "c2eaf1ba-5708-4679-9e15-945b8b432692",
"version": "1.1.1",
"id": "491ec988-3c77-4c37-af8a-39a0c4e7a2a1",
"version": "1.3.0",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "l2i",
"inputs": {
"board": {
"name": "board",
"label": ""
},
"metadata": {
"name": "metadata",
"label": ""
},
"latents": {
"name": "latents",
"label": ""
},
"vae": {
"name": "vae",
"label": ""
},
"tiled": {
"name": "tiled",
"label": "",
"value": false
},
"tile_size": {
"name": "tile_size",
"label": "",
"value": 0
},
"fp32": {
"name": "fp32",
"label": "",
"value": false
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 2037.861329274915,
"y": -329.8393457509562
}
},
{
"id": "fc9d0e35-a6de-4a19-84e1-c72497c823f6",
"type": "invocation",
"data": {
"id": "fc9d0e35-a6de-4a19-84e1-c72497c823f6",
"version": "1.2.0",
"nodePack": "invokeai",
"label": "",
"notes": "",
@@ -60,6 +112,69 @@
"clip": {
"name": "clip",
"label": ""
},
"mask": {
"name": "mask",
"label": ""
}
},
"isOpen": false,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 925,
"y": -275
}
},
{
"id": "d6353b7f-b447-4e17-8f2e-80a88c91d426",
"type": "invocation",
"data": {
"id": "d6353b7f-b447-4e17-8f2e-80a88c91d426",
"version": "1.0.3",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "main_model_loader",
"inputs": {
"model": {
"name": "model",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 0,
"y": -375
}
},
{
"id": "c2eaf1ba-5708-4679-9e15-945b8b432692",
"type": "invocation",
"data": {
"id": "c2eaf1ba-5708-4679-9e15-945b8b432692",
"version": "1.2.0",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "compel",
"inputs": {
"prompt": {
"name": "prompt",
"label": "",
"value": ""
},
"clip": {
"name": "clip",
"label": ""
},
"mask": {
"name": "mask",
"label": ""
}
},
"isOpen": false,
@@ -141,61 +256,6 @@
"y": -400
}
},
{
"id": "d6353b7f-b447-4e17-8f2e-80a88c91d426",
"type": "invocation",
"data": {
"id": "d6353b7f-b447-4e17-8f2e-80a88c91d426",
"version": "1.0.2",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "main_model_loader",
"inputs": {
"model": {
"name": "model",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 0,
"y": -375
}
},
{
"id": "fc9d0e35-a6de-4a19-84e1-c72497c823f6",
"type": "invocation",
"data": {
"id": "fc9d0e35-a6de-4a19-84e1-c72497c823f6",
"version": "1.1.1",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "compel",
"inputs": {
"prompt": {
"name": "prompt",
"label": "",
"value": ""
},
"clip": {
"name": "clip",
"label": ""
}
},
"isOpen": false,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 925,
"y": -275
}
},
{
"id": "0eb5f3f5-1b91-49eb-9ef0-41d67c7eae77",
"type": "invocation",
@@ -268,53 +328,6 @@
"y": -50
}
},
{
"id": "491ec988-3c77-4c37-af8a-39a0c4e7a2a1",
"type": "invocation",
"data": {
"id": "491ec988-3c77-4c37-af8a-39a0c4e7a2a1",
"version": "1.2.2",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "l2i",
"inputs": {
"board": {
"name": "board",
"label": ""
},
"metadata": {
"name": "metadata",
"label": ""
},
"latents": {
"name": "latents",
"label": ""
},
"vae": {
"name": "vae",
"label": ""
},
"tiled": {
"name": "tiled",
"label": "",
"value": false
},
"fp32": {
"name": "fp32",
"label": "",
"value": false
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 2037.861329274915,
"y": -329.8393457509562
}
},
{
"id": "2fb1577f-0a56-4f12-8711-8afcaaaf1d5e",
"type": "invocation",

View File

@@ -2,7 +2,7 @@
"name": "Text to Image - SD1.5",
"author": "InvokeAI",
"description": "Sample text to image workflow for Stable Diffusion 1.5/2",
"version": "2.0.0",
"version": "2.1.0",
"contact": "invoke@invoke.ai",
"tags": "text2image, SD1.5, SD2, default",
"notes": "",
@@ -33,16 +33,127 @@
}
],
"meta": {
"category": "default",
"version": "3.0.0"
"version": "3.0.0",
"category": "default"
},
"nodes": [
{
"id": "58c957f5-0d01-41fc-a803-b2bbf0413d4f",
"type": "invocation",
"data": {
"id": "58c957f5-0d01-41fc-a803-b2bbf0413d4f",
"version": "1.3.0",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "l2i",
"inputs": {
"board": {
"name": "board",
"label": ""
},
"metadata": {
"name": "metadata",
"label": ""
},
"latents": {
"name": "latents",
"label": ""
},
"vae": {
"name": "vae",
"label": ""
},
"tiled": {
"name": "tiled",
"label": "",
"value": false
},
"tile_size": {
"name": "tile_size",
"label": "",
"value": 0
},
"fp32": {
"name": "fp32",
"label": "",
"value": true
}
},
"isOpen": true,
"isIntermediate": false,
"useCache": true
},
"position": {
"x": 1800,
"y": 25
}
},
{
"id": "7d8bf987-284f-413a-b2fd-d825445a5d6c",
"type": "invocation",
"data": {
"id": "7d8bf987-284f-413a-b2fd-d825445a5d6c",
"version": "1.2.0",
"nodePack": "invokeai",
"label": "Positive Compel Prompt",
"notes": "",
"type": "compel",
"inputs": {
"prompt": {
"name": "prompt",
"label": "Positive Prompt",
"value": "Super cute tiger cub, national geographic award-winning photograph"
},
"clip": {
"name": "clip",
"label": ""
},
"mask": {
"name": "mask",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 1000,
"y": 25
}
},
{
"id": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
"type": "invocation",
"data": {
"id": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
"version": "1.0.3",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "main_model_loader",
"inputs": {
"model": {
"name": "model",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 600,
"y": 25
}
},
{
"id": "93dc02a4-d05b-48ed-b99c-c9b616af3402",
"type": "invocation",
"data": {
"id": "93dc02a4-d05b-48ed-b99c-c9b616af3402",
"version": "1.1.1",
"version": "1.2.0",
"nodePack": "invokeai",
"label": "Negative Compel Prompt",
"notes": "",
@@ -56,6 +167,10 @@
"clip": {
"name": "clip",
"label": ""
},
"mask": {
"name": "mask",
"label": ""
}
},
"isOpen": true,
@@ -108,61 +223,6 @@
"y": 325
}
},
{
"id": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
"type": "invocation",
"data": {
"id": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
"version": "1.0.2",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "main_model_loader",
"inputs": {
"model": {
"name": "model",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 600,
"y": 25
}
},
{
"id": "7d8bf987-284f-413a-b2fd-d825445a5d6c",
"type": "invocation",
"data": {
"id": "7d8bf987-284f-413a-b2fd-d825445a5d6c",
"version": "1.1.1",
"nodePack": "invokeai",
"label": "Positive Compel Prompt",
"notes": "",
"type": "compel",
"inputs": {
"prompt": {
"name": "prompt",
"label": "Positive Prompt",
"value": "Super cute tiger cub, national geographic award-winning photograph"
},
"clip": {
"name": "clip",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 1000,
"y": 25
}
},
{
"id": "ea94bc37-d995-4a83-aa99-4af42479f2f2",
"type": "invocation",
@@ -280,53 +340,6 @@
"x": 1400,
"y": 25
}
},
{
"id": "58c957f5-0d01-41fc-a803-b2bbf0413d4f",
"type": "invocation",
"data": {
"id": "58c957f5-0d01-41fc-a803-b2bbf0413d4f",
"version": "1.2.2",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "l2i",
"inputs": {
"board": {
"name": "board",
"label": ""
},
"metadata": {
"name": "metadata",
"label": ""
},
"latents": {
"name": "latents",
"label": ""
},
"vae": {
"name": "vae",
"label": ""
},
"tiled": {
"name": "tiled",
"label": "",
"value": false
},
"fp32": {
"name": "fp32",
"label": "",
"value": true
}
},
"isOpen": true,
"isIntermediate": false,
"useCache": true
},
"position": {
"x": 1800,
"y": 25
}
}
],
"edges": [

View File

@@ -2,7 +2,7 @@
"name": "Text to Image - SDXL",
"author": "InvokeAI",
"description": "Sample text to image workflow for SDXL",
"version": "2.0.0",
"version": "2.1.0",
"contact": "invoke@invoke.ai",
"tags": "text2image, SDXL, default",
"notes": "",
@@ -29,10 +29,271 @@
}
],
"meta": {
"category": "default",
"version": "3.0.0"
"version": "3.0.0",
"category": "default"
},
"nodes": [
{
"id": "0093692f-9cf4-454d-a5b8-62f0e3eb3bb8",
"type": "invocation",
"data": {
"id": "0093692f-9cf4-454d-a5b8-62f0e3eb3bb8",
"version": "1.0.3",
"label": "",
"notes": "",
"type": "vae_loader",
"inputs": {
"vae_model": {
"name": "vae_model",
"label": "VAE (use the FP16 model)",
"value": {
"key": "f20f9e5c-1bce-4c46-a84d-34ebfa7df069",
"hash": "blake3:9705ab1c31fa96b308734214fb7571a958621c7a9247eed82b7d277145f8d9fa",
"name": "sdxl-vae-fp16-fix",
"base": "sdxl",
"type": "vae"
}
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 375,
"y": -225
}
},
{
"id": "63e91020-83b2-4f35-b174-ad9692aabb48",
"type": "invocation",
"data": {
"id": "63e91020-83b2-4f35-b174-ad9692aabb48",
"version": "1.3.0",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "l2i",
"inputs": {
"board": {
"name": "board",
"label": ""
},
"metadata": {
"name": "metadata",
"label": ""
},
"latents": {
"name": "latents",
"label": ""
},
"vae": {
"name": "vae",
"label": ""
},
"tiled": {
"name": "tiled",
"label": "",
"value": false
},
"tile_size": {
"name": "tile_size",
"label": "",
"value": 0
},
"fp32": {
"name": "fp32",
"label": "",
"value": false
}
},
"isOpen": true,
"isIntermediate": false,
"useCache": false
},
"position": {
"x": 1475,
"y": -500
}
},
{
"id": "faf965a4-7530-427b-b1f3-4ba6505c2a08",
"type": "invocation",
"data": {
"id": "faf965a4-7530-427b-b1f3-4ba6505c2a08",
"version": "1.2.0",
"nodePack": "invokeai",
"label": "SDXL Positive Compel Prompt",
"notes": "",
"type": "sdxl_compel_prompt",
"inputs": {
"prompt": {
"name": "prompt",
"label": "Positive Prompt",
"value": ""
},
"style": {
"name": "style",
"label": "Positive Style",
"value": ""
},
"original_width": {
"name": "original_width",
"label": "",
"value": 1024
},
"original_height": {
"name": "original_height",
"label": "",
"value": 1024
},
"crop_top": {
"name": "crop_top",
"label": "",
"value": 0
},
"crop_left": {
"name": "crop_left",
"label": "",
"value": 0
},
"target_width": {
"name": "target_width",
"label": "",
"value": 1024
},
"target_height": {
"name": "target_height",
"label": "",
"value": 1024
},
"clip": {
"name": "clip",
"label": ""
},
"clip2": {
"name": "clip2",
"label": ""
},
"mask": {
"name": "mask",
"label": ""
}
},
"isOpen": false,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 750,
"y": -175
}
},
{
"id": "30d3289c-773c-4152-a9d2-bd8a99c8fd22",
"type": "invocation",
"data": {
"id": "30d3289c-773c-4152-a9d2-bd8a99c8fd22",
"version": "1.0.3",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "sdxl_model_loader",
"inputs": {
"model": {
"name": "model",
"label": "",
"value": {
"key": "4a63b226-e8ff-4da4-854e-0b9f04b562ba",
"hash": "blake3:d279309ea6e5ee6e8fd52504275865cc280dac71cbf528c5b07c98b888bddaba",
"name": "dreamshaper-xl-v2-turbo",
"base": "sdxl",
"type": "main"
}
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 375,
"y": -500
}
},
{
"id": "3193ad09-a7c2-4bf4-a3a9-1c61cc33a204",
"type": "invocation",
"data": {
"id": "3193ad09-a7c2-4bf4-a3a9-1c61cc33a204",
"version": "1.2.0",
"nodePack": "invokeai",
"label": "SDXL Negative Compel Prompt",
"notes": "",
"type": "sdxl_compel_prompt",
"inputs": {
"prompt": {
"name": "prompt",
"label": "Negative Prompt",
"value": ""
},
"style": {
"name": "style",
"label": "Negative Style",
"value": ""
},
"original_width": {
"name": "original_width",
"label": "",
"value": 1024
},
"original_height": {
"name": "original_height",
"label": "",
"value": 1024
},
"crop_top": {
"name": "crop_top",
"label": "",
"value": 0
},
"crop_left": {
"name": "crop_left",
"label": "",
"value": 0
},
"target_width": {
"name": "target_width",
"label": "",
"value": 1024
},
"target_height": {
"name": "target_height",
"label": "",
"value": 1024
},
"clip": {
"name": "clip",
"label": ""
},
"clip2": {
"name": "clip2",
"label": ""
},
"mask": {
"name": "mask",
"label": ""
}
},
"isOpen": false,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 750,
"y": 200
}
},
{
"id": "3774ec24-a69e-4254-864c-097d07a6256f",
"type": "invocation",
@@ -88,75 +349,6 @@
"y": -125
}
},
{
"id": "3193ad09-a7c2-4bf4-a3a9-1c61cc33a204",
"type": "invocation",
"data": {
"id": "3193ad09-a7c2-4bf4-a3a9-1c61cc33a204",
"version": "1.1.1",
"nodePack": "invokeai",
"label": "SDXL Negative Compel Prompt",
"notes": "",
"type": "sdxl_compel_prompt",
"inputs": {
"prompt": {
"name": "prompt",
"label": "Negative Prompt",
"value": ""
},
"style": {
"name": "style",
"label": "Negative Style",
"value": ""
},
"original_width": {
"name": "original_width",
"label": "",
"value": 1024
},
"original_height": {
"name": "original_height",
"label": "",
"value": 1024
},
"crop_top": {
"name": "crop_top",
"label": "",
"value": 0
},
"crop_left": {
"name": "crop_left",
"label": "",
"value": 0
},
"target_width": {
"name": "target_width",
"label": "",
"value": 1024
},
"target_height": {
"name": "target_height",
"label": "",
"value": 1024
},
"clip": {
"name": "clip",
"label": ""
},
"clip2": {
"name": "clip2",
"label": ""
}
},
"isOpen": false,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 750,
"y": 200
}
},
{
"id": "55705012-79b9-4aac-9f26-c0b10309785b",
"type": "invocation",
@@ -229,154 +421,6 @@
"y": -50
}
},
{
"id": "30d3289c-773c-4152-a9d2-bd8a99c8fd22",
"type": "invocation",
"data": {
"id": "30d3289c-773c-4152-a9d2-bd8a99c8fd22",
"version": "1.0.2",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "sdxl_model_loader",
"inputs": {
"model": {
"name": "model",
"label": "",
"value": {
"key": "4a63b226-e8ff-4da4-854e-0b9f04b562ba",
"hash": "blake3:d279309ea6e5ee6e8fd52504275865cc280dac71cbf528c5b07c98b888bddaba",
"name": "dreamshaper-xl-v2-turbo",
"base": "sdxl",
"type": "main"
}
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 375,
"y": -500
}
},
{
"id": "faf965a4-7530-427b-b1f3-4ba6505c2a08",
"type": "invocation",
"data": {
"id": "faf965a4-7530-427b-b1f3-4ba6505c2a08",
"version": "1.1.1",
"nodePack": "invokeai",
"label": "SDXL Positive Compel Prompt",
"notes": "",
"type": "sdxl_compel_prompt",
"inputs": {
"prompt": {
"name": "prompt",
"label": "Positive Prompt",
"value": ""
},
"style": {
"name": "style",
"label": "Positive Style",
"value": ""
},
"original_width": {
"name": "original_width",
"label": "",
"value": 1024
},
"original_height": {
"name": "original_height",
"label": "",
"value": 1024
},
"crop_top": {
"name": "crop_top",
"label": "",
"value": 0
},
"crop_left": {
"name": "crop_left",
"label": "",
"value": 0
},
"target_width": {
"name": "target_width",
"label": "",
"value": 1024
},
"target_height": {
"name": "target_height",
"label": "",
"value": 1024
},
"clip": {
"name": "clip",
"label": ""
},
"clip2": {
"name": "clip2",
"label": ""
}
},
"isOpen": false,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 750,
"y": -175
}
},
{
"id": "63e91020-83b2-4f35-b174-ad9692aabb48",
"type": "invocation",
"data": {
"id": "63e91020-83b2-4f35-b174-ad9692aabb48",
"version": "1.2.2",
"nodePack": "invokeai",
"label": "",
"notes": "",
"type": "l2i",
"inputs": {
"board": {
"name": "board",
"label": ""
},
"metadata": {
"name": "metadata",
"label": ""
},
"latents": {
"name": "latents",
"label": ""
},
"vae": {
"name": "vae",
"label": ""
},
"tiled": {
"name": "tiled",
"label": "",
"value": false
},
"fp32": {
"name": "fp32",
"label": "",
"value": false
}
},
"isOpen": true,
"isIntermediate": false,
"useCache": false
},
"position": {
"x": 1475,
"y": -500
}
},
{
"id": "50a36525-3c0a-4cc5-977c-e4bfc3fd6dfb",
"type": "invocation",
@@ -464,37 +508,6 @@
"y": -500
}
},
{
"id": "0093692f-9cf4-454d-a5b8-62f0e3eb3bb8",
"type": "invocation",
"data": {
"id": "0093692f-9cf4-454d-a5b8-62f0e3eb3bb8",
"version": "1.0.2",
"label": "",
"notes": "",
"type": "vae_loader",
"inputs": {
"vae_model": {
"name": "vae_model",
"label": "VAE (use the FP16 model)",
"value": {
"key": "f20f9e5c-1bce-4c46-a84d-34ebfa7df069",
"hash": "blake3:9705ab1c31fa96b308734214fb7571a958621c7a9247eed82b7d277145f8d9fa",
"name": "sdxl-vae-fp16-fix",
"base": "sdxl",
"type": "vae"
}
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 375,
"y": -225
}
},
{
"id": "ade2c0d3-0384-4157-b39b-29ce429cfa15",
"type": "invocation",

View File

@@ -2,7 +2,7 @@
"name": "Text to Image with LoRA",
"author": "InvokeAI",
"description": "Simple text to image workflow with a LoRA",
"version": "2.0.0",
"version": "2.1.0",
"contact": "invoke@invoke.ai",
"tags": "text to image, lora, default",
"notes": "",
@@ -37,28 +37,83 @@
}
],
"meta": {
"category": "default",
"version": "3.0.0"
"version": "3.0.0",
"category": "default"
},
"nodes": [
{
"id": "85b77bb2-c67a-416a-b3e8-291abe746c44",
"id": "a9683c0a-6b1f-4a5e-8187-c57e764b3400",
"type": "invocation",
"data": {
"id": "85b77bb2-c67a-416a-b3e8-291abe746c44",
"version": "1.1.1",
"id": "a9683c0a-6b1f-4a5e-8187-c57e764b3400",
"version": "1.3.0",
"label": "",
"notes": "",
"type": "l2i",
"inputs": {
"board": {
"name": "board",
"label": ""
},
"metadata": {
"name": "metadata",
"label": ""
},
"latents": {
"name": "latents",
"label": ""
},
"vae": {
"name": "vae",
"label": ""
},
"tiled": {
"name": "tiled",
"label": "",
"value": false
},
"tile_size": {
"name": "tile_size",
"label": "",
"value": 0
},
"fp32": {
"name": "fp32",
"label": "",
"value": false
}
},
"isOpen": true,
"isIntermediate": false,
"useCache": true
},
"position": {
"x": 4450,
"y": -550
}
},
{
"id": "c3fa6872-2599-4a82-a596-b3446a66cf8b",
"type": "invocation",
"data": {
"id": "c3fa6872-2599-4a82-a596-b3446a66cf8b",
"version": "1.2.0",
"label": "",
"notes": "",
"type": "compel",
"inputs": {
"prompt": {
"name": "prompt",
"label": "Negative Prompt",
"value": ""
"label": "Positive Prompt",
"value": "super cute tiger cub"
},
"clip": {
"name": "clip",
"label": ""
},
"mask": {
"name": "mask",
"label": ""
}
},
"isOpen": true,
@@ -67,31 +122,7 @@
},
"position": {
"x": 3425,
"y": -300
}
},
{
"id": "24e9d7ed-4836-4ec4-8f9e-e747721f9818",
"type": "invocation",
"data": {
"id": "24e9d7ed-4836-4ec4-8f9e-e747721f9818",
"version": "1.0.2",
"label": "",
"notes": "",
"type": "main_model_loader",
"inputs": {
"model": {
"name": "model",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 2500,
"y": -600
"y": -575
}
},
{
@@ -99,7 +130,7 @@
"type": "invocation",
"data": {
"id": "c41e705b-f2e3-4d1a-83c4-e34bb9344966",
"version": "1.0.2",
"version": "1.0.3",
"label": "",
"notes": "",
"type": "lora_loader",
@@ -132,23 +163,51 @@
}
},
{
"id": "c3fa6872-2599-4a82-a596-b3446a66cf8b",
"id": "24e9d7ed-4836-4ec4-8f9e-e747721f9818",
"type": "invocation",
"data": {
"id": "c3fa6872-2599-4a82-a596-b3446a66cf8b",
"version": "1.1.1",
"id": "24e9d7ed-4836-4ec4-8f9e-e747721f9818",
"version": "1.0.3",
"label": "",
"notes": "",
"type": "main_model_loader",
"inputs": {
"model": {
"name": "model",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": 2500,
"y": -600
}
},
{
"id": "85b77bb2-c67a-416a-b3e8-291abe746c44",
"type": "invocation",
"data": {
"id": "85b77bb2-c67a-416a-b3e8-291abe746c44",
"version": "1.2.0",
"label": "",
"notes": "",
"type": "compel",
"inputs": {
"prompt": {
"name": "prompt",
"label": "Positive Prompt",
"value": "super cute tiger cub"
"label": "Negative Prompt",
"value": ""
},
"clip": {
"name": "clip",
"label": ""
},
"mask": {
"name": "mask",
"label": ""
}
},
"isOpen": true,
@@ -157,7 +216,7 @@
},
"position": {
"x": 3425,
"y": -575
"y": -300
}
},
{
@@ -315,52 +374,6 @@
"x": 3425,
"y": 0
}
},
{
"id": "a9683c0a-6b1f-4a5e-8187-c57e764b3400",
"type": "invocation",
"data": {
"id": "a9683c0a-6b1f-4a5e-8187-c57e764b3400",
"version": "1.2.2",
"label": "",
"notes": "",
"type": "l2i",
"inputs": {
"board": {
"name": "board",
"label": ""
},
"metadata": {
"name": "metadata",
"label": ""
},
"latents": {
"name": "latents",
"label": ""
},
"vae": {
"name": "vae",
"label": ""
},
"tiled": {
"name": "tiled",
"label": "",
"value": false
},
"fp32": {
"name": "fp32",
"label": "",
"value": false
}
},
"isOpen": true,
"isIntermediate": false,
"useCache": true
},
"position": {
"x": 4450,
"y": -550
}
}
],
"edges": [

View File

@@ -2,7 +2,7 @@
"name": "Tiled Upscaling (Beta)",
"author": "Invoke",
"description": "A workflow to upscale an input image with tiled upscaling. ",
"version": "2.0.0",
"version": "2.1.0",
"contact": "invoke@invoke.ai",
"tags": "tiled, upscaling, sd1.5",
"notes": "",
@@ -41,10 +41,318 @@
}
],
"meta": {
"category": "default",
"version": "3.0.0"
"version": "3.0.0",
"category": "default"
},
"nodes": [
{
"id": "2ff466b8-5e2a-4d8f-923a-a3884c7ecbc5",
"type": "invocation",
"data": {
"id": "2ff466b8-5e2a-4d8f-923a-a3884c7ecbc5",
"version": "1.0.3",
"label": "",
"notes": "",
"type": "main_model_loader",
"inputs": {
"model": {
"name": "model",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": -4514.466823162653,
"y": -1235.7908800002283
}
},
{
"id": "287f134f-da8d-41d1-884e-5940e8f7b816",
"type": "invocation",
"data": {
"id": "287f134f-da8d-41d1-884e-5940e8f7b816",
"version": "1.4.1",
"label": "",
"notes": "",
"type": "ip_adapter",
"inputs": {
"image": {
"name": "image",
"label": ""
},
"ip_adapter_model": {
"name": "ip_adapter_model",
"label": "IP-Adapter Model (select ip_adapter_sd15)",
"value": {
"key": "1cc210bb-4d0a-4312-b36c-b5d46c43768e",
"hash": "blake3:3d669dffa7471b357b4df088b99ffb6bf4d4383d5e0ef1de5ec1c89728a3d5a5",
"name": "ip_adapter_sd15",
"base": "sd-1",
"type": "ip_adapter"
}
},
"clip_vision_model": {
"name": "clip_vision_model",
"label": "",
"value": "ViT-H"
},
"weight": {
"name": "weight",
"label": "",
"value": 0.2
},
"method": {
"name": "method",
"label": "",
"value": "full"
},
"begin_step_percent": {
"name": "begin_step_percent",
"label": "",
"value": 0
},
"end_step_percent": {
"name": "end_step_percent",
"label": "",
"value": 1
},
"mask": {
"name": "mask",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": -2855.8555540799207,
"y": -183.58854843775742
}
},
{
"id": "b76fe66f-7884-43ad-b72c-fadc81d7a73c",
"type": "invocation",
"data": {
"id": "b76fe66f-7884-43ad-b72c-fadc81d7a73c",
"version": "1.3.0",
"label": "",
"notes": "",
"type": "l2i",
"inputs": {
"board": {
"name": "board",
"label": ""
},
"metadata": {
"name": "metadata",
"label": ""
},
"latents": {
"name": "latents",
"label": ""
},
"vae": {
"name": "vae",
"label": ""
},
"tiled": {
"name": "tiled",
"label": "",
"value": false
},
"tile_size": {
"name": "tile_size",
"label": "",
"value": 0
},
"fp32": {
"name": "fp32",
"label": "",
"value": false
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": -1999.770193862987,
"y": -1075
}
},
{
"id": "d334f2da-016a-4524-9911-bdab85546888",
"type": "invocation",
"data": {
"id": "d334f2da-016a-4524-9911-bdab85546888",
"version": "1.1.2",
"label": "",
"notes": "",
"type": "controlnet",
"inputs": {
"image": {
"name": "image",
"label": ""
},
"control_model": {
"name": "control_model",
"label": "Control Model (select contro_v11f1e_sd15_tile)",
"value": {
"key": "773843c8-db1f-4502-8f65-59782efa7960",
"hash": "blake3:f0812e13758f91baf4e54b7dbb707b70642937d3b2098cd2b94cc36d3eba308e",
"name": "control_v11f1e_sd15_tile",
"base": "sd-1",
"type": "controlnet"
}
},
"control_weight": {
"name": "control_weight",
"label": "",
"value": 1
},
"begin_step_percent": {
"name": "begin_step_percent",
"label": "",
"value": 0
},
"end_step_percent": {
"name": "end_step_percent",
"label": "Structural Control",
"value": 1
},
"control_mode": {
"name": "control_mode",
"label": "",
"value": "more_control"
},
"resize_mode": {
"name": "resize_mode",
"label": "",
"value": "just_resize"
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": -2481.9569385477016,
"y": -181.06590482739782
}
},
{
"id": "338b883c-3728-4f18-b3a6-6e7190c2f850",
"type": "invocation",
"data": {
"id": "338b883c-3728-4f18-b3a6-6e7190c2f850",
"version": "1.1.0",
"label": "",
"notes": "",
"type": "i2l",
"inputs": {
"image": {
"name": "image",
"label": ""
},
"vae": {
"name": "vae",
"label": ""
},
"tiled": {
"name": "tiled",
"label": "",
"value": false
},
"tile_size": {
"name": "tile_size",
"label": "",
"value": 0
},
"fp32": {
"name": "fp32",
"label": "",
"value": false
}
},
"isOpen": false,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": -2908.4791167517287,
"y": -408.87504820159086
}
},
{
"id": "947c3f88-0305-4695-8355-df4abac64b1c",
"type": "invocation",
"data": {
"id": "947c3f88-0305-4695-8355-df4abac64b1c",
"version": "1.2.0",
"label": "",
"notes": "",
"type": "compel",
"inputs": {
"prompt": {
"name": "prompt",
"label": "",
"value": ""
},
"clip": {
"name": "clip",
"label": ""
},
"mask": {
"name": "mask",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": -4014.4136788915944,
"y": -968.5677253775948
}
},
{
"id": "9b2d8c58-ce8f-4162-a5a1-48de854040d6",
"type": "invocation",
"data": {
"id": "9b2d8c58-ce8f-4162-a5a1-48de854040d6",
"version": "1.2.0",
"label": "",
"notes": "",
"type": "compel",
"inputs": {
"prompt": {
"name": "prompt",
"label": "Positive Prompt",
"value": ""
},
"clip": {
"name": "clip",
"label": ""
},
"mask": {
"name": "mask",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": -4014.4136788915944,
"y": -1243.5677253775948
}
},
{
"id": "b875cae6-d8a3-4fdc-b969-4d53cbd03f9a",
"type": "invocation",
@@ -181,64 +489,6 @@
"y": 3.422855503409039
}
},
{
"id": "9b2d8c58-ce8f-4162-a5a1-48de854040d6",
"type": "invocation",
"data": {
"id": "9b2d8c58-ce8f-4162-a5a1-48de854040d6",
"version": "1.1.1",
"label": "",
"notes": "",
"type": "compel",
"inputs": {
"prompt": {
"name": "prompt",
"label": "Positive Prompt",
"value": ""
},
"clip": {
"name": "clip",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": -4014.4136788915944,
"y": -1243.5677253775948
}
},
{
"id": "947c3f88-0305-4695-8355-df4abac64b1c",
"type": "invocation",
"data": {
"id": "947c3f88-0305-4695-8355-df4abac64b1c",
"version": "1.1.1",
"label": "",
"notes": "",
"type": "compel",
"inputs": {
"prompt": {
"name": "prompt",
"label": "",
"value": ""
},
"clip": {
"name": "clip",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": -4014.4136788915944,
"y": -968.5677253775948
}
},
{
"id": "b3513fed-ed42-408d-b382-128fdb0de523",
"type": "invocation",
@@ -379,104 +629,6 @@
"y": -29.08699277598673
}
},
{
"id": "338b883c-3728-4f18-b3a6-6e7190c2f850",
"type": "invocation",
"data": {
"id": "338b883c-3728-4f18-b3a6-6e7190c2f850",
"version": "1.0.2",
"label": "",
"notes": "",
"type": "i2l",
"inputs": {
"image": {
"name": "image",
"label": ""
},
"vae": {
"name": "vae",
"label": ""
},
"tiled": {
"name": "tiled",
"label": "",
"value": false
},
"fp32": {
"name": "fp32",
"label": "",
"value": false
}
},
"isOpen": false,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": -2908.4791167517287,
"y": -408.87504820159086
}
},
{
"id": "d334f2da-016a-4524-9911-bdab85546888",
"type": "invocation",
"data": {
"id": "d334f2da-016a-4524-9911-bdab85546888",
"version": "1.1.1",
"label": "",
"notes": "",
"type": "controlnet",
"inputs": {
"image": {
"name": "image",
"label": ""
},
"control_model": {
"name": "control_model",
"label": "Control Model (select contro_v11f1e_sd15_tile)",
"value": {
"key": "773843c8-db1f-4502-8f65-59782efa7960",
"hash": "blake3:f0812e13758f91baf4e54b7dbb707b70642937d3b2098cd2b94cc36d3eba308e",
"name": "control_v11f1e_sd15_tile",
"base": "sd-1",
"type": "controlnet"
}
},
"control_weight": {
"name": "control_weight",
"label": "",
"value": 1
},
"begin_step_percent": {
"name": "begin_step_percent",
"label": "",
"value": 0
},
"end_step_percent": {
"name": "end_step_percent",
"label": "Structural Control",
"value": 1
},
"control_mode": {
"name": "control_mode",
"label": "",
"value": "more_control"
},
"resize_mode": {
"name": "resize_mode",
"label": "",
"value": "just_resize"
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": -2481.9569385477016,
"y": -181.06590482739782
}
},
{
"id": "1011539e-85de-4e02-a003-0b22358491b8",
"type": "invocation",
@@ -563,52 +715,6 @@
"y": -1006.415909408244
}
},
{
"id": "b76fe66f-7884-43ad-b72c-fadc81d7a73c",
"type": "invocation",
"data": {
"id": "b76fe66f-7884-43ad-b72c-fadc81d7a73c",
"version": "1.2.2",
"label": "",
"notes": "",
"type": "l2i",
"inputs": {
"board": {
"name": "board",
"label": ""
},
"metadata": {
"name": "metadata",
"label": ""
},
"latents": {
"name": "latents",
"label": ""
},
"vae": {
"name": "vae",
"label": ""
},
"tiled": {
"name": "tiled",
"label": "",
"value": false
},
"fp32": {
"name": "fp32",
"label": "",
"value": false
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": -1999.770193862987,
"y": -1075
}
},
{
"id": "ab6f5dda-4b60-4ddf-99f2-f61fb5937527",
"type": "invocation",
@@ -779,56 +885,6 @@
"y": -78.2819050861178
}
},
{
"id": "287f134f-da8d-41d1-884e-5940e8f7b816",
"type": "invocation",
"data": {
"id": "287f134f-da8d-41d1-884e-5940e8f7b816",
"version": "1.2.2",
"label": "",
"notes": "",
"type": "ip_adapter",
"inputs": {
"image": {
"name": "image",
"label": ""
},
"ip_adapter_model": {
"name": "ip_adapter_model",
"label": "IP-Adapter Model (select ip_adapter_sd15)",
"value": {
"key": "1cc210bb-4d0a-4312-b36c-b5d46c43768e",
"hash": "blake3:3d669dffa7471b357b4df088b99ffb6bf4d4383d5e0ef1de5ec1c89728a3d5a5",
"name": "ip_adapter_sd15",
"base": "sd-1",
"type": "ip_adapter"
}
},
"weight": {
"name": "weight",
"label": "",
"value": 0.2
},
"begin_step_percent": {
"name": "begin_step_percent",
"label": "",
"value": 0
},
"end_step_percent": {
"name": "end_step_percent",
"label": "",
"value": 1
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": -2855.8555540799207,
"y": -183.58854843775742
}
},
{
"id": "1f86c8bf-06f9-4e28-abee-02f46f445ac4",
"type": "invocation",
@@ -899,30 +955,6 @@
"y": -41.810810454906914
}
},
{
"id": "2ff466b8-5e2a-4d8f-923a-a3884c7ecbc5",
"type": "invocation",
"data": {
"id": "2ff466b8-5e2a-4d8f-923a-a3884c7ecbc5",
"version": "1.0.2",
"label": "",
"notes": "",
"type": "main_model_loader",
"inputs": {
"model": {
"name": "model",
"label": ""
}
},
"isOpen": true,
"isIntermediate": true,
"useCache": true
},
"position": {
"x": -4514.466823162653,
"y": -1235.7908800002283
}
},
{
"id": "f5d9bf3b-2646-4b17-9894-20fd2b4218ea",
"type": "invocation",

View File

@@ -81,7 +81,7 @@ def get_openapi_func(
# Add the output map to the schema
openapi_schema["components"]["schemas"]["InvocationOutputMap"] = {
"type": "object",
"properties": invocation_output_map_properties,
"properties": dict(sorted(invocation_output_map_properties.items())),
"required": invocation_output_map_required,
}

View File

@@ -1,90 +0,0 @@
from pathlib import Path
from typing import Literal
import cv2
import numpy as np
import torch
import torch.nn.functional as F
from einops import repeat
from PIL import Image
from torchvision.transforms import Compose
from invokeai.app.services.config.config_default import get_config
from invokeai.backend.image_util.depth_anything.model.dpt import DPT_DINOv2
from invokeai.backend.image_util.depth_anything.utilities.util import NormalizeImage, PrepareForNet, Resize
from invokeai.backend.util.logging import InvokeAILogger
config = get_config()
logger = InvokeAILogger.get_logger(config=config)
DEPTH_ANYTHING_MODELS = {
"large": "https://huggingface.co/spaces/LiheYoung/Depth-Anything/resolve/main/checkpoints/depth_anything_vitl14.pth?download=true",
"base": "https://huggingface.co/spaces/LiheYoung/Depth-Anything/resolve/main/checkpoints/depth_anything_vitb14.pth?download=true",
"small": "https://huggingface.co/spaces/LiheYoung/Depth-Anything/resolve/main/checkpoints/depth_anything_vits14.pth?download=true",
}
transform = Compose(
[
Resize(
width=518,
height=518,
resize_target=False,
keep_aspect_ratio=True,
ensure_multiple_of=14,
resize_method="lower_bound",
image_interpolation_method=cv2.INTER_CUBIC,
),
NormalizeImage(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
PrepareForNet(),
]
)
class DepthAnythingDetector:
def __init__(self, model: DPT_DINOv2, device: torch.device) -> None:
self.model = model
self.device = device
@staticmethod
def load_model(
model_path: Path, device: torch.device, model_size: Literal["large", "base", "small"] = "small"
) -> DPT_DINOv2:
match model_size:
case "small":
model = DPT_DINOv2(encoder="vits", features=64, out_channels=[48, 96, 192, 384])
case "base":
model = DPT_DINOv2(encoder="vitb", features=128, out_channels=[96, 192, 384, 768])
case "large":
model = DPT_DINOv2(encoder="vitl", features=256, out_channels=[256, 512, 1024, 1024])
model.load_state_dict(torch.load(model_path.as_posix(), map_location="cpu"))
model.eval()
model.to(device)
return model
def __call__(self, image: Image.Image, resolution: int = 512) -> Image.Image:
if not self.model:
logger.warn("DepthAnything model was not loaded. Returning original image")
return image
np_image = np.array(image, dtype=np.uint8)
np_image = np_image[:, :, ::-1] / 255.0
image_height, image_width = np_image.shape[:2]
np_image = transform({"image": np_image})["image"]
tensor_image = torch.from_numpy(np_image).unsqueeze(0).to(self.device)
with torch.no_grad():
depth = self.model(tensor_image)
depth = F.interpolate(depth[None], (image_height, image_width), mode="bilinear", align_corners=False)[0, 0]
depth = (depth - depth.min()) / (depth.max() - depth.min()) * 255.0
depth_map = repeat(depth, "h w -> h w 3").cpu().numpy().astype(np.uint8)
depth_map = Image.fromarray(depth_map)
new_height = int(image_height * (resolution / image_width))
depth_map = depth_map.resize((resolution, new_height))
return depth_map

View File

@@ -0,0 +1,31 @@
from typing import Optional
import torch
from PIL import Image
from transformers.pipelines import DepthEstimationPipeline
from invokeai.backend.raw_model import RawModel
class DepthAnythingPipeline(RawModel):
"""Custom wrapper for the Depth Estimation pipeline from transformers adding compatibility
for Invoke's Model Management System"""
def __init__(self, pipeline: DepthEstimationPipeline) -> None:
self._pipeline = pipeline
def generate_depth(self, image: Image.Image) -> Image.Image:
depth_map = self._pipeline(image)["depth"]
assert isinstance(depth_map, Image.Image)
return depth_map
def to(self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None):
if device is not None and device.type not in {"cpu", "cuda"}:
device = None
self._pipeline.model.to(device=device, dtype=dtype)
self._pipeline.device = self._pipeline.model.device
def calc_size(self) -> int:
from invokeai.backend.model_manager.load.model_util import calc_module_size
return calc_module_size(self._pipeline.model)

View File

@@ -1,145 +0,0 @@
import torch.nn as nn
def _make_scratch(in_shape, out_shape, groups=1, expand=False):
scratch = nn.Module()
out_shape1 = out_shape
out_shape2 = out_shape
out_shape3 = out_shape
if len(in_shape) >= 4:
out_shape4 = out_shape
if expand:
out_shape1 = out_shape
out_shape2 = out_shape * 2
out_shape3 = out_shape * 4
if len(in_shape) >= 4:
out_shape4 = out_shape * 8
scratch.layer1_rn = nn.Conv2d(
in_shape[0], out_shape1, kernel_size=3, stride=1, padding=1, bias=False, groups=groups
)
scratch.layer2_rn = nn.Conv2d(
in_shape[1], out_shape2, kernel_size=3, stride=1, padding=1, bias=False, groups=groups
)
scratch.layer3_rn = nn.Conv2d(
in_shape[2], out_shape3, kernel_size=3, stride=1, padding=1, bias=False, groups=groups
)
if len(in_shape) >= 4:
scratch.layer4_rn = nn.Conv2d(
in_shape[3], out_shape4, kernel_size=3, stride=1, padding=1, bias=False, groups=groups
)
return scratch
class ResidualConvUnit(nn.Module):
"""Residual convolution module."""
def __init__(self, features, activation, bn):
"""Init.
Args:
features (int): number of features
"""
super().__init__()
self.bn = bn
self.groups = 1
self.conv1 = nn.Conv2d(features, features, kernel_size=3, stride=1, padding=1, bias=True, groups=self.groups)
self.conv2 = nn.Conv2d(features, features, kernel_size=3, stride=1, padding=1, bias=True, groups=self.groups)
if self.bn:
self.bn1 = nn.BatchNorm2d(features)
self.bn2 = nn.BatchNorm2d(features)
self.activation = activation
self.skip_add = nn.quantized.FloatFunctional()
def forward(self, x):
"""Forward pass.
Args:
x (tensor): input
Returns:
tensor: output
"""
out = self.activation(x)
out = self.conv1(out)
if self.bn:
out = self.bn1(out)
out = self.activation(out)
out = self.conv2(out)
if self.bn:
out = self.bn2(out)
if self.groups > 1:
out = self.conv_merge(out)
return self.skip_add.add(out, x)
class FeatureFusionBlock(nn.Module):
"""Feature fusion block."""
def __init__(self, features, activation, deconv=False, bn=False, expand=False, align_corners=True, size=None):
"""Init.
Args:
features (int): number of features
"""
super(FeatureFusionBlock, self).__init__()
self.deconv = deconv
self.align_corners = align_corners
self.groups = 1
self.expand = expand
out_features = features
if self.expand:
out_features = features // 2
self.out_conv = nn.Conv2d(features, out_features, kernel_size=1, stride=1, padding=0, bias=True, groups=1)
self.resConfUnit1 = ResidualConvUnit(features, activation, bn)
self.resConfUnit2 = ResidualConvUnit(features, activation, bn)
self.skip_add = nn.quantized.FloatFunctional()
self.size = size
def forward(self, *xs, size=None):
"""Forward pass.
Returns:
tensor: output
"""
output = xs[0]
if len(xs) == 2:
res = self.resConfUnit1(xs[1])
output = self.skip_add.add(output, res)
output = self.resConfUnit2(output)
if (size is None) and (self.size is None):
modifier = {"scale_factor": 2}
elif size is None:
modifier = {"size": self.size}
else:
modifier = {"size": size}
output = nn.functional.interpolate(output, **modifier, mode="bilinear", align_corners=self.align_corners)
output = self.out_conv(output)
return output

View File

@@ -1,183 +0,0 @@
from pathlib import Path
import torch
import torch.nn as nn
import torch.nn.functional as F
from invokeai.backend.image_util.depth_anything.model.blocks import FeatureFusionBlock, _make_scratch
torchhub_path = Path(__file__).parent.parent / "torchhub"
def _make_fusion_block(features, use_bn, size=None):
return FeatureFusionBlock(
features,
nn.ReLU(False),
deconv=False,
bn=use_bn,
expand=False,
align_corners=True,
size=size,
)
class DPTHead(nn.Module):
def __init__(self, nclass, in_channels, features, out_channels, use_bn=False, use_clstoken=False):
super(DPTHead, self).__init__()
self.nclass = nclass
self.use_clstoken = use_clstoken
self.projects = nn.ModuleList(
[
nn.Conv2d(
in_channels=in_channels,
out_channels=out_channel,
kernel_size=1,
stride=1,
padding=0,
)
for out_channel in out_channels
]
)
self.resize_layers = nn.ModuleList(
[
nn.ConvTranspose2d(
in_channels=out_channels[0], out_channels=out_channels[0], kernel_size=4, stride=4, padding=0
),
nn.ConvTranspose2d(
in_channels=out_channels[1], out_channels=out_channels[1], kernel_size=2, stride=2, padding=0
),
nn.Identity(),
nn.Conv2d(
in_channels=out_channels[3], out_channels=out_channels[3], kernel_size=3, stride=2, padding=1
),
]
)
if use_clstoken:
self.readout_projects = nn.ModuleList()
for _ in range(len(self.projects)):
self.readout_projects.append(nn.Sequential(nn.Linear(2 * in_channels, in_channels), nn.GELU()))
self.scratch = _make_scratch(
out_channels,
features,
groups=1,
expand=False,
)
self.scratch.stem_transpose = None
self.scratch.refinenet1 = _make_fusion_block(features, use_bn)
self.scratch.refinenet2 = _make_fusion_block(features, use_bn)
self.scratch.refinenet3 = _make_fusion_block(features, use_bn)
self.scratch.refinenet4 = _make_fusion_block(features, use_bn)
head_features_1 = features
head_features_2 = 32
if nclass > 1:
self.scratch.output_conv = nn.Sequential(
nn.Conv2d(head_features_1, head_features_1, kernel_size=3, stride=1, padding=1),
nn.ReLU(True),
nn.Conv2d(head_features_1, nclass, kernel_size=1, stride=1, padding=0),
)
else:
self.scratch.output_conv1 = nn.Conv2d(
head_features_1, head_features_1 // 2, kernel_size=3, stride=1, padding=1
)
self.scratch.output_conv2 = nn.Sequential(
nn.Conv2d(head_features_1 // 2, head_features_2, kernel_size=3, stride=1, padding=1),
nn.ReLU(True),
nn.Conv2d(head_features_2, 1, kernel_size=1, stride=1, padding=0),
nn.ReLU(True),
nn.Identity(),
)
def forward(self, out_features, patch_h, patch_w):
out = []
for i, x in enumerate(out_features):
if self.use_clstoken:
x, cls_token = x[0], x[1]
readout = cls_token.unsqueeze(1).expand_as(x)
x = self.readout_projects[i](torch.cat((x, readout), -1))
else:
x = x[0]
x = x.permute(0, 2, 1).reshape((x.shape[0], x.shape[-1], patch_h, patch_w))
x = self.projects[i](x)
x = self.resize_layers[i](x)
out.append(x)
layer_1, layer_2, layer_3, layer_4 = out
layer_1_rn = self.scratch.layer1_rn(layer_1)
layer_2_rn = self.scratch.layer2_rn(layer_2)
layer_3_rn = self.scratch.layer3_rn(layer_3)
layer_4_rn = self.scratch.layer4_rn(layer_4)
path_4 = self.scratch.refinenet4(layer_4_rn, size=layer_3_rn.shape[2:])
path_3 = self.scratch.refinenet3(path_4, layer_3_rn, size=layer_2_rn.shape[2:])
path_2 = self.scratch.refinenet2(path_3, layer_2_rn, size=layer_1_rn.shape[2:])
path_1 = self.scratch.refinenet1(path_2, layer_1_rn)
out = self.scratch.output_conv1(path_1)
out = F.interpolate(out, (int(patch_h * 14), int(patch_w * 14)), mode="bilinear", align_corners=True)
out = self.scratch.output_conv2(out)
return out
class DPT_DINOv2(nn.Module):
def __init__(
self,
features,
out_channels,
encoder="vitl",
use_bn=False,
use_clstoken=False,
):
super(DPT_DINOv2, self).__init__()
assert encoder in ["vits", "vitb", "vitl"]
# # in case the Internet connection is not stable, please load the DINOv2 locally
# if use_local:
# self.pretrained = torch.hub.load(
# torchhub_path / "facebookresearch_dinov2_main",
# "dinov2_{:}14".format(encoder),
# source="local",
# pretrained=False,
# )
# else:
# self.pretrained = torch.hub.load(
# "facebookresearch/dinov2",
# "dinov2_{:}14".format(encoder),
# )
self.pretrained = torch.hub.load(
"facebookresearch/dinov2",
"dinov2_{:}14".format(encoder),
)
dim = self.pretrained.blocks[0].attn.qkv.in_features
self.depth_head = DPTHead(1, dim, features, out_channels=out_channels, use_bn=use_bn, use_clstoken=use_clstoken)
def forward(self, x):
h, w = x.shape[-2:]
features = self.pretrained.get_intermediate_layers(x, 4, return_class_token=True)
patch_h, patch_w = h // 14, w // 14
depth = self.depth_head(features, patch_h, patch_w)
depth = F.interpolate(depth, size=(h, w), mode="bilinear", align_corners=True)
depth = F.relu(depth)
return depth.squeeze(1)

View File

@@ -1,227 +0,0 @@
import math
import cv2
import numpy as np
import torch
import torch.nn.functional as F
def apply_min_size(sample, size, image_interpolation_method=cv2.INTER_AREA):
"""Rezise the sample to ensure the given size. Keeps aspect ratio.
Args:
sample (dict): sample
size (tuple): image size
Returns:
tuple: new size
"""
shape = list(sample["disparity"].shape)
if shape[0] >= size[0] and shape[1] >= size[1]:
return sample
scale = [0, 0]
scale[0] = size[0] / shape[0]
scale[1] = size[1] / shape[1]
scale = max(scale)
shape[0] = math.ceil(scale * shape[0])
shape[1] = math.ceil(scale * shape[1])
# resize
sample["image"] = cv2.resize(sample["image"], tuple(shape[::-1]), interpolation=image_interpolation_method)
sample["disparity"] = cv2.resize(sample["disparity"], tuple(shape[::-1]), interpolation=cv2.INTER_NEAREST)
sample["mask"] = cv2.resize(
sample["mask"].astype(np.float32),
tuple(shape[::-1]),
interpolation=cv2.INTER_NEAREST,
)
sample["mask"] = sample["mask"].astype(bool)
return tuple(shape)
class Resize(object):
"""Resize sample to given size (width, height)."""
def __init__(
self,
width,
height,
resize_target=True,
keep_aspect_ratio=False,
ensure_multiple_of=1,
resize_method="lower_bound",
image_interpolation_method=cv2.INTER_AREA,
):
"""Init.
Args:
width (int): desired output width
height (int): desired output height
resize_target (bool, optional):
True: Resize the full sample (image, mask, target).
False: Resize image only.
Defaults to True.
keep_aspect_ratio (bool, optional):
True: Keep the aspect ratio of the input sample.
Output sample might not have the given width and height, and
resize behaviour depends on the parameter 'resize_method'.
Defaults to False.
ensure_multiple_of (int, optional):
Output width and height is constrained to be multiple of this parameter.
Defaults to 1.
resize_method (str, optional):
"lower_bound": Output will be at least as large as the given size.
"upper_bound": Output will be at max as large as the given size. (Output size might be smaller
than given size.)
"minimal": Scale as least as possible. (Output size might be smaller than given size.)
Defaults to "lower_bound".
"""
self.__width = width
self.__height = height
self.__resize_target = resize_target
self.__keep_aspect_ratio = keep_aspect_ratio
self.__multiple_of = ensure_multiple_of
self.__resize_method = resize_method
self.__image_interpolation_method = image_interpolation_method
def constrain_to_multiple_of(self, x, min_val=0, max_val=None):
y = (np.round(x / self.__multiple_of) * self.__multiple_of).astype(int)
if max_val is not None and y > max_val:
y = (np.floor(x / self.__multiple_of) * self.__multiple_of).astype(int)
if y < min_val:
y = (np.ceil(x / self.__multiple_of) * self.__multiple_of).astype(int)
return y
def get_size(self, width, height):
# determine new height and width
scale_height = self.__height / height
scale_width = self.__width / width
if self.__keep_aspect_ratio:
if self.__resize_method == "lower_bound":
# scale such that output size is lower bound
if scale_width > scale_height:
# fit width
scale_height = scale_width
else:
# fit height
scale_width = scale_height
elif self.__resize_method == "upper_bound":
# scale such that output size is upper bound
if scale_width < scale_height:
# fit width
scale_height = scale_width
else:
# fit height
scale_width = scale_height
elif self.__resize_method == "minimal":
# scale as least as possbile
if abs(1 - scale_width) < abs(1 - scale_height):
# fit width
scale_height = scale_width
else:
# fit height
scale_width = scale_height
else:
raise ValueError(f"resize_method {self.__resize_method} not implemented")
if self.__resize_method == "lower_bound":
new_height = self.constrain_to_multiple_of(scale_height * height, min_val=self.__height)
new_width = self.constrain_to_multiple_of(scale_width * width, min_val=self.__width)
elif self.__resize_method == "upper_bound":
new_height = self.constrain_to_multiple_of(scale_height * height, max_val=self.__height)
new_width = self.constrain_to_multiple_of(scale_width * width, max_val=self.__width)
elif self.__resize_method == "minimal":
new_height = self.constrain_to_multiple_of(scale_height * height)
new_width = self.constrain_to_multiple_of(scale_width * width)
else:
raise ValueError(f"resize_method {self.__resize_method} not implemented")
return (new_width, new_height)
def __call__(self, sample):
width, height = self.get_size(sample["image"].shape[1], sample["image"].shape[0])
# resize sample
sample["image"] = cv2.resize(
sample["image"],
(width, height),
interpolation=self.__image_interpolation_method,
)
if self.__resize_target:
if "disparity" in sample:
sample["disparity"] = cv2.resize(
sample["disparity"],
(width, height),
interpolation=cv2.INTER_NEAREST,
)
if "depth" in sample:
sample["depth"] = cv2.resize(sample["depth"], (width, height), interpolation=cv2.INTER_NEAREST)
if "semseg_mask" in sample:
# sample["semseg_mask"] = cv2.resize(
# sample["semseg_mask"], (width, height), interpolation=cv2.INTER_NEAREST
# )
sample["semseg_mask"] = F.interpolate(
torch.from_numpy(sample["semseg_mask"]).float()[None, None, ...], (height, width), mode="nearest"
).numpy()[0, 0]
if "mask" in sample:
sample["mask"] = cv2.resize(
sample["mask"].astype(np.float32),
(width, height),
interpolation=cv2.INTER_NEAREST,
)
# sample["mask"] = sample["mask"].astype(bool)
# print(sample['image'].shape, sample['depth'].shape)
return sample
class NormalizeImage(object):
"""Normlize image by given mean and std."""
def __init__(self, mean, std):
self.__mean = mean
self.__std = std
def __call__(self, sample):
sample["image"] = (sample["image"] - self.__mean) / self.__std
return sample
class PrepareForNet(object):
"""Prepare sample for usage as network input."""
def __init__(self):
pass
def __call__(self, sample):
image = np.transpose(sample["image"], (2, 0, 1))
sample["image"] = np.ascontiguousarray(image).astype(np.float32)
if "mask" in sample:
sample["mask"] = sample["mask"].astype(np.float32)
sample["mask"] = np.ascontiguousarray(sample["mask"])
if "depth" in sample:
depth = sample["depth"].astype(np.float32)
sample["depth"] = np.ascontiguousarray(depth)
if "semseg_mask" in sample:
sample["semseg_mask"] = sample["semseg_mask"].astype(np.float32)
sample["semseg_mask"] = np.ascontiguousarray(sample["semseg_mask"])
return sample

View File

@@ -0,0 +1,22 @@
from pydantic import BaseModel, ConfigDict
class BoundingBox(BaseModel):
"""Bounding box helper class."""
xmin: int
ymin: int
xmax: int
ymax: int
class DetectionResult(BaseModel):
"""Detection result from Grounding DINO."""
score: float
label: str
box: BoundingBox
model_config = ConfigDict(
# Allow arbitrary types for mask, since it will be a numpy array.
arbitrary_types_allowed=True
)

View File

@@ -0,0 +1,37 @@
from typing import Optional
import torch
from PIL import Image
from transformers.pipelines import ZeroShotObjectDetectionPipeline
from invokeai.backend.image_util.grounding_dino.detection_result import DetectionResult
from invokeai.backend.raw_model import RawModel
class GroundingDinoPipeline(RawModel):
"""A wrapper class for a ZeroShotObjectDetectionPipeline that makes it compatible with the model manager's memory
management system.
"""
def __init__(self, pipeline: ZeroShotObjectDetectionPipeline):
self._pipeline = pipeline
def detect(self, image: Image.Image, candidate_labels: list[str], threshold: float = 0.1) -> list[DetectionResult]:
results = self._pipeline(image=image, candidate_labels=candidate_labels, threshold=threshold)
assert results is not None
results = [DetectionResult.model_validate(result) for result in results]
return results
def to(self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None):
# HACK(ryand): The GroundingDinoPipeline does not work on MPS devices. We only allow it to be moved to CPU or
# CUDA.
if device is not None and device.type not in {"cpu", "cuda"}:
device = None
self._pipeline.model.to(device=device, dtype=dtype)
self._pipeline.device = self._pipeline.model.device
def calc_size(self) -> int:
# HACK(ryand): Fix the circular import issue.
from invokeai.backend.model_manager.load.model_util import calc_module_size
return calc_module_size(self._pipeline.model)

View File

@@ -98,7 +98,7 @@ class UnetSkipConnectionBlock(nn.Module):
"""
super(UnetSkipConnectionBlock, self).__init__()
self.outermost = outermost
if type(norm_layer) == functools.partial:
if isinstance(norm_layer, functools.partial):
use_bias = norm_layer.func == nn.InstanceNorm2d
else:
use_bias = norm_layer == nn.InstanceNorm2d

View File

@@ -0,0 +1,50 @@
# This file contains utilities for Grounded-SAM mask refinement based on:
# https://github.com/NielsRogge/Transformers-Tutorials/blob/a39f33ac1557b02ebfb191ea7753e332b5ca933f/Grounding%20DINO/GroundingDINO_with_Segment_Anything.ipynb
import cv2
import numpy as np
import numpy.typing as npt
def mask_to_polygon(mask: npt.NDArray[np.uint8]) -> list[tuple[int, int]]:
"""Convert a binary mask to a polygon.
Returns:
list[list[int]]: List of (x, y) coordinates representing the vertices of the polygon.
"""
# Find contours in the binary mask.
contours, _ = cv2.findContours(mask.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Find the contour with the largest area.
largest_contour = max(contours, key=cv2.contourArea)
# Extract the vertices of the contour.
polygon = largest_contour.reshape(-1, 2).tolist()
return polygon
def polygon_to_mask(
polygon: list[tuple[int, int]], image_shape: tuple[int, int], fill_value: int = 1
) -> npt.NDArray[np.uint8]:
"""Convert a polygon to a segmentation mask.
Args:
polygon (list): List of (x, y) coordinates representing the vertices of the polygon.
image_shape (tuple): Shape of the image (height, width) for the mask.
fill_value (int): Value to fill the polygon with.
Returns:
np.ndarray: Segmentation mask with the polygon filled (with value 255).
"""
# Create an empty mask.
mask = np.zeros(image_shape, dtype=np.uint8)
# Convert polygon to an array of points.
pts = np.array(polygon, dtype=np.int32)
# Fill the polygon with white color (255).
cv2.fillPoly(mask, [pts], color=(fill_value,))
return mask

View File

@@ -0,0 +1,53 @@
from typing import Optional
import torch
from PIL import Image
from transformers.models.sam import SamModel
from transformers.models.sam.processing_sam import SamProcessor
from invokeai.backend.raw_model import RawModel
class SegmentAnythingPipeline(RawModel):
"""A wrapper class for the transformers SAM model and processor that makes it compatible with the model manager."""
def __init__(self, sam_model: SamModel, sam_processor: SamProcessor):
self._sam_model = sam_model
self._sam_processor = sam_processor
def to(self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None):
# HACK(ryand): The SAM pipeline does not work on MPS devices. We only allow it to be moved to CPU or CUDA.
if device is not None and device.type not in {"cpu", "cuda"}:
device = None
self._sam_model.to(device=device, dtype=dtype)
def calc_size(self) -> int:
# HACK(ryand): Fix the circular import issue.
from invokeai.backend.model_manager.load.model_util import calc_module_size
return calc_module_size(self._sam_model)
def segment(self, image: Image.Image, bounding_boxes: list[list[int]]) -> torch.Tensor:
"""Run the SAM model.
Args:
image (Image.Image): The image to segment.
bounding_boxes (list[list[int]]): The bounding box prompts. Each bounding box is in the format
[xmin, ymin, xmax, ymax].
Returns:
torch.Tensor: The segmentation masks. dtype: torch.bool. shape: [num_masks, channels, height, width].
"""
# Add batch dimension of 1 to the bounding boxes.
boxes = [bounding_boxes]
inputs = self._sam_processor(images=image, input_boxes=boxes, return_tensors="pt").to(self._sam_model.device)
outputs = self._sam_model(**inputs)
masks = self._sam_processor.post_process_masks(
masks=outputs.pred_masks,
original_sizes=inputs.original_sizes,
reshaped_input_sizes=inputs.reshaped_input_sizes,
)
# There should be only one batch.
assert len(masks) == 1
return masks[0]

View File

@@ -124,16 +124,14 @@ class IPAdapter(RawModel):
self.device, dtype=self.dtype
)
def to(
self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None, non_blocking: bool = False
):
def to(self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None):
if device is not None:
self.device = device
if dtype is not None:
self.dtype = dtype
self._image_proj_model.to(device=self.device, dtype=self.dtype, non_blocking=non_blocking)
self.attn_weights.to(device=self.device, dtype=self.dtype, non_blocking=non_blocking)
self._image_proj_model.to(device=self.device, dtype=self.dtype)
self.attn_weights.to(device=self.device, dtype=self.dtype)
def calc_size(self) -> int:
# HACK(ryand): Fix this issue with circular imports.

View File

@@ -3,15 +3,15 @@
import bisect
from pathlib import Path
from typing import Dict, List, Optional, Tuple, Union
from typing import Dict, List, Optional, Set, Tuple, Union
import torch
from safetensors.torch import load_file
from typing_extensions import Self
import invokeai.backend.util.logging as logger
from invokeai.backend.model_manager import BaseModelType
from invokeai.backend.raw_model import RawModel
from invokeai.backend.util.devices import TorchDevice
class LoRALayerBase:
@@ -47,9 +47,19 @@ class LoRALayerBase:
self.rank = None # set in layer implementation
self.layer_key = layer_key
def get_weight(self, orig_weight: Optional[torch.Tensor]) -> torch.Tensor:
def get_weight(self, orig_weight: torch.Tensor) -> torch.Tensor:
raise NotImplementedError()
def get_bias(self, orig_bias: torch.Tensor) -> Optional[torch.Tensor]:
return self.bias
def get_parameters(self, orig_module: torch.nn.Module) -> Dict[str, torch.Tensor]:
params = {"weight": self.get_weight(orig_module.weight)}
bias = self.get_bias(orig_module.bias)
if bias is not None:
params["bias"] = bias
return params
def calc_size(self) -> int:
model_size = 0
for val in [self.bias]:
@@ -57,14 +67,20 @@ class LoRALayerBase:
model_size += val.nelement() * val.element_size()
return model_size
def to(
self,
device: Optional[torch.device] = None,
dtype: Optional[torch.dtype] = None,
non_blocking: bool = False,
) -> None:
def to(self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None) -> None:
if self.bias is not None:
self.bias = self.bias.to(device=device, dtype=dtype, non_blocking=non_blocking)
self.bias = self.bias.to(device=device, dtype=dtype)
def check_keys(self, values: Dict[str, torch.Tensor], known_keys: Set[str]):
"""Log a warning if values contains unhandled keys."""
# {"alpha", "bias_indices", "bias_values", "bias_size"} are hard-coded, because they are handled by
# `LoRALayerBase`. Sub-classes should provide the known_keys that they handled.
all_known_keys = known_keys | {"alpha", "bias_indices", "bias_values", "bias_size"}
unknown_keys = set(values.keys()) - all_known_keys
if unknown_keys:
logger.warning(
f"Unexpected keys found in LoRA/LyCORIS layer, model might work incorrectly! Keys: {unknown_keys}"
)
# TODO: find and debug lora/locon with bias
@@ -82,14 +98,19 @@ class LoRALayer(LoRALayerBase):
self.up = values["lora_up.weight"]
self.down = values["lora_down.weight"]
if "lora_mid.weight" in values:
self.mid: Optional[torch.Tensor] = values["lora_mid.weight"]
else:
self.mid = None
self.mid = values.get("lora_mid.weight", None)
self.rank = self.down.shape[0]
self.check_keys(
values,
{
"lora_up.weight",
"lora_down.weight",
"lora_mid.weight",
},
)
def get_weight(self, orig_weight: Optional[torch.Tensor]) -> torch.Tensor:
def get_weight(self, orig_weight: torch.Tensor) -> torch.Tensor:
if self.mid is not None:
up = self.up.reshape(self.up.shape[0], self.up.shape[1])
down = self.down.reshape(self.down.shape[0], self.down.shape[1])
@@ -106,19 +127,14 @@ class LoRALayer(LoRALayerBase):
model_size += val.nelement() * val.element_size()
return model_size
def to(
self,
device: Optional[torch.device] = None,
dtype: Optional[torch.dtype] = None,
non_blocking: bool = False,
) -> None:
super().to(device=device, dtype=dtype, non_blocking=non_blocking)
def to(self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None) -> None:
super().to(device=device, dtype=dtype)
self.up = self.up.to(device=device, dtype=dtype, non_blocking=non_blocking)
self.down = self.down.to(device=device, dtype=dtype, non_blocking=non_blocking)
self.up = self.up.to(device=device, dtype=dtype)
self.down = self.down.to(device=device, dtype=dtype)
if self.mid is not None:
self.mid = self.mid.to(device=device, dtype=dtype, non_blocking=non_blocking)
self.mid = self.mid.to(device=device, dtype=dtype)
class LoHALayer(LoRALayerBase):
@@ -136,20 +152,23 @@ class LoHALayer(LoRALayerBase):
self.w1_b = values["hada_w1_b"]
self.w2_a = values["hada_w2_a"]
self.w2_b = values["hada_w2_b"]
if "hada_t1" in values:
self.t1: Optional[torch.Tensor] = values["hada_t1"]
else:
self.t1 = None
if "hada_t2" in values:
self.t2: Optional[torch.Tensor] = values["hada_t2"]
else:
self.t2 = None
self.t1 = values.get("hada_t1", None)
self.t2 = values.get("hada_t2", None)
self.rank = self.w1_b.shape[0]
self.check_keys(
values,
{
"hada_w1_a",
"hada_w1_b",
"hada_w2_a",
"hada_w2_b",
"hada_t1",
"hada_t2",
},
)
def get_weight(self, orig_weight: Optional[torch.Tensor]) -> torch.Tensor:
def get_weight(self, orig_weight: torch.Tensor) -> torch.Tensor:
if self.t1 is None:
weight: torch.Tensor = (self.w1_a @ self.w1_b) * (self.w2_a @ self.w2_b)
@@ -167,23 +186,18 @@ class LoHALayer(LoRALayerBase):
model_size += val.nelement() * val.element_size()
return model_size
def to(
self,
device: Optional[torch.device] = None,
dtype: Optional[torch.dtype] = None,
non_blocking: bool = False,
) -> None:
def to(self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None) -> None:
super().to(device=device, dtype=dtype)
self.w1_a = self.w1_a.to(device=device, dtype=dtype, non_blocking=non_blocking)
self.w1_b = self.w1_b.to(device=device, dtype=dtype, non_blocking=non_blocking)
self.w1_a = self.w1_a.to(device=device, dtype=dtype)
self.w1_b = self.w1_b.to(device=device, dtype=dtype)
if self.t1 is not None:
self.t1 = self.t1.to(device=device, dtype=dtype, non_blocking=non_blocking)
self.t1 = self.t1.to(device=device, dtype=dtype)
self.w2_a = self.w2_a.to(device=device, dtype=dtype, non_blocking=non_blocking)
self.w2_b = self.w2_b.to(device=device, dtype=dtype, non_blocking=non_blocking)
self.w2_a = self.w2_a.to(device=device, dtype=dtype)
self.w2_b = self.w2_b.to(device=device, dtype=dtype)
if self.t2 is not None:
self.t2 = self.t2.to(device=device, dtype=dtype, non_blocking=non_blocking)
self.t2 = self.t2.to(device=device, dtype=dtype)
class LoKRLayer(LoRALayerBase):
@@ -202,37 +216,45 @@ class LoKRLayer(LoRALayerBase):
):
super().__init__(layer_key, values)
if "lokr_w1" in values:
self.w1: Optional[torch.Tensor] = values["lokr_w1"]
self.w1_a = None
self.w1_b = None
else:
self.w1 = None
self.w1 = values.get("lokr_w1", None)
if self.w1 is None:
self.w1_a = values["lokr_w1_a"]
self.w1_b = values["lokr_w1_b"]
if "lokr_w2" in values:
self.w2: Optional[torch.Tensor] = values["lokr_w2"]
self.w2_a = None
self.w2_b = None
else:
self.w2 = None
self.w1_b = None
self.w1_a = None
self.w2 = values.get("lokr_w2", None)
if self.w2 is None:
self.w2_a = values["lokr_w2_a"]
self.w2_b = values["lokr_w2_b"]
if "lokr_t2" in values:
self.t2: Optional[torch.Tensor] = values["lokr_t2"]
else:
self.t2 = None
self.w2_a = None
self.w2_b = None
if "lokr_w1_b" in values:
self.rank = values["lokr_w1_b"].shape[0]
elif "lokr_w2_b" in values:
self.rank = values["lokr_w2_b"].shape[0]
self.t2 = values.get("lokr_t2", None)
if self.w1_b is not None:
self.rank = self.w1_b.shape[0]
elif self.w2_b is not None:
self.rank = self.w2_b.shape[0]
else:
self.rank = None # unscaled
def get_weight(self, orig_weight: Optional[torch.Tensor]) -> torch.Tensor:
self.check_keys(
values,
{
"lokr_w1",
"lokr_w1_a",
"lokr_w1_b",
"lokr_w2",
"lokr_w2_a",
"lokr_w2_b",
"lokr_t2",
},
)
def get_weight(self, orig_weight: torch.Tensor) -> torch.Tensor:
w1: Optional[torch.Tensor] = self.w1
if w1 is None:
assert self.w1_a is not None
@@ -264,12 +286,7 @@ class LoKRLayer(LoRALayerBase):
model_size += val.nelement() * val.element_size()
return model_size
def to(
self,
device: Optional[torch.device] = None,
dtype: Optional[torch.dtype] = None,
non_blocking: bool = False,
) -> None:
def to(self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None) -> None:
super().to(device=device, dtype=dtype)
if self.w1 is not None:
@@ -277,23 +294,25 @@ class LoKRLayer(LoRALayerBase):
else:
assert self.w1_a is not None
assert self.w1_b is not None
self.w1_a = self.w1_a.to(device=device, dtype=dtype, non_blocking=non_blocking)
self.w1_b = self.w1_b.to(device=device, dtype=dtype, non_blocking=non_blocking)
self.w1_a = self.w1_a.to(device=device, dtype=dtype)
self.w1_b = self.w1_b.to(device=device, dtype=dtype)
if self.w2 is not None:
self.w2 = self.w2.to(device=device, dtype=dtype, non_blocking=non_blocking)
self.w2 = self.w2.to(device=device, dtype=dtype)
else:
assert self.w2_a is not None
assert self.w2_b is not None
self.w2_a = self.w2_a.to(device=device, dtype=dtype, non_blocking=non_blocking)
self.w2_b = self.w2_b.to(device=device, dtype=dtype, non_blocking=non_blocking)
self.w2_a = self.w2_a.to(device=device, dtype=dtype)
self.w2_b = self.w2_b.to(device=device, dtype=dtype)
if self.t2 is not None:
self.t2 = self.t2.to(device=device, dtype=dtype, non_blocking=non_blocking)
self.t2 = self.t2.to(device=device, dtype=dtype)
class FullLayer(LoRALayerBase):
# bias handled in LoRALayerBase(calc_size, to)
# weight: torch.Tensor
# bias: Optional[torch.Tensor]
def __init__(
self,
@@ -303,15 +322,12 @@ class FullLayer(LoRALayerBase):
super().__init__(layer_key, values)
self.weight = values["diff"]
if len(values.keys()) > 1:
_keys = list(values.keys())
_keys.remove("diff")
raise NotImplementedError(f"Unexpected keys in lora diff layer: {_keys}")
self.bias = values.get("diff_b", None)
self.rank = None # unscaled
self.check_keys(values, {"diff", "diff_b"})
def get_weight(self, orig_weight: Optional[torch.Tensor]) -> torch.Tensor:
def get_weight(self, orig_weight: torch.Tensor) -> torch.Tensor:
return self.weight
def calc_size(self) -> int:
@@ -319,15 +335,10 @@ class FullLayer(LoRALayerBase):
model_size += self.weight.nelement() * self.weight.element_size()
return model_size
def to(
self,
device: Optional[torch.device] = None,
dtype: Optional[torch.dtype] = None,
non_blocking: bool = False,
) -> None:
def to(self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None) -> None:
super().to(device=device, dtype=dtype)
self.weight = self.weight.to(device=device, dtype=dtype, non_blocking=non_blocking)
self.weight = self.weight.to(device=device, dtype=dtype)
class IA3Layer(LoRALayerBase):
@@ -345,8 +356,9 @@ class IA3Layer(LoRALayerBase):
self.on_input = values["on_input"]
self.rank = None # unscaled
self.check_keys(values, {"weight", "on_input"})
def get_weight(self, orig_weight: Optional[torch.Tensor]) -> torch.Tensor:
def get_weight(self, orig_weight: torch.Tensor) -> torch.Tensor:
weight = self.weight
if not self.on_input:
weight = weight.reshape(-1, 1)
@@ -359,19 +371,46 @@ class IA3Layer(LoRALayerBase):
model_size += self.on_input.nelement() * self.on_input.element_size()
return model_size
def to(
self,
device: Optional[torch.device] = None,
dtype: Optional[torch.dtype] = None,
non_blocking: bool = False,
):
def to(self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None):
super().to(device=device, dtype=dtype)
self.weight = self.weight.to(device=device, dtype=dtype, non_blocking=non_blocking)
self.on_input = self.on_input.to(device=device, dtype=dtype, non_blocking=non_blocking)
self.weight = self.weight.to(device=device, dtype=dtype)
self.on_input = self.on_input.to(device=device, dtype=dtype)
AnyLoRALayer = Union[LoRALayer, LoHALayer, LoKRLayer, FullLayer, IA3Layer]
class NormLayer(LoRALayerBase):
# bias handled in LoRALayerBase(calc_size, to)
# weight: torch.Tensor
# bias: Optional[torch.Tensor]
def __init__(
self,
layer_key: str,
values: Dict[str, torch.Tensor],
):
super().__init__(layer_key, values)
self.weight = values["w_norm"]
self.bias = values.get("b_norm", None)
self.rank = None # unscaled
self.check_keys(values, {"w_norm", "b_norm"})
def get_weight(self, orig_weight: torch.Tensor) -> torch.Tensor:
return self.weight
def calc_size(self) -> int:
model_size = super().calc_size()
model_size += self.weight.nelement() * self.weight.element_size()
return model_size
def to(self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None) -> None:
super().to(device=device, dtype=dtype)
self.weight = self.weight.to(device=device, dtype=dtype)
AnyLoRALayer = Union[LoRALayer, LoHALayer, LoKRLayer, FullLayer, IA3Layer, NormLayer]
class LoRAModelRaw(RawModel): # (torch.nn.Module):
@@ -390,15 +429,10 @@ class LoRAModelRaw(RawModel): # (torch.nn.Module):
def name(self) -> str:
return self._name
def to(
self,
device: Optional[torch.device] = None,
dtype: Optional[torch.dtype] = None,
non_blocking: bool = False,
) -> None:
def to(self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None) -> None:
# TODO: try revert if exception?
for _key, layer in self.layers.items():
layer.to(device=device, dtype=dtype, non_blocking=non_blocking)
layer.to(device=device, dtype=dtype)
def calc_size(self) -> int:
model_size = 0
@@ -494,16 +528,19 @@ class LoRAModelRaw(RawModel): # (torch.nn.Module):
state_dict = cls._convert_sdxl_keys_to_diffusers_format(state_dict)
for layer_key, values in state_dict.items():
# Detect layers according to LyCORIS detection logic(`weight_list_det`)
# https://github.com/KohakuBlueleaf/LyCORIS/tree/8ad8000efb79e2b879054da8c9356e6143591bad/lycoris/modules
# lora and locon
if "lora_down.weight" in values:
if "lora_up.weight" in values:
layer: AnyLoRALayer = LoRALayer(layer_key, values)
# loha
elif "hada_w1_b" in values:
elif "hada_w1_a" in values:
layer = LoHALayer(layer_key, values)
# lokr
elif "lokr_w1_b" in values or "lokr_w1" in values:
elif "lokr_w1" in values or "lokr_w1_a" in values:
layer = LoKRLayer(layer_key, values)
# diff
@@ -511,9 +548,13 @@ class LoRAModelRaw(RawModel): # (torch.nn.Module):
layer = FullLayer(layer_key, values)
# ia3
elif "weight" in values and "on_input" in values:
elif "on_input" in values:
layer = IA3Layer(layer_key, values)
# norms
elif "w_norm" in values:
layer = NormLayer(layer_key, values)
else:
print(f">> Encountered unknown lora layer module in {model.name}: {layer_key} - {list(values.keys())}")
raise Exception("Unknown lora format!")
@@ -521,7 +562,7 @@ class LoRAModelRaw(RawModel): # (torch.nn.Module):
# lower memory consumption by removing already parsed layer values
state_dict[layer_key].clear()
layer.to(device=device, dtype=dtype, non_blocking=TorchDevice.get_non_blocking(device))
layer.to(device=device, dtype=dtype)
model.layers[layer_key] = layer
return model

View File

@@ -67,6 +67,7 @@ class ModelType(str, Enum):
IPAdapter = "ip_adapter"
CLIPVision = "clip_vision"
T2IAdapter = "t2i_adapter"
SpandrelImageToImage = "spandrel_image_to_image"
class SubModelType(str, Enum):
@@ -353,7 +354,7 @@ class CLIPVisionDiffusersConfig(DiffusersConfigBase):
"""Model config for CLIPVision."""
type: Literal[ModelType.CLIPVision] = ModelType.CLIPVision
format: Literal[ModelFormat.Diffusers]
format: Literal[ModelFormat.Diffusers] = ModelFormat.Diffusers
@staticmethod
def get_tag() -> Tag:
@@ -364,13 +365,24 @@ class T2IAdapterConfig(DiffusersConfigBase, ControlAdapterConfigBase):
"""Model config for T2I."""
type: Literal[ModelType.T2IAdapter] = ModelType.T2IAdapter
format: Literal[ModelFormat.Diffusers]
format: Literal[ModelFormat.Diffusers] = ModelFormat.Diffusers
@staticmethod
def get_tag() -> Tag:
return Tag(f"{ModelType.T2IAdapter.value}.{ModelFormat.Diffusers.value}")
class SpandrelImageToImageConfig(ModelConfigBase):
"""Model config for Spandrel Image to Image models."""
type: Literal[ModelType.SpandrelImageToImage] = ModelType.SpandrelImageToImage
format: Literal[ModelFormat.Checkpoint] = ModelFormat.Checkpoint
@staticmethod
def get_tag() -> Tag:
return Tag(f"{ModelType.SpandrelImageToImage.value}.{ModelFormat.Checkpoint.value}")
def get_model_discriminator_value(v: Any) -> str:
"""
Computes the discriminator value for a model config.
@@ -407,6 +419,7 @@ AnyModelConfig = Annotated[
Annotated[IPAdapterInvokeAIConfig, IPAdapterInvokeAIConfig.get_tag()],
Annotated[IPAdapterCheckpointConfig, IPAdapterCheckpointConfig.get_tag()],
Annotated[T2IAdapterConfig, T2IAdapterConfig.get_tag()],
Annotated[SpandrelImageToImageConfig, SpandrelImageToImageConfig.get_tag()],
Annotated[CLIPVisionDiffusersConfig, CLIPVisionDiffusersConfig.get_tag()],
],
Discriminator(get_model_discriminator_value),

View File

@@ -167,7 +167,8 @@ class ModelCache(ModelCacheBase[AnyModel]):
size = calc_model_size_by_data(self.logger, model)
self.make_room(size)
state_dict = model.state_dict() if isinstance(model, torch.nn.Module) else None
running_on_cpu = self.execution_device == torch.device("cpu")
state_dict = model.state_dict() if isinstance(model, torch.nn.Module) and not running_on_cpu else None
cache_record = CacheRecord(key=key, model=model, device=self.storage_device, state_dict=state_dict, size=size)
self._cached_models[key] = cache_record
self._cache_stack.append(key)
@@ -289,11 +290,9 @@ class ModelCache(ModelCacheBase[AnyModel]):
else:
new_dict: Dict[str, torch.Tensor] = {}
for k, v in cache_entry.state_dict.items():
new_dict[k] = v.to(
target_device, copy=True, non_blocking=TorchDevice.get_non_blocking(target_device)
)
new_dict[k] = v.to(target_device, copy=True)
cache_entry.model.load_state_dict(new_dict, assign=True)
cache_entry.model.to(target_device, non_blocking=TorchDevice.get_non_blocking(target_device))
cache_entry.model.to(target_device)
cache_entry.device = target_device
except Exception as e: # blow away cache entry
self._delete_cache_entry(cache_entry)

View File

@@ -0,0 +1,45 @@
from pathlib import Path
from typing import Optional
import torch
from invokeai.backend.model_manager.config import (
AnyModel,
AnyModelConfig,
BaseModelType,
ModelFormat,
ModelType,
SubModelType,
)
from invokeai.backend.model_manager.load.load_default import ModelLoader
from invokeai.backend.model_manager.load.model_loader_registry import ModelLoaderRegistry
from invokeai.backend.spandrel_image_to_image_model import SpandrelImageToImageModel
@ModelLoaderRegistry.register(
base=BaseModelType.Any, type=ModelType.SpandrelImageToImage, format=ModelFormat.Checkpoint
)
class SpandrelImageToImageModelLoader(ModelLoader):
"""Class for loading Spandrel Image-to-Image models (i.e. models wrapped by spandrel.ImageModelDescriptor)."""
def _load_model(
self,
config: AnyModelConfig,
submodel_type: Optional[SubModelType] = None,
) -> AnyModel:
if submodel_type is not None:
raise ValueError("Unexpected submodel requested for Spandrel model.")
model_path = Path(config.path)
model = SpandrelImageToImageModel.load_from_file(model_path)
torch_dtype = self._torch_dtype
if not model.supports_dtype(torch_dtype):
self._logger.warning(
f"The configured dtype ('{self._torch_dtype}') is not supported by the {model.get_model_type_name()} "
"model. Falling back to 'float32'."
)
torch_dtype = torch.float32
model.to(dtype=torch_dtype)
return model

View File

@@ -98,6 +98,9 @@ class StableDiffusionDiffusersModel(GenericDiffusersLoader):
ModelVariantType.Normal: StableDiffusionXLPipeline,
ModelVariantType.Inpaint: StableDiffusionXLInpaintPipeline,
},
BaseModelType.StableDiffusionXLRefiner: {
ModelVariantType.Normal: StableDiffusionXLPipeline,
},
}
assert isinstance(config, MainCheckpointConfig)
try:

View File

@@ -11,10 +11,14 @@ from diffusers.pipelines.pipeline_utils import DiffusionPipeline
from diffusers.schedulers.scheduling_utils import SchedulerMixin
from transformers import CLIPTokenizer
from invokeai.backend.image_util.depth_anything.depth_anything_pipeline import DepthAnythingPipeline
from invokeai.backend.image_util.grounding_dino.grounding_dino_pipeline import GroundingDinoPipeline
from invokeai.backend.image_util.segment_anything.segment_anything_pipeline import SegmentAnythingPipeline
from invokeai.backend.ip_adapter.ip_adapter import IPAdapter
from invokeai.backend.lora import LoRAModelRaw
from invokeai.backend.model_manager.config import AnyModel
from invokeai.backend.onnx.onnx_runtime import IAIOnnxRuntimeModel
from invokeai.backend.spandrel_image_to_image_model import SpandrelImageToImageModel
from invokeai.backend.textual_inversion import TextualInversionModelRaw
@@ -33,7 +37,18 @@ def calc_model_size_by_data(logger: logging.Logger, model: AnyModel) -> int:
elif isinstance(model, CLIPTokenizer):
# TODO(ryand): Accurately calculate the tokenizer's size. It's small enough that it shouldn't matter for now.
return 0
elif isinstance(model, (TextualInversionModelRaw, IPAdapter, LoRAModelRaw)):
elif isinstance(
model,
(
TextualInversionModelRaw,
IPAdapter,
LoRAModelRaw,
SpandrelImageToImageModel,
GroundingDinoPipeline,
SegmentAnythingPipeline,
DepthAnythingPipeline,
),
):
return model.calc_size()
else:
# TODO(ryand): Promote this from a log to an exception once we are confident that we are handling all of the

Some files were not shown because too many files have changed in this diff Show More