DavidM94 commited on
Commit
b5f91c1
·
1 Parent(s): dd1af92

Upload 10 files

Browse files
regional-prompter-main/LICENCE ADDED
@@ -0,0 +1,663 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ GNU AFFERO GENERAL PUBLIC LICENSE
2
+ Version 3, 19 November 2007
3
+
4
+ Copyright (c) 2023 hako-mikan
5
+
6
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
7
+ Everyone is permitted to copy and distribute verbatim copies
8
+ of this license document, but changing it is not allowed.
9
+
10
+ Preamble
11
+
12
+ The GNU Affero General Public License is a free, copyleft license for
13
+ software and other kinds of works, specifically designed to ensure
14
+ cooperation with the community in the case of network server software.
15
+
16
+ The licenses for most software and other practical works are designed
17
+ to take away your freedom to share and change the works. By contrast,
18
+ our General Public Licenses are intended to guarantee your freedom to
19
+ share and change all versions of a program--to make sure it remains free
20
+ software for all its users.
21
+
22
+ When we speak of free software, we are referring to freedom, not
23
+ price. Our General Public Licenses are designed to make sure that you
24
+ have the freedom to distribute copies of free software (and charge for
25
+ them if you wish), that you receive source code or can get it if you
26
+ want it, that you can change the software or use pieces of it in new
27
+ free programs, and that you know you can do these things.
28
+
29
+ Developers that use our General Public Licenses protect your rights
30
+ with two steps: (1) assert copyright on the software, and (2) offer
31
+ you this License which gives you legal permission to copy, distribute
32
+ and/or modify the software.
33
+
34
+ A secondary benefit of defending all users' freedom is that
35
+ improvements made in alternate versions of the program, if they
36
+ receive widespread use, become available for other developers to
37
+ incorporate. Many developers of free software are heartened and
38
+ encouraged by the resulting cooperation. However, in the case of
39
+ software used on network servers, this result may fail to come about.
40
+ The GNU General Public License permits making a modified version and
41
+ letting the public access it on a server without ever releasing its
42
+ source code to the public.
43
+
44
+ The GNU Affero General Public License is designed specifically to
45
+ ensure that, in such cases, the modified source code becomes available
46
+ to the community. It requires the operator of a network server to
47
+ provide the source code of the modified version running there to the
48
+ users of that server. Therefore, public use of a modified version, on
49
+ a publicly accessible server, gives the public access to the source
50
+ code of the modified version.
51
+
52
+ An older license, called the Affero General Public License and
53
+ published by Affero, was designed to accomplish similar goals. This is
54
+ a different license, not a version of the Affero GPL, but Affero has
55
+ released a new version of the Affero GPL which permits relicensing under
56
+ this license.
57
+
58
+ The precise terms and conditions for copying, distribution and
59
+ modification follow.
60
+
61
+ TERMS AND CONDITIONS
62
+
63
+ 0. Definitions.
64
+
65
+ "This License" refers to version 3 of the GNU Affero General Public License.
66
+
67
+ "Copyright" also means copyright-like laws that apply to other kinds of
68
+ works, such as semiconductor masks.
69
+
70
+ "The Program" refers to any copyrightable work licensed under this
71
+ License. Each licensee is addressed as "you". "Licensees" and
72
+ "recipients" may be individuals or organizations.
73
+
74
+ To "modify" a work means to copy from or adapt all or part of the work
75
+ in a fashion requiring copyright permission, other than the making of an
76
+ exact copy. The resulting work is called a "modified version" of the
77
+ earlier work or a work "based on" the earlier work.
78
+
79
+ A "covered work" means either the unmodified Program or a work based
80
+ on the Program.
81
+
82
+ To "propagate" a work means to do anything with it that, without
83
+ permission, would make you directly or secondarily liable for
84
+ infringement under applicable copyright law, except executing it on a
85
+ computer or modifying a private copy. Propagation includes copying,
86
+ distribution (with or without modification), making available to the
87
+ public, and in some countries other activities as well.
88
+
89
+ To "convey" a work means any kind of propagation that enables other
90
+ parties to make or receive copies. Mere interaction with a user through
91
+ a computer network, with no transfer of a copy, is not conveying.
92
+
93
+ An interactive user interface displays "Appropriate Legal Notices"
94
+ to the extent that it includes a convenient and prominently visible
95
+ feature that (1) displays an appropriate copyright notice, and (2)
96
+ tells the user that there is no warranty for the work (except to the
97
+ extent that warranties are provided), that licensees may convey the
98
+ work under this License, and how to view a copy of this License. If
99
+ the interface presents a list of user commands or options, such as a
100
+ menu, a prominent item in the list meets this criterion.
101
+
102
+ 1. Source Code.
103
+
104
+ The "source code" for a work means the preferred form of the work
105
+ for making modifications to it. "Object code" means any non-source
106
+ form of a work.
107
+
108
+ A "Standard Interface" means an interface that either is an official
109
+ standard defined by a recognized standards body, or, in the case of
110
+ interfaces specified for a particular programming language, one that
111
+ is widely used among developers working in that language.
112
+
113
+ The "System Libraries" of an executable work include anything, other
114
+ than the work as a whole, that (a) is included in the normal form of
115
+ packaging a Major Component, but which is not part of that Major
116
+ Component, and (b) serves only to enable use of the work with that
117
+ Major Component, or to implement a Standard Interface for which an
118
+ implementation is available to the public in source code form. A
119
+ "Major Component", in this context, means a major essential component
120
+ (kernel, window system, and so on) of the specific operating system
121
+ (if any) on which the executable work runs, or a compiler used to
122
+ produce the work, or an object code interpreter used to run it.
123
+
124
+ The "Corresponding Source" for a work in object code form means all
125
+ the source code needed to generate, install, and (for an executable
126
+ work) run the object code and to modify the work, including scripts to
127
+ control those activities. However, it does not include the work's
128
+ System Libraries, or general-purpose tools or generally available free
129
+ programs which are used unmodified in performing those activities but
130
+ which are not part of the work. For example, Corresponding Source
131
+ includes interface definition files associated with source files for
132
+ the work, and the source code for shared libraries and dynamically
133
+ linked subprograms that the work is specifically designed to require,
134
+ such as by intimate data communication or control flow between those
135
+ subprograms and other parts of the work.
136
+
137
+ The Corresponding Source need not include anything that users
138
+ can regenerate automatically from other parts of the Corresponding
139
+ Source.
140
+
141
+ The Corresponding Source for a work in source code form is that
142
+ same work.
143
+
144
+ 2. Basic Permissions.
145
+
146
+ All rights granted under this License are granted for the term of
147
+ copyright on the Program, and are irrevocable provided the stated
148
+ conditions are met. This License explicitly affirms your unlimited
149
+ permission to run the unmodified Program. The output from running a
150
+ covered work is covered by this License only if the output, given its
151
+ content, constitutes a covered work. This License acknowledges your
152
+ rights of fair use or other equivalent, as provided by copyright law.
153
+
154
+ You may make, run and propagate covered works that you do not
155
+ convey, without conditions so long as your license otherwise remains
156
+ in force. You may convey covered works to others for the sole purpose
157
+ of having them make modifications exclusively for you, or provide you
158
+ with facilities for running those works, provided that you comply with
159
+ the terms of this License in conveying all material for which you do
160
+ not control copyright. Those thus making or running the covered works
161
+ for you must do so exclusively on your behalf, under your direction
162
+ and control, on terms that prohibit them from making any copies of
163
+ your copyrighted material outside their relationship with you.
164
+
165
+ Conveying under any other circumstances is permitted solely under
166
+ the conditions stated below. Sublicensing is not allowed; section 10
167
+ makes it unnecessary.
168
+
169
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
170
+
171
+ No covered work shall be deemed part of an effective technological
172
+ measure under any applicable law fulfilling obligations under article
173
+ 11 of the WIPO copyright treaty adopted on 20 December 1996, or
174
+ similar laws prohibiting or restricting circumvention of such
175
+ measures.
176
+
177
+ When you convey a covered work, you waive any legal power to forbid
178
+ circumvention of technological measures to the extent such circumvention
179
+ is effected by exercising rights under this License with respect to
180
+ the covered work, and you disclaim any intention to limit operation or
181
+ modification of the work as a means of enforcing, against the work's
182
+ users, your or third parties' legal rights to forbid circumvention of
183
+ technological measures.
184
+
185
+ 4. Conveying Verbatim Copies.
186
+
187
+ You may convey verbatim copies of the Program's source code as you
188
+ receive it, in any medium, provided that you conspicuously and
189
+ appropriately publish on each copy an appropriate copyright notice;
190
+ keep intact all notices stating that this License and any
191
+ non-permissive terms added in accord with section 7 apply to the code;
192
+ keep intact all notices of the absence of any warranty; and give all
193
+ recipients a copy of this License along with the Program.
194
+
195
+ You may charge any price or no price for each copy that you convey,
196
+ and you may offer support or warranty protection for a fee.
197
+
198
+ 5. Conveying Modified Source Versions.
199
+
200
+ You may convey a work based on the Program, or the modifications to
201
+ produce it from the Program, in the form of source code under the
202
+ terms of section 4, provided that you also meet all of these conditions:
203
+
204
+ a) The work must carry prominent notices stating that you modified
205
+ it, and giving a relevant date.
206
+
207
+ b) The work must carry prominent notices stating that it is
208
+ released under this License and any conditions added under section
209
+ 7. This requirement modifies the requirement in section 4 to
210
+ "keep intact all notices".
211
+
212
+ c) You must license the entire work, as a whole, under this
213
+ License to anyone who comes into possession of a copy. This
214
+ License will therefore apply, along with any applicable section 7
215
+ additional terms, to the whole of the work, and all its parts,
216
+ regardless of how they are packaged. This License gives no
217
+ permission to license the work in any other way, but it does not
218
+ invalidate such permission if you have separately received it.
219
+
220
+ d) If the work has interactive user interfaces, each must display
221
+ Appropriate Legal Notices; however, if the Program has interactive
222
+ interfaces that do not display Appropriate Legal Notices, your
223
+ work need not make them do so.
224
+
225
+ A compilation of a covered work with other separate and independent
226
+ works, which are not by their nature extensions of the covered work,
227
+ and which are not combined with it such as to form a larger program,
228
+ in or on a volume of a storage or distribution medium, is called an
229
+ "aggregate" if the compilation and its resulting copyright are not
230
+ used to limit the access or legal rights of the compilation's users
231
+ beyond what the individual works permit. Inclusion of a covered work
232
+ in an aggregate does not cause this License to apply to the other
233
+ parts of the aggregate.
234
+
235
+ 6. Conveying Non-Source Forms.
236
+
237
+ You may convey a covered work in object code form under the terms
238
+ of sections 4 and 5, provided that you also convey the
239
+ machine-readable Corresponding Source under the terms of this License,
240
+ in one of these ways:
241
+
242
+ a) Convey the object code in, or embodied in, a physical product
243
+ (including a physical distribution medium), accompanied by the
244
+ Corresponding Source fixed on a durable physical medium
245
+ customarily used for software interchange.
246
+
247
+ b) Convey the object code in, or embodied in, a physical product
248
+ (including a physical distribution medium), accompanied by a
249
+ written offer, valid for at least three years and valid for as
250
+ long as you offer spare parts or customer support for that product
251
+ model, to give anyone who possesses the object code either (1) a
252
+ copy of the Corresponding Source for all the software in the
253
+ product that is covered by this License, on a durable physical
254
+ medium customarily used for software interchange, for a price no
255
+ more than your reasonable cost of physically performing this
256
+ conveying of source, or (2) access to copy the
257
+ Corresponding Source from a network server at no charge.
258
+
259
+ c) Convey individual copies of the object code with a copy of the
260
+ written offer to provide the Corresponding Source. This
261
+ alternative is allowed only occasionally and noncommercially, and
262
+ only if you received the object code with such an offer, in accord
263
+ with subsection 6b.
264
+
265
+ d) Convey the object code by offering access from a designated
266
+ place (gratis or for a charge), and offer equivalent access to the
267
+ Corresponding Source in the same way through the same place at no
268
+ further charge. You need not require recipients to copy the
269
+ Corresponding Source along with the object code. If the place to
270
+ copy the object code is a network server, the Corresponding Source
271
+ may be on a different server (operated by you or a third party)
272
+ that supports equivalent copying facilities, provided you maintain
273
+ clear directions next to the object code saying where to find the
274
+ Corresponding Source. Regardless of what server hosts the
275
+ Corresponding Source, you remain obligated to ensure that it is
276
+ available for as long as needed to satisfy these requirements.
277
+
278
+ e) Convey the object code using peer-to-peer transmission, provided
279
+ you inform other peers where the object code and Corresponding
280
+ Source of the work are being offered to the general public at no
281
+ charge under subsection 6d.
282
+
283
+ A separable portion of the object code, whose source code is excluded
284
+ from the Corresponding Source as a System Library, need not be
285
+ included in conveying the object code work.
286
+
287
+ A "User Product" is either (1) a "consumer product", which means any
288
+ tangible personal property which is normally used for personal, family,
289
+ or household purposes, or (2) anything designed or sold for incorporation
290
+ into a dwelling. In determining whether a product is a consumer product,
291
+ doubtful cases shall be resolved in favor of coverage. For a particular
292
+ product received by a particular user, "normally used" refers to a
293
+ typical or common use of that class of product, regardless of the status
294
+ of the particular user or of the way in which the particular user
295
+ actually uses, or expects or is expected to use, the product. A product
296
+ is a consumer product regardless of whether the product has substantial
297
+ commercial, industrial or non-consumer uses, unless such uses represent
298
+ the only significant mode of use of the product.
299
+
300
+ "Installation Information" for a User Product means any methods,
301
+ procedures, authorization keys, or other information required to install
302
+ and execute modified versions of a covered work in that User Product from
303
+ a modified version of its Corresponding Source. The information must
304
+ suffice to ensure that the continued functioning of the modified object
305
+ code is in no case prevented or interfered with solely because
306
+ modification has been made.
307
+
308
+ If you convey an object code work under this section in, or with, or
309
+ specifically for use in, a User Product, and the conveying occurs as
310
+ part of a transaction in which the right of possession and use of the
311
+ User Product is transferred to the recipient in perpetuity or for a
312
+ fixed term (regardless of how the transaction is characterized), the
313
+ Corresponding Source conveyed under this section must be accompanied
314
+ by the Installation Information. But this requirement does not apply
315
+ if neither you nor any third party retains the ability to install
316
+ modified object code on the User Product (for example, the work has
317
+ been installed in ROM).
318
+
319
+ The requirement to provide Installation Information does not include a
320
+ requirement to continue to provide support service, warranty, or updates
321
+ for a work that has been modified or installed by the recipient, or for
322
+ the User Product in which it has been modified or installed. Access to a
323
+ network may be denied when the modification itself materially and
324
+ adversely affects the operation of the network or violates the rules and
325
+ protocols for communication across the network.
326
+
327
+ Corresponding Source conveyed, and Installation Information provided,
328
+ in accord with this section must be in a format that is publicly
329
+ documented (and with an implementation available to the public in
330
+ source code form), and must require no special password or key for
331
+ unpacking, reading or copying.
332
+
333
+ 7. Additional Terms.
334
+
335
+ "Additional permissions" are terms that supplement the terms of this
336
+ License by making exceptions from one or more of its conditions.
337
+ Additional permissions that are applicable to the entire Program shall
338
+ be treated as though they were included in this License, to the extent
339
+ that they are valid under applicable law. If additional permissions
340
+ apply only to part of the Program, that part may be used separately
341
+ under those permissions, but the entire Program remains governed by
342
+ this License without regard to the additional permissions.
343
+
344
+ When you convey a copy of a covered work, you may at your option
345
+ remove any additional permissions from that copy, or from any part of
346
+ it. (Additional permissions may be written to require their own
347
+ removal in certain cases when you modify the work.) You may place
348
+ additional permissions on material, added by you to a covered work,
349
+ for which you have or can give appropriate copyright permission.
350
+
351
+ Notwithstanding any other provision of this License, for material you
352
+ add to a covered work, you may (if authorized by the copyright holders of
353
+ that material) supplement the terms of this License with terms:
354
+
355
+ a) Disclaiming warranty or limiting liability differently from the
356
+ terms of sections 15 and 16 of this License; or
357
+
358
+ b) Requiring preservation of specified reasonable legal notices or
359
+ author attributions in that material or in the Appropriate Legal
360
+ Notices displayed by works containing it; or
361
+
362
+ c) Prohibiting misrepresentation of the origin of that material, or
363
+ requiring that modified versions of such material be marked in
364
+ reasonable ways as different from the original version; or
365
+
366
+ d) Limiting the use for publicity purposes of names of licensors or
367
+ authors of the material; or
368
+
369
+ e) Declining to grant rights under trademark law for use of some
370
+ trade names, trademarks, or service marks; or
371
+
372
+ f) Requiring indemnification of licensors and authors of that
373
+ material by anyone who conveys the material (or modified versions of
374
+ it) with contractual assumptions of liability to the recipient, for
375
+ any liability that these contractual assumptions directly impose on
376
+ those licensors and authors.
377
+
378
+ All other non-permissive additional terms are considered "further
379
+ restrictions" within the meaning of section 10. If the Program as you
380
+ received it, or any part of it, contains a notice stating that it is
381
+ governed by this License along with a term that is a further
382
+ restriction, you may remove that term. If a license document contains
383
+ a further restriction but permits relicensing or conveying under this
384
+ License, you may add to a covered work material governed by the terms
385
+ of that license document, provided that the further restriction does
386
+ not survive such relicensing or conveying.
387
+
388
+ If you add terms to a covered work in accord with this section, you
389
+ must place, in the relevant source files, a statement of the
390
+ additional terms that apply to those files, or a notice indicating
391
+ where to find the applicable terms.
392
+
393
+ Additional terms, permissive or non-permissive, may be stated in the
394
+ form of a separately written license, or stated as exceptions;
395
+ the above requirements apply either way.
396
+
397
+ 8. Termination.
398
+
399
+ You may not propagate or modify a covered work except as expressly
400
+ provided under this License. Any attempt otherwise to propagate or
401
+ modify it is void, and will automatically terminate your rights under
402
+ this License (including any patent licenses granted under the third
403
+ paragraph of section 11).
404
+
405
+ However, if you cease all violation of this License, then your
406
+ license from a particular copyright holder is reinstated (a)
407
+ provisionally, unless and until the copyright holder explicitly and
408
+ finally terminates your license, and (b) permanently, if the copyright
409
+ holder fails to notify you of the violation by some reasonable means
410
+ prior to 60 days after the cessation.
411
+
412
+ Moreover, your license from a particular copyright holder is
413
+ reinstated permanently if the copyright holder notifies you of the
414
+ violation by some reasonable means, this is the first time you have
415
+ received notice of violation of this License (for any work) from that
416
+ copyright holder, and you cure the violation prior to 30 days after
417
+ your receipt of the notice.
418
+
419
+ Termination of your rights under this section does not terminate the
420
+ licenses of parties who have received copies or rights from you under
421
+ this License. If your rights have been terminated and not permanently
422
+ reinstated, you do not qualify to receive new licenses for the same
423
+ material under section 10.
424
+
425
+ 9. Acceptance Not Required for Having Copies.
426
+
427
+ You are not required to accept this License in order to receive or
428
+ run a copy of the Program. Ancillary propagation of a covered work
429
+ occurring solely as a consequence of using peer-to-peer transmission
430
+ to receive a copy likewise does not require acceptance. However,
431
+ nothing other than this License grants you permission to propagate or
432
+ modify any covered work. These actions infringe copyright if you do
433
+ not accept this License. Therefore, by modifying or propagating a
434
+ covered work, you indicate your acceptance of this License to do so.
435
+
436
+ 10. Automatic Licensing of Downstream Recipients.
437
+
438
+ Each time you convey a covered work, the recipient automatically
439
+ receives a license from the original licensors, to run, modify and
440
+ propagate that work, subject to this License. You are not responsible
441
+ for enforcing compliance by third parties with this License.
442
+
443
+ An "entity transaction" is a transaction transferring control of an
444
+ organization, or substantially all assets of one, or subdividing an
445
+ organization, or merging organizations. If propagation of a covered
446
+ work results from an entity transaction, each party to that
447
+ transaction who receives a copy of the work also receives whatever
448
+ licenses to the work the party's predecessor in interest had or could
449
+ give under the previous paragraph, plus a right to possession of the
450
+ Corresponding Source of the work from the predecessor in interest, if
451
+ the predecessor has it or can get it with reasonable efforts.
452
+
453
+ You may not impose any further restrictions on the exercise of the
454
+ rights granted or affirmed under this License. For example, you may
455
+ not impose a license fee, royalty, or other charge for exercise of
456
+ rights granted under this License, and you may not initiate litigation
457
+ (including a cross-claim or counterclaim in a lawsuit) alleging that
458
+ any patent claim is infringed by making, using, selling, offering for
459
+ sale, or importing the Program or any portion of it.
460
+
461
+ 11. Patents.
462
+
463
+ A "contributor" is a copyright holder who authorizes use under this
464
+ License of the Program or a work on which the Program is based. The
465
+ work thus licensed is called the contributor's "contributor version".
466
+
467
+ A contributor's "essential patent claims" are all patent claims
468
+ owned or controlled by the contributor, whether already acquired or
469
+ hereafter acquired, that would be infringed by some manner, permitted
470
+ by this License, of making, using, or selling its contributor version,
471
+ but do not include claims that would be infringed only as a
472
+ consequence of further modification of the contributor version. For
473
+ purposes of this definition, "control" includes the right to grant
474
+ patent sublicenses in a manner consistent with the requirements of
475
+ this License.
476
+
477
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
478
+ patent license under the contributor's essential patent claims, to
479
+ make, use, sell, offer for sale, import and otherwise run, modify and
480
+ propagate the contents of its contributor version.
481
+
482
+ In the following three paragraphs, a "patent license" is any express
483
+ agreement or commitment, however denominated, not to enforce a patent
484
+ (such as an express permission to practice a patent or covenant not to
485
+ sue for patent infringement). To "grant" such a patent license to a
486
+ party means to make such an agreement or commitment not to enforce a
487
+ patent against the party.
488
+
489
+ If you convey a covered work, knowingly relying on a patent license,
490
+ and the Corresponding Source of the work is not available for anyone
491
+ to copy, free of charge and under the terms of this License, through a
492
+ publicly available network server or other readily accessible means,
493
+ then you must either (1) cause the Corresponding Source to be so
494
+ available, or (2) arrange to deprive yourself of the benefit of the
495
+ patent license for this particular work, or (3) arrange, in a manner
496
+ consistent with the requirements of this License, to extend the patent
497
+ license to downstream recipients. "Knowingly relying" means you have
498
+ actual knowledge that, but for the patent license, your conveying the
499
+ covered work in a country, or your recipient's use of the covered work
500
+ in a country, would infringe one or more identifiable patents in that
501
+ country that you have reason to believe are valid.
502
+
503
+ If, pursuant to or in connection with a single transaction or
504
+ arrangement, you convey, or propagate by procuring conveyance of, a
505
+ covered work, and grant a patent license to some of the parties
506
+ receiving the covered work authorizing them to use, propagate, modify
507
+ or convey a specific copy of the covered work, then the patent license
508
+ you grant is automatically extended to all recipients of the covered
509
+ work and works based on it.
510
+
511
+ A patent license is "discriminatory" if it does not include within
512
+ the scope of its coverage, prohibits the exercise of, or is
513
+ conditioned on the non-exercise of one or more of the rights that are
514
+ specifically granted under this License. You may not convey a covered
515
+ work if you are a party to an arrangement with a third party that is
516
+ in the business of distributing software, under which you make payment
517
+ to the third party based on the extent of your activity of conveying
518
+ the work, and under which the third party grants, to any of the
519
+ parties who would receive the covered work from you, a discriminatory
520
+ patent license (a) in connection with copies of the covered work
521
+ conveyed by you (or copies made from those copies), or (b) primarily
522
+ for and in connection with specific products or compilations that
523
+ contain the covered work, unless you entered into that arrangement,
524
+ or that patent license was granted, prior to 28 March 2007.
525
+
526
+ Nothing in this License shall be construed as excluding or limiting
527
+ any implied license or other defenses to infringement that may
528
+ otherwise be available to you under applicable patent law.
529
+
530
+ 12. No Surrender of Others' Freedom.
531
+
532
+ If conditions are imposed on you (whether by court order, agreement or
533
+ otherwise) that contradict the conditions of this License, they do not
534
+ excuse you from the conditions of this License. If you cannot convey a
535
+ covered work so as to satisfy simultaneously your obligations under this
536
+ License and any other pertinent obligations, then as a consequence you may
537
+ not convey it at all. For example, if you agree to terms that obligate you
538
+ to collect a royalty for further conveying from those to whom you convey
539
+ the Program, the only way you could satisfy both those terms and this
540
+ License would be to refrain entirely from conveying the Program.
541
+
542
+ 13. Remote Network Interaction; Use with the GNU General Public License.
543
+
544
+ Notwithstanding any other provision of this License, if you modify the
545
+ Program, your modified version must prominently offer all users
546
+ interacting with it remotely through a computer network (if your version
547
+ supports such interaction) an opportunity to receive the Corresponding
548
+ Source of your version by providing access to the Corresponding Source
549
+ from a network server at no charge, through some standard or customary
550
+ means of facilitating copying of software. This Corresponding Source
551
+ shall include the Corresponding Source for any work covered by version 3
552
+ of the GNU General Public License that is incorporated pursuant to the
553
+ following paragraph.
554
+
555
+ Notwithstanding any other provision of this License, you have
556
+ permission to link or combine any covered work with a work licensed
557
+ under version 3 of the GNU General Public License into a single
558
+ combined work, and to convey the resulting work. The terms of this
559
+ License will continue to apply to the part which is the covered work,
560
+ but the work with which it is combined will remain governed by version
561
+ 3 of the GNU General Public License.
562
+
563
+ 14. Revised Versions of this License.
564
+
565
+ The Free Software Foundation may publish revised and/or new versions of
566
+ the GNU Affero General Public License from time to time. Such new versions
567
+ will be similar in spirit to the present version, but may differ in detail to
568
+ address new problems or concerns.
569
+
570
+ Each version is given a distinguishing version number. If the
571
+ Program specifies that a certain numbered version of the GNU Affero General
572
+ Public License "or any later version" applies to it, you have the
573
+ option of following the terms and conditions either of that numbered
574
+ version or of any later version published by the Free Software
575
+ Foundation. If the Program does not specify a version number of the
576
+ GNU Affero General Public License, you may choose any version ever published
577
+ by the Free Software Foundation.
578
+
579
+ If the Program specifies that a proxy can decide which future
580
+ versions of the GNU Affero General Public License can be used, that proxy's
581
+ public statement of acceptance of a version permanently authorizes you
582
+ to choose that version for the Program.
583
+
584
+ Later license versions may give you additional or different
585
+ permissions. However, no additional obligations are imposed on any
586
+ author or copyright holder as a result of your choosing to follow a
587
+ later version.
588
+
589
+ 15. Disclaimer of Warranty.
590
+
591
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592
+ APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593
+ HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594
+ OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596
+ PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597
+ IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598
+ ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599
+
600
+ 16. Limitation of Liability.
601
+
602
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604
+ THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605
+ GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606
+ USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607
+ DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608
+ PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609
+ EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610
+ SUCH DAMAGES.
611
+
612
+ 17. Interpretation of Sections 15 and 16.
613
+
614
+ If the disclaimer of warranty and limitation of liability provided
615
+ above cannot be given local legal effect according to their terms,
616
+ reviewing courts shall apply local law that most closely approximates
617
+ an absolute waiver of all civil liability in connection with the
618
+ Program, unless a warranty or assumption of liability accompanies a
619
+ copy of the Program in return for a fee.
620
+
621
+ END OF TERMS AND CONDITIONS
622
+
623
+ How to Apply These Terms to Your New Programs
624
+
625
+ If you develop a new program, and you want it to be of the greatest
626
+ possible use to the public, the best way to achieve this is to make it
627
+ free software which everyone can redistribute and change under these terms.
628
+
629
+ To do so, attach the following notices to the program. It is safest
630
+ to attach them to the start of each source file to most effectively
631
+ state the exclusion of warranty; and each file should have at least
632
+ the "copyright" line and a pointer to where the full notice is found.
633
+
634
+ <one line to give the program's name and a brief idea of what it does.>
635
+ Copyright (C) <year> <name of author>
636
+
637
+ This program is free software: you can redistribute it and/or modify
638
+ it under the terms of the GNU Affero General Public License as published
639
+ by the Free Software Foundation, either version 3 of the License, or
640
+ (at your option) any later version.
641
+
642
+ This program is distributed in the hope that it will be useful,
643
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
644
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645
+ GNU Affero General Public License for more details.
646
+
647
+ You should have received a copy of the GNU Affero General Public License
648
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
649
+
650
+ Also add information on how to contact you by electronic and paper mail.
651
+
652
+ If your software can interact with users remotely through a computer
653
+ network, you should also make sure that it provides a way for users to
654
+ get its source. For example, if your program is a web application, its
655
+ interface could display a "Source" link that leads users to an archive
656
+ of the code. There are many ways you could offer source, and different
657
+ solutions will be better for different programs; see section 13 for the
658
+ specific requirements.
659
+
660
+ You should also get your employer (if you work as a programmer) or school,
661
+ if any, to sign a "copyright disclaimer" for the program, if necessary.
662
+ For more information on this, and how to apply and follow the GNU AGPL, see
663
+ <https://www.gnu.org/licenses/>.
regional-prompter-main/README.JP.md ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Regional Prompter
2
+ ![top](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/top.jpg)
3
+ - custom script for [AUTOMATIC1111's stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui)
4
+ - Different prompts can be specified for different regions
5
+
6
+ - [AUTOMATIC1111's stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) 用のスクリプトです
7
+ - 垂直/平行方向に分割された領域ごとに異なるプロンプトを指定できます
8
+
9
+ ## Language control / 言語制御
10
+ ENGLISH: [![en](https://img.shields.io/badge/lang-en-red.svg)](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/main/README.md)
11
+
12
+ ## 更新情報
13
+ - [APIを通しての利用について](#apiを通した利用方法)
14
+ - プロンプトによる領域指定の[チュートリアル](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/main/prompt_ja.md)
15
+ - 新機能 : [インペイントによる領域指定](#inpaint) (thanks [Symbiomatrix](https://github.com/Symbiomatrix))
16
+ - 新機能 : [プロンプトによる領域指定](#divprompt)
17
+
18
+
19
+ [Symbiomatrix](https://github.com/Symbiomatrix)氏の協力によりより[柔軟な領域指定](#2次元領域指定実験的機能)が可能になりました。
20
+
21
+
22
+ # 概要
23
+ Latent couple extentionではプロンプトごとにU-Netの計算を行っていますが、このエクステンションではU-Netの内部でプロンプトごとの計算を行います。詳しくは[こちら](https://note.com/gcem156/n/nb3d516e376d7)をご参照ください。アイデアを発案されたfurusu様に感謝いたします。
24
+
25
+ ## 使い方
26
+ 次の画像の作り方を解説しつつ、使い方を説明します。
27
+ ![sample](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/sample.jpg)
28
+ 以下がプロンプトです。
29
+ ```
30
+ green hair twintail BREAK
31
+ red blouse BREAK
32
+ blue skirt
33
+ ```
34
+ 設定
35
+ ```
36
+ Active : On
37
+ Use base prompt : Off
38
+ Divide mode : Vertical
39
+ Divide Ratio : 1,1,1
40
+ Base Ratio :
41
+ ```
42
+ この設定では縦方向に三分割し、上から順にgreen hair twintail ,red blouse ,blue skirtというプロンプトを適用しています。
43
+ ### Active
44
+ ここにチェックが入っている場合有効化します。
45
+
46
+ ### Prompt
47
+ 領域別のプロンプト同士はBREAKで区切ります。水平の場合は左から、垂直の場合は上から順にプロンプトを入力します。
48
+ ネガティブプロンプトもBREAKで区切ることで領域ごとに設定できますが、BREAKを入力しない場合すべての領域に同一のネガティブプロンプトが設定されます。
49
+
50
+ ### Use base prompt
51
+ ベースプロンプトとはすべての領域に共通のプロンプトを使用したい場合チェックを入れます。領域で一貫した場面にしたい場合などは使ってください。
52
+ ベースプロンプトを使用する場合、BREAK区切られた最初のプロンプトがベースとして扱われます。
53
+ ADDBASEが入力された場合、自動的にオンになります。
54
+
55
+ ### Base ratio
56
+ ベースプロンプトの比率を設定します。0.2と入力された場合、ベースの割合が0.2になります。領域ごとにも指定可能で、0.2,0.3,0.5などと入力できます。単一の値を入力した場合はすべての領域に同じ値が適応されます。
57
+
58
+ ### Divide ratio
59
+ 領域の広さを指定します。1,1,1と入力した場合、三分割されます(33,3%,33,3%,33,3%)。3,1,1と入力した場合60%,20%,20%となります。小数点でも入力可能です。0.1,0.1,0.1は1,1,1と同じ結果になります。
60
+
61
+ ### calcutation mode
62
+ #### Attention
63
+ 通常はこちらを使用して下さい
64
+ #### Latent
65
+ LoRAを分離したい場合こちらを使用して下さい。生成時間は長くなりますが、ある程度LoRAを分離できます。
66
+
67
+ [ねんどろいど](https://civitai.com/models/7269/nendoroid-figures-lora),
68
+ [figma](https://civitai.com/models/7984/figma-anime-figures)LoRAを左右に分離して作成した例。
69
+ <img src="https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/sample2.jpg" width="400">
70
+
71
+ ### Split mode
72
+ 分割方向を指定します。水平、垂直方向が指定できます。
73
+
74
+ ### Use common prompt
75
+ このオプションを有効化すると最初のプロンプトをすべてのプロンプトに加算します。
76
+ `ADDCOMM`が入力された場合自動的にオンになります。
77
+ ```
78
+ best quality, 20yo lady in garden BREAK
79
+ green hair twintail BREAK
80
+ red blouse BREAK
81
+ blue skirt
82
+ ```
83
+ このようなプロンプトがあるときに、この機能を有効化すると以下のように扱われます。
84
+ ```
85
+ best quality, 20yo lady in garden, green hair twintail BREAK
86
+ best quality, 20yo lady in garden, red blouse BREAK
87
+ best quality, 20yo lady in garden, blue skirt
88
+ ```
89
+ よって、3つの領域に分ける場合4つのプロンプトをセットする必要があります。Use base promptが有効になっている場合は5つ必要になります。設定順はcommon,base, prompt1,prompt2,...となります。
90
+
91
+ ### 2次元領域指定(実験的機能)
92
+ 領域を2次元的に指定できます。特別なセパレイター(`ADDCOL/ADDROW`)を用いることで領域を縦横に分割することができます。左上を始点として、`ADDCOL`で区切ると横方向、`ADDROW`で区切ると縦方向に分割されます。分割の比率はセミコロンで区切られた比率で指定します。以下に例を示します。`BREAK`のみで記述し、比率のみで記述することも可能ですが、明示的にCOL/ROWを指定した方がわかりやすいです。最初のセパレーターとして`ADDBASE`を使用すると、ベースプロンプトになります。比率を指定しない場合や比率がセパレーターの数と一致しないときは自動的にすべて等倍として処理されます。`ADDCOMM`を最初のセパレーターとして入力した場合共通プロンプトになります。Divide modeで選択された方向は有効であり、上から/左から順に`ADDCOL/ADDROW`が処理されます。
93
+
94
+ ```
95
+ (blue sky:1.2) ADDCOL
96
+ green hair twintail ADDCOL
97
+ (aquarium:1.3) ADDROW
98
+ (messy desk:1.2) ADDCOL
99
+ orange dress and sofa
100
+ ```
101
+
102
+ ```
103
+ Active : On
104
+ Use base prompt : Off
105
+ Divide mode : Columns
106
+ Divide Ratio : 1,2,1,1;2,4,6
107
+ Base Ratio :
108
+ ```
109
+
110
+ ![2d](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/2d.jpg)
111
+
112
+ ## <a id="inpaint">Mask regions aka inpaint+ (experimental function)</a>
113
+ 手描きマスク、またはアップロードされたマスクを使って領域を指定することができるようになりました。
114
+ - まず、`Columns` / `Rows` の横にある `mask divide mode` に切り替えていることを確認してください。そうしないと、マスクは無視され、領域は通常通り分割されます。
115
+ - キャンバスの幅と高さを希望する画像に合わせて設定し、`create mask area`を押してください。異なる比率やサイズを指定すると、マスクが正確に適用されないことがあります。(インペイントの「リサイズだけ」のように)。
116
+ - キャンバス領域に必要な領域の輪郭を描くか、完全に塗りつぶした後、`draw region`を押してください。これにより、マスクに対応する塗りつぶし多角形が追加され、`region` の番号に従って色が付けられます。
117
+ - draw region` を押すと、region が +1 ずつ増えていき、次のregion を素早く描画することができます。また、後でマスクを作るためにどのリージョンが使われたかのリストも保持されます。現在、最大で ~360~ 256 のリージョンが使用できます。
118
+ - 既存のリージョンに追加するには、以前に使用された色を選択し、通常通り描画することが可能です。現在のところ、新しいマスク領域以外の領域をクリアする方法はありません(そのうちクリア機能は追加されるかもしれません)。
119
+ - `make mask`ボタンは、以前に描いたリージョンについて、`region`の番号で指定されたマスクを表示します。マスクはリージョン固有の色によって検出されます。
120
+ - リージョンマスクの準備ができたら、いつも通りプロンプトを書きます: 分割比率は無視されます。`base ratio`は各リージョンに適用されます。すべてのオプションがサポートされ、すべての BREAK / ADDX キーワード (ROW/COL は BREAK に変換されるだけです)。アテンションモードとレイテンモードがサポートされています。
121
+ - ベースはマスクモードでは特別な変化をします: base が off のとき、色がついていない領域は最初のマスクに追加されます (したがって、最初のプロンプトで埋められるべきです)。base がオンのとき、色のついていないリージョンは base のプロンプトを反映します、色のついたリージョンは通常の base のウェイトを受け取ります。このため、baseはbase weight = 0で、シーン/背景を指定するのに特に便利なツールです。
122
+ - 描画の代わりにマスクをアップロードしたい人向けです: この機能はまだ **非常に多くのWIP** であることに注意してください。マスクを適用するためには、すべての色に何らかのタグを付ける必要があります(コードでLCOLOUR変数を変更するか、手動で各色を画像に追加してください)。色はすべて `HSV(degree,50%,50%)` の変形で、 degree (0:360) は以前のすべての色から最大に離れた値として計算されます(そのため、色は容易に区別できます)。最初のいくつかの値は、基本的に 0、180、90、270、45、135、225、315、22.5などです。色の選択によって、どの領域に対応するかが決まります。
123
+
124
+ ![RegionalMaskGuideB](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/RegionalMaskGuideB.jpg)
125
+
126
+ ### visualise and make template
127
+ 複雑な領域指定をする場合など領域を可視化して、テンプレートを作成します。
128
+
129
+ ![tutorial](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/tutorial.jpg)
130
+
131
+ 入力を終えてボタンを押すと、画像のように領域とテンプレートが出力されます。テンプレートをコピペして使用して下さい。以下は入力例と出力結果です。
132
+
133
+ ```
134
+ fantasy ADDCOMM
135
+ sky ADDROW
136
+ castle ADDROW
137
+ street stalls ADDCOL
138
+ 2girls eating and walking on street ADDCOL
139
+ street stalls
140
+ ```
141
+
142
+ ![tutorial](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/sample3.jpg
143
+ )
144
+
145
+
146
+ '1,1;2,3,2;3,2,3'を指定してColumnsを選んだ場合、
147
+ ![flip](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/msapmle1.png)
148
+ Rowsに変えると
149
+ ![flip](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/msapmle2.png)
150
+ flipを有効にすると
151
+ ![flip](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/msapmle3.png)
152
+
153
+ ## <a id="divprompt">region specification by prompt (experimental)</a>
154
+ プロンプトによる領域指定です。これまでの領域指定では分割された領域に対してプロンプトを設定していました。この領域指定にはいくつかの問題があり、例えば縦に分割した場合、指定したオブジェクトがそこに限定されてしまします。プロンプトによる領域指定では指定したプロンプトを反映した領域が画像生成中に作成され、そこに対応したプロンプトが適用されます。よって、より柔軟な領域指定が可能になります。以下に例を示します。`apple printed`は`shirt`にだけ効果が反映されて欲しいわけですが、shirtには反映されず、林檎の現物が出てきたりするわけです。
155
+ ```
156
+ lady smiling and sitting, twintails green hair, white skirt, apple printed shirt
157
+ ```
158
+ ![prompt](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/psample1.png)
159
+ そこで`apple printedの強度を1.4にするとこうなるわけです。
160
+
161
+ ![prompt](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/psample4.png)
162
+ プロンプトによる領域指定ではshirtに対して領域を計算して、そこに`apple printed`を適用します。
163
+ ![prompt](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/psample6.png)
164
+ ```
165
+ lady smiling and sitting, twintails green hair, white skirt, shirt BREAK
166
+ (apple printed:1.4),shirt
167
+ ```
168
+ ![prompt](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/psample2.png)
169
+ すると、目的の効果が得られるわけです。これまでの領域指定ではshirtの位置を詳細に指定しなければいけなかったわけですが、その必要がなくなりました。
170
+ ### つかいかた
171
+ ### 書式
172
+ ```
173
+ baseprompt target1 target2 BREAK
174
+ effect1, target1 BREAK
175
+ effect2 ,target2
176
+ ```
177
+ まず、ベースプロンプトを書きます。ベースプロンプトにはマスクを作成する単語(target1、target2)を書きます。次にBREAKで区切ります。次に、target1に対応するプロンプトを書きます。そしてカンマを入力しtarget1を記載します。ベースプロンプトのtargetの順番とBREAKで区切られたtargetの順番は前後しても問題ありません。targetは大まかな単語でも問題なく、例えば`tops`と指定して、`effect`に`red camisole`などと書いてもいいわけです。
178
+
179
+ ```
180
+ target2 baseprompt target1 BREAK
181
+ effect1, target1 BREAK
182
+ effect2 ,target2
183
+ ```
184
+ ベースプロンプトの順番は考慮されません。effectの順番は考慮されます。
185
+
186
+ ### threshold
187
+ プロンプトによって作られるマスクの判定に使われる閾値です。これは対象となるプロンプトによって範囲が大きく異なるのでマスクの数だけ設定できます。複数の領域を使うときはカンマで区切って入力して下さい。例えば髪は領域が曖昧になりがちなので小さな値が必要ですが、顔は領域が大きくなりがちなので小さな値が必要です。これはBREAKで区切られた順に並べて下さい。
188
+
189
+ ```
190
+ a lady ,hair, face BREAK
191
+ red, hair BREAK
192
+ tanned ,face
193
+ ```
194
+ `threshold : 0.4,0.6`
195
+ 単一の値が入力された場合、すべての領域に同じ値が適用されます。
196
+
197
+ ### Prompt and Prompt-EX
198
+ 領域がかぶった場合の計算方式です。Promptだと加算されます。Prompt-EXだと順番に上書きされます。つまり、target1とtarget2の領域が重複していた場合、target2の領域が優先されます。target1にtopsを指定してthretholdを小さくして大きな領域にして、target2をbottomsとしてthresholdを大きくすれ���良い分離が得られます。この場合、targetは領域が大きい順に記載されるべきです。
199
+
200
+ ### Accuracy
201
+ 12 x 512 サイズの場合、Attention modeではU-netの深い領域では 8 x 8 で計算されます。これでは小さい領域しては意味をなしません。よって領域の浸食が起きやすくなります。Latentモードでは 64*64で計算されるため領域が厳密になります。
202
+ ```
203
+ girl hair twintail frills,ribbons, dress, face BREAK
204
+ girl, ,face
205
+ ```
206
+ Prompt-EX/Attention
207
+ ![prompt](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/psample5.png)
208
+ Prompt-EX/Latent
209
+ ![prompt](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/psample3.png)
210
+
211
+
212
+
213
+ ### ベースと共通の違い
214
+ ```
215
+ a girl ADDROMM(or ADDBASE)
216
+ red hair BREAK
217
+ green dress
218
+ ```
219
+ と言うプロンプトがあった場合、共通の場合には領域1は`a girl red hair`というプロンプトで生成されます。ベースの場合で比率が0.2の場合には` (a girl) * 0.2 + (red hair) * 0.8`というプロンプトで生成されます。基本的には共通プロンプトで問題ありません。共通プロンプトの効きが強いという場合などはベースにしてみてもいいかもしれません。
220
+
221
+ ## APIを通した利用方法
222
+ APIを通してこの拡張を利用する場合には次の書式を使います。
223
+ ```
224
+ "prompt": "green hair twintail BREAK red blouse BREAK blue skirt",
225
+ "alwayson_scripts": {
226
+ "Regional Prompter": {
227
+ "args": [True,False,"Matrix","Vertical","Mask","Prompt","1,1,1","",False,False,False,"Attention",False,"0","0","0",""]
228
+ }}
229
+ ```
230
+ `args`の各設定は下の表を参照して下さい。No.は順番に対応します。typeがtextになっている場合は`""`で囲って下さい。3-6のモード設定は3.のモードで選択したモードに対応するサブモード以外は無視されます。17.のマスクは画像データのアドレスを指定して下さい。アドレスは絶対パスか、web-uiルートからの相対パスが利用できます。マスクはマスクの項で指定された色を使用して作成して下さい。
231
+
232
+ | No. | setting |choice| type | default |
233
+ | ---- | ---- |---- |----| ----|
234
+ | 1 | Active |True, False|Bool|False|
235
+ | 2 | debug |True, False|Bool|False|
236
+ | 3 | Mode |Matrix, Mask, Prompt|Text| Matrix|
237
+ | 4 | Mode (Matrix)|Horizontal, Vertical, Columns, Rows|Text|Columns
238
+ | 5 | Mode (Mask)| Mask |Text|Mask
239
+ | 6 | Mode (Prompt)| Prompt, Prompt-Ex |Text|Prompt
240
+ | 7 | Ratios||Text|1,1,1
241
+ | 8 | Base Ratios | |Text| 0
242
+ | 9 | Use Base |True, False|Bool|False|
243
+ | 10 | Use Common |True, False|Bool|False|
244
+ | 11 | Use Neg-Common |True, False|Bool| False|
245
+ | 12 | Calcmode| Attention, Latent | Text | Attention
246
+ | 13 | Not Change AND |True, False|Bool|False|
247
+ | 14 | LoRA Textencoder ||Text|0|
248
+ | 15 | LoRA U-Net | | Text | 0
249
+ | 16 | Threshold | |Text| 0
250
+ | 17 | Mask | | Text |
251
+
252
+ ### 設定例
253
+ #### Matrix
254
+ ```
255
+ "prompt": "green hair twintail BREAK red blouse BREAK blue skirt",
256
+ "alwayson_scripts": {
257
+ "Regional Prompter": {
258
+ "args": [True,False,"Matrix","Vertical","Mask","Prompt","1,1,1","",False,False,False,"Attention",False,"0","0","0",""]
259
+ }}
260
+ ```
261
+ 結果
262
+ ![sample](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/asample1.png)
263
+
264
+ #### Mask
265
+ ```
266
+ "prompt": "masterpiece,best quality 8k photo of BREAK (red:1.2) forest BREAK yellow chair BREAK blue dress girl",
267
+ "alwayson_scripts": {
268
+ "Regional Prompter": {
269
+ "args": [True,False,"Mask","Vertical","Mask","Prompt","1,1,1","",False,True,False,"Attention",False,"0","0","0","mask.png"]
270
+ ```
271
+ 使用したマスク
272
+ ![sample](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/mask.png)
273
+ 結果
274
+ ![sample](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/asample2.png)
275
+
276
+ ### Prompt
277
+ ```
278
+ "prompt": "masterpiece,best quality 8k photo of BREAK a girl hair blouse skirt with bag BREAK (red:1.8) ,hair BREAK (green:1.5),blouse BREAK,(blue:1.7), skirt BREAK (yellow:1.7), bag",
279
+ "alwayson_scripts": {
280
+ "Regional Prompter": {
281
+ "args": [True,False,"Prompt","Vertical","Mask","Prompt-EX","1,1,1","",False,True,False,"Attention",False,"0","0","0.5,0.6,0.5",""]
282
+ }}
283
+ ```
284
+ ![sample](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/asample3.png)
285
+
286
+ ### 謝辞
287
+ Attention coupleを提案された[furusu](https://note.com/gcem156)氏、Latent coupleを提案された[opparco](https://github.com/opparco)氏、2D生成のコード作成に協力して頂いた[Symbiomatrix](https://github.com/Symbiomatrix)に感謝します。
288
+
289
+
290
+
291
+ - 新機能2D領域を追加しました
292
+ - 新しい計算方式「Latent」を追加しました。生成が遅くなりますがLoRAをある程度分離できます
293
+ - 75トークン以上を入力できるようになりました
294
+ - 共通プロンプトを設定できるようになりました
295
+ - 設定がPNG infoに保存されるようにな��ました
regional-prompter-main/README.md ADDED
@@ -0,0 +1,406 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Regional Prompter
2
+ ![top](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/top.jpg)
3
+ - custom script for [AUTOMATIC1111's stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui)
4
+ - Different prompts can be specified for different regions
5
+
6
+ - [AUTOMATIC1111's stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) 用のスクリプトです
7
+ - 垂直/平行方向に分割された領域ごとに異なるプロンプトを指定できます
8
+
9
+ ## Language control / 言語制御
10
+ 日本語: [![jp](https://img.shields.io/badge/lang-jp-green.svg)](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/main/README.JP.md)
11
+
12
+ ### Updates
13
+ - モード名が変更になりました。`Horizontal` -> `columns`, `Vertical` -> `Rows`
14
+ (日本語で横に分割を英訳したSplit Horizontalは英語圏では逆の意味になるようです。水平線「で」分割するという意味になるそう)
15
+ - `,`,`;`を入れ替えるオプションを追加
16
+
17
+ - Split Mode name changed, `Horizontal` -> `columns`, `Vertical` -> `Rows`
18
+ - flip `,`,`;` option added
19
+
20
+ - add LoRA stop step
21
+ LoRAを適用するのをやめるstepを指定できます。10 step程度で停止することで浸食、ノイズ等の防止、生成速度の向上を期待できます。
22
+ You can specify the step at which to stop applying LoRA. By stopping around 10 steps, you can expect to prevent erosion and noise, and to improve generation speed.
23
+ (0に設定すると無効になります。0 is disable)
24
+
25
+ - support SDXL
26
+ - support web-ui 1.5
27
+
28
+ - add [guide for API users](#how-to-use-via-api)
29
+
30
+ - prompt mode improved
31
+ - プロンプトモードの動作が改善しました
32
+ (The process has been adjusted to generate masks in three steps, and to recommence generation from the first stage./3ステップでマスクを生成し、そこから生成を1stepからやり直すよう修正しました)
33
+
34
+ - New feature : [regions by inpaint](#inpaint) (thanks [Symbiomatrix](https://github.com/Symbiomatrix))
35
+ - New feature : [regions by prompt](#divprompt) ([Tutorial](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/main/prompt_en.md))
36
+ - 新機能 : [インペイントによる領域指定](#inpaint) (thanks [Symbiomatrix](https://github.com/Symbiomatrix))
37
+ - 新機能 : [プロンプトによる領域指定](#divprompt) ([チュートリアル](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/main/prompt_ja.md))
38
+
39
+
40
+ # Overview
41
+ Latent couple extention performs U-Net calculations on a per-prompt basis, but this extension performs per-prompt calculations inside U-Net. See [here(Japanese)](https://note.com/gcem156/n/nb3d516e376d7) for details. Thanks to furusu for initiating the idea. Additional, Latent mode also supported.
42
+
43
+ ## index
44
+ - [2D regions](#2D)
45
+ - [Latent mode(LoRA)](#latent)
46
+ - [regions by inpaint](#inpaint)
47
+ - [regions by prompt](#divprompt)
48
+
49
+
50
+
51
+
52
+ ## Usage
53
+ This section explains how to use the following image, explaining how to create the following image.
54
+ ![sample](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/sample.jpg)
55
+ Here is the prompt.
56
+ ```
57
+ green hair twintail BREAK
58
+ red blouse BREAK
59
+ blue skirt
60
+ ```
61
+ setting
62
+ ```
63
+ Active : On
64
+ Use base prompt : Off
65
+ Divide mode : Vertical
66
+ Divide Ratio : 1,1,1
67
+ Base Ratio :
68
+ ````
69
+ This setting divides the image vertically into three parts and applies the prompts "green hair twintail" ,"red blouse" ,"blue skirt", from top to bottom in order.
70
+
71
+ ### Active
72
+ This extention is enabled only if "Active" is toggled.
73
+
74
+ ### Prompt
75
+ Prompts for different regions are separated by `BREAK` keywords.
76
+ Negative prompts can also be set for each area by separating them with `BREAK`, but if `BREAK` is not entered, the same negative prompt will be set for all areas.
77
+
78
+ Using `ADDROW` or `ADDCOL` anywhere in the prompt will automatically activate [2D region mode](#2D).
79
+
80
+ ### Use base prompt
81
+ Check this if you want to use the base prompt, which is the same prompt for all areas. Use this option if you want the prompt to be consistent across all areas.
82
+ When using base prompt, the first prompt separated by `BREAK` is treated as the base prompt.
83
+ Therefore, when this option is enabled, one extra `BREAK`-separated prompt is required compared to Divide ratios.
84
+
85
+ Automatically turned on when `ADDBASE` is entered.
86
+
87
+
88
+ ### Divide ratio
89
+ If you enter 1,1,1, the image will be divided into three equal regions (33,3%, 33,3%, 33,3%); if you enter 3,1,1, the image will be divided into 60%, 20%, and 20%. Fractions can also be entered: 0.1,0.1,0.1 is equivalent to 1,1,1. For greatest accuracy, enter pixel values corresponding to height / width (vertical / horizontal mode respectively), eg 300,100,112 -> 512.
90
+
91
+
92
+
93
+
94
+
95
+
96
+
97
+ Using a `;` separator will automatically activate 2D region mode.
98
+
99
+
100
+
101
+ ### Base ratio
102
+ Sets the ratio of the base prompt; if base ratio is set to 0.2, then resulting images will consist of `20%*BASE_PROMPT + 80%*REGION_PROMPT`. It can also be specified for each region, in the same way as "Divide ratio" - 0.2, 0.3, 0.5, etc. If a single value is entered, the same value will be applied to all areas.
103
+
104
+ ### split mode
105
+ Specifies the direction of division. Horizontal and vertical directions can be specified.
106
+ In order to specify both horizontal and vertical regions, see 2D region mode.
107
+
108
+ ## calcutation mode
109
+ ### Attention
110
+ Normally, use this one.
111
+ ### Latent
112
+ Slower, but allows separating LoRAs to some extent. The generation time is the number of areas x the generation time of one pic. See [known issues](#knownissues).
113
+
114
+ Example of Latent mode for [nendoorid](https://civitai.com/models/7269/nendoroid-figures-lora),
115
+ [figma](https://civitai.com/models/7984/figma-anime-figures) LoRA separated into left and right sides to create.
116
+ <img src="https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/sample2.jpg" width="400">
117
+
118
+ ### Use common prompt
119
+ If this option enabled, first part of the prompt is added to all region parts.
120
+
121
+ Automatically turned on when `ADDCOMM` is entered.
122
+ ```
123
+ best quality, 20yo lady in garden BREAK
124
+ green hair twintail BREAK
125
+ red blouse BREAK
126
+ blue skirt
127
+ ```
128
+ If common is enabled, this prompt is converted to the following:
129
+ ```
130
+ best quality, 20yo lady in garden, green hair twintail BREAK
131
+ best quality, 20yo lady in garden, red blouse BREAK
132
+ best quality, 20yo lady in garden, blue skirt
133
+ ```
134
+ So you must set 4 prompts for 3 regions. If `Use base prompt` is also enabled 5 prompts are needed. The order is as follows: common, base, prompt1,prompt2,...
135
+
136
+ ## <a id="2D">2D region assignment</a>
137
+ You can specify a region in two dimensions. Using a special separator (`ADDCOL/ADDROW`), the area can be divided horizontally and vertically. Starting at the upper left corner, the area is splited by columns when separated by `ADDCOL` and rows when separated by `ADDROW`. The ratio of division is specified as a ratio separated by a semicolon. An example is shown below; although it is possible to use `BREAK` alone to describe only the ratio, it is easier to understand if COL/ROW is explicitly specified. Using `ADDBASE `as the first separator will result in the base prompt. If no ratio is specified or if the ratio does not match the number of separators, all regions are automatically treated as equal multiples.
138
+ In this mode, the direction selected in `Divide mode` changes which separator is applied first:
139
+ - In `Coloms` mode, the image is first split to rows with `ADDROW` or `;` in Divide ratio, then each row is split to regions with `ADDCOL` or `,` in Divide ratio.
140
+ - In `Rows` mode, the image is first split to columns with `ADDCOL` or `,` in Divide ratio, then each column is split to regions with `ADDROW` or `;` in Divide ratio.
141
+ - When the flip option is enabled, it swaps the , and ;. This allows you to obtain an area that is rotated 90 degrees while keeping the same ratios used in Columns/Rows.
142
+
143
+ In any case, the conversion of prompt clauses to rows and columns is from top to bottom, left to right.
144
+
145
+ ```
146
+ (blue sky:1.2) ADDCOL
147
+ green hair twintail ADDCOL
148
+ (aquarium:1.3) ADDROW
149
+ (messy desk:1.2) ADDCOL
150
+ orange dress and sofa
151
+ ```
152
+
153
+ ```
154
+ Active : On
155
+ Use base prompt : Off
156
+ Main splitting : Columns
157
+ Divide Ratio : 1,2,1,1;2,4,6
158
+ Base Ratio :
159
+ ```
160
+
161
+ ![2d](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/2d.jpg)
162
+
163
+
164
+
165
+
166
+
167
+
168
+
169
+
170
+
171
+
172
+
173
+
174
+
175
+
176
+ ## <a id="visualize">Visualise and make template</a>
177
+ Areas can be visualized and templates for prompts can be created.
178
+
179
+ ![tutorial](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/tutorial.jpg)
180
+
181
+ Enter the area ratio and press the button to make the area appear. Next, copy and paste the prompt template into the prompt input field.
182
+
183
+ ```
184
+ fantasy ADDCOMM
185
+ sky ADDROW
186
+ castle ADDROW
187
+ street stalls ADDCOL
188
+ 2girls eating and walking on street ADDCOL
189
+ street stalls
190
+ ```
191
+ Result is following,
192
+ ![tutorial](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/sample3.jpg)
193
+
194
+
195
+ This is an example of an area using 1,1;2,3,2;3,2,3. In Columns, it would look like this:
196
+ ![flip](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/msapmle1.png)
197
+ In Rows, it would appear as follows:
198
+ ![flip](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/msapmle2.png)
199
+ When the flip option is enabled in Rows, it would appear as follows:
200
+ ![flip](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/msapmle3.png)
201
+
202
+
203
+ ## <a id="inpaint">Mask regions aka inpaint+ (experimental function)</a>
204
+ It is now possible to specify regions using either multiple hand drawn masks or an uploaded image containing said masks (more on that later).
205
+ - First, make sure you switch to `mask divide mode` next to `Colums` / `Rows`. Otherwise the mask will be ignored and regions will be split by ratios as usual.
206
+ - Set `canvas width and height` according to desired image's, then press `create mask area`. If a different ratio or size is specified, the masks may be applied inaccurately (like in inpaint "just resize").
207
+ - Draw an outline / area of the region desired on the canvas, then press `draw region`. This will fill out the area, and colour it according to the `region` number you picked. **Note that the drawing is in black only, filling and colouring are performed automatically.** The region mask will be displayed below, to the right.
208
+ - Pressing `draw region` will automatically advance to the next region. It will also keep a list of which regions were used for building the masks later. Up to 360 regions can be used currently, but note that a few of them on the higher end are identical.
209
+ - It's possible to add to existing regions by reselecting the same number and drawing as usual.
210
+ - The special region number -1 will clear out (colour white) any drawn areas, and display which parts still contain regions in mask.
211
+ - Once the region masks are ready, write your prompt as usual: Divide ratios are ignored. Base ratios still apply to each region. All flags are supported, and all BREAK / ADDX keywords (ROW/COL will just be converted to BREAK). Attention and latent mode supported (loras maybe).
212
+ - `Base` has unique rules in mask mode: When base is off, any non coloured regions are added to the first mask (therefore should be filled with the first prompt). When base is on, any non coloured regions will receive the base prompt in full, whilst coloured regions will receive the usual base weight. This makes base a particularly useful tool for specifying scene / background, with base weight = 0.
213
+ - Masks are saved to and loaded from presets whose divide mode is `mask`. The mask is saved in the extension directory, under the folder `regional_masks`, as {preset}.png file.
214
+ - Masks can be uploaded from any image by using the empty component labelled `upload mask here`. It will automatically filter and tag the colours approximating matching those used for regions, and ignore the rest. The region / nonregion sections will be displayed under mask. **Do not upload directly to sketch area, and read the [known issues](#knownissues) section.**
215
+ - If you wish to draw masks in an image editor, this is how the colours correspond to regions: The colours are all variants of `HSV(degree,50%,50%)`, where degree (0:360) is calculated as the maximally distant value from all previous colours (so colours are easily distinguishable). The first few values are essentially: 0, 180, 90, 270, 45, 135, 225, 315, 22.5 and so on. The choice of colours decides to which region they correspond.
216
+ - Protip: You may upload an openpose / depthmap / any other image, then trace the regions accordingly. Masking will ignore colours which don't belong to the expected colour standard.
217
+
218
+ ![RegionalMaskGuide2](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/RegionalMaskGuide2.jpg)
219
+ ![RegionalMaskGuide2B](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/RegionalMaskGuide2B.jpg)
220
+
221
+
222
+ Here is sample and code
223
+ using mask and prompt`landscape BREAK moon BREAK girl`.
224
+ Using XYZ plot prompt S/R, changed `moon BREAK girl` to others.
225
+ ![RegionalMaskSample](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/isample1.png)
226
+
227
+
228
+
229
+ ## <a id="divprompt">region specification by prompt (experimental)</a>
230
+ The region is specified by the prompt. The picture below was created with the following prompt, but the prompt `apple printed` should only affect the shirt, but the actual apples are shown and so on.
231
+ ```
232
+ lady smiling and sitting, twintails green hair, white skirt, apple printed shirt
233
+ ```
234
+ ![prompt](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/psample1.png)
235
+ If you enhance the effect of `apple printed` to `:1.4`, you get,
236
+
237
+ ![prompt](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/psample4.png)
238
+ The prompt region specification allows you to calculate the region for the "shirt" and adapt the "printed apples".
239
+
240
+ ![prompt](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/psample6.png)
241
+ ```
242
+ lady smiling and sitting, twintails green hair, white skirt, shirt BREAK
243
+ (apple printed:1.4),shirt
244
+ ```
245
+ ![prompt](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/psample2.png)
246
+
247
+ ### How to use
248
+ ### syntax
249
+ ```
250
+ baseprompt target1 target2 BREAK
251
+ effect1, target1 BREAK
252
+ effect2 ,target2
253
+ ```
254
+
255
+
256
+ First, write the base prompt. In the base prompt, write the words (target1, target2) for which you want to create a mask. Next, separate them with BREAK. Next, write the prompt corresponding to target1. Then enter a comma and write target1. The order of the targets in the base prompt and the order of the BREAK-separated targets can be back to back.
257
+
258
+ ```
259
+ target2 baseprompt target1 BREAK
260
+ effect1, target1 BREAK
261
+ effect2 ,target2
262
+ ```
263
+ is also effective.
264
+
265
+ ### threshold
266
+ The threshold used to determine the mask created by the prompt. This can be set as many times as there are masks, as the range varies widely depending on the target prompt. If multiple areas are used, enter them separated by commas. For example, hair tends to be ambiguous and requires a small value, while face tends to be large and requires a small value. These should be ordered by BREAK.
267
+
268
+ ```
269
+ a lady ,hair, face BREAK
270
+ red, hair BREAK
271
+ tanned ,face
272
+ ```
273
+ `threshold : 0.4,0.6`
274
+ If only one input is given for multiple regions, they are all assumed to be the same value.
275
+
276
+ ### Prompt and Prompt-EX
277
+ The difference is that in Prompt, duplicate areas are added, whereas in Prompt-EX, duplicate areas are overwritten sequentially. Since they are processed in order, setting a TARGET with a large area first makes it easier for the effect of small areas to remain unmuffled.
278
+
279
+ ### Accuracy
280
+ In the case of a 512 x 512 image, Attention mode reduces the size of the region to about 8 x 8 pixels deep in the U-Net, so that small areas get mixed up; Latent mode calculates 64*64, so that the region is exact.
281
+ ```
282
+ girl hair twintail frills,ribbons, dress, face BREAK
283
+ girl, ,face
284
+ ```
285
+ Prompt-EX/Attention
286
+ ![prompt](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/psample5.png)
287
+ Prompt-EX/Latent
288
+ ![prompt](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/psample3.png)
289
+
290
+
291
+ ### Mask
292
+ When an image is generated, the generated mask is displayed. It is generated at the same size as the image, but is actually used at a much smaller size.
293
+
294
+ ## Difference between base and common
295
+ ```
296
+ a girl ADDCOMM (or ADDBASE)
297
+ red hair BREAK
298
+ green dress
299
+ ```
300
+ If there is a prompt that says `a girl` in the common clause, region 1 is generated with the prompt `a girl , red hair`. In the base clause, if the base ratio is 0.2, it is generated with the prompt `a girl` * 0.2 + `red hair` * 0.8. Basically, common clause combines prompts, and base clause combines weights (like img2img denoising strength). You may want to try the base if the common prompt is too strong, or fine tune the (emphasis).
301
+ The immediate strength that corresponds to the target should be stronger than normal. Even 1.6 doesn't break anything.
302
+
303
+ ## <a id="knownissues">Known issues</a>
304
+ - Due to an [issue with gradio](https://github.com/gradio-app/gradio/issues/4088), uploading a mask or loading a mask preset more than twice in a row will fail. There are two workarounds for this:
305
+ 1) Before EVERY upload / load, press `create mask area`.
306
+ 2) Modify the code in gradio.components.Image.preprocess; add the following at the beginning of the function (temporarily):
307
+ ```
308
+ if self.tool == "sketch" and self.source in ["upload", "webcam"]:
309
+ if x is not None and isinstance(x, str):
310
+ x = {"image":x, "mask": x[:]}
311
+ ```
312
+ The extension cannot perform this override automatically, because gradio doesn't currently support [custom components](https://github.com/gradio-app/gradio/issues/1432). Attempting to override the component / method in the extension causes the application to not load at all.
313
+
314
+ 3) Wait until a fix is published.
315
+
316
+ - Lora corruption in latent mode. Some attempts have been made to improve the output, but no solution as of yet. Suggestions below.
317
+ 1) Reduce cfg, reduce lora weight, increase sampling steps.
318
+ 2) Use the `negative textencoder` + `negative U-net` parameters: these are weights between 0 and 1, comma separated like base. One is applied to each lora in order of appearance in the prompt. A value of 0 (the default) will negate the effect of the lora on other regions, but may cause it to be corrupted. A value of 1 should be closer to the natural effect, but may corrupt other regions (greenout, blackout, SBAHJified etc), even if they don't contain any loras. In both cases, a higher lora weight amplifies the effect. The effect seems to vary per lora, possibly per combination.
319
+ 3) It has been suggested that [lora block weight](https://github.com/hako-mikan/sd-webui-lora-block-weight) can help.
320
+ 4) If all else fails, inpaint.
321
+
322
+ Here are samples of a simple prompt, two loras with negative te/unet values per lora of: (0,0) default, (1,0), (0,1), (1,1).
323
+ ![MeguminMigurdiaCmp](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/MeguminMigurdiaCmp.jpg)
324
+
325
+ If you come across any useful insights on the phenomenon, do share.
326
+
327
+ ## How to Use via API
328
+ The following format is used when utilizing this extension via the API.
329
+
330
+ ```
331
+ "prompt": "green hair twintail BREAK red blouse BREAK blue skirt",
332
+ "alwayson_scripts": {
333
+ "Regional Prompter": {
334
+ "args": [True,False,"Matrix","Vertical","Mask","Prompt","1,1,1","",False,False,False,"Attention",False,"0","0","0",""]
335
+ }}
336
+ ```
337
+ Please refer to the table below for each setting in `args`. No. corresponds to the order. When the type is text, please enclose it with `""`. Modes 3-6 ignore submodes that do not correspond to the mode selected in mode 3. For the mask in 17., please specify the address of the image data. Absolute paths or relative paths from the web-ui root can be used. Please create the mask using the color specified in the mask item.
338
+
339
+ | No. | setting |choice| type | default |
340
+ | ---- | ---- |---- |----| ----|
341
+ | 1 | Active |True, False|Bool|False|
342
+ | 2 | debug |True, False|Bool|False|
343
+ | 3 | Mode |Matrix, Mask, Prompt|Text| Matrix|
344
+ | 4 | Mode (Matrix)|Horizontal, Vertical, Colums, Rows|Text|Columns
345
+ | 5 | Mode (Mask)| Mask |Text|Mask
346
+ | 6 | Mode (Prompt)| Prompt, Prompt-Ex |Text|Prompt
347
+ | 7 | Ratios||Text|1,1,1
348
+ | 8 | Base Ratios | |Text| 0
349
+ | 9 | Use Base |True, False|Bool|False|
350
+ | 10 | Use Common |True, False|Bool|False|
351
+ | 11 | Use Neg-Common |True, False|Bool| False|
352
+ | 12 | Calcmode| Attention, Latent | Text | Attention
353
+ | 13 | Not Change AND |True, False|Bool|False|
354
+ | 14 | LoRA Textencoder ||Text|0|
355
+ | 15 | LoRA U-Net | | Text | 0
356
+ | 16 | Threshold | |Text| 0
357
+ | 17 | Mask | | Text |
358
+ | 18 | LoRA stop step | | Text | 0
359
+ | 19 | LoRA Hires stop step | | Text | 0
360
+ | 20 | flip |True, False| Bool | False
361
+
362
+ ### Example Settings
363
+ #### Matrix
364
+ ```
365
+ "prompt": "green hair twintail BREAK red blouse BREAK blue skirt",
366
+ "alwayson_scripts": {
367
+ "Regional Prompter": {
368
+ "args": [True,False,"Matrix","Vertical","Mask","Prompt","1,1,1","",False,False,False,"Attention",False,"0","0","0",""]
369
+ }}
370
+ ```
371
+ Result
372
+ ![sample](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/asample1.png)
373
+
374
+ #### Mask
375
+ ```
376
+ "prompt": "masterpiece,best quality 8k photo of BREAK (red:1.2) forest BREAK yellow chair BREAK blue dress girl",
377
+ "alwayson_scripts": {
378
+ "Regional Prompter": {
379
+ "args": [True,False,"Mask","Vertical","Mask","Prompt","1,1,1","",False,True,False,"Attention",False,"0","0","0","mask.png"]
380
+ ```
381
+ Mask used
382
+ ![sample](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/mask.png)
383
+ Result
384
+ ![sample](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/asample2.png)
385
+
386
+ #### Prompt
387
+ ```
388
+ "prompt": "masterpiece,best quality 8k photo of BREAK a girl hair blouse skirt with bag BREAK (red:1.8) ,hair BREAK (green:1.5),blouse BREAK,(blue:1.7), skirt BREAK (yellow:1.7), bag",
389
+ "alwayson_scripts": {
390
+ "Regional Prompter": {
391
+ "args": [True,False,"Prompt","Vertical","Mask","Prompt-EX","1,1,1","",False,True,False,"Attention",False,"0","0","0.5,0.6,0.5",""]
392
+ }}
393
+ ```
394
+ ![sample](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/asample3.png)
395
+
396
+
397
+ ## Acknowledgments
398
+ I thank [furusu](https://note.com/gcem156) for suggesting the Attention couple, [opparco](https://github.com/opparco) for suggesting the Latent couple, and [Symbiomatrix](https://github.com/Symbiomatrix) for helping to create the 2D generation code.
399
+
400
+
401
+ ## Updates
402
+ - New feature, "2D-Region"
403
+ - New generation method "Latent" added. Generation is slower, but LoRA can be separated to some extent.
404
+ - Supports over 75 tokens
405
+ - Common prompts can be set
406
+ - Setting parameters saved in PNG info
regional-prompter-main/prompt_en.md ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Tutorial on specifying areas with prompts
2
+
3
+ There are limitations to methods of specifying areas in advance. This is because specifying areas can be a hindrance when designating complex shapes or dynamic compositions. In the region specified by the prompt, the area is determined after the image generation has begun. This allows us to accommodate compositions and complex areas.
4
+
5
+ Let's take a look at an example.
6
+ The following image was created by the next prompt. It's a grand color transition.
7
+ ```
8
+ sfw (8k realistic masterpiece:1.3) a Asian girl ,dark green dress,pink belt,yellow bag,
9
+ blond hair, in rainy street, holding red umbrella
10
+ ```
11
+ ![1](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/ptutorial9.png)
12
+ Now, if we try to manage this with the usual area designation, we will have trouble specifying the area for the umbrella and the bag. They appear in various places, and some areas are initially small.
13
+
14
+ With prompt-based area specification, we calculate the area corresponding to each word.
15
+
16
+ Let's first turn the umbrella red. We change the prompt as follows: we change red umbrella to umbrella and after BREAK, we add (red:1.7), umbrella. This is because the system calculates the area of the word written after the last comma in the prompt following BREAK. In the case of (red:1.7), umbrella, the area of umbrella is calculated, and (red:1.7) is applied to that area.
17
+
18
+ Intensity adjustment is very important. Normally, if you input 1.7, it tends to fall apart, but with prompt-based area specification, it doesn't work unless you put in about this value. It's especially better to increase the intensity if you're trying to specify a color that doesn't seem to have learned much.
19
+ ```
20
+ sfw (8k realistic masterpiece:1.3) a girl, (dress:1.2), belt, bag, hair, in rainy street, holding umbrella BREAK
21
+ (red:1.7), umbrella
22
+ ```
23
+
24
+ ```
25
+ Divide mode : Prompt-EX
26
+ Calcmode : Attention
27
+ threshold : 0.7
28
+ negative common prompt : Enable
29
+ ```
30
+ Prompt-EX mode is an effective mode for specifying multiple areas and has the effect of overwriting areas with the ones that come later. Therefore, it is effective to specify the areas in a larger order.
31
+
32
+ Then the umbrella became properly red. The second image is the calculated area. It's properly shaped like an umbrella, and the head part is out of the area. This area varies depending on the prompt, so it's necessary to adjust it with the Threshold. If the Threshold is small, the area will be wider.
33
+ ![1](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/ptutorial11.png)
34
+
35
+ Here, negative prompts are also set up.
36
+ ```
37
+ nsfw, (worst quality:1.6), (low quality:1.6), (normal quality:1.6), monochrome
38
+ [(black:1.5)::3] BREAK BREAK (dark,transparent, black, blue:2)
39
+ ```
40
+ Umbrellas and bags tend to be predominantly black in the data the model was trained on, so caution is required when specifying colors for them. In this case, we've added a prompt to prevent the umbrella area from becoming black. The reason there are two BREAKs is because nega is enabled. [(black:1.5)::3] prevents the image from becoming black before the region specification by the prompt begins. In the region specification by the prompt, the calculation of the region is not valid until the third step.
41
+
42
+ Now, with similar region specification, the prompt becomes as follows, and we were able to obtain a result where the colors were properly separated.
43
+
44
+ ```
45
+ sfw (8k realistic masterpiece:1.3) a girl, (dress:1.2), belt, bag, hair, in rainy street, holding umbrella BREAK
46
+ (red:1.7), umbrella BREAK
47
+ (dark green:1.7) ,dress BREAK
48
+ (blond:1.7), hair BREAK
49
+ (pink:1.7), belt BREAK
50
+ (yellow:1.7), bag
51
+ ```
52
+
53
+ ![1](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/ptutorial10.png)
54
+
55
+
56
+
57
+ The following image was created with the prompt below. Although it would be ideal if 'forest' was only applied to the T-shirt, the background has also become a forest.
58
+ ```
59
+ girl in street (forest printed:1.3) T-shirt, shortshorts daytime
60
+ ```
61
+ ![1](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/ptutorial1.png)
62
+ To apply the 'forest' print only to the T-shirt, we configure the prompt and the Regional Prompter as follows.
63
+ ```
64
+ lady in street shirt,shortshorts daytime BREAK
65
+ (forest printed:1.3) T-shirt ,shirt
66
+ ```
67
+ What's important here is that `shirt` is both placed before the `BREAK` and at the end after the 'BREAK', and is separated by a comma. In prompt mode, the word that is separated by a comma at the end is the target for region calculation.
68
+ ```
69
+ Divide mode : Prompt
70
+ Calcmode : Attention
71
+ threshold : 0.7
72
+ ```
73
+
74
+ With these settings, the generation will look like this.
75
+ ![2](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/ptutorial2.png)
76
+ mask
77
+ ![3](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/ptutorial3.png)
78
+ This is a property of the Attention mode, where it actually shrinks to about 12×8, making it more ambiguous, which might be better.
79
+
80
+ Now, there seems to be a strange string attached to the "shortshort", so let's change this next. We rewrite the prompt as follows.
81
+ ```
82
+ girl in street shirt,shortshorts daytime BREAK
83
+ (forest printed:1.3) T-shirt ,shirt BREAK
84
+ (skirt:1.7) ,shortshorts
85
+ ```
86
+ ```
87
+ Divide mode : Prompt
88
+ Calcmode : Attention
89
+ threshold : 0.7,0.75
90
+ ```
91
+ It turns out like this.
92
+ ![4](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/ptutorial4.png)
93
+ The `shortshorts` became a `skirt`. I wrote it as `shortshorts` because that's what it was initially, but using a word like `bottoms` can make region selection easier. The reason why I kept it as `shortshorts` this time is because I didn't want to change the base prompt. If you change `shortshorts` to `bottoms`, it changes the initial image itself.
94
+
95
+ ```
96
+ girl in street shirt,bottoms daytime BREAK
97
+ (forest printed:1.3) T-shirt ,shirt BREAK
98
+ (red skirt:1.7) ,bottoms
99
+ ```
100
+ third image is made from this prompt.
101
+ ![5](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/ptutorial5.png)
102
+ Keeping it as `shortshorts` only changes the area around the `shortshorts`, but if you change `shortshorts` to `bottoms`, it changes the base, which changes the whole image. In other words, with prompt-based specifications, you can do something like inpainting where only a part is changed.
103
+
104
+ Let's change the settings a bit and try dressing up. The reason why I set it as` (shortshorts:0.5)` is to prioritize the `skirt`. Weakening the `shortshorts` for the region won't be a problem. Normally, you target the same item, but if the word is too strong, it will have too much impact, so weakening it is an option.
105
+
106
+ ```
107
+ girl in street shirt,shortshorts daytime BREAK
108
+ (forest printed:1.3) T-shirt ,shirt BREAK
109
+ (red skirt:1.5) ,(shortshorts:0.5)
110
+ ```
111
+ ```
112
+ Divide mode : Prompt
113
+ Calcmode : Attention
114
+ threshold : 0.7,0.55
115
+ ```
116
+ I'm broadening the `threshold` for `shortshorts` to accommodate things like long skirt. I think bottoms or lower body would be easier.
117
+ ![6](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/ptutorial6.png)
118
+ We were able to change the clothes, but there's some erosion in the pink. This is because the intensity is set uniformly. If we refine the settings, the erosion should disappear.
119
+
120
+ ```
121
+ girl in street shirt,shortshorts daytime BREAK
122
+ (forest printed:1.3) T-shirt ,shirt BREAK
123
+ (red skirt:1.5) ,(shortshorts:0.5) BREAK
124
+ (Japan landscape:1.6),street
125
+ ```
126
+ ```
127
+ Divide mode : Prompt
128
+ Calcmode : Attention
129
+ threshold : 0.7,0.55,0.7
130
+ ```
131
+ Let's change the `Japan` part.
132
+ ![7](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/ptutorial7.png)
133
+ The word `landscape` is a bit tricky. It's quite strong and often cancels out the effect of other words. However, by calculating the region using street instead of `landscape`, we can prevent it from becoming overly dominant. By calculating regions with different words like this, you might be able to expand the range of your expressions.
134
+
135
+ When the Regional Prompter is disabled, it looks like this.
136
+ This is pretty good as it is.
137
+ ![8](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/ptutorial8.png)
regional-prompter-main/prompt_ja.md ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## promptで指定する領域のチュートリアル
2
+ あらかじめ領域を指定するタイプの方法には限界があります。複雑な形状や動的な構図を指定する場合には領域指定が足かせになるためです。promptで指定する領域では画像を生成し始めたあとで領域を決定します。これにより構図や複雑な領域にも対応できるようになります。
3
+
4
+ では例を見てみましょう。
5
+ 下記の画像は次のプロンプトによって作成されました。まぁ盛大に色移りするわけです。
6
+ ```
7
+ sfw (8k realistic masterpiece:1.3) a Asian girl ,dark green dress,pink belt,yellow bag,
8
+ blond hair, in rainy street, holding red umbrella
9
+ ```
10
+ ![1](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/ptutorial9.png)
11
+ さてこれを通常の領域指定でなんとかしようとすると傘やバッグの領域指定に困ってしまうわけです。色々な場所に出てくるし、そもそも領域が小さかったりします。
12
+
13
+ プロンプトによる領域指定では1単語に対応する領域を計算します。
14
+
15
+ まずは傘から赤くしてみましょう。プロンプトを以下のように変更します。`red umbrella`を`umbrella`に変更し、`BREAK`のあとに`(red:1.7)`, `umbrella`を追記します。これは`BREAK`のあとに続くプロンプトでは最後のカンマのあとに書かれた単語の領域を計算する仕組みだからです。`(red:1.7), umbrella`の場合、`umbrella`の領域が計算され、その領域に`(red:1.7)`が掛かります。
16
+ 強度調節はとても大切で、通常1.7を入力すると崩壊気味になるわけですが、プロンプトによる領域指定ではこれぐらいの値を入れないと効きません。特にあまり学習していないような色を指定しようとするなら強度を高めたほうが良いです。
17
+ ```
18
+ sfw (8k realistic masterpiece:1.3) a girl, (dress:1.2), belt, bag, hair, in rainy street, holding umbrella BREAK
19
+ (red:1.7), umbrella
20
+ ```
21
+
22
+ ```
23
+ Divide mode : Prompt-EX
24
+ Calcmode : Attention
25
+ threshold : 0.7
26
+ negative common prompt : Enable
27
+ ```
28
+ Prompt-EXモードは複数の領域を指定する場合に有効なモードで、あとに来る領域によって領域を上書きする効果があります。よって大きな順に領域を指定すると効果的です。
29
+ するとちゃんと傘が赤くなりました。2枚目の画像は計算された領域でです。ちゃんと傘の形になっていて、かつ頭の部分は領域外になっているわけです。この領域はpromptによってまちまちなのでThresholdで調節してあげる必要があります。Thresholdは小さいと領域が広くなります。
30
+ ![1](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/ptutorial11.png)
31
+
32
+ ここではネガティブプロンプトも設定しています。
33
+ ```
34
+ nsfw, (worst quality:1.6), (low quality:1.6), (normal quality:1.6), monochrome
35
+ [(black:1.5)::3] BREAK BREAK (dark,transparent, black, blue:2)
36
+ ```
37
+ 傘やバッグなどはもともと黒いものが多く学習されている傾向があるので色を指定するときには注意が必要です。ここでは傘の領域に黒くなるのを防ぐpromptを入れています。`BREAK`が2つ並んでいるのはnegative commom promptを有効にしているためです。`[(black:1.5)::3]`はpromptによる領域指定が始まる前の段階で黒くなるのを防いでいます。promptによる領域指定では3stepまでは領域計算ができていないので有効になっていません。
38
+
39
+ さて、同様の領域指定を行うことでプロンプトは下記のようになり、ちゃんと色分けできた結果が得られました。
40
+
41
+ ```
42
+ sfw (8k realistic masterpiece:1.3) a girl, (dress:1.2), belt, bag, hair, in rainy street, holding umbrella BREAK
43
+ (red:1.7), umbrella BREAK
44
+ (dark green:1.7) ,dress BREAK
45
+ (blond:1.7), hair BREAK
46
+ (pink:1.7), belt BREAK
47
+ (yellow:1.7), bag
48
+ ```
49
+
50
+ ![1](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/ptutorial10.png)
51
+
52
+  
53
+
54
+ 次の絵は以下のプロンプトで作られました。forestはT-シャツにだけ書いてくれればいいものの、背景まで森になっています。
55
+ ```
56
+ girl in street (forest printed:1.3) T-shirt, shortshorts daytime
57
+ ```
58
+ ![1](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/ptutorial1.png)
59
+ そこで、Tシャツのみにforest printetを効かせるためにプロンプトとRegional Prompterを以下のように設定します。
60
+ ```
61
+ lady in street shirt,shortshorts daytime BREAK
62
+ (forest printed:1.3) T-shirt ,shirt
63
+ ```
64
+ ここで大切なのはshirtがBREAKの前に入っていることと、BREAKのあと、最後にあることとカンマで区切られていることです。promptモードでは最後にカンマで区切られた単語を領域計算の対象にします。
65
+ ```
66
+ Divide mode : Prompt
67
+ Calcmode : Attention
68
+ threshold : 0.7
69
+ ```
70
+ divide ratioとbase ratioはいまのところ使用しません。
71
+ この設定で生成すると次のようになります。
72
+ ![2](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/ptutorial2.png)
73
+ 実際に生成されたマスクはこんな感じです。
74
+ ![3](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/ptutorial3.png)
75
+ これはAttention modeの性質で、実際には12×8程度にまで縮小されて曖昧になるのでむしろこれくらいの方がよかったりします。
76
+
77
+ さて、なんだかshortshortにへんなひもがついているので次はここを変えてみましょう。プロンプトを以下のように書き換えます。
78
+ ```
79
+ girl in street shirt,shortshorts daytime BREAK
80
+ (forest printed:1.3) T-shirt ,shirt BREAK
81
+ (skirt:1.7) ,shortshorts
82
+ ```
83
+ ```
84
+ Divide mode : Prompt
85
+ Calcmode : Attention
86
+ threshold : 0.7,0.75
87
+ ```
88
+ するとこうなります。
89
+ ![4](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/ptutorial4.png)
90
+ `shortshorts`が`skirt`になりました。今回は最初がshortshortsだったのでそのまま書き換えましたが、`bottoms`などのような単語を使った方が領域選択が簡単です。なぜ今回`shortshors`のままにしたかというと、ベースとなるプロンプトを変えたくなかったからです。`shortshorts`を`bottoms`に変えてしまうと、初期画像そのものが変わってしまうのです。
91
+
92
+ ```
93
+ girl in street shirt,bottoms daytime BREAK
94
+ (forest printed:1.3) T-shirt ,shirt BREAK
95
+ (red skirt:1.7) ,bottoms
96
+ ```
97
+ で作った画像を三番目に置きました。
98
+ ![5](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/ptutorial5.png)
99
+ shortshortsのままで作成した場合はshortshorts周辺だけが変わっていますが、shortshortsをbottomsに変えた場合はベースが変わることになるので全体が変わってしまいます。つまり、プロンプト指定では一部だけを変更するインペイントのようなことができるというわけです。
100
+
101
+ 少し設定を変えて着せ替えてみましょう。`(shortshorts:0.5)`としているのは、skirtなどを優先するためです。領域用のshortshortsは弱めても問題ありません。普通は同じものを対象としますが、強い単語だと影響が出すぎてしまうので弱めるのも手です。
102
+
103
+ ```
104
+ girl in street shirt,shortshorts daytime BREAK
105
+ (forest printed:1.3) T-shirt ,shirt BREAK
106
+ (red skirt:1.5) ,(shortshorts:0.5)
107
+ ```
108
+ ```
109
+ Divide mode : Prompt
110
+ Calcmode : Attention
111
+ threshold : 0.7,0.55
112
+ ```
113
+ `long skirt`などに対応するために`shortshorts`の`threshold`を広くしています。bottoms やlower bodyの方が楽だと思います。
114
+ ![6](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/ptutorial6.png)
115
+ 着せ替えできましたが、pinkは浸食が出ていますね。これは強度の指定を一括で行っているからで、設定を詰めれば浸食はなくなりはずです。
116
+
117
+ さて次は背景を変えてみましょう。
118
+
119
+ ```
120
+ girl in street shirt,shortshorts daytime BREAK
121
+ (forest printed:1.3) T-shirt ,shirt BREAK
122
+ (red skirt:1.5) ,(shortshorts:0.5) BREAK
123
+ (Japan landscape:1.6),street
124
+ ```
125
+ ```
126
+ Divide mode : Prompt
127
+ Calcmode : Attention
128
+ threshold : 0.7,0.55,0.7
129
+ ```
130
+ Japanの部分を変えてみます。
131
+ ![7](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/ptutorial7.png)
132
+ landscapeという単語はなかなかくせ者で、この単語はかなり強いので他の単語の効果を打ち消すことが多々あるわけですが、landscapeの代わりにstreetで領域計算して適用することで強く出過ぎることを抑えることができるわけです。このように、別な単語で領域を計算するということで表現の幅が広がるのではないでしょうか。
133
+
134
+ Regional Prompterを無効にするとこうなります。
135
+ これはこれでいいですね。
136
+ ![8](https://github.com/hako-mikan/sd-webui-regional-prompter/blob/imgs/ptutorial8.png)
regional-prompter-main/scripts/attention.py ADDED
@@ -0,0 +1,575 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ from pprint import pprint
3
+ import ldm.modules.attention as atm
4
+ import torch
5
+ import torchvision
6
+ import torchvision.transforms.functional as F
7
+ from torchvision.transforms import InterpolationMode, Resize # Mask.
8
+
9
+ TOKENSCON = 77
10
+ TOKENS = 75
11
+
12
+ def db(self,text):
13
+ if self.debug:
14
+ print(text)
15
+
16
+ def main_forward(module,x,context,mask,divide,isvanilla = False,userpp = False,tokens=[],width = 64,height = 64,step = 0, isxl = False, negpip = None):
17
+
18
+ # Forward.
19
+
20
+ if negpip:
21
+ conds, contokens = negpip
22
+ context = torch.cat((context,conds),1)
23
+
24
+ h = module.heads
25
+ if isvanilla: # SBM Ddim / plms have the context split ahead along with x.
26
+ pass
27
+ else: # SBM I think divide may be redundant.
28
+ h = h // divide
29
+ q = module.to_q(x)
30
+
31
+ context = atm.default(context, x)
32
+ k = module.to_k(context)
33
+ v = module.to_v(context)
34
+
35
+ q, k, v = map(lambda t: atm.rearrange(t, 'b n (h d) -> (b h) n d', h=h), (q, k, v))
36
+
37
+ sim = atm.einsum('b i d, b j d -> b i j', q, k) * module.scale
38
+
39
+ if negpip:
40
+ conds, contokens = negpip
41
+ if contokens:
42
+ for contoken in contokens:
43
+ start = (v.shape[1]//77 - len(contokens)) * 77
44
+ v[:,start+1:start+contoken,:] = -v[:,start+1:start+contoken,:]
45
+
46
+ if atm.exists(mask):
47
+ mask = atm.rearrange(mask, 'b ... -> b (...)')
48
+ max_neg_value = -torch.finfo(sim.dtype).max
49
+ mask = atm.repeat(mask, 'b j -> (b h) () j', h=h)
50
+ sim.masked_fill_(~mask, max_neg_value)
51
+
52
+ attn = sim.softmax(dim=-1)
53
+
54
+ ## for prompt mode make basemask from attention maps
55
+
56
+ global pmaskshw,pmasks
57
+
58
+ hiresscaler(height,width,attn)
59
+
60
+ if userpp and step > 0:
61
+ for b in range(attn.shape[0] // 8):
62
+ if pmaskshw == []:
63
+ pmaskshw = [(height,width)]
64
+ elif (height,width) not in pmaskshw:
65
+ pmaskshw.append((height,width))
66
+
67
+ for t in tokens:
68
+ power = 4 if isxl else 1.2
69
+ add = attn[8*b:8*(b+1),:,t[0]:t[0]+len(t)]**power
70
+ add = torch.sum(add,dim = 2)
71
+ t = f"{t}-{b}"
72
+ if t not in pmasks:
73
+ pmasks[t] = add
74
+ else:
75
+ if pmasks[t].shape[1] != add.shape[1]:
76
+ add = add.view(8,height,width)
77
+ add = F.resize(add,pmaskshw[0])
78
+ add = add.reshape_as(pmasks[t])
79
+
80
+ pmasks[t] = pmasks[t] + add
81
+
82
+ out = atm.einsum('b i j, b j d -> b i d', attn, v)
83
+ out = atm.rearrange(out, '(b h) n d -> b n (h d)', h=h)
84
+ out = module.to_out(out)
85
+
86
+ return out
87
+
88
+ def hook_forwards(self, root_module: torch.nn.Module, remove=False):
89
+ self.hooked = True if not remove else False
90
+ for name, module in root_module.named_modules():
91
+ if "attn2" in name and module.__class__.__name__ == "CrossAttention":
92
+ module.forward = hook_forward(self, module)
93
+ if remove:
94
+ del module.forward
95
+
96
+ ################################################################################
97
+ ##### Attention mode
98
+
99
+ def hook_forward(self, module):
100
+ def forward(x, context=None, mask=None, additional_tokens=None, n_times_crossframe_attn_in_self=0):
101
+ if self.debug :
102
+ print("input : ", x.size())
103
+ print("tokens : ", context.size())
104
+ print("module : ", getattr(module, self.layer_name,None))
105
+
106
+ if self.xsize == 0: self.xsize = x.shape[1]
107
+ if "input" in getattr(module, self.layer_name,""):
108
+ if x.shape[1] > self.xsize:
109
+ self.in_hr = True
110
+
111
+ height = self.hr_h if self.in_hr and self.hr else self.h
112
+ width = self.hr_w if self.in_hr and self.hr else self.w
113
+
114
+ xs = x.size()[1]
115
+ scale = round(math.sqrt(height * width / xs))
116
+
117
+ dsh = round(height / scale)
118
+ dsw = round(width / scale)
119
+ ha, wa = xs % dsh, xs % dsw
120
+ if ha == 0:
121
+ dsw = int(xs / dsh)
122
+ elif wa == 0:
123
+ dsh = int(xs / dsw)
124
+
125
+ contexts = context.clone()
126
+
127
+ # SBM Matrix mode.
128
+ def matsepcalc(x,contexts,mask,pn,divide):
129
+ h_states = []
130
+ xs = x.size()[1]
131
+ (dsh,dsw) = split_dims(xs, height, width, self)
132
+
133
+ if "Horizontal" in self.mode: # Map columns / rows first to outer / inner.
134
+ dsout = dsw
135
+ dsin = dsh
136
+ elif "Vertical" in self.mode:
137
+ dsout = dsh
138
+ dsin = dsw
139
+
140
+ tll = self.pt if pn else self.nt
141
+
142
+ i = 0
143
+ outb = None
144
+ if self.usebase:
145
+ context = contexts[:,tll[i][0] * TOKENSCON:tll[i][1] * TOKENSCON,:]
146
+ # SBM Controlnet sends extra conds at the end of context, apply it to all regions.
147
+ cnet_ext = contexts.shape[1] - (contexts.shape[1] // TOKENSCON) * TOKENSCON
148
+ if cnet_ext > 0:
149
+ context = torch.cat([context,contexts[:,-cnet_ext:,:]],dim = 1)
150
+
151
+ negpip = negpipdealer(i,pn)
152
+
153
+ i = i + 1
154
+
155
+ out = main_forward(module, x, context, mask, divide, self.isvanilla,userpp =True,step = self.step, isxl = self.isxl, negpip = negpip)
156
+
157
+ if len(self.nt) == 1 and not pn:
158
+ db(self,"return out for NP")
159
+ return out
160
+ # if self.usebase:
161
+ outb = out.clone()
162
+ outb = outb.reshape(outb.size()[0], dsh, dsw, outb.size()[2]) if "Ran" not in self.mode else outb
163
+
164
+ sumout = 0
165
+ db(self,f"tokens : {tll},pn : {pn}")
166
+ db(self,[r for r in self.aratios])
167
+
168
+ for drow in self.aratios:
169
+ v_states = []
170
+ sumin = 0
171
+ for dcell in drow.cols:
172
+ # Grabs a set of tokens depending on number of unrelated breaks.
173
+ context = contexts[:,tll[i][0] * TOKENSCON:tll[i][1] * TOKENSCON,:]
174
+ # SBM Controlnet sends extra conds at the end of context, apply it to all regions.
175
+ cnet_ext = contexts.shape[1] - (contexts.shape[1] // TOKENSCON) * TOKENSCON
176
+ if cnet_ext > 0:
177
+ context = torch.cat([context,contexts[:,-cnet_ext:,:]],dim = 1)
178
+
179
+ negpip = negpipdealer(i,pn)
180
+
181
+ db(self,f"tokens : {tll[i][0]*TOKENSCON}-{tll[i][1]*TOKENSCON}")
182
+ i = i + 1 + dcell.breaks
183
+ # if i >= contexts.size()[1]:
184
+ # indlast = True
185
+
186
+ out = main_forward(module, x, context, mask, divide, self.isvanilla,userpp = self.pn, step = self.step, isxl = self.isxl,negpip = negpip)
187
+ db(self,f" dcell.breaks : {dcell.breaks}, dcell.ed : {dcell.ed}, dcell.st : {dcell.st}")
188
+ if len(self.nt) == 1 and not pn:
189
+ db(self,"return out for NP")
190
+ return out
191
+ # Actual matrix split by region.
192
+ if "Ran" in self.mode:
193
+ v_states.append(out)
194
+ continue
195
+
196
+ out = out.reshape(out.size()[0], dsh, dsw, out.size()[2]) # convert to main shape.
197
+ # if indlast:
198
+ addout = 0
199
+ addin = 0
200
+ sumin = sumin + int(dsin*dcell.ed) - int(dsin*dcell.st)
201
+ if dcell.ed >= 0.999:
202
+ addin = sumin - dsin
203
+ sumout = sumout + int(dsout*drow.ed) - int(dsout*drow.st)
204
+ if drow.ed >= 0.999:
205
+ addout = sumout - dsout
206
+ if "Horizontal" in self.mode:
207
+ out = out[:,int(dsh*drow.st) + addout:int(dsh*drow.ed),
208
+ int(dsw*dcell.st) + addin:int(dsw*dcell.ed),:]
209
+ if self.debug : print(f"{int(dsh*drow.st) + addout}:{int(dsh*drow.ed)},{int(dsw*dcell.st) + addin}:{int(dsw*dcell.ed)}")
210
+ if self.usebase :
211
+ # outb_t = outb[:,:,int(dsw*drow.st):int(dsw*drow.ed),:].clone()
212
+ outb_t = outb[:,int(dsh*drow.st) + addout:int(dsh*drow.ed),
213
+ int(dsw*dcell.st) + addin:int(dsw*dcell.ed),:].clone()
214
+ out = out * (1 - dcell.base) + outb_t * dcell.base
215
+ elif "Vertical" in self.mode: # Cols are the outer list, rows are cells.
216
+ out = out[:,int(dsh*dcell.st) + addin:int(dsh*dcell.ed),
217
+ int(dsw*drow.st) + addout:int(dsw*drow.ed),:]
218
+ db(self,f"{int(dsh*dcell.st) + addin}:{int(dsh*dcell.ed)}-{int(dsw*drow.st) + addout}:{int(dsw*drow.ed)}")
219
+ if self.usebase :
220
+ # outb_t = outb[:,:,int(dsw*drow.st):int(dsw*drow.ed),:].clone()
221
+ outb_t = outb[:,int(dsh*dcell.st) + addin:int(dsh*dcell.ed),
222
+ int(dsw*drow.st) + addout:int(dsw*drow.ed),:].clone()
223
+ out = out * (1 - dcell.base) + outb_t * dcell.base
224
+ db(self,f"sumin:{sumin},sumout:{sumout},dsh:{dsh},dsw:{dsw}")
225
+
226
+ v_states.append(out)
227
+ if self.debug :
228
+ for h in v_states:
229
+ print(h.size())
230
+
231
+ if "Horizontal" in self.mode:
232
+ ox = torch.cat(v_states,dim = 2) # First concat the cells to rows.
233
+ elif "Vertical" in self.mode:
234
+ ox = torch.cat(v_states,dim = 1) # Cols first mode, concat to cols.
235
+ elif "Ran" in self.mode:
236
+ if self.usebase:
237
+ ox = outb * makerrandman(self.ranbase,dsh,dsw).view(-1, 1)
238
+ ox = torch.zeros_like(v_states[0])
239
+ for state, filter in zip(v_states, self.ransors):
240
+ filter = makerrandman(filter,dsh,dsw)
241
+ ox = ox + state * filter.view(-1, 1)
242
+ return ox
243
+
244
+ h_states.append(ox)
245
+ if "Horizontal" in self.mode:
246
+ ox = torch.cat(h_states,dim = 1) # Second, concat rows to layer.
247
+ elif "Vertical" in self.mode:
248
+ ox = torch.cat(h_states,dim = 2) # Or cols.
249
+ ox = ox.reshape(x.size()[0],x.size()[1],x.size()[2]) # Restore to 3d source.
250
+ return ox
251
+
252
+ def masksepcalc(x,contexts,mask,pn,divide):
253
+ xs = x.size()[1]
254
+ (dsh,dsw) = split_dims(xs, height, width, self)
255
+
256
+ tll = self.pt if pn else self.nt
257
+
258
+ # Base forward.
259
+ i = 0
260
+ outb = None
261
+ if self.usebase:
262
+ context = contexts[:,tll[i][0] * TOKENSCON:tll[i][1] * TOKENSCON,:]
263
+ # SBM Controlnet sends extra conds at the end of context, apply it to all regions.
264
+ cnet_ext = contexts.shape[1] - (contexts.shape[1] // TOKENSCON) * TOKENSCON
265
+ if cnet_ext > 0:
266
+ context = torch.cat([context,contexts[:,-cnet_ext:,:]],dim = 1)
267
+
268
+ negpip = negpipdealer(i,pn)
269
+
270
+ i = i + 1
271
+ out = main_forward(module, x, context, mask, divide, self.isvanilla, isxl = self.isxl, negpip = negpip)
272
+
273
+ if len(self.nt) == 1 and not pn:
274
+ db(self,"return out for NP")
275
+ return out
276
+ # if self.usebase:
277
+ outb = out.clone()
278
+ outb = outb.reshape(outb.size()[0], dsh, dsw, outb.size()[2])
279
+
280
+ db(self,f"tokens : {tll},pn : {pn}")
281
+
282
+ ox = torch.zeros_like(x)
283
+ ox = ox.reshape(ox.shape[0], dsh, dsw, ox.shape[2])
284
+ ftrans = Resize((dsh, dsw), interpolation = InterpolationMode("nearest"))
285
+ for rmask in self.regmasks:
286
+ # Need to delay mask tensoring so it's on the correct gpu.
287
+ # Dunno if caching masks would be an improvement.
288
+ if self.usebase:
289
+ bweight = self.bratios[0][i - 1]
290
+ # Resize mask to current dims.
291
+ # Since it's a mask, we prefer a binary value, nearest is the only option.
292
+ rmask2 = ftrans(rmask.reshape([1, *rmask.shape])) # Requires dimensions N,C,{d}.
293
+ rmask2 = rmask2.reshape(1, dsh, dsw, 1)
294
+
295
+ # Grabs a set of tokens depending on number of unrelated breaks.
296
+ context = contexts[:,tll[i][0] * TOKENSCON:tll[i][1] * TOKENSCON,:]
297
+ # SBM Controlnet sends extra conds at the end of context, apply it to all regions.
298
+ cnet_ext = contexts.shape[1] - (contexts.shape[1] // TOKENSCON) * TOKENSCON
299
+ if cnet_ext > 0:
300
+ context = torch.cat([context,contexts[:,-cnet_ext:,:]],dim = 1)
301
+
302
+ db(self,f"tokens : {tll[i][0]*TOKENSCON}-{tll[i][1]*TOKENSCON}")
303
+ i = i + 1
304
+ # if i >= contexts.size()[1]:
305
+ # indlast = True
306
+ out = main_forward(module, x, context, mask, divide, self.isvanilla, isxl = self.isxl)
307
+ if len(self.nt) == 1 and not pn:
308
+ db(self,"return out for NP")
309
+ return out
310
+
311
+ out = out.reshape(out.size()[0], dsh, dsw, out.size()[2]) # convert to main shape.
312
+ if self.usebase:
313
+ out = out * (1 - bweight) + outb * bweight
314
+ ox = ox + out * rmask2
315
+
316
+ if self.usebase:
317
+ rmask = self.regbase
318
+ rmask2 = ftrans(rmask.reshape([1, *rmask.shape])) # Requires dimensions N,C,{d}.
319
+ rmask2 = rmask2.reshape(1, dsh, dsw, 1)
320
+ ox = ox + outb * rmask2
321
+ ox = ox.reshape(x.size()[0],x.size()[1],x.size()[2]) # Restore to 3d source.
322
+ return ox
323
+
324
+ def promptsepcalc(x, contexts, mask, pn,divide):
325
+ h_states = []
326
+
327
+ tll = self.pt if pn else self.nt
328
+ db(self,f"tokens : {tll},pn : {pn}")
329
+
330
+ for i, tl in enumerate(tll):
331
+ context = contexts[:, tl[0] * TOKENSCON : tl[1] * TOKENSCON, :]
332
+ # SBM Controlnet sends extra conds at the end of context, apply it to all regions.
333
+ cnet_ext = contexts.shape[1] - (contexts.shape[1] // TOKENSCON) * TOKENSCON
334
+ if cnet_ext > 0:
335
+ context = torch.cat([context,contexts[:,-cnet_ext:,:]],dim = 1)
336
+
337
+ db(self,f"tokens : {tl[0]*TOKENSCON}-{tl[1]*TOKENSCON}")
338
+
339
+ userpp = self.pn and i == 0 and self.pfirst
340
+
341
+ negpip = negpipdealer(self.condi,pn) if "La" in self.calc else negpipdealer(i,pn)
342
+
343
+ out = main_forward(module, x, context, mask, divide, self.isvanilla, userpp = userpp, width = dsw, height = dsh, tokens = self.pe, step = self.step, isxl = self.isxl, negpip = negpip)
344
+
345
+ if (len(self.nt) == 1 and not pn) or ("Pro" in self.mode and "La" in self.calc):
346
+ db(self,"return out for NP or Latent")
347
+ return out
348
+
349
+ db(self,[scale, dsh, dsw, dsh * dsw, x.size()[1]])
350
+
351
+ if i == 0:
352
+ outb = out.clone()
353
+ continue
354
+ else:
355
+ h_states.append(out)
356
+
357
+ if self.debug:
358
+ for h in h_states :
359
+ print(f"divided : {h.size()}")
360
+ print(pmaskshw)
361
+
362
+ if pmaskshw == []:
363
+ return outb
364
+
365
+ ox = outb.clone() if self.ex else outb * 0
366
+
367
+ db(self,[pmaskshw,maskready,(dsh,dsw) in pmaskshw and maskready,len(pmasksf),len(h_states)])
368
+
369
+ if (dsh,dsw) in pmaskshw and maskready:
370
+ depth = pmaskshw.index((dsh,dsw))
371
+ maskb = None
372
+ for masks , state in zip(pmasksf.values(),h_states):
373
+ mask = masks[depth]
374
+ masked = torch.multiply(state, mask)
375
+ if self.ex:
376
+ ox = torch.where(masked !=0 , masked, ox)
377
+ else:
378
+ ox = ox + masked
379
+ maskb = maskb + mask if maskb is not None else mask
380
+ maskb = 1 - maskb
381
+ if not self.ex : ox = ox + torch.multiply(outb, maskb)
382
+ return ox
383
+ else:
384
+ return outb
385
+
386
+ if self.eq:
387
+ db(self,"same token size and divisions")
388
+ if "Mas" in self.mode:
389
+ ox = masksepcalc(x, contexts, mask, True, 1)
390
+ elif "Pro" in self.mode:
391
+ ox = promptsepcalc(x, contexts, mask, True, 1)
392
+ else:
393
+ ox = matsepcalc(x, contexts, mask, True, 1)
394
+ elif x.size()[0] == 1 * self.batch_size:
395
+ db(self,"different tokens size")
396
+ if "Mas" in self.mode:
397
+ ox = masksepcalc(x, contexts, mask, self.pn, 1)
398
+ elif "Pro" in self.mode:
399
+ ox = promptsepcalc(x, contexts, mask, self.pn, 1)
400
+ else:
401
+ ox = matsepcalc(x, contexts, mask, self.pn, 1)
402
+ else:
403
+ db(self,"same token size and different divisions")
404
+ # SBM You get 2 layers of x, context for pos/neg.
405
+ # Each should be forwarded separately, pairing them up together.
406
+ if self.isvanilla: # SBM Ddim reverses cond/uncond.
407
+ nx, px = x.chunk(2)
408
+ conn,conp = contexts.chunk(2)
409
+ else:
410
+ px, nx = x.chunk(2)
411
+ conp,conn = contexts.chunk(2)
412
+ if "Mas" in self.mode:
413
+ opx = masksepcalc(px, conp, mask, True, 2)
414
+ onx = masksepcalc(nx, conn, mask, False, 2)
415
+ elif "Pro" in self.mode:
416
+ opx = promptsepcalc(px, conp, mask, True, 2)
417
+ onx = promptsepcalc(nx, conn, mask, False, 2)
418
+ else:
419
+ # SBM I think division may have been an incorrect patch.
420
+ # But I'm not sure, haven't tested beyond DDIM / PLMS.
421
+ opx = matsepcalc(px, conp, mask, True, 2)
422
+ onx = matsepcalc(nx, conn, mask, False, 2)
423
+ if self.isvanilla: # SBM Ddim reverses cond/uncond.
424
+ ox = torch.cat([onx, opx])
425
+ else:
426
+ ox = torch.cat([opx, onx])
427
+
428
+ self.count += 1
429
+
430
+ limit = 70 if self.isxl else 16
431
+
432
+ if self.count == limit:
433
+ self.pn = not self.pn
434
+ self.count = 0
435
+ self.pfirst = False
436
+ self.condi += 1
437
+ db(self,f"output : {ox.size()}")
438
+ return ox
439
+
440
+ return forward
441
+
442
+ def split_dims(xs, height, width, self):
443
+ """Split an attention layer dimension to height + width.
444
+
445
+ Originally, the estimate was dsh = sqrt(hw_ratio*xs),
446
+ rounding to the nearest value. But this proved inaccurate.
447
+ What seems to be the actual operation is as follows:
448
+ - Divide h,w by 8, rounding DOWN.
449
+ (However, webui forces dims to be divisible by 8 unless set explicitly.)
450
+ - For every new layer (of 4), divide both by 2 and round UP (then back up)
451
+ - Multiply h*w to yield xs.
452
+ There is no inverse function to this set of operations,
453
+ so instead we mimic them sans the multiplication part with orig h+w.
454
+ The only alternative is brute forcing integer guesses,
455
+ which might be inaccurate too.
456
+ No known checkpoints follow a different system of layering,
457
+ but it's theoretically possible. Please report if encountered.
458
+ """
459
+ # OLD METHOD.
460
+ # scale = round(math.sqrt(height*width/xs))
461
+ # dsh = round_dim(height, scale)
462
+ # dsw = round_dim(width, scale)
463
+ scale = math.ceil(math.log2(math.sqrt(height * width / xs)))
464
+ dsh = repeat_div(height,scale)
465
+ dsw = repeat_div(width,scale)
466
+ if xs > dsh * dsw and hasattr(self,"nei_multi"):
467
+ dsh, dsw = self.nei_multi[1], self.nei_multi[0]
468
+ while dsh*dsw != xs:
469
+ dsh, dsw = dsh//2, dsw//2
470
+
471
+ if self.debug : print(scale,dsh,dsw,dsh*dsw,xs, height, width)
472
+
473
+ return dsh,dsw
474
+
475
+ def repeat_div(x,y):
476
+ """Imitates dimension halving common in convolution operations.
477
+
478
+ This is a pretty big assumption of the model,
479
+ but then if some model doesn't work like that it will be easy to spot.
480
+ """
481
+ while y > 0:
482
+ x = math.ceil(x / 2)
483
+ y = y - 1
484
+ return x
485
+
486
+ #################################################################################
487
+ ##### for Prompt mode
488
+ pmasks = {} #maked from attention maps
489
+ pmaskshw =[] #height,width set of u-net blocks
490
+ pmasksf = {} #maked from pmasks for regions
491
+ maskready = False
492
+
493
+ def reset_pmasks(self): # init parameters in every batch
494
+ global pmasks, pmaskshw, pmasksf, maskready
495
+ self.step = 0
496
+ pmasks = {}
497
+ pmaskshw =[]
498
+ pmasksf = {}
499
+ maskready = False
500
+ self.x = None
501
+ self.rebacked = False
502
+
503
+ def savepmasks(self,processed):
504
+ for mask ,th in zip(pmasks.values(),self.th):
505
+ img, _ , _= makepmask(mask, self.h, self.w,th, self.step)
506
+ processed.images.append(img)
507
+ return processed
508
+
509
+ def hiresscaler(height,width,attn):
510
+ global pmaskshw,pmasks,pmasksf
511
+ if pmaskshw != []:
512
+ if height > pmaskshw[0][0]: # [0][0] has largest height, if in hires, height will be larger than [0][0]
513
+ (oh, ow) = pmaskshw[0]
514
+ del pmaskshw
515
+ pmaskshw = [(height,width)]
516
+ hiresmask(pmasks,oh, ow, height, width,attn[:,:,0])
517
+ for i in range(4):
518
+ m = (2 ** (i))
519
+ hiresmask(pmasksf,oh//m, ow//m, height//m,width//m ,torch.zeros(1,height*width //m**2,1),i = i )
520
+
521
+ def hiresmask(masks,oh,ow,nh,nw,at,i = None):
522
+ for key in masks.keys():
523
+ mask = masks[key] if i is None else masks[key][i]
524
+ mask = mask.view(8 if i is None else 1,oh,ow)
525
+ mask = F.resize(mask,(nh,nw))
526
+ mask = mask.reshape_as(at)
527
+ if i is None:
528
+ masks[key] = mask
529
+ else:
530
+ masks[key][i] = mask
531
+
532
+ def makepmask(mask, h, w, th, step, bratio = 1): # make masks from attention cache return [for preview, for attention, for Latent]
533
+ th = th - step * 0.005
534
+ bratio = 1 - bratio
535
+ mask = torch.mean(mask,dim=0)
536
+ mask = mask / mask.max().item()
537
+ mask = torch.where(mask > th ,1,0)
538
+ mask = mask.float()
539
+ mask = mask.view(1,pmaskshw[0][0],pmaskshw[0][1])
540
+ img = torchvision.transforms.functional.to_pil_image(mask)
541
+ img = img.resize((w,h))
542
+ mask = F.resize(mask,(h,w),interpolation=F.InterpolationMode.NEAREST)
543
+ lmask = mask
544
+ mask = mask.reshape(h*w)
545
+ mask = torch.where(mask > 0.1 ,1,0)
546
+ return img,mask * bratio , lmask * bratio
547
+
548
+ def makerrandman(mask, h, w, latent = False): # make masks from attention cache return [for preview, for attention, for Latent]
549
+ mask = mask.float()
550
+ mask = mask.view(1,mask.shape[0],mask.shape[1])
551
+ img = torchvision.transforms.functional.to_pil_image(mask)
552
+ img = img.resize((w,h))
553
+ mask = F.resize(mask,(h,w),interpolation=F.InterpolationMode.NEAREST)
554
+ if latent: return mask
555
+ mask = mask.reshape(h*w)
556
+ mask = torch.round(mask).long()
557
+ return mask
558
+
559
+ def negpipdealer(i,pn):
560
+ negpip = None
561
+ from modules.scripts import scripts_txt2img
562
+ for script in scripts_txt2img.alwayson_scripts:
563
+ if "negpip.py" in script.filename:
564
+ negpip = script
565
+
566
+ if negpip:
567
+ conds = negpip.conds if pn else negpip.unconds
568
+ tokens = negpip.contokens if pn else negpip.untokens
569
+ if conds and len(conds) >= i + 1:
570
+ if conds[i] is not None:
571
+ return [conds[i],tokens[i]]
572
+ else:
573
+ return None
574
+ else:
575
+ return None
regional-prompter-main/scripts/latent.py ADDED
@@ -0,0 +1,534 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from difflib import restore
2
+ import random
3
+ import copy
4
+ from pprint import pprint
5
+ import re
6
+ from typing import Union
7
+ import torch
8
+ from modules import devices, shared, extra_networks
9
+ from modules.script_callbacks import CFGDenoisedParams, CFGDenoiserParams
10
+ from torchvision.transforms import InterpolationMode, Resize # Mask.
11
+ import scripts.attention as att
12
+ from scripts.regions import floatdef
13
+ from scripts.attention import makerrandman
14
+
15
+ islora = True
16
+ in_hr = False
17
+ layer_name = "lora_layer_name"
18
+ orig_Linear_forward = None
19
+ orig_lora_functional = False
20
+ lactive = False
21
+ labug =False
22
+ MINID = 1000
23
+ MAXID = 10000
24
+ LORAID = MINID # Discriminator for repeated lora usage / across gens, presumably.
25
+
26
+ def setloradevice(self):
27
+ global LORAID
28
+ regioner.__init__()
29
+ import lora
30
+ if self.debug : print("change LoRA device for new lora")
31
+ self.log["lora_apply_weights"] = hasattr(lora,"lora_apply_weights")
32
+
33
+ if hasattr(lora,"lora_apply_weights") or not self.isbefore15: # for new LoRA applying
34
+ oldnew =[]
35
+ for l in lora.loaded_loras:
36
+ LORAID = LORAID + 1
37
+ if LORAID > MAXID:
38
+ LORAID = MINID
39
+ old = l.name
40
+ new = l.name = l.name + "_in_RP" + str(LORAID)
41
+ oldnew.append([old,new])
42
+
43
+ for key in l.modules.keys():
44
+ changethedevice(l.modules[key])
45
+
46
+ if regioner.ctl:
47
+ import lora_ctl_network as ctl
48
+ for old,new in oldnew:
49
+ if old in ctl.lora_weights.keys():
50
+ ctl.lora_weights[new] = ctl.lora_weights[old]
51
+
52
+ def setuploras(self):
53
+ global lactive, labug, islora, orig_Linear_forward, orig_lora_functional, layer_name
54
+ lactive = True
55
+ labug = self.debug
56
+ islora = self.isbefore15
57
+ layer_name = self.layer_name
58
+ orig_lora_functional = shared.opts.lora_functional
59
+
60
+ if 150 <= self.ui_version <= 159:
61
+ shared.opts.lora_functional = False
62
+ else:
63
+ shared.opts.lora_functional = True
64
+ orig_Linear_forward = torch.nn.Linear.forward
65
+ torch.nn.Linear.forward = h_Linear_forward
66
+
67
+ def cloneparams(orig,target):
68
+ target.x = orig.x.clone()
69
+ target.image_cond = orig.image_cond.clone()
70
+ target.sigma = orig.sigma.clone()
71
+
72
+ ###################################################
73
+ ###### Latent Method denoise call back
74
+ # Using the AND syntax with shared.batch_cond_uncond = False
75
+ # the U-NET is calculated (the number of prompts divided by AND) + 1 times.
76
+ # This means that the calculation is performed for the area + 1 times.
77
+ # This mechanism is used to apply LoRA by region by changing the LoRA application rate for each U-NET calculation.
78
+ # The problem here is that in the web-ui system, if more than two batch sizes are set,
79
+ # a problem will occur if the number of areas and the batch size are not the same.
80
+ # If the batch is 1 for 3 areas, the calculation is performed 4 times: Area1, Area2, Area3, and Negative.
81
+ # However, if the batch is 2,
82
+ # [Batch1-Area1, Batch1-Area2]
83
+ # [Batch1-Area3, Batch2-Area1]
84
+ # [Batch2-Area2, Batch2-Area3]
85
+ # [Batch1-Negative, Batch2-Negative]
86
+ # and the areas of simultaneous computation will be different.
87
+ # Therefore, it is necessary to change the order in advance.
88
+ # [Batch1-Area1, Batch1-Area2] -> [Batch1-Area1, Batch2-Area1]
89
+ # [Batch1-Area3, Batch2-Area1] -> [Batch1-Area2, Batch2-Area2]
90
+ # [Batch2-Area2, Batch2-Area3] -> [Batch1-Area3, Batch2-Area3]
91
+
92
+ def denoiser_callback_s(self, params: CFGDenoiserParams):
93
+ if "Pro" in self.mode: # in Prompt mode, make masks from sum of attension maps
94
+ if self.x == None : cloneparams(params,self) # return to step 0 if mask is ready
95
+ self.step = params.sampling_step
96
+ self.pfirst = True
97
+
98
+ lim = 1 if self.isxl else 3
99
+
100
+ if len(att.pmaskshw) > lim:
101
+ if "La" in self.calc:
102
+ self.filters = []
103
+ for b in range(self.batch_size):
104
+
105
+ allmask = []
106
+ basemask = None
107
+ for t, th, bratio in zip(self.pe, self.th, self.bratios):
108
+ key = f"{t}-{b}"
109
+ _, _, mask = att.makepmask(att.pmasks[key], params.x.shape[2], params.x.shape[3], th, self.step, bratio = bratio)
110
+ mask = mask.repeat(params.x.shape[1],1,1)
111
+ basemask = 1 - mask if basemask is None else basemask - mask
112
+ if self.ex:
113
+ for l in range(len(allmask)):
114
+ mt = allmask[l] - mask
115
+ allmask[l] = torch.where(mt > 0, 1,0)
116
+ allmask.append(mask)
117
+ if not self.ex:
118
+ sum = torch.stack(allmask, dim=0).sum(dim=0)
119
+ sum = torch.where(sum == 0, 1 , sum)
120
+ allmask = [mask / sum for mask in allmask]
121
+ basemask = torch.where(basemask > 0, 1, 0)
122
+ allmask.insert(0,basemask)
123
+ self.filters.extend(allmask)
124
+ att.maskready = True
125
+ else:
126
+ for t, th, bratio in zip(self.pe, self.th, self.bratios):
127
+ allmask = []
128
+ for hw in att.pmaskshw:
129
+ masks = None
130
+ for b in range(self.batch_size):
131
+ key = f"{t}-{b}"
132
+ _, mask, _ = att.makepmask(att.pmasks[key], hw[0], hw[1], th, self.step, bratio = bratio)
133
+ mask = mask.unsqueeze(0).unsqueeze(-1)
134
+ masks = mask if b ==0 else torch.cat((masks,mask),dim=0)
135
+ allmask.append(mask)
136
+ att.pmasksf[key] = allmask
137
+ att.maskready = True
138
+
139
+ if not self.rebacked:
140
+ cloneparams(self,params)
141
+ self.rebacked = True
142
+
143
+ if "La" in self.calc:
144
+ self.condi = 0
145
+ global in_hr, regioner
146
+ regioner.step = params.sampling_step
147
+ in_hr = self.in_hr
148
+ regioner.u_count = 0
149
+ if "u_list" not in self.log.keys() and hasattr(regioner,"u_llist"):
150
+ self.log["u_list"] = regioner.u_llist.copy()
151
+ if "u_list_hr" not in self.log.keys() and hasattr(regioner,"u_llist") and in_hr:
152
+ self.log["u_list_hr"] = regioner.u_llist.copy()
153
+ xt = params.x.clone()
154
+ ict = params.image_cond.clone()
155
+ st = params.sigma.clone()
156
+ batch = self.batch_size
157
+ areas = xt.shape[0] // batch -1
158
+ # SBM Stale version workaround.
159
+ if hasattr(params,"text_cond"):
160
+ if "DictWithShape" in params.text_cond.__class__.__name__:
161
+ ct = {}
162
+ for key in params.text_cond.keys():
163
+ ct[key] = params.text_cond[key].clone()
164
+ else:
165
+ ct = params.text_cond.clone()
166
+
167
+ for a in range(areas):
168
+ for b in range(batch):
169
+ params.x[b+a*batch] = xt[a + b * areas]
170
+ params.image_cond[b+a*batch] = ict[a + b * areas]
171
+ params.sigma[b+a*batch] = st[a + b * areas]
172
+ # SBM Stale version workaround.
173
+ if hasattr(params,"text_cond"):
174
+ if "DictWithShape" in params.text_cond.__class__.__name__:
175
+ for key in params.text_cond.keys():
176
+ params.text_cond[key][b+a*batch] = ct[key][a + b * areas]
177
+ else:
178
+ params.text_cond[b+a*batch] = ct[a + b * areas]
179
+
180
+ def denoised_callback_s(self, params: CFGDenoisedParams):
181
+ if "La" in self.calc:
182
+ x = params.x
183
+ xt = params.x.clone()
184
+ batch = self.batch_size
185
+ areas = xt.shape[0] // batch -1
186
+
187
+ # x.shape = [batch_size, C, H // 8, W // 8]
188
+
189
+ if not "Pro" in self.mode:
190
+ indrebuild = self.filters == [] or self.filters[0].size() != x[0].size()
191
+
192
+ if indrebuild:
193
+ if "Ran" in self.mode:
194
+ if self.filters == []:
195
+ self.filters = [self.ranbase] + self.ransors if self.usebase else self.ransors
196
+ elif self.filters[0][:,:].size() != x[0,0,:,:].size():
197
+ self.filters = hrchange(self.ransors,x.shape[2], x.shape[3])
198
+ else:
199
+ if "Mask" in self.mode:
200
+ masks = (self.regmasks,self.regbase)
201
+ else:
202
+ masks = self.aratios #makefilters(c,h,w,masks,mode,usebase,bratios,indmask = None)
203
+ self.filters = makefilters(x.shape[1], x.shape[2], x.shape[3],masks, self.mode, self.usebase, self.bratios, "Mas" in self.mode)
204
+ self.filters = [f for f in self.filters]*batch
205
+ else:
206
+ if not att.maskready:
207
+ self.filters = [1,*[0 for a in range(areas - 1)]] * batch
208
+
209
+ if self.debug : print("filterlength : ",len(self.filters))
210
+
211
+ for b in range(batch):
212
+ for a in range(areas) :
213
+ fil = self.filters[a + b*areas]
214
+ if self.debug : print(f"x = {x.size()}i = {a + b*areas}, j = {b + a*batch}, cond = {a + b*areas},filsum = {fil if type(fil) is int else torch.sum(fil)}, uncon = {x.size()[0]+(b-batch)}")
215
+ x[a + b * areas, :, :, :] = xt[b + a*batch, :, :, :] * fil + x[x.size()[0]+(b-batch), :, :, :] * (1 - fil)
216
+
217
+ ######################################################
218
+ ##### Latent Method
219
+
220
+ def hrchange(filters,h, w):
221
+ out = []
222
+ for filter in filters:
223
+ out.append(makerrandman(filter,h,w,True))
224
+ return out
225
+
226
+ # Remove tags from called lora names.
227
+ flokey = lambda x: (x.split("added_by_regional_prompter")[0]
228
+ .split("added_by_lora_block_weight")[0].split("_in_LBW")[0].split("_in_RP")[0])
229
+
230
+ def lora_namer(self, p, lnter, lnur):
231
+ ldict_u = {}
232
+ ldict_te = {}
233
+ lorder = [] # Loras call order for matching with u/te lists.
234
+ import lora as loraclass
235
+ for lora in loraclass.loaded_loras:
236
+ ldict_u[lora.name] =lora.multiplier if self.isbefore15 else lora.unet_multiplier
237
+ ldict_te[lora.name] =lora.multiplier if self.isbefore15 else lora.te_multiplier
238
+
239
+ subprompts = self.current_prompts[0].split("AND")
240
+ ldictlist_u =[ldict_u.copy() for i in range(len(subprompts)+1)]
241
+ ldictlist_te =[ldict_te.copy() for i in range(len(subprompts)+1)]
242
+
243
+ for i, prompt in enumerate(subprompts):
244
+ _, extranets = extra_networks.parse_prompts([prompt])
245
+ calledloras = extranets["lora"]
246
+
247
+ names = ""
248
+ tdict = {}
249
+
250
+ for called in calledloras:
251
+ names = names + called.items[0]
252
+ tdict[called.items[0]] = syntaxdealer(called.items,"unet=",1)
253
+
254
+ for key in ldictlist_u[i].keys():
255
+ shin_key = flokey(key)
256
+ if shin_key in names:
257
+ ldictlist_u[i+1][key] = float(tdict[shin_key])
258
+ ldictlist_te[i+1][key] = float(tdict[shin_key])
259
+ if key not in lorder:
260
+ lorder.append(key)
261
+ else:
262
+ ldictlist_u[i+1][key] = 0
263
+ ldictlist_te[i+1][key] = 0
264
+
265
+ if self.debug: print("Regioner lorder: ",lorder)
266
+ global regioner
267
+ regioner.__init__(self.lstop,self.lstop_hr)
268
+ u_llist = [d.copy() for d in ldictlist_u[1:]]
269
+ u_llist.append(ldictlist_u[0].copy())
270
+ regioner.te_llist = ldictlist_te
271
+ regioner.u_llist = u_llist
272
+ regioner.ndeleter(lnter, lnur, lorder)
273
+ if self.debug:
274
+ print("LoRA regioner : TE list",regioner.te_llist)
275
+ print("LoRA regioner : U list",regioner.u_llist)
276
+
277
+ def syntaxdealer(items,type,index): #type "unet=", "x=", "lwbe="
278
+ for item in items:
279
+ if type in item:
280
+ if "@" in item:return 1 #for loractl
281
+ return item.replace(type,"")
282
+ return items[index] if "@" not in items[index] else 1
283
+
284
+ def makefilters(c,h,w,masks,mode,usebase,bratios,indmask):
285
+ if indmask:
286
+ (regmasks, regbase) = masks
287
+
288
+ filters = []
289
+ x = torch.zeros(c, h, w).to(devices.device)
290
+ if usebase:
291
+ x0 = torch.zeros(c, h, w).to(devices.device)
292
+ i=0
293
+ if indmask:
294
+ ftrans = Resize((h, w), interpolation = InterpolationMode("nearest"))
295
+ for rmask, bratio in zip(regmasks,bratios[0]):
296
+ # Resize mask to current dims.
297
+ # Since it's a mask, we prefer a binary value, nearest is the only option.
298
+ rmask2 = ftrans(rmask.reshape([1, *rmask.shape])) # Requires dimensions N,C,{d}.
299
+ rmask2 = rmask2.reshape([1, h, w])
300
+ fx = x.clone()
301
+ if usebase:
302
+ fx[:,:,:] = fx + rmask2 * (1 - bratio)
303
+ x0[:,:,:] = x0 + rmask2 * bratio
304
+ else:
305
+ fx[:,:,:] = fx + rmask2 * 1
306
+ filters.append(fx)
307
+
308
+ if usebase: # Add base to x0.
309
+ rmask = regbase
310
+ rmask2 = ftrans(rmask.reshape([1, *rmask.shape])) # Requires dimensions N,C,{d}.
311
+ rmask2 = rmask2.reshape([1, h, w])
312
+ x0 = x0 + rmask2
313
+ else:
314
+ for drow in masks:
315
+ for dcell in drow.cols:
316
+ fx = x.clone()
317
+ if "Horizontal" in mode:
318
+ if usebase:
319
+ fx[:,int(h*drow.st):int(h*drow.ed),int(w*dcell.st):int(w*dcell.ed)] = 1 - dcell.base
320
+ x0[:,int(h*drow.st):int(h*drow.ed),int(w*dcell.st):int(w*dcell.ed)] = dcell.base
321
+ else:
322
+ fx[:,int(h*drow.st):int(h*drow.ed),int(w*dcell.st):int(w*dcell.ed)] = 1
323
+ elif "Vertical" in mode:
324
+ if usebase:
325
+ fx[:,int(h*dcell.st):int(h*dcell.ed),int(w*drow.st):int(w*drow.ed)] = 1 - dcell.base
326
+ x0[:,int(h*dcell.st):int(h*dcell.ed),int(w*drow.st):int(w*drow.ed)] = dcell.base
327
+ else:
328
+ fx[:,int(h*dcell.st):int(h*dcell.ed),int(w*drow.st):int(w*drow.ed)] = 1
329
+ filters.append(fx)
330
+ i +=1
331
+ if usebase : filters.insert(0,x0)
332
+ if labug : print(i,len(filters))
333
+
334
+ return filters
335
+
336
+ ######################################################
337
+ ##### Latent Method LoRA changer
338
+
339
+ TE_START_NAME = "transformer_text_model_encoder_layers_0_self_attn_q_proj"
340
+ UNET_START_NAME = "diffusion_model_time_embed_0"
341
+
342
+ TE_START_NAME_XL = "0_transformer_text_model_encoder_layers_0_self_attn_q_proj"
343
+
344
+ class LoRARegioner:
345
+
346
+ def __init__(self,stop=0,stop_hr=0):
347
+ self.te_count = 0
348
+ self.u_count = 0
349
+ self.te_llist = [{}]
350
+ self.u_llist = [{}]
351
+ self.mlist = {}
352
+ self.ctl = False
353
+ self.step = 0
354
+ self.stop = stop
355
+ self.stop_hr = stop_hr
356
+
357
+ try:
358
+ import lora_ctl_network as ctl
359
+ self.ctlweight = copy.deepcopy(ctl.lora_weights)
360
+ for set in self.ctlweight.values():
361
+ for weight in set.values():
362
+ if type(weight) == list:
363
+ self.ctl = True
364
+ except:
365
+ pass
366
+
367
+ def expand_del(self, val, lorder):
368
+ """Broadcast single / comma separated val to lora list.
369
+
370
+ """
371
+ lval = val.split(",")
372
+ if len(lval) > len(lorder):
373
+ lval = lval[:len(lorder)]
374
+ lval = [floatdef(v, 0) for v in lval]
375
+ if len(lval) < len(lorder): # Propagate difference.
376
+ lval.extend([lval[-1]] * (len(lorder) - len(lval)))
377
+ return lval
378
+
379
+ def ndeleter(self, lnter, lnur, lorder = None):
380
+ """Multiply global weights by 0:1 factor.
381
+
382
+ Can be any value, negative too, but doesn't help much.
383
+ """
384
+ if lorder is None:
385
+ lkeys = self.te_llist[0].keys()
386
+ else:
387
+ lkeys = lorder
388
+ lnter = self.expand_del(lnter, lkeys)
389
+ for (key, val) in zip(lkeys, lnter):
390
+ self.te_llist[0][key] *= val
391
+ if lorder is None:
392
+ lkeys = self.u_llist[-1].keys()
393
+ else:
394
+ lkeys = lorder
395
+ lnur = self.expand_del(lnur, lkeys)
396
+ for (key, val) in zip(lkeys, lnur):
397
+ self.u_llist[-1][key] *= val
398
+
399
+ def search_key(self,lora,i,xlist):
400
+ lorakey = lora.loaded_loras[i].name
401
+ if lorakey not in xlist.keys():
402
+ shin_key = flokey(lorakey)
403
+ picked = False
404
+ for mlkey in xlist.keys():
405
+ if mlkey.startswith(shin_key):
406
+ lorakey = mlkey
407
+ picked = True
408
+ if not picked:
409
+ print(f"key is not found in:{xlist.keys()}")
410
+ return lorakey
411
+
412
+ def te_start(self):
413
+ self.mlist = self.te_llist[self.te_count % len(self.te_llist)]
414
+ self.te_count += 1
415
+ import lora
416
+ for i in range(len(lora.loaded_loras)):
417
+ lorakey = self.search_key(lora,i,self.mlist)
418
+ lora.loaded_loras[i].multiplier = self.mlist[lorakey]
419
+ lora.loaded_loras[i].te_multiplier = self.mlist[lorakey]
420
+
421
+ def u_start(self):
422
+ if labug : print("u_count",self.u_count ,"u_count '%' divide", self.u_count % len(self.u_llist))
423
+ self.mlist = self.u_llist[self.u_count % len(self.u_llist)]
424
+ self.u_count += 1
425
+
426
+ stopstep = self.stop_hr if in_hr else self.stop
427
+
428
+ import lora
429
+ for i in range(len(lora.loaded_loras)):
430
+ lorakey = self.search_key(lora,i,self.mlist)
431
+ lora.loaded_loras[i].multiplier = 0 if self.step + 2 > stopstep and stopstep else self.mlist[lorakey]
432
+ lora.loaded_loras[i].unet_multiplier = 0 if self.step + 2 > stopstep and stopstep else self.mlist[lorakey]
433
+ if labug :print(lorakey,lora.loaded_loras[i].multiplier,lora.loaded_loras[i].multiplier )
434
+ if self.ctl:
435
+ import lora_ctl_network as ctl
436
+ key = "hrunet" if in_hr else "unet"
437
+ if self.mlist[lorakey] == 0 or (self.step + 2 > stopstep and stopstep):
438
+ ctl.lora_weights[lorakey][key] = [[0],[0]]
439
+ if labug :print(ctl.lora_weights[lorakey])
440
+ else:
441
+ if key in self.ctlweight[lorakey].keys():
442
+ ctl.lora_weights[lorakey][key] = self.ctlweight[lorakey][key]
443
+ else:
444
+ ctl.lora_weights[lorakey][key] = self.ctlweight[lorakey]["unet"]
445
+ if labug :print(ctl.lora_weights[lorakey])
446
+
447
+ def reset(self):
448
+ self.te_count = 0
449
+ self.u_count = 0
450
+
451
+ regioner = LoRARegioner()
452
+
453
+ ############################################################
454
+ ##### for new lora apply method in web-ui
455
+
456
+ def h_Linear_forward(self, input):
457
+ changethelora(getattr(self, layer_name, None))
458
+ if islora:
459
+ import lora
460
+ return lora.lora_forward(self, input, torch.nn.Linear_forward_before_lora)
461
+ else:
462
+ import networks
463
+ if shared.opts.lora_functional:
464
+ return networks.network_forward(self, input, networks.originals.Linear_forward)
465
+ networks.network_apply_weights(self)
466
+ return torch.nn.Linear_forward_before_network(self, input)
467
+
468
+ def changethelora(name):
469
+ if lactive:
470
+ global regioner
471
+ if name == TE_START_NAME or name == TE_START_NAME_XL:
472
+ regioner.te_start()
473
+ elif name == UNET_START_NAME:
474
+ regioner.u_start()
475
+
476
+ LORAANDSOON = {
477
+ "IA3Module" : "w",
478
+ "LoraKronModule" : "w1",
479
+ "LycoKronModule" : "w1",
480
+ }
481
+
482
+ def changethedevice(module):
483
+ ltype = type(module).__name__
484
+ if ltype == "LoraUpDownModule" or ltype == "LycoUpDownModule" :
485
+ if hasattr(module,"up_model") :
486
+ module.up_model.weight = torch.nn.Parameter(module.up_model.weight.to(devices.device, dtype = torch.float))
487
+ module.down_model.weight = torch.nn.Parameter(module.down_model.weight.to(devices.device, dtype=torch.float))
488
+ else:
489
+ module.up.weight = torch.nn.Parameter(module.up.weight.to(devices.device, dtype = torch.float))
490
+ if hasattr(module.down, "weight"):
491
+ module.down.weight = torch.nn.Parameter(module.down.weight.to(devices.device, dtype=torch.float))
492
+
493
+ elif ltype == "LoraHadaModule" or ltype == "LycoHadaModule":
494
+ module.w1a = torch.nn.Parameter(module.w1a.to(devices.device, dtype=torch.float))
495
+ module.w1b = torch.nn.Parameter(module.w1b.to(devices.device, dtype=torch.float))
496
+ module.w2a = torch.nn.Parameter(module.w2a.to(devices.device, dtype=torch.float))
497
+ module.w2b = torch.nn.Parameter(module.w2b.to(devices.device, dtype=torch.float))
498
+
499
+ if module.t1 is not None:
500
+ module.t1 = torch.nn.Parameter(module.t1.to(devices.device, dtype=torch.float))
501
+
502
+ if module.t2 is not None:
503
+ module.t2 = torch.nn.Parameter(module.t2.to(devices.device, dtype=torch.float))
504
+
505
+ elif ltype == "FullModule":
506
+ module.weight = torch.nn.Parameter(module.weight.to(devices.device, dtype=torch.float))
507
+
508
+ if hasattr(module, 'bias') and module.bias != None:
509
+ module.bias = torch.nn.Parameter(module.bias.to(devices.device, dtype=torch.float))
510
+
511
+
512
+ def restoremodel(p):
513
+ model = p.sd_model
514
+ for name, module in model.named_modules():
515
+ if hasattr(module, "lora_weights_backup"):
516
+ if module.lora_weights_backup is not None:
517
+ if isinstance(module, torch.nn.MultiheadAttention):
518
+ module.in_proj_weight.copy_(module.lora_weights_backup[0])
519
+ module.out_proj.weight.copy_(module.lora_weights_backup[1])
520
+ else:
521
+ module.weight.copy_(module.lora_weights_backup)
522
+ module.lora_weights_backup = None
523
+ module.lora_current_names = None
524
+
525
+ def unloadlorafowards(p):
526
+ global orig_Linear_forward, lactive, labug
527
+ lactive = labug = False
528
+ shared.opts.lora_functional = orig_lora_functional
529
+
530
+ import lora
531
+ lora.loaded_loras.clear()
532
+ if orig_Linear_forward != None :
533
+ torch.nn.Linear.forward = orig_Linear_forward
534
+ orig_Linear_forward = None
regional-prompter-main/scripts/regions.py ADDED
@@ -0,0 +1,831 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import colorsys # Polygon regions.
2
+ from pprint import pprint
3
+ import cv2 # Polygon regions.
4
+ import gradio as gr
5
+ import numpy as np
6
+ import PIL
7
+ import torch
8
+ from modules import devices
9
+
10
+ def lange(l):
11
+ return range(len(l))
12
+
13
+ # SBM Keywords and delimiters for region breaks, following matlab rules.
14
+ # BREAK keyword is now passed through,
15
+ KEYROW = "ADDROW"
16
+ KEYCOL = "ADDCOL"
17
+ KEYBASE = "ADDBASE"
18
+ KEYCOMM = "ADDCOMM"
19
+ KEYBRK = "BREAK"
20
+ KEYPROMPT = "ADDP"
21
+ DELIMROW = ";"
22
+ DELIMCOL = ","
23
+ MCOLOUR = 256
24
+ NLN = "\n"
25
+ DKEYINOUT = { # Out/in, horizontal/vertical or row/col first.
26
+ ("out",False): KEYROW,
27
+ ("in",False): KEYCOL,
28
+ ("out",True): KEYCOL,
29
+ ("in",True): KEYROW,
30
+ }
31
+
32
+ ALLKEYS = [KEYCOMM,KEYROW, KEYCOL, KEYBASE, KEYPROMPT]
33
+ ALLALLKEYS = [KEYCOMM,KEYROW, KEYCOL, KEYBASE, KEYPROMPT, KEYBRK, "AND"]
34
+
35
+ fidentity = lambda x: x
36
+ ffloatd = lambda c: (lambda x: floatdef(x,c))
37
+ fcolourise = lambda: np.random.randint(0,MCOLOUR,size = 3)
38
+ fspace = lambda x: " {} ".format(x)
39
+
40
+ """
41
+ SBM mod: Two dimensional regions (of variable size, NOT a matrix).
42
+ - Adds keywords ADDROW, ADDCOL and respective delimiters for aratios.
43
+ - A/bratios become list dicts: Inner dict of cols (varying length list) + start/end + number of breaks,
44
+ outer layer is rows list.
45
+ First value in each row is the row's ratio, the rest are col ratios.
46
+ This fits prompts going left -> right, top -> down.
47
+ - Unrelated BREAKS are counted per cell, and later extracted as multiple context indices.
48
+ - Each layer is cut up by both row + col ratios.
49
+ - Style improvements: Created classes for rows + cells and functions for some of the splitting.
50
+ - Base prompt overhaul: Added keyword ADDBASE, when present will trigger "use_base" automatically;
51
+ base is excluded from the main prompt for dim calcs; returned to start before hook (+ base break count);
52
+ during hook, context index skips base break count + 1. Rest is applied normally.
53
+ - To specify cols first, use "vertical" mode. eg 1st col:2 rows, 2nd col:1 row.
54
+ In effect, this merely reverses the order of iteration for every row/col loop and whatnot.
55
+ """
56
+
57
+ class RegionCell():
58
+ """Cell used to split a layer to single prompts."""
59
+ def __init__(self, st, ed, base, breaks):
60
+ """Range with start and end values, base weight and breaks count for context splitting."""
61
+ self.st = st # Range for the cell (cols only).
62
+ self.ed = ed
63
+ self.base = base # How much of the base prompt is applied (difference).
64
+ self.breaks = breaks # How many unrelated breaks the prompt contains.
65
+
66
+ def __repr__(self):
67
+ """Debug print."""
68
+ return "({:.2f}:{:.2f})".format(self.st,self.ed)
69
+
70
+ class RegionRow():
71
+ """Row containing cell refs and its own ratio range."""
72
+ def __init__(self, st, ed, cols):
73
+ """Range with start and end values, base weight and breaks count for context splitting."""
74
+ self.st = st # Range for the row.
75
+ self.ed = ed
76
+ self.cols = cols # List of cells.
77
+
78
+ def __repr__(self):
79
+ """Debug print."""
80
+ return "Outer ({:.2f}:{:.2f}), contains {}".format(self.st, self.ed, self.cols) + NLN
81
+
82
+ def floatdef(x, vdef):
83
+ """Attempt conversion to float, use default value on error.
84
+
85
+ Mainly for empty ratios, double commas.
86
+ """
87
+ try:
88
+ return float(x)
89
+ except ValueError:
90
+ print("'{}' is not a number, converted to {}".format(x,vdef))
91
+ return vdef
92
+
93
+ def split_l2(s, kr, kc, indsingles = False, fmap = fidentity, basestruct = None, indflip = False):
94
+ """Split string to 2d list (ie L2) per row and col keys.
95
+
96
+ The output is a list of lists, each of varying length.
97
+ If a L2 basestruct is provided,
98
+ will adhere to its structure using the following broadcast rules:
99
+ - Basically matches row by row of base and new.
100
+ - If a new row is shorter than base, the last value is repeated to fill the row.
101
+ - If both are the same length, copied as is.
102
+ - If new row is longer, then additional values will overflow to the next row.
103
+ This might be unintended sometimes, but allows making all items col separated,
104
+ then the new structure is simply adapted to the base structure.
105
+ - If there are too many values in new, they will be ignored.
106
+ - If there are too few values in new, the last one is repeated to fill base.
107
+ For mixed row + col ratios, singles flag is provided -
108
+ will extract the first value of each row to a separate list,
109
+ and output structure is (row L1,cell L2).
110
+ There MUST be at least one value for row, one value for col when singles is on;
111
+ to prevent errors, the row value is copied to col if it's alone (shouldn't affect results).
112
+ Singles still respects base broadcast rules, and repeats its own last value.
113
+ The fmap function is applied to each cell before insertion to L2;
114
+ if it fails, a default value is used.
115
+ If flipped, the keyword for columns is applied before rows.
116
+ TODO: Needs to be a case insensitive split. Use re.split.
117
+ """
118
+ if indflip:
119
+ tmp = kr
120
+ kr = kc
121
+ kc = tmp
122
+ lret = []
123
+ if basestruct is None:
124
+ lrows = s.split(kr)
125
+ lrows = [row.split(kc) for row in lrows]
126
+ for r in lrows:
127
+ cell = [fmap(x) for x in r]
128
+ lret.append(cell)
129
+ if indsingles:
130
+ lsingles = [row[0] for row in lret]
131
+ lcells = [row[1:] if len(row) > 1 else row for row in lret]
132
+ lret = (lsingles,lcells)
133
+ else:
134
+ lrows = s.split(kr)
135
+ r = 0
136
+ lcells = []
137
+ lsingles = []
138
+ vlast = 1
139
+ for row in lrows:
140
+ row2 = row.split(kc)
141
+ row2 = [fmap(x) for x in row2]
142
+ vlast = row2[-1]
143
+ indstop = False
144
+ while not indstop:
145
+ if (r >= len(basestruct) # Too many cell values, ignore.
146
+ or (len(row2) == 0 and len(basestruct) > 0)): # Cell exhausted.
147
+ indstop = True
148
+ if not indstop:
149
+ if indsingles: # Singles split.
150
+ lsingles.append(row2[0]) # Row ratio.
151
+ if len(row2) > 1:
152
+ row2 = row2[1:]
153
+ if len(basestruct[r]) >= len(row2): # Repeat last value.
154
+ indstop = True
155
+ broadrow = row2 + [row2[-1]] * (len(basestruct[r]) - len(row2))
156
+ r = r + 1
157
+ lcells.append(broadrow)
158
+ else: # Overfilled this row, cut and move to next.
159
+ broadrow = row2[:len(basestruct[r])]
160
+ row2 = row2[len(basestruct[r]):]
161
+ r = r + 1
162
+ lcells.append(broadrow)
163
+ # If not enough new rows, repeat the last one for entire base, preserving structure.
164
+ cur = len(lcells)
165
+ while cur < len(basestruct):
166
+ lcells.append([vlast] * len(basestruct[cur]))
167
+ cur = cur + 1
168
+ lret = lcells
169
+ if indsingles:
170
+ lsingles = lsingles + [lsingles[-1]] * (len(basestruct) - len(lsingles))
171
+ lret = (lsingles,lcells)
172
+ return lret
173
+
174
+ def is_l2(l):
175
+ return isinstance(l[0],list)
176
+
177
+ def l2_count(l):
178
+ cnt = 0
179
+ for row in l:
180
+ cnt + cnt + len(row)
181
+ return cnt
182
+
183
+ def list_percentify(l):
184
+ """Convert each row in L2 to relative part of 100%.
185
+
186
+ Also works on L1, applying once globally.
187
+ """
188
+ lret = []
189
+ if is_l2(l):
190
+ for row in l:
191
+ # row2 = [float(v) for v in row]
192
+ row2 = [v / sum(row) for v in row]
193
+ lret.append(row2)
194
+ else:
195
+ row = l[:]
196
+ # row2 = [float(v) for v in row]
197
+ row2 = [v / sum(row) for v in row]
198
+ lret = row2
199
+ return lret
200
+
201
+ def list_cumsum(l):
202
+ """Apply cumsum to L2 per row, ie newl[n] = l[0:n].sum .
203
+
204
+ Works with L1.
205
+ Actually edits l inplace, idc.
206
+ """
207
+ lret = []
208
+ if is_l2(l):
209
+ for row in l:
210
+ for (i,v) in enumerate(row):
211
+ if i > 0:
212
+ row[i] = v + row[i - 1]
213
+ lret.append(row)
214
+ else:
215
+ row = l[:]
216
+ for (i,v) in enumerate(row):
217
+ if i > 0:
218
+ row[i] = v + row[i - 1]
219
+ lret = row
220
+ return lret
221
+
222
+ def list_rangify(l):
223
+ """Merge every 2 elems in L2 to a range, starting from 0.
224
+
225
+ """
226
+ lret = []
227
+ if is_l2(l):
228
+ for row in l:
229
+ row2 = [0] + row
230
+ row3 = []
231
+ for i in range(len(row2) - 1):
232
+ row3.append([row2[i],row2[i + 1]])
233
+ lret.append(row3)
234
+ else:
235
+ row2 = [0] + l
236
+ row3 = []
237
+ for i in range(len(row2) - 1):
238
+ row3.append([row2[i],row2[i + 1]])
239
+ lret = row3
240
+ return lret
241
+
242
+ def round_dim(x,y):
243
+ """Return division of two numbers, rounding 0.5 up.
244
+
245
+ Seems that dimensions which are exactly 0.5 are rounded up - see 680x488, second iter.
246
+ A simple mod check should get the job done.
247
+ If not, can always brute force the divisor with +-1 on each of h/w.
248
+ """
249
+ return x // y + (x % y >= y // 2)
250
+
251
+
252
+ def isfloat(t):
253
+ try:
254
+ float(t)
255
+ return True
256
+ except Exception:
257
+ return False
258
+
259
+ def ratiosdealer(aratios2,aratios2r):
260
+ aratios2 = list_percentify(aratios2)
261
+ aratios2 = list_cumsum(aratios2)
262
+ aratios2 = list_rangify(aratios2)
263
+ aratios2r = list_percentify(aratios2r)
264
+ aratios2r = list_cumsum(aratios2r)
265
+ aratios2r = list_rangify(aratios2r)
266
+ return aratios2,aratios2r
267
+
268
+ def changecs(ratios):
269
+ ratios = ratios.replace(",","_")
270
+ ratios = ratios.replace(";",",")
271
+ ratios = ratios.replace("_",";")
272
+ return ratios
273
+
274
+ def makeimgtmp(aratios,mode,usecom,usebase, flipper, inprocess = False):
275
+ if mode == "Columns":mode = "Horizontal"
276
+ if mode == "Rows":mode = "Vertical"
277
+
278
+ if flipper: aratios = changecs(aratios)
279
+
280
+ indflip = ("Ver" in mode)
281
+ if DELIMROW not in aratios: # Commas only - interpret as 1d.
282
+ aratios2 = split_l2(aratios, DELIMROW, DELIMCOL, fmap = ffloatd(1), indflip = False)
283
+ aratios2r = [1]
284
+ else:
285
+ (aratios2r,aratios2) = split_l2(aratios, DELIMROW, DELIMCOL,
286
+ indsingles = True, fmap = ffloatd(1), indflip = indflip)
287
+ print(aratios2r,aratios2)
288
+ (aratios2,aratios2r) = ratiosdealer(aratios2,aratios2r)
289
+
290
+ h = w = 128
291
+ fx = np.zeros((h,w, 3), np.uint8)
292
+ # Base image is coloured according to region divisions, roughly.
293
+ for (i,ocell) in enumerate(aratios2r):
294
+ for icell in aratios2[i]:
295
+ # SBM Creep: Colour by delta so that distinction is more reliable.
296
+ if not indflip:
297
+ fx[int(h*ocell[0]):int(h*ocell[1]),int(w*icell[0]):int(w*icell[1]),:] = fcolourise()
298
+ else:
299
+ fx[int(h*icell[0]):int(h*icell[1]),int(w*ocell[0]):int(w*ocell[1]),:] = fcolourise()
300
+ img = PIL.Image.fromarray(fx)
301
+ draw = PIL.ImageDraw.Draw(img)
302
+ c = 0
303
+ def coldealer(col):
304
+ if sum(col) > 380:return "black"
305
+ else:return "white"
306
+ # Add region counters at the top left corner, coloured according to hue.
307
+ for (i,ocell) in enumerate(aratios2r):
308
+ for icell in aratios2[i]:
309
+ if not indflip:
310
+ draw.text((int(w*icell[0]),int(h*ocell[0])),f"{c}",coldealer(fx[int(h*ocell[0]),int(w*icell[0])]))
311
+ else:
312
+ draw.text((int(w*ocell[0]),int(h*icell[0])),f"{c}",coldealer(fx[int(h*icell[0]),int(w*ocell[0])]))
313
+ c += 1
314
+
315
+ # Create ROW+COL template from regions.
316
+ txtkey = fspace(DKEYINOUT[("in", indflip)]) + NLN
317
+ lkeys = [txtkey.join([""] * len(cell)) for cell in aratios2]
318
+ txtkey = fspace(DKEYINOUT[("out", indflip)]) + NLN
319
+ template = txtkey.join(lkeys)
320
+ if usebase:
321
+ template = fspace(KEYBASE) + NLN + template
322
+ if usecom:
323
+ template = fspace(KEYCOMM) + NLN + template
324
+
325
+ if inprocess:
326
+ changer = template.split(NLN)
327
+ changer = [l.strip() for l in changer]
328
+ return changer
329
+
330
+ return img,gr.update(value = template)
331
+
332
+ ################################################################
333
+ ##### matrix
334
+ fcountbrk = lambda x: x.count(KEYBRK)
335
+ fint = lambda x: int(x)
336
+
337
+ def matrixdealer(self, p, aratios, bratios, mode):
338
+ print(aratios, bratios, mode)
339
+ if "Ran" in mode:
340
+ randdealer(self,p,aratios,bratios)
341
+ return
342
+ # The addrow/addcol syntax is better, cannot detect regular breaks without it.
343
+ # In any case, the preferred method will anchor the L2 structure.
344
+ # No prompt formatting is performed. Used only for region calculations
345
+ prompt = p.prompt
346
+ if self.debug: print("in matrixdealer",prompt)
347
+ if KEYCOMM in prompt: prompt = prompt.split(KEYCOMM,1)[1]
348
+ if KEYBASE in prompt: prompt = prompt.split(KEYBASE,1)[1]
349
+
350
+ indflip = ("Ver" in mode)
351
+ if (KEYCOL in prompt.upper() or KEYROW in prompt.upper()):
352
+ breaks = prompt.count(KEYROW) + prompt.count(KEYCOL) + int(self.usebase)
353
+ # Prompt anchors, count breaks between special keywords.
354
+ lbreaks = split_l2(prompt, KEYROW, KEYCOL, fmap = fcountbrk, indflip = indflip)
355
+ if (DELIMROW not in aratios
356
+ and (KEYROW in prompt.upper()) != (KEYCOL in prompt.upper())):
357
+ # By popular demand, 1d integrated into 2d.
358
+ # This works by either adding a single row value (inner),
359
+ # or setting flip to the reverse (outer).
360
+ # Only applies when using just ADDROW / ADDCOL keys, and commas in ratio.
361
+ indflip2 = False
362
+ if (KEYROW in prompt.upper()) == indflip:
363
+ aratios = "1" + DELIMCOL + aratios
364
+ else:
365
+ indflip2 = True
366
+ (aratios2r,aratios2) = split_l2(aratios, DELIMROW, DELIMCOL, indsingles = True,
367
+ fmap = ffloatd(1), basestruct = lbreaks,
368
+ indflip = indflip2)
369
+ else: # Standard ratios, split to rows and cols.
370
+ (aratios2r,aratios2) = split_l2(aratios, DELIMROW, DELIMCOL, indsingles = True,
371
+ fmap = ffloatd(1), basestruct = lbreaks, indflip = indflip)
372
+ # More like "bweights", applied per cell only.
373
+ bratios2 = split_l2(bratios, DELIMROW, DELIMCOL, fmap = ffloatd(0), basestruct = lbreaks, indflip = indflip)
374
+ else:
375
+ breaks = prompt.count(KEYBRK) + int(self.usebase)
376
+ (aratios2r,aratios2) = split_l2(aratios, DELIMROW, DELIMCOL, indsingles = True, fmap = ffloatd(1), indflip = indflip)
377
+ # Cannot determine which breaks matter.
378
+ lbreaks = split_l2("0", KEYROW, KEYCOL, fmap = fint, basestruct = aratios2, indflip = indflip)
379
+ bratios2 = split_l2(bratios, DELIMROW, DELIMCOL, fmap = ffloatd(0), basestruct = lbreaks, indflip = indflip)
380
+ # If insufficient breaks, try to broadcast prompt - a bit dumb.
381
+ breaks = fcountbrk(prompt)
382
+ lastprompt = prompt.rsplit(KEYBRK)[-1]
383
+ if l2_count(aratios2) > breaks:
384
+ prompt = prompt + (fspace(KEYBRK) + lastprompt) * (l2_count(aratios2) - breaks)
385
+ (aratios,aratiosr) = ratiosdealer(aratios2,aratios2r)
386
+ bratios = bratios2
387
+
388
+ # Merge various L2s to cells and rows.
389
+ drows = []
390
+ for r,_ in enumerate(lbreaks):
391
+ dcells = []
392
+ for c,_ in enumerate(lbreaks[r]):
393
+ d = RegionCell(aratios[r][c][0], aratios[r][c][1], bratios[r][c], lbreaks[r][c])
394
+ dcells.append(d)
395
+ drow = RegionRow(aratiosr[r][0], aratiosr[r][1], dcells)
396
+ drows.append(drow)
397
+
398
+ self.aratios = drows
399
+ self.bratios = bratios
400
+
401
+ ################################################################
402
+ ##### inpaint
403
+
404
+ """
405
+ SBM mod: Mask polygon region.
406
+ - Basically a version of inpainting, where polygon outlines are drawn and added to a coloured image.
407
+ - Colours from the image are picked apart for masks corresponding to regions.
408
+ - In new mask mode, masks are stored instead of aratios, and applied to each region forward.
409
+ - Mask can be uploaded (alpha, no save), and standard colours are detected from it.
410
+ - Uncoloured regions default to the first colour detected;
411
+ however, if base mode is used, instead base will be applied to the remainder at 100% strength.
412
+ I think this makes it far more useful. At 0 strength, it will apply ONLY to said regions.
413
+ - V2: Corrects and detects colours from upload.
414
+ - Mask mode presets save mask to a file, which is loaded with the preset.
415
+ - Added -1 colour to clear sections, an eraser.
416
+ """
417
+
418
+ POLYFACTOR = 1.5 # Small lines are detected as shapes.
419
+ COLREG = None # Computed colour regions cache. Array. Extended whenever a new colour is requested.
420
+ REGUSE = dict() # Used regions. Reset on new canvas / upload (preset).
421
+ IDIM = 512
422
+ CBLACK = 255
423
+ MAXCOLREG = 360 - 1 # Hsv goes by degrees.
424
+ VARIANT = 0 # Ensures that the sketch canvas is actually refreshed.
425
+ # Permitted hsv error range for mask upload (due to compression).
426
+ # Mind, wrong hue might throw off the mask entirely and is not corrected.
427
+ # HSV_RANGE = (125,130)
428
+ # HSV_VAL = 128
429
+ HSV_RANGE = (0.49,0.51)
430
+ HSV_VAL = 0.5
431
+ CCHANNELS = 3
432
+ COLWHITE = (255,255,255)
433
+ # Optional replacement mode of nonstandard colours from the mask during upload with white.
434
+ # Pros: Clear and obvious display of regions.
435
+ # Cons: Cannot use the image as a background for tracing (eg openpose or depthmap).
436
+ # Compromise: Do not replace, but show the used regions.
437
+ INDCOLREPL = False
438
+
439
+ def get_colours(img):
440
+ """List colours used in image (as nxc array).
441
+
442
+ """
443
+ return np.unique(img.reshape(-1, img.shape[-1]), axis=0)
444
+
445
+ def generate_unique_colours(n):
446
+ """Generate n visually distinct colors as a list of RGB tuples.
447
+
448
+ Uses the hue of hsv, with balanced saturation & value.
449
+ """
450
+ hsv_colors = [(x*1.0/n, 0.5, 0.5) for x in range(n)]
451
+ rgb_colors = [tuple(int(i * CBLACK) for i in colorsys.hsv_to_rgb(*hsv)) for hsv in hsv_colors]
452
+ return rgb_colors
453
+
454
+ def deterministic_colours(n, lcol = None):
455
+ """Generate n visually distinct & consistent colours as a list of RGB tuples.
456
+
457
+ Uses the hue of hsv, with balanced saturation & value.
458
+ Goes around the cyclical 0-256 and picks each /2 value for every round.
459
+ Continuation rules: If pcyv != ccyv in next round, then we don't care.
460
+ If pcyv == ccyv, we want to get the cval + delta of last elem.
461
+ If lcol > n, will return it as is.
462
+ """
463
+ if n <= 0:
464
+ return None
465
+ pcyc = -1
466
+ cval = 0
467
+ if lcol is None:
468
+ st = 0
469
+ elif n <= len(lcol):
470
+ # return lcol[:n] # Truncating the list is accurate, but pointless.
471
+ return lcol
472
+ else:
473
+ st = len(lcol)
474
+ if st > 0:
475
+ pcyc = np.ceil(np.log2(st))
476
+ # This is erroneous on st=2^n, but we don't care.
477
+ dlt = 1 / (2 ** pcyc)
478
+ cval = dlt + 2 * dlt * (st % (2 ** (pcyc - 1)) - 1)
479
+
480
+ lhsv = []
481
+ for i in range(st,n):
482
+ ccyc = np.ceil(np.log2(i + 1))
483
+ if ccyc == 0: # First col = 0.
484
+ cval = 0
485
+ pcyc = ccyc
486
+ elif pcyc != ccyc: # New cycle, start from the half point between 0 and first point.
487
+ dlt = 1 / (2 ** ccyc)
488
+ cval = dlt
489
+ pcyc = ccyc
490
+ else:
491
+ cval = cval + 2 * dlt # Jumps over existing vals.
492
+ lhsv.append(cval)
493
+ lhsv = [(v, 0.5, 0.5) for v in lhsv] # Hsv conversion only works 0:1.
494
+ lrgb = [colorsys.hsv_to_rgb(*hsv) for hsv in lhsv]
495
+ lrgb = (np.array(lrgb) * (CBLACK + 1)).astype(np.uint8) # Convert to colour uints.
496
+ lrgb = lrgb.reshape(-1, CCHANNELS)
497
+ if lcol is not None:
498
+ lrgb = np.concatenate([lcol, lrgb])
499
+ return lrgb
500
+
501
+ def index_rows(mat):
502
+ """In 2D matrix, add column containing row number.
503
+
504
+ Pandas stuff, can't find a clever way to find first row in np.
505
+ """
506
+ return np.concatenate([np.arange(len(mat)).reshape(-1,1),mat],axis = 1)
507
+
508
+ def detect_image_colours(img, inddict = False):
509
+ """Detect relevant hsv colours in image and clean up the standard mask.
510
+
511
+ Basically, converts colours to hsv, checks which ones are within range,
512
+ converts them to the exact sv value we need, deletes irrelevant colours,
513
+ and creates a list of used colours via a form of np first row lookup.
514
+ Problem: Rgb->hsb and back is not lossless in np / cv. Getting 128->127.
515
+ Looks like the only option is to use colorsys which is contiguous.
516
+ To maximise efficiency, I've applied it to the unique colours instead of entire image,
517
+ and then each colour is mapped via np masking (propagation),
518
+ by adding a third fake dim for each of colours, flattened image.
519
+ It might be possible to use cv2 one way for the filter, but I think that's risky,
520
+ and likely doesn't save much processing (heaviest op is get_colours for large image).
521
+ Creep: Apply erosion so thin regions are ignored. This would need be applied on processing as well.
522
+ """
523
+ global REGUSE
524
+ global COLREG
525
+ global VARIANT
526
+ if img is None: # Do nothing if no image passed.
527
+ return None, None
528
+ VARIANT = 0 # Upload doesn't need variance, it refreshes automatically.
529
+ (h,w,c) = img.shape
530
+ # Get unique colours, create rgb-hsv mapping and filtering.
531
+ # hsv_img = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
532
+ # skimg = cv2.cvtColor(hsv_img, cv2.COLOR_HSV2RGB)
533
+ lrgb = get_colours(img)
534
+ lhsv = np.apply_along_axis(lambda x: colorsys.rgb_to_hsv(*x), axis=-1, arr = lrgb / CBLACK)
535
+ msk = ((lhsv[:,1] >= HSV_RANGE[0]) & (lhsv[:,1] <= HSV_RANGE[1]) &
536
+ (lhsv[:,2] >= HSV_RANGE[0]) & (lhsv[:,2] <= HSV_RANGE[1]))
537
+ lfltrgb = lrgb[msk]
538
+ lflthsv = lhsv[msk]
539
+ lflthsv[:,1:] = HSV_VAL
540
+ if len(lfltrgb) > 0:
541
+ lfltfix = np.apply_along_axis(lambda x: colorsys.hsv_to_rgb(*x), axis=-1, arr=lflthsv)
542
+ lfltfix = (lfltfix * (CBLACK + 1)).astype(np.uint8)
543
+ else: # No relevant colours.
544
+ lfltfix = lfltrgb
545
+ # Mask update each colour in the image.
546
+ # I tried to use isin, but it seems to detect any permutation.
547
+ # It's better to roll colour channel to the front, add extra fake dims,
548
+ # then use direct comparison, relying on np broadcasting.
549
+ # Shape: colour x search x img
550
+ cnt = len(lfltrgb)
551
+ img2 = img.reshape(-1,c,1)
552
+ img2 = np.moveaxis(img2,0,-1)
553
+ lfltrgb2 = np.moveaxis(lfltrgb,-1,0)
554
+ lfltrgb2 = lfltrgb2.reshape(c,-1,1)
555
+ msk2 = (img2 == lfltrgb2).all(axis = 0).reshape(cnt,h,w)
556
+ for i,_ in enumerate(lfltrgb):
557
+ img[msk2[i]] = lfltfix[i]
558
+ # Empty all nonfiltered regions.
559
+ msk3 = ~(msk2.any(axis = 0))
560
+ if INDCOLREPL: # Don't remove nonstandard.
561
+ img[msk3] = COLWHITE
562
+ # Gen all colours, match with the fixed filtered list.
563
+ # I can think of no mathematical function to inverse the colour gen function.
564
+ # Also, imperfect hash, so ~60 colours go over the edge. Should have 100% matches at x2.
565
+ COLREG = deterministic_colours(2 * MAXCOLREG, COLREG)
566
+ cow = index_rows(COLREG)
567
+ regrows = [cow[(COLREG == f).all(axis = 1)] for f in lfltfix]
568
+ # MAX_KEY_VALUE provides a threshold value. Only those colors are added to REGUSE, for which the key values
569
+ # (i.e., the indices of the colors in the 'regrows' array) are less than this threshold.
570
+ # Colors with indices greater than MAX_KEY_VALUE are considered "similar colors" and are not treated as separate masks.
571
+ unique_keys = set(reg[0,0] for reg in regrows if len(reg) > 0)
572
+ # The purpose of this is to reduce the number of colors being processed, particularly for colors that are
573
+ # close to each other, which may be slightly different due to noise in the image or minor differences in color encoding.
574
+ # By setting an appropriate MAX_KEY_VALUE, these minor color differences can be effectively filtered out,
575
+ # thereby reducing the number of colors being processed and making color processing more accurate and efficient.
576
+ MAX_KEY_VALUE = len(unique_keys) + 20
577
+ REGUSE = {reg[0,0]: reg[0,1:].tolist() for reg in regrows if len(reg) > 0 and reg[0,0] <= MAX_KEY_VALUE}
578
+ # REGUSE.discard(COLWHITE)
579
+
580
+ # Must set to dict due to gradio preprocess assertion, in preset load.
581
+ # CONT: This doesn't work. Postprocess expects image. Maybe use dict for preset, not upload.
582
+ if inddict:
583
+ img = {"image":img, "mask":None}
584
+
585
+ return img, None # Clears the upload area. A bit cleaner.
586
+
587
+ def save_mask(img, flpath):
588
+ """Save mask to file.
589
+
590
+ These will be loaded as part of a preset.
591
+ Cv's colour scheme is an annoyance, but avoiding yet another import.
592
+ """
593
+ # Cv's colour scheme is annoying.
594
+ try:
595
+ img = img["image"]
596
+ except Exception:
597
+ pass
598
+ if VARIANT != 0: # Always save without variance.
599
+ img = img[:-VARIANT,:-VARIANT,:]
600
+ img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
601
+ cv2.imwrite(flpath, img)
602
+
603
+ def load_mask(flpath):
604
+ """Load mask from file.
605
+
606
+ Does not edit mask automatically (detect colours).
607
+ """
608
+ try:
609
+ img = cv2.imread(flpath)
610
+ img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
611
+ except Exception: # Could not load mask.
612
+ img = None
613
+ return img
614
+
615
+ def detect_polygons(img,num):
616
+ """Convert stroke + region to standard coloured mask.
617
+
618
+ Negative colours will clear the mask instead, and not ++.
619
+ """
620
+ global COLREG
621
+ global VARIANT
622
+ global REGUSE
623
+
624
+ # I dunno why, but mask has a 4th colour channel, which contains nothing. Alpha?
625
+ if VARIANT != 0:
626
+ out = img["image"][:-VARIANT,:-VARIANT,:CCHANNELS]
627
+ img = img["mask"][:-VARIANT,:-VARIANT,:CCHANNELS]
628
+ else:
629
+ out = img["image"][:,:,:CCHANNELS]
630
+ img = img["mask"][:,:,:CCHANNELS]
631
+
632
+ # Convert the binary image to grayscale
633
+ if img is None:
634
+ img = np.zeros([IDIM,IDIM,CCHANNELS],dtype = np.uint8) + CBLACK # Stupid cv.
635
+ if out is None:
636
+ out = np.zeros_like(img) + CBLACK # Stupid cv.
637
+ bimg = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
638
+
639
+ # Find contours in the image
640
+ # Must reverse colours, otherwise draws an outer box (0->255). Dunno why gradio uses 255 for white anyway.
641
+ contours, hierarchy = cv2.findContours(bimg, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
642
+
643
+ #img2 = np.zeros_like(img) + 255 # Fresh image.
644
+ img2 = out # Update current image.
645
+
646
+ if num < 0:
647
+ color = COLWHITE
648
+ else:
649
+ COLREG = deterministic_colours(int(num) + 1, COLREG)
650
+ color = COLREG[int(num),:]
651
+ REGUSE[num] = color.tolist()
652
+ # Loop through each contour and detect polygons
653
+ for cnt in contours:
654
+ # Approximate the contour to a polygon
655
+ approx = cv2.approxPolyDP(cnt, 0.0001 * cv2.arcLength(cnt, True), True)
656
+
657
+ # If the polygon has 3 or more sides and is fully enclosed, fill it with a random color
658
+ # if len(approx) >= 3: # BAD test.
659
+ if cv2.contourArea(cnt) > cv2.arcLength(cnt, True) * POLYFACTOR: # Better, still messes up on large brush.
660
+ #SBM BUGGY, prevents contours from . cv2.pointPolygonTest(approx, (approx[0][0][0], approx[0][0][1]), False) >= 0:
661
+
662
+ # Draw the polygon on the image with a new random color
663
+ color = [int(v) for v in color] # Opencv is dumb / C based and can't handle an int64 array.
664
+ #cv2.drawContours(img2, [approx], 0, color = color) # Only outer sketch.
665
+ cv2.fillPoly(img2,[approx],color = color)
666
+
667
+ # Convert the grayscale image back to RGB
668
+ #img2 = cv2.cvtColor(img2, cv2.COLOR_GRAY2RGB) # Converting to grayscale is dumb.
669
+
670
+ skimg = create_canvas(img2.shape[0], img2.shape[1], indwipe = False)
671
+ if VARIANT != 0:
672
+ skimg[:-VARIANT,:-VARIANT,:] = img2
673
+ else:
674
+ skimg[:,:,:] = img2
675
+ print("Region sketch size", skimg.shape)
676
+ return skimg, num + 1 if (num >= 0 and num + 1 <= CBLACK) else num
677
+
678
+ def detect_mask(img, num, mult = CBLACK):
679
+ """Extract specific colour and return mask.
680
+
681
+ Multiplier for correct display.
682
+ Also tags colour in case someone uses the upload interface.
683
+ """
684
+ global REGUSE
685
+ try:
686
+ img = img["image"]
687
+ except Exception:
688
+ pass
689
+ if img is None:
690
+ return None
691
+ indnot = False
692
+ if num < 0: # Detect unmasked region.
693
+ if INDCOLREPL: # In replacement mode, all colours are either region or white.
694
+ color = np.array(COLWHITE).reshape([1,1,CCHANNELS])
695
+ else: # In nonrepl mode, mask all the regions and invert.
696
+ color = np.array(list(REGUSE.values())) # nx3
697
+ color = np.moveaxis(color,-1,0) # 3xn
698
+ color = color.reshape(1,1,*color.shape) # 1x1x3xn
699
+ img = img.reshape(*img.shape,1) # Same.
700
+ indnot = True
701
+ else:
702
+ color = deterministic_colours(int(num) + 1)[-1]
703
+ color = color.reshape([1,1,CCHANNELS])
704
+ if indnot: # Negation of a list of regions.
705
+ mask = (~(img == color)).all(-1).all(-1)
706
+ mask = mask * mult
707
+ else:
708
+ mask = ((img == color).all(-1)) * mult
709
+ if mask.sum() > 0 and num >= 0:
710
+ REGUSE[num] = color.reshape(-1).tolist()
711
+ return mask
712
+
713
+ def draw_region(img, num):
714
+ """Simply runs polygon detection, followed by mask on result.
715
+
716
+ Saves extra inconvenient button. Since num is auto incremented, we take the old val.
717
+ """
718
+ img, num2 = detect_polygons(img, num)
719
+ mask = detect_mask(img, num)
720
+ # Gradio is stupid, I have to force feed it a dict so preprocess doesn't break.
721
+ # Disabled here, can only be fixed reliably in preprocess.
722
+ # dimg = {"image":img, "mask": None}
723
+ dimg = img
724
+ return dimg, num2, mask
725
+
726
+ def draw_image(img, inddict = False):
727
+ """Runs colour detection followed by mask on -1 to show which colours are regions.
728
+
729
+ """
730
+ img, clearer = detect_image_colours(img,inddict)
731
+ mask = detect_mask(img, -1)
732
+ dimg = img
733
+ return dimg, clearer, mask
734
+
735
+ def create_canvas(h, w, indwipe = True):
736
+ """New region sketch area.
737
+
738
+ Small variant value is added (and ignored later) due to gradio refresh bug.
739
+ Meant to be used only to start over or when the image dims change.
740
+ """
741
+ global VARIANT
742
+ global REGUSE
743
+ VARIANT = 1 - VARIANT
744
+ if indwipe:
745
+ REGUSE = dict()
746
+ vret = np.zeros(shape = (h + VARIANT, w + VARIANT, CCHANNELS), dtype = np.uint8) + CBLACK
747
+ return vret
748
+
749
+ # SBM In mask mode, grabs each mask from coloured mask image.
750
+ # If there's no base, remainder goes to first mask.
751
+ # If there's a base, it will receive its own remainder mask, applied at 100%.
752
+ def inpaintmaskdealer(self, p, bratios, usebase, polymask):
753
+ prompt = p.prompt
754
+ if self.debug: print("in inpaintmaskdealer",prompt)
755
+ if KEYCOMM in prompt: prompt = prompt.split(KEYCOMM,1)[1]
756
+ if KEYBASE in prompt: prompt = prompt.split(KEYBASE,1)[1]
757
+ # Prep masks.
758
+ self.regmasks = []
759
+ tm = None
760
+ # Sort colour dict by key, return value for masking.
761
+ #for _,c in sorted(REGUSE.items(), key = lambda x: x[0]):
762
+ for c in sorted(REGUSE.keys()):
763
+ m = detect_mask(polymask, c, 1)
764
+ if VARIANT != 0:
765
+ m = m[:-VARIANT,:-VARIANT]
766
+ if m.any():
767
+ if tm is None:
768
+ tm = np.zeros_like(m) # First mask is ignored deliberately.
769
+ if self.usebase: # In base mode, base gets the outer regions.
770
+ tm = tm + m
771
+ else:
772
+ tm = tm + m
773
+ m = m.reshape([1, *m.shape]).astype(np.float16)
774
+ t = torch.from_numpy(m).to(devices.device)
775
+ self.regmasks.append(t)
776
+ # First mask applies to all unmasked regions.
777
+ m = 1 - tm
778
+ m = m.reshape([1, *m.shape]).astype(np.float16)
779
+ t = torch.from_numpy(m).to(devices.device)
780
+ if self.usebase:
781
+ self.regbase = t
782
+ else:
783
+ self.regbase = None
784
+ self.regmasks[0] = t
785
+
786
+ # Simulated region anchroing for base weights.
787
+ breaks = prompt.count(KEYBRK)
788
+ self.bratios = split_l2(bratios, DELIMROW, DELIMCOL, fmap = ffloatd(0),
789
+ basestruct = [[0] * (breaks + 1)], indflip = False)
790
+
791
+ def randdealer(self,p,aratios,bratios):
792
+ # h*w の大きさのテンソルを作成
793
+ tensor = torch.zeros((p.height//8, p.width//8)).to("cuda")
794
+ x,y = int(aratios.split(",")[0]),int(aratios.split(",")[1])
795
+
796
+
797
+
798
+ # 領域ごとのサイズを計算
799
+ dh, dw = p.height//8 // x, p.width//8 // y
800
+ lbreaks = p.prompt.count(KEYBRK) + 1
801
+
802
+ bratios = bratios.split(",") if self.usebase else [0]
803
+ bratios = [float(b) for b in bratios]
804
+ while len(bratios) <= lbreaks:
805
+ bratios.append(bratios[0])
806
+
807
+ # 領域ごとに0から3までのランダムな値を設定
808
+ for i in range(x):
809
+ for j in range(y):
810
+ random_value = torch.randint(0, lbreaks, (1,))
811
+ tensor[i*dh:(i+1)*dh, j*dw:(j+1)*dw] = random_value
812
+ tensors = []
813
+
814
+ ranbase = torch.ones_like(tensor)
815
+
816
+ for i in range(lbreaks):
817
+ add = torch.where(tensor==i, 1*(1-bratios[i]),0)
818
+ tensors.append(add)
819
+ ranbase = ranbase - add
820
+
821
+ drows = []
822
+ dcells = []
823
+ for c in range(lbreaks):
824
+ d = RegionCell(0,0 , 0, 0)
825
+ dcells.append(d)
826
+ drow = RegionRow(0, 1, dcells)
827
+ drows.append(drow)
828
+
829
+ self.aratios = drows
830
+ self.ransors = tensors
831
+ self.ranbase = ranbase
regional-prompter-main/scripts/rp.py ADDED
@@ -0,0 +1,1051 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os.path
2
+ from importlib import reload
3
+ import launch
4
+ from pprint import pprint
5
+ import gradio as gr
6
+ import numpy as np
7
+ from PIL import Image
8
+ import modules.ui
9
+ import modules # SBM Apparently, basedir only works when accessed directly.
10
+ from modules import paths, scripts, shared, extra_networks
11
+ from modules.processing import Processed
12
+ from modules.script_callbacks import (on_ui_settings,
13
+ CFGDenoisedParams, CFGDenoiserParams, on_cfg_denoised, on_cfg_denoiser)
14
+ import scripts.attention
15
+ import scripts.latent
16
+ import scripts.regions
17
+ try:
18
+ reload(scripts.regions) # update without restarting web-ui.bat
19
+ reload(scripts.attention)
20
+ reload(scripts.latent)
21
+ except:
22
+ pass
23
+ import json # Presets.
24
+ from json.decoder import JSONDecodeError
25
+ from scripts.attention import (TOKENS, hook_forwards, reset_pmasks, savepmasks)
26
+ from scripts.latent import (denoised_callback_s, denoiser_callback_s, lora_namer,
27
+ restoremodel, setloradevice, setuploras, unloadlorafowards)
28
+ from scripts.regions import (MAXCOLREG, IDIM, KEYBRK, KEYBASE, KEYCOMM, KEYPROMPT, ALLKEYS, ALLALLKEYS,
29
+ create_canvas, draw_region, #detect_mask, detect_polygons,
30
+ draw_image, save_mask, load_mask, changecs,
31
+ floatdef, inpaintmaskdealer, makeimgtmp, matrixdealer)
32
+
33
+ FLJSON = "regional_prompter_presets.json"
34
+ # Modules.basedir points to extension's dir. script_path or scripts.basedir points to root.
35
+ PTPRESET = modules.scripts.basedir()
36
+ PTPRESETALT = os.path.join(paths.script_path, "scripts")
37
+
38
+ def lange(l):
39
+ return range(len(l))
40
+
41
+ orig_batch_cond_uncond = shared.opts.batch_cond_uncond if hasattr(shared.opts,"batch_cond_uncond") else shared.batch_cond_uncond
42
+
43
+ PRESETSDEF =[
44
+ ["Vertical-3", "Vertical",'1,1,1',"",False,False,False,"Attention",False,"0","0"],
45
+ ["Horizontal-3", "Horizontal",'1,1,1',"",False,False,False,"Attention",False,"0","0"],
46
+ ["Horizontal-7", "Horizontal",'1,1,1,1,1,1,1',"0.2",True,False,False,"Attention",False,"0","0"],
47
+ ["Twod-2-1", "Horizontal",'1,2,3;1,1',"0.2",False,False,False,"Attention",False,"0","0"],
48
+ ]
49
+
50
+ ATTNSCALE = 8 # Initial image compression in attention layers.
51
+
52
+ fhurl = lambda url, label: r"""<a href="{}">{}</a>""".format(url, label)
53
+ GUIDEURL = r"https://github.com/hako-mikan/sd-webui-regional-prompter"
54
+ MATRIXURL = GUIDEURL + r"#2d-region-assignment"
55
+ MASKURL = GUIDEURL + r"#mask-regions-aka-inpaint-experimental-function"
56
+ PROMPTURL = GUIDEURL + r"/blob/main/prompt_en.md"
57
+ PROMPTURL2 = GUIDEURL + r"/blob/main/prompt_ja.md"
58
+
59
+
60
+ def ui_tab(mode, submode):
61
+ """Structures components for mode tab.
62
+
63
+ Semi harcoded but it's clearer this way.
64
+ """
65
+ vret = None
66
+ if mode == "Matrix":
67
+ with gr.Row():
68
+ mguide = gr.HTML(value = fhurl(MATRIXURL, "Matrix mode guide"))
69
+ with gr.Row():
70
+ mmode = gr.Radio(label="Main Splitting", choices=submode, value="Columns", type="value", interactive=True)
71
+ ratios = gr.Textbox(label="Divide Ratio",lines=1,value="1,1",interactive=True,elem_id="RP_divide_ratio",visible=True)
72
+ with gr.Row():
73
+ with gr.Column():
74
+ maketemp = gr.Button(value="visualize and make template")
75
+ template = gr.Textbox(label="template",interactive=True,visible=True)
76
+ flipper = gr.Checkbox(label = 'flip "," and ";"', value = False)
77
+ with gr.Column():
78
+ areasimg = gr.Image(type="pil", show_label = False, height=256, width=256)
79
+ # Need to add maketemp function based on base / common checks.
80
+ vret = [mmode, ratios, maketemp, template, areasimg, flipper]
81
+ elif mode == "Mask":
82
+ with gr.Row():
83
+ xguide = gr.HTML(value = fhurl(MASKURL, "Inpaint+ mode guide"))
84
+ with gr.Row(): # Creep: Placeholder, should probably make this invisible.
85
+ xmode = gr.Radio(label="Mask mode", choices=submode, value="Mask", type="value", interactive=True)
86
+ with gr.Row(): # CREEP: Css magic to make the canvas bigger? I think it's in style.css: #img2maskimg -> height.
87
+ polymask = gr.Image(label = "Do not upload here until bugfix",elem_id="polymask",
88
+ source = "upload", mirror_webcam = False, type = "numpy", tool = "sketch")#.style(height=480)
89
+ with gr.Row():
90
+ with gr.Column():
91
+ num = gr.Slider(label="Region", minimum=-1, maximum=MAXCOLREG, step=1, value=1)
92
+ canvas_width = gr.Slider(label="Inpaint+ Width", minimum=64, maximum=2048, value=512, step=8)
93
+ canvas_height = gr.Slider(label="Inpaint+ Height", minimum=64, maximum=2048, value=512, step=8)
94
+ btn = gr.Button(value = "Draw region + show mask")
95
+ # btn2 = gr.Button(value = "Display mask") # Not needed.
96
+ cbtn = gr.Button(value="Create mask area")
97
+ with gr.Column():
98
+ showmask = gr.Image(label = "Mask", shape=(IDIM, IDIM))
99
+ # CONT: Awaiting fix for https://github.com/gradio-app/gradio/issues/4088.
100
+ uploadmask = gr.Image(label="Upload mask here cus gradio",source = "upload", type = "numpy")
101
+ # btn.click(detect_polygons, inputs = [polymask,num], outputs = [polymask,num])
102
+ btn.click(draw_region, inputs = [polymask, num], outputs = [polymask, num, showmask])
103
+ # btn2.click(detect_mask, inputs = [polymask,num], outputs = [showmask])
104
+ cbtn.click(fn=create_canvas, inputs=[canvas_height, canvas_width], outputs=[polymask])
105
+ uploadmask.upload(fn = draw_image, inputs = [uploadmask], outputs = [polymask, uploadmask, showmask])
106
+
107
+ vret = [xmode, polymask, num, canvas_width, canvas_height, btn, cbtn, showmask, uploadmask]
108
+ elif mode == "Prompt":
109
+ with gr.Row():
110
+ pguide = gr.HTML(value = fhurl(PROMPTURL, "Prompt mode guide"))
111
+ pguide2 = gr.HTML(value = fhurl(PROMPTURL2, "Extended prompt guide (jp)"))
112
+ with gr.Row():
113
+ pmode = gr.Radio(label="Prompt mode", choices=submode, value="Prompt", type="value", interactive=True)
114
+ threshold = gr.Textbox(label = "threshold", value = 0.4, interactive=True)
115
+
116
+ vret = [pmode, threshold]
117
+
118
+ return vret
119
+
120
+ # modes, submodes. Order must be maintained so dict is inadequate. Must have submode for component consistency.
121
+ RPMODES = [
122
+ ("Matrix", ("Columns","Rows","Random")),
123
+ ("Mask", ("Mask",)),
124
+ ("Prompt", ("Prompt", "Prompt-Ex")),
125
+ ]
126
+ fgrprop = lambda x: {"label": x, "id": "t" + x, "elem_id": "RP_" + x}
127
+
128
+ def mode2tabs(mode):
129
+ """Converts mode (in preset) to gradio tab + submodes.
130
+
131
+ I dunno if it's possible to nest components or make them optional (probably not),
132
+ so this is the best we can do.
133
+ """
134
+ vret = ["Nope"] + [None] * len(RPMODES)
135
+ for (i,(k,v)) in enumerate(RPMODES):
136
+ if mode in v:
137
+ vret[0] = k
138
+ vret[i + 1] = mode
139
+ return vret
140
+
141
+ def tabs2mode(tab, *submode):
142
+ """Converts ui tab + submode list to a single value mode.
143
+
144
+ Picks current submode based on tab, nothing clever. Submodes must be unique.
145
+ """
146
+ for (i,(k,_)) in enumerate(RPMODES):
147
+ if tab == k:
148
+ return submode[i]
149
+ return "Nope"
150
+
151
+ def expand_components(l):
152
+ """Converts json preset to component format.
153
+
154
+ Assumes mode is the first value in list.
155
+ """
156
+ l = list(l) # Tuples cannot be altered.
157
+ tabs = mode2tabs(l[0])
158
+ return tabs + l[1:]
159
+
160
+ def compress_components(l):
161
+ """Converts component values to preset format.
162
+
163
+ Assumes tab + submodes are the first values in list.
164
+ """
165
+ l = list(l)
166
+ mode = tabs2mode(*l[:len(RPMODES) + 1])
167
+ return [mode] + l[len(RPMODES) + 1:]
168
+
169
+ class Script(modules.scripts.Script):
170
+ def __init__(self,active = False,mode = "Matrix",calc = "Attention",h = 0, w =0, debug = False, usebase = False, usecom = False, usencom = False, batch = 1,isxl = False, lstop=0, lstop_hr=0):
171
+ self.active = active
172
+ if mode == "Columns": mode = "Horizontal"
173
+ if mode == "Rows": mode = "Vertical"
174
+ self.mode = mode
175
+ self.calc = calc
176
+ self.h = h
177
+ self.w = w
178
+ self.debug = debug
179
+ self.usebase = usebase
180
+ self.usecom = usecom
181
+ self.usencom = usencom
182
+ self.batch_size = batch
183
+ self.isxl = isxl
184
+
185
+ self.aratios = []
186
+ self.bratios = []
187
+ self.divide = 0
188
+ self.count = 0
189
+ self.eq = True
190
+ self.pn = True
191
+ self.hr = False
192
+ self.hr_scale = 0
193
+ self.hr_w = 0
194
+ self.hr_h = 0
195
+ self.in_hr = False
196
+ self.xsize = 0
197
+ self.imgcount = 0
198
+ # for latent mode
199
+ self.filters = []
200
+ self.lora_applied = False
201
+ self.lstop = int(lstop)
202
+ self.lstop_hr = int(lstop_hr)
203
+ # for inpaintmask
204
+ self.regmasks = None
205
+ self.regbase = None
206
+ #for prompt region
207
+ self.pe = []
208
+ self.step = 0
209
+
210
+ #script communicator
211
+ self.hooked = False
212
+ self.condi = 0
213
+
214
+ self.log = {}
215
+
216
+
217
+ def title(self):
218
+ return "Regional Prompter"
219
+
220
+ def show(self, is_img2img):
221
+ return modules.scripts.AlwaysVisible
222
+
223
+ infotext_fields = None
224
+ paste_field_names = []
225
+
226
+ def ui(self, is_img2img):
227
+ filepath = os.path.join(PTPRESET, FLJSON)
228
+
229
+ presets = []
230
+
231
+ presets = loadpresets(filepath)
232
+ presets = LPRESET.update(presets)
233
+
234
+ with gr.Accordion("Regional Prompter", open=False, elem_id="RP_main"):
235
+ with gr.Row():
236
+ active = gr.Checkbox(value=False, label="Active",interactive=True,elem_id="RP_active")
237
+ urlguide = gr.HTML(value = fhurl(GUIDEURL, "Usage guide"))
238
+ with gr.Row():
239
+ # mode = gr.Radio(label="Divide mode", choices=["Horizontal", "Vertical","Mask","Prompt","Prompt-Ex"], value="Horizontal", type="value", interactive=True)
240
+ calcmode = gr.Radio(label="Generation mode", choices=["Attention", "Latent"], value="Attention", type="value", interactive=True)
241
+ with gr.Row(visible=True):
242
+ # ratios = gr.Textbox(label="Divide Ratio",lines=1,value="1,1",interactive=True,elem_id="RP_divide_ratio",visible=True)
243
+ baseratios = gr.Textbox(label="Base Ratio", lines=1,value="0.2",interactive=True, elem_id="RP_base_ratio", visible=True)
244
+ with gr.Row():
245
+ usebase = gr.Checkbox(value=False, label="Use base prompt",interactive=True, elem_id="RP_usebase")
246
+ usecom = gr.Checkbox(value=False, label="Use common prompt",interactive=True,elem_id="RP_usecommon")
247
+ usencom = gr.Checkbox(value=False, label="Use common negative prompt",interactive=True,elem_id="RP_usecommon")
248
+
249
+ # Tabbed modes.
250
+ with gr.Tabs(elem_id="RP_mode") as tabs:
251
+ rp_selected_tab = gr.State("Matrix") # State component to document current tab for gen.
252
+ # ltabs = []
253
+ ltabp = []
254
+ for (i, (md,smd)) in enumerate(RPMODES):
255
+ with gr.TabItem(**fgrprop(md)) as tab: # Tabs with a formatted id.
256
+ # ltabs.append(tab)
257
+ ltabp.append(ui_tab(md, smd))
258
+ # Tab switch tags state component.
259
+ tab.select(fn = lambda tabnum = i: RPMODES[tabnum][0], inputs=[], outputs=[rp_selected_tab])
260
+
261
+ # Hardcode expansion back to components for any specific events.
262
+ (mmode, ratios, maketemp, template, areasimg, flipper) = ltabp[0]
263
+ (xmode, polymask, num, canvas_width, canvas_height, btn, cbtn, showmask, uploadmask) = ltabp[1]
264
+ (pmode, threshold) = ltabp[2]
265
+
266
+ with gr.Accordion("Presets",open = False):
267
+ with gr.Row():
268
+ availablepresets = gr.Dropdown(label="Presets", choices=presets, type="index")
269
+ applypresets = gr.Button(value="Apply Presets",variant='primary',elem_id="RP_applysetting")
270
+ with gr.Row():
271
+ presetname = gr.Textbox(label="Preset Name",lines=1,value="",interactive=True,elem_id="RP_preset_name",visible=True)
272
+ savesets = gr.Button(value="Save to Presets",variant='primary',elem_id="RP_savesetting")
273
+ with gr.Row():
274
+ lstop = gr.Textbox(label="LoRA stop step",value="0",interactive=True,elem_id="RP_ne_tenc_ratio",visible=True)
275
+ lstop_hr = gr.Textbox(label="LoRA Hires stop step",value="0",interactive=True,elem_id="RP_ne_unet_ratio",visible=True)
276
+ lnter = gr.Textbox(label="LoRA in negative textencoder",value="0",interactive=True,elem_id="RP_ne_tenc_ratio",visible=True)
277
+ lnur = gr.Textbox(label="LoRA in negative U-net",value="0",interactive=True,elem_id="RP_ne_unet_ratio",visible=True)
278
+ with gr.Row():
279
+ nchangeand = gr.Checkbox(value=False, label="disable convert 'AND' to 'BREAK'", interactive=True, elem_id="RP_ncand")
280
+ debug = gr.Checkbox(value=False, label="debug", interactive=True, elem_id="RP_debug")
281
+ mode = gr.Textbox(value = "Matrix",visible = False)
282
+
283
+ def changetabs(mode):
284
+ modes = ["Matrix", "Mask", "Prompt"]
285
+ if mode not in modes: mode = "Maxtix"
286
+ return gr.Tabs.update(selected="t"+mode)
287
+
288
+ mode.change(fn = changetabs,inputs=[mode],outputs=[tabs])
289
+ settings = [rp_selected_tab, mmode, xmode, pmode, ratios, baseratios, usebase, usecom, usencom, calcmode, nchangeand, lnter, lnur, threshold, polymask, lstop, lstop_hr, flipper]
290
+
291
+ self.infotext_fields = [
292
+ (active, "RP Active"),
293
+ # (mode, "RP Divide mode"),
294
+ (mode, "RP Divide mode"),
295
+ (mmode, "RP Matrix submode"),
296
+ (xmode, "RP Mask submode"),
297
+ (pmode, "RP Prompt submode"),
298
+ (calcmode, "RP Calc Mode"),
299
+ (ratios, "RP Ratios"),
300
+ (baseratios, "RP Base Ratios"),
301
+ (usebase, "RP Use Base"),
302
+ (usecom, "RP Use Common"),
303
+ (usencom, "RP Use Ncommon"),
304
+ (nchangeand,"RP Change AND"),
305
+ (lnter,"RP LoRA Neg Te Ratios"),
306
+ (lnur,"RP LoRA Neg U Ratios"),
307
+ (threshold,"RP threshold"),
308
+ (lstop,"RP LoRA Stop Step"),
309
+ (lstop_hr,"RP LoRA Hires Stop Step"),
310
+ (flipper, "RP Flip")
311
+ ]
312
+
313
+ for _,name in self.infotext_fields:
314
+ self.paste_field_names.append(name)
315
+
316
+ def setpreset(select, *settings):
317
+ """Load preset from list.
318
+
319
+ SBM: The only way I know how to get the old values in gradio,
320
+ is to pass them all as input.
321
+ Tab mode converts ui to single value.
322
+ """
323
+ # Need to swap all masked images to the source,
324
+ # getting "valueerror: cannot process this value as image".
325
+ # Gradio bug in components.postprocess, most likely.
326
+ settings = [s["image"] if (isinstance(s,dict) and "image" in s) else s for s in settings]
327
+ presets = loadpresets(filepath)
328
+ preset = presets[select]
329
+ preset = loadblob(preset)
330
+ preset = [fmt(preset.get(k, vdef)) for (k,fmt,vdef) in PRESET_KEYS]
331
+ preset = preset[1:] # Remove name.
332
+ preset = expand_components(preset)
333
+ # Change nulls to original value.
334
+ preset = [settings[i] if p is None else p for (i,p) in enumerate(preset)]
335
+ while len(settings) >= len(preset):
336
+ preset.append(0)
337
+ # return [gr.update(value = pr) for pr in preset] # SBM Why update? Shouldn't regular return do the job?
338
+ return preset
339
+
340
+ maketemp.click(fn=makeimgtmp, inputs =[ratios,mmode,usecom,usebase,flipper],outputs = [areasimg,template])
341
+ applypresets.click(fn=setpreset, inputs = [availablepresets, *settings], outputs=settings)
342
+ savesets.click(fn=savepresets, inputs = [presetname,*settings],outputs=availablepresets)
343
+
344
+ return [active, debug, rp_selected_tab, mmode, xmode, pmode, ratios, baseratios,
345
+ usebase, usecom, usencom, calcmode, nchangeand, lnter, lnur, threshold, polymask, lstop, lstop_hr, flipper]
346
+
347
+ def process(self, p, active, debug, rp_selected_tab, mmode, xmode, pmode, aratios, bratios,
348
+ usebase, usecom, usencom, calcmode, nchangeand, lnter, lnur, threshold, polymask, lstop, lstop_hr, flipper):
349
+ if type(polymask) == str:
350
+ try:
351
+ polymask,_,_ = draw_image(np.array(Image.open(polymask)))
352
+ except:
353
+ pass
354
+
355
+ if debug: pprint([active, debug, rp_selected_tab, mmode, xmode, pmode, aratios, bratios,
356
+ usebase, usecom, usencom, calcmode, nchangeand, lnter, lnur, threshold, polymask, lstop, lstop_hr, flipper])
357
+
358
+ tprompt = p.prompt[0] if type(p.prompt) == list else p.prompt
359
+ if not any(key in tprompt for key in ALLALLKEYS) or not active:
360
+ return unloader(self,p)
361
+
362
+ p.extra_generation_params.update({
363
+ "RP Active":active,
364
+ "RP Divide mode": rp_selected_tab,
365
+ "RP Matrix submode": mmode,
366
+ "RP Mask submode": xmode,
367
+ "RP Prompt submode": pmode,
368
+ "RP Calc Mode":calcmode,
369
+ "RP Ratios": aratios,
370
+ "RP Base Ratios": bratios,
371
+ "RP Use Base":usebase,
372
+ "RP Use Common":usecom,
373
+ "RP Use Ncommon": usencom,
374
+ "RP Change AND" : nchangeand,
375
+ "RP LoRA Neg Te Ratios": lnter,
376
+ "RP LoRA Neg U Ratios": lnur,
377
+ "RP threshold": threshold,
378
+ "RP LoRA Stop Step":lstop,
379
+ "RP LoRA Hires Stop Step":lstop_hr,
380
+ "RP Flip": flipper,
381
+ })
382
+
383
+ savepresets("lastrun",rp_selected_tab, mmode, xmode, pmode, aratios,bratios,
384
+ usebase, usecom, usencom, calcmode, nchangeand, lnter, lnur, threshold, polymask,lstop, lstop_hr, flipper)
385
+
386
+ if flipper:aratios = changecs(aratios)
387
+
388
+ self.__init__(active, tabs2mode(rp_selected_tab, mmode, xmode, pmode) ,calcmode ,p.height, p.width, debug, usebase, usecom, usencom, p.batch_size, hasattr(shared.sd_model,"conditioner"),lstop, lstop_hr)
389
+ self.all_prompts = p.all_prompts.copy()
390
+ self.all_negative_prompts = p.all_negative_prompts.copy()
391
+
392
+ # SBM ddim / plms detection.
393
+ self.isvanilla = p.sampler_name in ["DDIM", "PLMS", "UniPC"]
394
+
395
+ if self.h % ATTNSCALE != 0 or self.w % ATTNSCALE != 0:
396
+ # Testing shows a round down occurs in model.
397
+ print("Warning: Nonstandard height / width.")
398
+ self.h = self.h - self.h % ATTNSCALE
399
+ self.w = self.w - self.w % ATTNSCALE
400
+
401
+ if hasattr(p,"enable_hr"): # Img2img doesn't have it.
402
+ self.hr = p.enable_hr
403
+ self.hr_w = (p.hr_resize_x if p.hr_resize_x > p.width else p.width * p.hr_scale)
404
+ self.hr_h = (p.hr_resize_y if p.hr_resize_y > p.height else p.height * p.hr_scale)
405
+ if self.hr_h % ATTNSCALE != 0 or self.hr_w % ATTNSCALE != 0:
406
+ # Testing shows a round down occurs in model.
407
+ print("Warning: Nonstandard height / width for ulscaled size")
408
+ self.hr_h = self.hr_h - self.hr_h % ATTNSCALE
409
+ self.hr_w = self.hr_w - self.hr_w % ATTNSCALE
410
+
411
+ loraverchekcer(self) #check web-ui version
412
+ if not nchangeand: allchanger(p, "AND", KEYBRK) #Change AND to BREAK
413
+ if any(x in self.mode for x in ["Ver","Hor"]):
414
+ keyconverter(aratios, self.mode, usecom, usebase, p) #convert BREAKs to ADDROMM/ADDCOL/ADDROW
415
+ bckeydealer(self, p) #detect COMM/BASE keys
416
+ keycounter(self, p) #count keys and set to self.divide
417
+
418
+ if "Pro" not in self.mode: # skip region assign in prompt mode
419
+ ##### region mode
420
+ if "Mask" in self.mode:
421
+ keyreplacer(p) #change all keys to BREAK
422
+ inpaintmaskdealer(self, p, bratios, usebase, polymask)
423
+
424
+ elif any(x in self.mode for x in ["Ver","Hor","Ran"]):
425
+ matrixdealer(self, p, aratios, bratios, self.mode)
426
+
427
+ ##### calcmode
428
+ if "Att" in calcmode:
429
+ self.handle = hook_forwards(self, p.sd_model.model.diffusion_model)
430
+ if hasattr(shared.opts,"batch_cond_uncond"):
431
+ shared.opts.batch_cond_uncond = orig_batch_cond_uncond
432
+ else:
433
+ shared.batch_cond_uncond = orig_batch_cond_uncond
434
+
435
+ else:
436
+ self.handle = hook_forwards(self, p.sd_model.model.diffusion_model,remove = True)
437
+ setuploras(self)
438
+ # SBM It is vital to use local activation because callback registration is permanent,
439
+ # and there are multiple script instances (txt2img / img2img).
440
+
441
+ elif "Pro" in self.mode: #Prompt mode use both calcmode
442
+ self.ex = "Ex" in self.mode
443
+ if not usebase : bratios = "0"
444
+ self.handle = hook_forwards(self, p.sd_model.model.diffusion_model)
445
+ denoiserdealer(self)
446
+
447
+ neighbor(self,p) #detect other extention
448
+ keyreplacer(p) #replace all keys to BREAK
449
+ commondealer(p, self.usecom, self.usencom) #add commom prompt to all region
450
+ if "La" in self.calc: allchanger(p, KEYBRK,"AND") #replace BREAK to AND in Latent mode
451
+ if tokendealer(self, p): return unloader(self,p) #count tokens and calcrate target tokens
452
+ thresholddealer(self, p, threshold) #set threshold
453
+
454
+ bratioprompt(self, bratios)
455
+ hrdealer(p)
456
+
457
+ print(f"Regional Prompter Active, Pos tokens : {self.ppt}, Neg tokens : {self.pnt}")
458
+ if debug : debugall(self)
459
+
460
+ def before_process_batch(self, p, *args, **kwargs):
461
+ if self.active:
462
+ self.current_prompts = kwargs["prompts"].copy()
463
+ p.disable_extra_networks = False
464
+
465
+ def before_hr(self, p, active, debug, rp_selected_tab, mmode, xmode, pmode, aratios, bratios,
466
+ usebase, usecom, usencom, calcmode,nchangeand, lnter, lnur, threshold, polymask,lstop, lstop_hr, flipper):
467
+ if self.active:
468
+ self.in_hr = True
469
+ if "La" in self.calc:
470
+ setloradevice(self) #change lora device cup to gup and restore model in new web-ui lora method
471
+ lora_namer(self, p, lnter, lnur)
472
+ self.log["before_hr"] = "passed"
473
+ try:
474
+ import lora
475
+ self.log["before_hr_loralist"] = [x.name for x in lora.loaded_loras]
476
+ except:
477
+ pass
478
+
479
+ def process_batch(self, p, active, debug, rp_selected_tab, mmode, xmode, pmode, aratios, bratios,
480
+ usebase, usecom, usencom, calcmode,nchangeand, lnter, lnur, threshold, polymask,lstop, lstop_hr,flipper,**kwargs):
481
+ # print(kwargs["prompts"])
482
+ if self.active:
483
+ resetpcache(p)
484
+ self.in_hr = False
485
+ self.xsize = 0
486
+ # SBM Before_process_batch was added in feb-mar, adding fallback.
487
+ if not hasattr(self,"current_prompts"):
488
+ self.current_prompts = kwargs["prompts"].copy()
489
+ p.all_prompts[p.iteration * p.batch_size:(p.iteration + 1) * p.batch_size] = self.all_prompts[p.iteration * p.batch_size:(p.iteration + 1) * p.batch_size]
490
+ p.all_negative_prompts[p.iteration * p.batch_size:(p.iteration + 1) * p.batch_size] = self.all_negative_prompts[p.iteration * p.batch_size:(p.iteration + 1) * p.batch_size]
491
+ if "Pro" in self.mode:
492
+ reset_pmasks(self)
493
+ if "La" in self.calc:
494
+ setloradevice(self) #change lora device cup to gup and restore model in new web-ui lora method
495
+ lora_namer(self, p, lnter, lnur)
496
+ try:
497
+ import lora
498
+ self.log["loralist"] = [x.name for x in lora.loaded_loras]
499
+ except:
500
+ pass
501
+
502
+ if self.lora_applied: # SBM Don't override orig twice on batch calls.
503
+ pass
504
+ else:
505
+ restoremodel(p)
506
+ denoiserdealer(self)
507
+ self.lora_applied = True
508
+ #escape reload loras in hires-fix
509
+
510
+ def postprocess(self, p, processed, *args):
511
+ if self.active :
512
+ with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file:
513
+ processedx = Processed(p, [], p.seed, "")
514
+ file.write(processedx.infotext(p, 0))
515
+
516
+ if "Pro" in self.mode and not fseti("hidepmask"):
517
+ savepmasks(self, processed)
518
+
519
+ if self.debug : debugall(self)
520
+
521
+ unloader(self, p)
522
+
523
+ def denoiser_callback(self, params: CFGDenoiserParams):
524
+ denoiser_callback_s(self, params)
525
+
526
+ def denoised_callback(self, params: CFGDenoisedParams):
527
+ denoised_callback_s(self, params)
528
+
529
+
530
+ def unloader(self,p):
531
+ if hasattr(self,"handle"):
532
+ print("unloaded")
533
+ hook_forwards(self, p.sd_model.model.diffusion_model, remove=True)
534
+ del self.handle
535
+
536
+ self.__init__()
537
+
538
+ if hasattr(shared.opts,"batch_cond_uncond"):
539
+ shared.opts.batch_cond_uncond = orig_batch_cond_uncond
540
+ else:
541
+ shared.batch_cond_uncond = orig_batch_cond_uncond
542
+
543
+ unloadlorafowards(p)
544
+
545
+ def denoiserdealer(self):
546
+ if self.calc =="Latent": # prompt mode use only denoiser callbacks
547
+ if not hasattr(self,"dd_callbacks"):
548
+ self.dd_callbacks = on_cfg_denoised(self.denoised_callback)
549
+ if hasattr(shared.opts,"batch_cond_uncond"):
550
+ shared.opts.batch_cond_uncond = False
551
+ else:
552
+ shared.batch_cond_uncond = False
553
+
554
+ if not hasattr(self,"dr_callbacks"):
555
+ self.dr_callbacks = on_cfg_denoiser(self.denoiser_callback)
556
+
557
+
558
+ ############################################################
559
+ ##### prompts, tokens
560
+ def commondealer(p, usecom, usencom):
561
+ all_prompts = []
562
+ all_negative_prompts = []
563
+ def comadder(prompt):
564
+ ppl = prompt.split(KEYBRK)
565
+ for i in range(len(ppl)):
566
+ if i == 0:
567
+ continue
568
+ ppl[i] = ppl[0] + ", " + ppl[i]
569
+ ppl = ppl[1:]
570
+ prompt = f"{KEYBRK} ".join(ppl)
571
+ return prompt
572
+
573
+ if usecom:
574
+ for pr in p.all_prompts:
575
+ all_prompts.append(comadder(pr))
576
+ p.all_prompts = all_prompts
577
+ p.prompt = all_prompts[0]
578
+
579
+ if usencom:
580
+ for pr in p.all_negative_prompts:
581
+ all_negative_prompts.append(comadder(pr))
582
+ p.all_negative_prompts = all_negative_prompts
583
+ p.negative_prompt = all_negative_prompts[0]
584
+
585
+ def hrdealer(p):
586
+ p.hr_prompt = p.prompt
587
+ p.hr_negative_prompt = p.negative_prompt
588
+ p.all_hr_prompts = p.all_prompts
589
+ p.all_hr_negative_prompts = p.all_negative_prompts
590
+
591
+ def allchanger(p, a, b):
592
+ p.prompt = p.prompt.replace(a, b)
593
+ for i in lange(p.all_prompts):
594
+ p.all_prompts[i] = p.all_prompts[i].replace(a, b)
595
+ p.negative_prompt = p.negative_prompt.replace(a, b)
596
+ for i in lange(p.all_negative_prompts):
597
+ p.all_negative_prompts[i] = p.all_negative_prompts[i].replace(a, b)
598
+
599
+ def tokendealer(self, p):
600
+ seps = "AND" if "La" in self.calc else KEYBRK
601
+ self.seps = seps
602
+ text, _ = extra_networks.parse_prompt(p.all_prompts[0]) # SBM From update_token_counter.
603
+ ppl = text.split(seps)
604
+ npl = p.all_negative_prompts[0].split(seps)
605
+ targets =[p.split(",")[-1] for p in ppl[1:]]
606
+ pt, nt, ppt, pnt, tt = [], [], [], [], []
607
+
608
+ padd = 0
609
+
610
+ tokenizer = shared.sd_model.conditioner.embedders[0].tokenize_line if self.isxl else shared.sd_model.cond_stage_model.tokenize_line
611
+
612
+ for pp in ppl:
613
+ tokens, tokensnum = tokenizer(pp)
614
+ pt.append([padd, tokensnum // TOKENS + 1 + padd])
615
+ ppt.append(tokensnum)
616
+ padd = tokensnum // TOKENS + 1 + padd
617
+
618
+ if "Pro" in self.mode:
619
+ for target in targets:
620
+ ptokens, tokensnum = tokenizer(ppl[0])
621
+ ttokens, _ = tokenizer(target)
622
+
623
+ i = 1
624
+ tlist = []
625
+ while ttokens[0].tokens[i] != 49407:
626
+ for (j, maintok) in enumerate(ptokens): # SBM Long prompt.
627
+ if ttokens[0].tokens[i] in maintok.tokens:
628
+ tlist.append(maintok.tokens.index(ttokens[0].tokens[i]) + 75 * j)
629
+ i += 1
630
+ if tlist != [] : tt.append(tlist)
631
+
632
+ paddp = padd
633
+ padd = 0
634
+ for np in npl:
635
+ _, tokensnum = tokenizer(np)
636
+ nt.append([padd, tokensnum // TOKENS + 1 + padd])
637
+ pnt.append(tokensnum)
638
+ padd = tokensnum // TOKENS + 1 + padd
639
+
640
+ self.eq = paddp == padd
641
+
642
+ self.pt = pt
643
+ self.nt = nt
644
+ self.pe = tt
645
+ self.ppt = ppt
646
+ self.pnt = pnt
647
+
648
+ notarget = "Pro" in self.mode and tt == []
649
+ if notarget:
650
+ print("No target word is detected in Prompt mode")
651
+ return notarget
652
+
653
+ def thresholddealer(self, p ,threshold):
654
+ if "Pro" in self.mode:
655
+ threshold = threshold.split(",")
656
+ while len(self.pe) >= len(threshold) + 1:
657
+ threshold.append(threshold[0])
658
+ self.th = [floatdef(t, 0.4) for t in threshold] * self.batch_size
659
+ if self.debug :print ("threshold", self.th)
660
+
661
+ def bratioprompt(self, bratios):
662
+ if not "Pro" in self.mode: return self
663
+ bratios = bratios.split(",")
664
+ bratios = [floatdef(b, 0) for b in bratios]
665
+ while len(self.pe) >= len(bratios) + 1:
666
+ bratios.append(bratios[0])
667
+ self.bratios = bratios
668
+
669
+ def neighbor(self,p):
670
+ from modules.scripts import scripts_txt2img
671
+ for script in scripts_txt2img.alwayson_scripts:
672
+ if "negpip.py" in script.filename:
673
+ self.negpip = script
674
+
675
+ try:
676
+ args = p.script_args
677
+ multi = ["MultiDiffusion",'Mixture of Diffusers']
678
+ if any(x in args for x in multi):
679
+ for key in multi:
680
+ if key in args:
681
+ self.nei_multi = [args[args.index(key)+5],args[args.index(key)+6]]
682
+ except:
683
+ pass
684
+
685
+ #####################################################
686
+ ##### Presets - Save and Load Settings
687
+
688
+ fimgpt = lambda flnm, fext, *dirparts: os.path.join(*dirparts, flnm + fext)
689
+
690
+ class PresetList():
691
+ """Preset list must be the same object throughout its lifetime, otherwise updates will err.
692
+
693
+ See gradio issue #4210 for details.
694
+ """
695
+ def __init__(self):
696
+ self.lpr = []
697
+
698
+ def update(self, newpr):
699
+ """Replace all values, return the reference.
700
+
701
+ Will convert dicts to the names only.
702
+ Might be more efficient to add the new names only, but meh.
703
+ """
704
+ if len(newpr) > 0 and isinstance(newpr[0],dict):
705
+ newpr = [pr["name"] for pr in newpr]
706
+ self.lpr.clear()
707
+ self.lpr.extend(newpr)
708
+ return self.lpr
709
+
710
+ def get(self):
711
+ return self.lpr
712
+
713
+ class JsonMask():
714
+ """Mask saved as image with some editing work.
715
+
716
+ """
717
+ blobdir = "regional_masks"
718
+ ext = ".png"
719
+
720
+ def __init__(self, img):
721
+ self.img = img
722
+
723
+ def makepath(self, name):
724
+ pt = fimgpt(name, self.ext, PTPRESET, self.blobdir)
725
+ os.makedirs(os.path.dirname(pt), exist_ok = True)
726
+ return pt
727
+
728
+ def save(self, name, preset = None):
729
+ """Save image to subdir.
730
+
731
+ Only saved when in mask mode - Hardcoded, don't have a better idea atm.
732
+ """
733
+ if (preset is None) or (preset[1] == "Mask"): # Check mode.
734
+ save_mask(self.img, self.makepath(name))
735
+ return name
736
+ return None
737
+
738
+ def load(self, name, preset = None):
739
+ """Load image from subdir (no editing, that comes later).
740
+
741
+ Prefer to use the given key, rather than name. SBM CONT: Load / save in dict mode? Debugging needed.
742
+ """
743
+ if name is None or self.img is None:
744
+ return None
745
+ return load_mask(self.makepath(self.img))
746
+
747
+ LPRESET = PresetList()
748
+
749
+ fcountbrk = lambda x: x.count(KEYBRK)
750
+ fint = lambda x: int(x)
751
+
752
+ # Json formatters.
753
+ fjstr = lambda x: x.strip()
754
+ #fjbool = lambda x: (x.upper() == "TRUE" or x.upper() == "T")
755
+ fjbool = lambda x: x # Json can store booleans reliably.
756
+ fjmask = lambda x: draw_image(x, inddict = False)[0] # Ignore mask reset value.
757
+
758
+ # (json_name, value_format, default)
759
+ # If default = none then will use current gradio value.
760
+ PRESET_KEYS = [
761
+ ("name",fjstr,"") , # Name is special, preset's key.
762
+ ("mode", fjstr, None) ,
763
+ ("ratios", fjstr, None) ,
764
+ ("baseratios", fjstr, None) ,
765
+ ("usebase", fjbool, None) ,
766
+ ("usecom", fjbool, False) ,
767
+ ("usencom", fjbool, False) ,
768
+ ("calcmode", fjstr, "Attention") , # Generation mode.
769
+ ("nchangeand", fjbool, False) ,
770
+ ("lnter", fjstr, "0") ,
771
+ ("lnur", fjstr, "0") ,
772
+ ("threshold", fjstr, "0") ,
773
+ ("polymask", fjmask, "") , # Mask has special corrections and logging.
774
+ ]
775
+ # (json_name,blob_class)
776
+ # Handles save + lazy load of blob data outside of presets.
777
+ BLOB_KEYS = {
778
+ "polymask": JsonMask
779
+ }
780
+
781
+ def saveblob(preset):
782
+ """Preset variables saved externally (blob).
783
+
784
+ Returns modified list containing the refernces instead of data.
785
+ Currently, this includes polymask, which is saved as an image,
786
+ with a filename = preset.
787
+ A blob class should contain a save method which returns the reference.
788
+ """
789
+ preset = list(preset) # Tuples don't have copy.
790
+ for (i,(vkey,vfun,vdef)) in enumerate(PRESET_KEYS):
791
+ if vkey in BLOB_KEYS:
792
+ # Func should accept raw form and convert it to a class.
793
+ x = BLOB_KEYS[vkey](preset[i])
794
+ # Class should have a save func given identifier, returning an access key.
795
+ x = x.save(preset[0], preset)
796
+ # Update the preset.
797
+ preset[i] = x
798
+ return preset
799
+
800
+ def loadblob(preset):
801
+ """Load blob presets based on key.
802
+
803
+ Returns modified list containing the refernces instead of
804
+ Currently, this includes polymask, which is saved as an image,
805
+ with a filename = preset.
806
+ A blob class should contain a load method which retrieves the data based on reference.
807
+ """
808
+ for (vkey,vval) in BLOB_KEYS.items():
809
+ # Func should accept refrence form and convert it to a class.
810
+ x = vval(preset.get(vkey))
811
+ # Class should have a load func given identifier, returning data.
812
+ x = x.load(preset["name"], preset)
813
+ # Update the preset.
814
+ preset[vkey] = x
815
+ return preset
816
+
817
+ def savepresets(*settings):
818
+ # NAME must come first.
819
+ name = settings[0]
820
+ settings = [name] + compress_components(settings[1:])
821
+ settings = saveblob(settings)
822
+
823
+ # path_root = modules.scripts.basedir()
824
+ # filepath = os.path.join(path_root, "scripts", "regional_prompter_presets.json")
825
+ filepath = os.path.join(PTPRESET, FLJSON)
826
+
827
+ try:
828
+ with open(filepath, mode='r', encoding="utf-8") as f:
829
+ # presets = json.loads(json.load(f))
830
+ presets = json.load(f)
831
+ pr = {PRESET_KEYS[i][0]:settings[i] for i,_ in enumerate(PRESET_KEYS)}
832
+ # SBM Ordereddict might be better than list, quick search.
833
+ written = False
834
+ # if name == "lastrun": # SBM We should check the preset is unique in any case.
835
+ for i, preset in enumerate(presets):
836
+ if name == preset["name"]:
837
+ # if "lastrun" in preset["name"]:
838
+ presets[i] = pr
839
+ written = True
840
+ if not written:
841
+ presets.append(pr)
842
+ with open(filepath, mode='w', encoding="utf-8") as f:
843
+ # json.dump(json.dumps(presets), f, indent = 2)
844
+ json.dump(presets, f, indent = 2)
845
+ except Exception as e:
846
+ print(e)
847
+
848
+ presets = loadpresets(filepath)
849
+ presets = LPRESET.update(presets)
850
+ return gr.update(choices=presets)
851
+
852
+ def presetfallback():
853
+ """Swaps main json dir to alt if exists, attempts reload.
854
+
855
+ """
856
+ global PTPRESET
857
+ global PTPRESETALT
858
+
859
+ if PTPRESETALT is not None:
860
+ print("Unknown preset error, fallback.")
861
+ PTPRESET = PTPRESETALT
862
+ PTPRESETALT = None
863
+ return loadpresets(PTPRESET)
864
+ else: # Already attempted swap.
865
+ print("Presets could not be loaded.")
866
+ return None
867
+
868
+ def loadpresets(filepath):
869
+ presets = []
870
+ try:
871
+ with open(filepath, encoding="utf-8") as f:
872
+ # presets = json.loads(json.load(f))
873
+ presets = json.load(f)
874
+ # presets = loadblob(presets) # DO NOT load all blobs - that's the point.
875
+ except OSError as e:
876
+ print("Init / preset error.")
877
+ presets = initpresets(filepath)
878
+ except TypeError:
879
+ print("Corrupted preset file, resetting.")
880
+ presets = initpresets(filepath)
881
+ except JSONDecodeError:
882
+ print("Preset file could not be decoded.")
883
+ presets = initpresets(filepath)
884
+ return presets
885
+
886
+ def initpresets(filepath):
887
+ lpr = PRESETSDEF
888
+ # if not os.path.isfile(filepath):
889
+ try:
890
+ with open(filepath, mode='w', encoding="utf-8") as f:
891
+ lprj = []
892
+ for pr in lpr:
893
+ plen = min(len(PRESET_KEYS), len(pr)) # Future setting additions ignored.
894
+ prj = {PRESET_KEYS[i][0]:pr[i] for i in range(plen)}
895
+ lprj.append(prj)
896
+ #json.dump(json.dumps(lprj), f, indent = 2)
897
+ json.dump(lprj, f, indent = 2)
898
+ return lprj
899
+ except Exception as e:
900
+ return presetfallback()
901
+
902
+ #####################################################
903
+ ##### Global settings
904
+
905
+ EXTKEY = "regprp"
906
+ EXTNAME = "Regional Prompter"
907
+ # (id, label, type, extra_parms)
908
+ EXTSETS = [
909
+ ("debug", "(PLACEHOLDER, USE THE ONE IN 2IMG) Enable debug mode", "check", dict()),
910
+ ("hidepmask", "Hide subprompt masks in prompt mode", "check", dict()),
911
+
912
+ ]
913
+ # Dynamically constructed list of default values, because shared doesn't allocate a value automatically.
914
+ # (id: def)
915
+ DEXTSETV = dict()
916
+ fseti = lambda x: shared.opts.data.get(EXTKEY + "_" + x, DEXTSETV[x])
917
+
918
+ class Setting_Component():
919
+ """Creates gradio components with some standard req values.
920
+
921
+ All must supply an id (used in code), label, component type.
922
+ Default value and specific type settings can be overridden.
923
+ """
924
+ section = (EXTKEY, EXTNAME)
925
+ def __init__(self, cid, clabel, ctyp, vdef = None, **kwargs):
926
+ self.cid = EXTKEY + "_" + cid
927
+ self.clabel = clabel
928
+ self.ctyp = ctyp
929
+ method = getattr(self, self.ctyp)
930
+ method(**kwargs)
931
+ if vdef is not None:
932
+ self.vdef = vdef
933
+
934
+ def get(self):
935
+ """Get formatted setting.
936
+
937
+ Input for shared.opts.add_option().
938
+ """
939
+ if self.ctyp == "textb":
940
+ return (self.cid, shared.OptionInfo(self.vdef, self.clabel, section = self.section))
941
+ return (self.cid, shared.OptionInfo(self.vdef, self.clabel,
942
+ self.ccomp, self.cparms, section = self.section))
943
+
944
+ def textb(self, **kwargs):
945
+ """Textbox unusually requires no component.
946
+
947
+ """
948
+ self.ccomp = gr.Textbox
949
+ self.vdef = ""
950
+ self.cparms = {}
951
+ self.cparms.update(kwargs)
952
+
953
+ def check(self, **kwargs):
954
+ self.ccomp = gr.Checkbox
955
+ self.vdef = False
956
+ self.cparms = {"interactive": True}
957
+ self.cparms.update(kwargs)
958
+
959
+ def slider(self, **kwargs):
960
+ self.ccomp = gr.Slider
961
+ self.vdef = 0
962
+ self.cparms = {"minimum": 1, "maximum": 10, "step": 1}
963
+ self.cparms.update(kwargs)
964
+
965
+ def ext_on_ui_settings():
966
+ for (cid, clabel, ctyp, kwargs) in EXTSETS:
967
+ comp = Setting_Component(cid, clabel, ctyp, **kwargs)
968
+ opt = comp.get()
969
+ shared.opts.add_option(*opt)
970
+ DEXTSETV[cid] = comp.vdef
971
+
972
+ def debugall(self):
973
+ print(f"mode : {self.mode}\ncalcmode : {self.calc}\nusebase : {self.usebase}")
974
+ print(f"base ratios : {self.bratios}\nusecommon : {self.usecom}\nusenegcom : {self.usencom}")
975
+ print(f"divide : {self.divide}\neq : {self.eq}")
976
+ print(f"tokens : {self.ppt},{self.pnt},{self.pt},{self.nt}")
977
+ print(f"ratios : {self.aratios}\n")
978
+ print(f"prompt : {self.pe}")
979
+ print(f"env : before15:{self.isbefore15},isxl:{self.isxl}")
980
+ print(f"loras{self.log}")
981
+
982
+ def bckeydealer(self, p):
983
+ '''
984
+ detect COMM/BASE keys and set flags and change to BREAK
985
+ '''
986
+ if KEYCOMM in p.prompt:
987
+ self.usecom = True
988
+ if self.usecom and KEYCOMM not in p.prompt:
989
+ p.prompt = p.prompt.replace(KEYBRK,KEYCOMM,1)
990
+
991
+ if KEYCOMM in p.negative_prompt:
992
+ self.usencom = True
993
+ if self.usencom and KEYCOMM not in p.negative_prompt:
994
+ p.negative_prompt = p.negative_prompt.replace(KEYBRK,KEYCOMM,1)
995
+
996
+ if KEYBASE in p.prompt:
997
+ self.usebase = True
998
+ if self.usebase and KEYBASE not in p.prompt:
999
+ p.prompt = p.prompt.replace(KEYBRK,KEYBASE,1)
1000
+
1001
+ if KEYPROMPT in p.prompt.upper():
1002
+ self.mode = "Prompt"
1003
+
1004
+ def keyconverter(aratios,mode,usecom,usebase,p):
1005
+ '''convert BREAKS to ADDCOMM/ADDBASE/ADDCOL/ADDROW'''
1006
+ keychanger = makeimgtmp(aratios,mode,usecom,usebase,False, inprocess = True)
1007
+ keychanger = keychanger[:-1]
1008
+ #print(keychanger,p.prompt)
1009
+ for change in keychanger:
1010
+ if change == KEYCOMM and KEYCOMM in p.prompt: continue
1011
+ if change == KEYBASE and KEYBASE in p.prompt: continue
1012
+ p.prompt= p.prompt.replace(KEYBRK,change,1)
1013
+
1014
+ def keyreplacer(p):
1015
+ '''
1016
+ replace all separators to BREAK
1017
+ p.all_prompt and p.all_negative_prompt
1018
+ '''
1019
+ for key in ALLKEYS:
1020
+ for i in lange(p.all_prompts):
1021
+ p.all_prompts[i]= p.all_prompts[i].replace(key,KEYBRK)
1022
+
1023
+ for i in lange(p.all_negative_prompts):
1024
+ p.all_negative_prompts[i] = p.all_negative_prompts[i].replace(key,KEYBRK)
1025
+
1026
+ def keycounter(self, p):
1027
+ pc = sum([p.prompt.count(text) for text in ALLALLKEYS])
1028
+ npc = sum([p.negative_prompt.count(text) for text in ALLALLKEYS])
1029
+ self.divide = [pc + 1, npc + 1]
1030
+
1031
+ def resetpcache(p):
1032
+ p.cached_c = [None,None]
1033
+ p.cached_uc = [None,None]
1034
+ p.cached_hr_c = [None, None]
1035
+ p.cached_hr_uc = [None, None]
1036
+
1037
+ def loraverchekcer(self):
1038
+ try:
1039
+ self.ui_version = int(launch.git_tag().replace("v","").replace(".",""))
1040
+ except:
1041
+ self.ui_version = 100
1042
+
1043
+ try:
1044
+ import lora
1045
+ self.isbefore15 = "assign_lora_names_to_compvis_modules" in dir(lora)
1046
+ self.layer_name = "lora_layer_name" if self.isbefore15 else "network_layer_name"
1047
+ except:
1048
+ self.isbefore15 = False
1049
+ self.layer_name = "lora_layer_name"
1050
+
1051
+ on_ui_settings(ext_on_ui_settings)
regional-prompter-main/style.css ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #polymask, #polymask > .h-60, #polymask > .h-60 > div, #polymask > .h-60 > div > img
2
+ {
3
+ height: 512px !important;
4
+ max-height: 512px !important;
5
+ min-height: 512px !important;
6
+ }