├── LICENSE.txt
├── Line2Normalmap.py
├── Line2Normalmap_ReadMe.txt
├── Line2Normalmap_gui.py
├── Line2Normalmap_install.ps1
├── Line2Normalmap_model_DL.cmd
├── Line2Normalmap_modules
├── config_states.py
├── gitpython_hack.py
├── launch_utils_Line2Normalmap.py
├── sd1_clip.py
├── sd2_clip.py
├── sdxl_clip.py
├── shared_cmd_options.py
└── ui_extensions.py
├── Line2Normalmap_setup.py
├── README.md
├── utils
├── application.py
├── request_api.py
└── tagger.py
└── venv.cmd
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 | Version 3, 19 November 2007
3 |
4 | Copyright (c) 2023 AUTOMATIC1111
5 |
6 | Copyright (C) 2007 Free Software Foundation, Inc.
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 |
635 | Copyright (C)
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 by
639 | 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 .
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 | .
664 |
--------------------------------------------------------------------------------
/Line2Normalmap.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | from modules import launch_utils_Line2Normalmap
4 |
5 | # Default arguments
6 | default_args = ["--nowebui", "--xformers", "--skip-python-version-check", "--skip-torch-cuda-test", "--skip-torch-cuda-test"]
7 |
8 | # Check if custom arguments are provided; if not, append default arguments
9 | if len(sys.argv) == 1:
10 | sys.argv.extend(default_args)
11 | else:
12 | # 独自の引数がある場合、default_argsの中で未指定の引数のみを追加する
13 | # 引数を解析しやすくするため、setを使用
14 | provided_args_set = set(sys.argv)
15 | for arg in default_args:
16 | # "--"で始まるオプションのみを考慮する
17 | if arg.startswith("--"):
18 | option = arg.split("=")[0] if "=" in arg else arg
19 | if option not in provided_args_set:
20 | sys.argv.append(arg)
21 | else:
22 | # "--"で始まらないオプションは直接追加
23 | sys.argv.append(arg)
24 |
25 | args = launch_utils_Line2Normalmap.args
26 |
27 | start = launch_utils_Line2Normalmap.start
28 |
29 | def main():
30 | start()
31 |
32 | if __name__ == "__main__":
33 | main()
--------------------------------------------------------------------------------
/Line2Normalmap_ReadMe.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tori29umai0123/Line2Normalmap/cbb6a109eddcfe02225e1a2ef1278f6fd9b665e3/Line2Normalmap_ReadMe.txt
--------------------------------------------------------------------------------
/Line2Normalmap_gui.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 | import os
3 | import time
4 | import socket
5 | import atexit
6 | from threading import Thread, Event
7 | import signal
8 |
9 | from modules import timer
10 | from modules import initialize_util
11 | from modules import initialize
12 | from modules_forge.initialization import initialize_forge
13 | from modules_forge import main_thread
14 | from utils import application
15 |
16 |
17 | from uvicorn import Config, Server
18 | import asyncio
19 |
20 |
21 | startup_timer = timer.startup_timer
22 | startup_timer.record("launcher")
23 |
24 | initialize_forge()
25 | initialize.imports()
26 | initialize.check_versions()
27 | initialize.initialize()
28 |
29 | shutdown_event = Event()
30 |
31 | def create_api(app):
32 | from modules.api.api import Api
33 | from modules.call_queue import queue_lock
34 |
35 | api = Api(app, queue_lock)
36 | return api
37 |
38 | def is_port_in_use(port):
39 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
40 | return s.connect_ex(('localhost', port)) == 0
41 |
42 | def find_available_port(starting_port):
43 | port = starting_port
44 | while is_port_in_use(port):
45 | print(f"Port {port} is in use, trying next one.")
46 | port += 1
47 | return port
48 |
49 | async def api_only_worker(shutdown_event: Event):
50 | from fastapi import FastAPI
51 | import uvicorn
52 |
53 | app = FastAPI()
54 |
55 | app = FastAPI()
56 | initialize_util.setup_middleware(app)
57 | api = create_api(app)
58 |
59 | from modules import script_callbacks
60 | script_callbacks.before_ui_callback()
61 | script_callbacks.app_started_callback(None, app)
62 |
63 | print(f"Startup time: {startup_timer.summary()}.")
64 |
65 | starting_port = 7861
66 | port = find_available_port(starting_port)
67 |
68 | def run_server():
69 | uvicorn.run(app, host="127.0.0.1", port=port)
70 |
71 | server_thread = Thread(target=run_server)
72 | server_thread.daemon = True
73 | server_thread.start()
74 |
75 | config = uvicorn.Config(app=app, host="127.0.0.1", port={port}, log_level="info")
76 | server = uvicorn.Server(config=config)
77 |
78 | loop = asyncio.get_event_loop()
79 | loop.create_task(server.serve())
80 |
81 | application.start(f"http://127.0.0.1:{port}")
82 |
83 | shutdown_event.set()
84 |
85 | await os.kill(os.getpid(), signal.SIGTERM)
86 |
87 | def api_only():
88 | loop = asyncio.get_event_loop()
89 | loop.run_until_complete(api_only_worker(shutdown_event))
90 |
91 | def on_exit():
92 | print("Cleaning up...")
93 | shutdown_event.set()
94 |
95 | if __name__ == "__main__":
96 | atexit.register(on_exit)
97 | api_only()
98 | main_thread.loop()
--------------------------------------------------------------------------------
/Line2Normalmap_install.ps1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tori29umai0123/Line2Normalmap/cbb6a109eddcfe02225e1a2ef1278f6fd9b665e3/Line2Normalmap_install.ps1
--------------------------------------------------------------------------------
/Line2Normalmap_model_DL.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 | setlocal enabledelayedexpansion
3 |
4 | REM モデルディレクトリの基本パスを実行ディレクトリのmodelsサブディレクトリに設定
5 | set "dpath=%~dp0models"
6 |
7 | REM Taggerモデルダウンロード
8 | set "MODEL_DIR=%dpath%\tagger"
9 | set "MODEL_ID=SmilingWolf/wd-swinv2-tagger-v3"
10 | set "FILES=config.json model.onnx selected_tags.csv sw_jax_cv_config.json"
11 |
12 | if not exist "%MODEL_DIR%" mkdir "%MODEL_DIR%"
13 |
14 | for %%f in (%FILES%) do (
15 | set "FILE_PATH=%MODEL_DIR%\%%f"
16 | if not exist "!FILE_PATH!" (
17 | curl -L "https://huggingface.co/%MODEL_ID%/resolve/main/%%f" -o "!FILE_PATH!"
18 | echo Downloaded %%f
19 | ) else (
20 | echo %%f already exists.
21 | )
22 | )
23 |
24 | REM Loraモデルダウンロード
25 | set "MODEL_DIR=%dpath%\Lora"
26 | set "MODEL_ID=tori29umai/SDXL_shadow"
27 | set "FILES=sdxl-testlora-normalmap_04b_dim32.safetensors"
28 |
29 | if not exist "%MODEL_DIR%" mkdir "%MODEL_DIR%"
30 | for %%f in (%FILES%) do (
31 | set "FILE_PATH=%MODEL_DIR%\%%f"
32 | if not exist "!FILE_PATH!" (
33 | curl -L "https://huggingface.co/%MODEL_ID%/resolve/main/%%f" -o "!FILE_PATH!"
34 | echo Downloaded %%f
35 | ) else (
36 | echo %%f already exists.
37 | )
38 | )
39 |
40 | REM ControlNetモデルダウンロード
41 | set "MODEL_DIR=%dpath%\ControlNet"
42 | set "MODEL_ID=stabilityai/control-lora"
43 | set "FILES=control-lora-canny-rank256.safetensors"
44 |
45 | if not exist "%MODEL_DIR%" mkdir "%MODEL_DIR%"
46 | for %%f in (%FILES%) do (
47 | set "FILE_PATH=%MODEL_DIR%\%%f"
48 | if not exist "!FILE_PATH!" (
49 | curl -L "https://huggingface.co/%MODEL_ID%/resolve/main/control-LoRAs-rank256/%%f" -o "!FILE_PATH!"
50 | echo Downloaded %%f
51 | ) else (
52 | echo %%f already exists.
53 | )
54 | )
55 |
56 | REM Stable-diffusionモデルダウンロード
57 | set "MODEL_DIR=%dpath%\Stable-diffusion"
58 | set "MODEL_ID=cagliostrolab/animagine-xl-3.0"
59 | set "FILES=animagine-xl-3.0.safetensors"
60 |
61 | if not exist "%MODEL_DIR%" mkdir "%MODEL_DIR%"
62 | for %%f in (%FILES%) do (
63 | set "FILE_PATH=%MODEL_DIR%\%%f"
64 | if not exist "!FILE_PATH!" (
65 | curl -L "https://huggingface.co/%MODEL_ID%/resolve/main/%%f" -o "!FILE_PATH!"
66 | echo Downloaded %%f
67 | ) else (
68 | echo %%f already exists.
69 | )
70 | )
71 |
72 | endlocal
73 | exit
74 |
--------------------------------------------------------------------------------
/Line2Normalmap_modules/config_states.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tori29umai0123/Line2Normalmap/cbb6a109eddcfe02225e1a2ef1278f6fd9b665e3/Line2Normalmap_modules/config_states.py
--------------------------------------------------------------------------------
/Line2Normalmap_modules/gitpython_hack.py:
--------------------------------------------------------------------------------
1 | import io
2 | from typing import Tuple
3 |
4 | import pygit2
5 |
6 |
7 | class Git:
8 | """
9 | Git wrapper class.
10 | """
11 |
12 | def __init__(self, repo_path: str):
13 | self.repo_path = repo_path
14 |
15 | def get_object_header(self, ref: str) -> Tuple[str, str, int]:
16 | repo = pygit2.Repository(self.repo_path)
17 | obj = repo.revparse_single(ref)
18 | return obj.hex, obj.type, obj.size
19 |
20 | def stream_object_data(self, ref: str) -> Tuple[str, str, int, io.BytesIO]:
21 | repo = pygit2.Repository(self.repo_path)
22 | obj = repo.revparse_single(ref)
23 | data = obj.data
24 | bio = io.BytesIO(data)
25 | return obj.hex, obj.type, obj.size, bio
26 |
27 |
28 | class Repo:
29 | def __init__(self, repo_path: str):
30 | self.repo_path = repo_path
31 | self.git = Git(repo_path)
32 |
--------------------------------------------------------------------------------
/Line2Normalmap_modules/launch_utils_Line2Normalmap.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from modules import cmd_args
3 |
4 |
5 | args, _ = cmd_args.parser.parse_known_args()
6 |
7 |
8 |
9 | def start():
10 | print(f"Launching {'API server' if '--nowebui' in sys.argv else 'Web UI'} with arguments: {' '.join(sys.argv[1:])}")
11 | import Line2Normalmap_gui
12 | Line2Normalmap_gui.api_only()
13 |
14 | from modules_forge import main_thread
15 |
16 | main_thread.loop()
17 | return
18 |
19 |
20 | def dump_sysinfo():
21 | from modules import sysinfo
22 | import datetime
23 |
24 | text = sysinfo.get()
25 | filename = f"sysinfo-{datetime.datetime.utcnow().strftime('%Y-%m-%d-%H-%M')}.json"
26 |
27 | with open(filename, "w", encoding="utf8") as file:
28 | file.write(text)
29 |
30 | return filename
31 |
--------------------------------------------------------------------------------
/Line2Normalmap_modules/sd1_clip.py:
--------------------------------------------------------------------------------
1 | import sys
2 | # 'frozen' 状態に応じて適切なファイルパスを取得する関数
3 | def get_appropriate_file_path():
4 | if getattr(sys, 'frozen', False):
5 | return sys.executable + "/Line2Normalmap/"
6 | else:
7 | return __file__
8 | appropriate_file_path = get_appropriate_file_path()
9 | import sys
10 | # 'frozen'状態に応じて適切なファイルパスを取得する関数
11 | def get_appropriate_file_path():
12 | if getattr(sys, 'frozen', False):
13 | # ビルドされたアプリケーションの場合、sys.executableのパスを使用
14 | return sys.executable + "/Line2Normalmap/"
15 | else:
16 | # そうでない場合は、従来通りappropriate_file_pathを使用
17 | return appropriate_file_path
18 |
19 | # 適切なファイルパスを取得
20 | appropriate_file_path = get_appropriate_file_path()
21 |
22 | import sys
23 | import os
24 |
25 | # 'frozen'状態に応じて適切なファイルパスを取得する関数
26 | def get_appropriate_file_path():
27 | if getattr(sys, 'frozen', False):
28 | # ビルドされたアプリケーションの場合、os.path.dirname(sys.executable)のパスを使用
29 | return os.path.dirname(sys.executable) + "/ldm_patched/modules"
30 | else:
31 | # そうでない場合は、従来通りappropriate_file_pathを使用
32 | return os.path.dirname(appropriate_file_path)
33 |
34 | # 適切なファイルパスを取得
35 | appropriate_file_path = get_appropriate_file_path()
36 |
37 | # Implementation of CLIPTextModel transformer
38 |
39 | # using https://github.com/comfyanonymous/ComfyUI/blob/master/comfy/sd1_clip.py as reference
40 | # written by Forge
41 |
42 |
43 | import os
44 |
45 | from transformers import CLIPTokenizer
46 | import ldm_patched.modules.ops
47 | import torch
48 | import traceback
49 | import zipfile
50 | from . import model_management
51 | import ldm_patched.modules.clip_model
52 | import json
53 | from transformers import CLIPTextModel, CLIPTextConfig, modeling_utils
54 |
55 |
56 | # Taken from https://github.com/comfyanonymous/ComfyUI/blob/master/comfy/sd1_clip.py
57 | # This function is only for reference, and not used in the backend or runtime.
58 | def gen_empty_tokens(special_tokens, length):
59 | start_token = special_tokens.get("start", None)
60 | end_token = special_tokens.get("end", None)
61 | pad_token = special_tokens.get("pad")
62 | output = []
63 | if start_token is not None:
64 | output.append(start_token)
65 | if end_token is not None:
66 | output.append(end_token)
67 | output += [pad_token] * (length - len(output))
68 | return output
69 |
70 | class ClipTokenWeightEncoder:
71 |
72 | # Taken from https://github.com/comfyanonymous/ComfyUI/blob/master/comfy/sd1_clip.py
73 | # This function is only for reference, and not used in the backend or runtime.
74 | def encode_token_weights(self, token_weight_pairs):
75 | to_encode = list()
76 | max_token_len = 0
77 | has_weights = False
78 | for x in token_weight_pairs:
79 | tokens = list(map(lambda a: a[0], x))
80 | max_token_len = max(len(tokens), max_token_len)
81 | has_weights = has_weights or not all(map(lambda a: a[1] == 1.0, x))
82 | to_encode.append(tokens)
83 |
84 | sections = len(to_encode)
85 | if has_weights or sections == 0:
86 | to_encode.append(gen_empty_tokens(self.special_tokens, max_token_len))
87 |
88 | out, pooled = self.encode(to_encode)
89 | if pooled is not None:
90 | first_pooled = pooled[0:1].to(model_management.intermediate_device())
91 | else:
92 | first_pooled = pooled
93 |
94 | output = []
95 | for k in range(0, sections):
96 | z = out[k:k+1]
97 | if has_weights:
98 | z_empty = out[-1]
99 | for i in range(len(z)):
100 | for j in range(len(z[i])):
101 | weight = token_weight_pairs[k][j][1]
102 | if weight != 1.0:
103 | z[i][j] = (z[i][j] - z_empty[j]) * weight + z_empty[j]
104 | output.append(z)
105 |
106 | if (len(output) == 0):
107 | return out[-1:].to(model_management.intermediate_device()), first_pooled
108 | return torch.cat(output, dim=-2).to(model_management.intermediate_device()), first_pooled
109 |
110 | class SDClipModel(torch.nn.Module, ClipTokenWeightEncoder):
111 | """Uses the CLIP transformer encoder for text (from huggingface)"""
112 | LAYERS = [
113 | "last",
114 | "pooled",
115 | "hidden"
116 | ]
117 | def __init__(self, version="openai/clip-vit-large-patch14", device="cpu", max_length=77,
118 | freeze=True, layer="last", layer_idx=None, textmodel_json_config=None, dtype=None, model_class=ldm_patched.modules.clip_model.CLIPTextModel,
119 | special_tokens={"start": 49406, "end": 49407, "pad": 49407}, layer_norm_hidden_state=True): # clip-vit-base-patch32
120 | super().__init__()
121 | assert layer in self.LAYERS
122 |
123 | if textmodel_json_config is None:
124 | textmodel_json_config = os.path.join(appropriate_file_path, "sd1_clip_config.json")
125 |
126 | config = CLIPTextConfig.from_json_file(textmodel_json_config)
127 | self.num_layers = config.num_hidden_layers
128 |
129 | with ldm_patched.modules.ops.use_patched_ops(ldm_patched.modules.ops.manual_cast):
130 | with modeling_utils.no_init_weights():
131 | self.transformer = CLIPTextModel(config)
132 |
133 | if dtype is not None:
134 | self.transformer.to(dtype)
135 |
136 | self.transformer.text_model.embeddings.to(torch.float32)
137 |
138 | self.max_length = max_length
139 | if freeze:
140 | self.freeze()
141 | self.layer = layer
142 | self.layer_idx = None
143 | self.special_tokens = special_tokens
144 | self.text_projection = torch.nn.Parameter(torch.eye(self.transformer.get_input_embeddings().weight.shape[1]))
145 | self.logit_scale = torch.nn.Parameter(torch.tensor(4.6055))
146 | self.enable_attention_masks = False
147 |
148 | self.layer_norm_hidden_state = layer_norm_hidden_state
149 | if layer == "hidden":
150 | assert layer_idx is not None
151 | assert abs(layer_idx) < self.num_layers
152 | self.clip_layer(layer_idx)
153 | self.layer_default = (self.layer, self.layer_idx)
154 |
155 | def freeze(self):
156 | self.transformer = self.transformer.eval()
157 | #self.train = disabled_train
158 | for param in self.parameters():
159 | param.requires_grad = False
160 |
161 | def clip_layer(self, layer_idx):
162 | if abs(layer_idx) > self.num_layers:
163 | self.layer = "last"
164 | else:
165 | self.layer = "hidden"
166 | self.layer_idx = layer_idx
167 |
168 | def reset_clip_layer(self):
169 | self.layer = self.layer_default[0]
170 | self.layer_idx = self.layer_default[1]
171 |
172 | # Taken from https://github.com/comfyanonymous/ComfyUI/blob/master/comfy/sd1_clip.py
173 | # This function is only for reference, and not used in the backend or runtime.
174 | def set_up_textual_embeddings(self, tokens, current_embeds):
175 | out_tokens = []
176 | next_new_token = token_dict_size = current_embeds.weight.shape[0] - 1
177 | embedding_weights = []
178 |
179 | for x in tokens:
180 | tokens_temp = []
181 | for y in x:
182 | if isinstance(y, int):
183 | if y == token_dict_size: #EOS token
184 | y = -1
185 | tokens_temp += [y]
186 | else:
187 | if y.shape[0] == current_embeds.weight.shape[1]:
188 | embedding_weights += [y]
189 | tokens_temp += [next_new_token]
190 | next_new_token += 1
191 | else:
192 | print("WARNING: shape mismatch when trying to apply embedding, embedding will be ignored", y.shape[0], current_embeds.weight.shape[1])
193 | while len(tokens_temp) < len(x):
194 | tokens_temp += [self.special_tokens["pad"]]
195 | out_tokens += [tokens_temp]
196 |
197 | n = token_dict_size
198 | if len(embedding_weights) > 0:
199 | new_embedding = torch.nn.Embedding(next_new_token + 1, current_embeds.weight.shape[1], device=current_embeds.weight.device, dtype=current_embeds.weight.dtype)
200 | new_embedding.weight[:token_dict_size] = current_embeds.weight[:-1]
201 | for x in embedding_weights:
202 | new_embedding.weight[n] = x
203 | n += 1
204 | new_embedding.weight[n] = current_embeds.weight[-1] #EOS embedding
205 | self.transformer.set_input_embeddings(new_embedding)
206 |
207 | processed_tokens = []
208 | for x in out_tokens:
209 | processed_tokens += [list(map(lambda a: n if a == -1 else a, x))] #The EOS token should always be the largest one
210 |
211 | return processed_tokens
212 |
213 | # Taken from https://github.com/comfyanonymous/ComfyUI/blob/master/comfy/sd1_clip.py
214 | # This function is only for reference, and not used in the backend or runtime.
215 | def forward(self, tokens):
216 | backup_embeds = self.transformer.get_input_embeddings()
217 | device = backup_embeds.weight.device
218 | tokens = self.set_up_textual_embeddings(tokens, backup_embeds)
219 | tokens = torch.LongTensor(tokens).to(device)
220 |
221 | attention_mask = None
222 | if self.enable_attention_masks:
223 | attention_mask = torch.zeros_like(tokens)
224 | max_token = self.transformer.get_input_embeddings().weight.shape[0] - 1
225 | for x in range(attention_mask.shape[0]):
226 | for y in range(attention_mask.shape[1]):
227 | attention_mask[x, y] = 1
228 | if tokens[x, y] == max_token:
229 | break
230 |
231 | outputs = self.transformer(input_ids=tokens, attention_mask=attention_mask,
232 | output_hidden_states=self.layer == "hidden")
233 | self.transformer.set_input_embeddings(backup_embeds)
234 |
235 | if self.layer == "last":
236 | z = outputs.last_hidden_state
237 | elif self.layer == "pooled":
238 | z = outputs.pooler_output[:, None, :]
239 | else:
240 | z = outputs.hidden_states[self.layer_idx]
241 | if self.layer_norm_hidden_state:
242 | z = self.transformer.text_model.final_layer_norm(z)
243 |
244 | if hasattr(outputs, "pooler_output"):
245 | pooled_output = outputs.pooler_output.float()
246 | else:
247 | pooled_output = None
248 |
249 | if self.text_projection is not None and pooled_output is not None:
250 | pooled_output = pooled_output.float().to(self.text_projection.device) @ self.text_projection.float()
251 | return z.float(), pooled_output
252 |
253 | def encode(self, tokens):
254 | return self(tokens)
255 |
256 | def load_sd(self, sd):
257 | if "text_projection" in sd:
258 | self.text_projection[:] = sd.pop("text_projection")
259 | if "text_projection.weight" in sd:
260 | self.text_projection[:] = sd.pop("text_projection.weight").transpose(0, 1)
261 | return self.transformer.load_state_dict(sd, strict=False)
262 |
263 |
264 | # Taken from https://github.com/comfyanonymous/ComfyUI/blob/master/comfy/sd1_clip.py
265 | # This function is only for reference, and not used in the backend or runtime.
266 | def parse_parentheses(string):
267 | result = []
268 | current_item = ""
269 | nesting_level = 0
270 | for char in string:
271 | if char == "(":
272 | if nesting_level == 0:
273 | if current_item:
274 | result.append(current_item)
275 | current_item = "("
276 | else:
277 | current_item = "("
278 | else:
279 | current_item += char
280 | nesting_level += 1
281 | elif char == ")":
282 | nesting_level -= 1
283 | if nesting_level == 0:
284 | result.append(current_item + ")")
285 | current_item = ""
286 | else:
287 | current_item += char
288 | else:
289 | current_item += char
290 | if current_item:
291 | result.append(current_item)
292 | return result
293 |
294 |
295 | # Taken from https://github.com/comfyanonymous/ComfyUI/blob/master/comfy/sd1_clip.py
296 | # This function is only for reference, and not used in the backend or runtime.
297 | def token_weights(string, current_weight):
298 | a = parse_parentheses(string)
299 | out = []
300 | for x in a:
301 | weight = current_weight
302 | if len(x) >= 2 and x[-1] == ')' and x[0] == '(':
303 | x = x[1:-1]
304 | xx = x.rfind(":")
305 | weight *= 1.1
306 | if xx > 0:
307 | try:
308 | weight = float(x[xx+1:])
309 | x = x[:xx]
310 | except:
311 | pass
312 | out += token_weights(x, weight)
313 | else:
314 | out += [(x, current_weight)]
315 | return out
316 |
317 |
318 | # Taken from https://github.com/comfyanonymous/ComfyUI/blob/master/comfy/sd1_clip.py
319 | # This function is only for reference, and not used in the backend or runtime.
320 | def escape_important(text):
321 | text = text.replace("\\)", "\0\1")
322 | text = text.replace("\\(", "\0\2")
323 | return text
324 |
325 |
326 | # Taken from https://github.com/comfyanonymous/ComfyUI/blob/master/comfy/sd1_clip.py
327 | # This function is only for reference, and not used in the backend or runtime.
328 | def unescape_important(text):
329 | text = text.replace("\0\1", ")")
330 | text = text.replace("\0\2", "(")
331 | return text
332 |
333 |
334 | # Taken from https://github.com/comfyanonymous/ComfyUI/blob/master/comfy/sd1_clip.py
335 | # This function is only for reference, and not used in the backend or runtime.
336 | def safe_load_embed_zip(embed_path):
337 | with zipfile.ZipFile(embed_path) as myzip:
338 | names = list(filter(lambda a: "data/" in a, myzip.namelist()))
339 | names.reverse()
340 | for n in names:
341 | with myzip.open(n) as myfile:
342 | data = myfile.read()
343 | number = len(data) // 4
344 | length_embed = 1024 #sd2.x
345 | if number < 768:
346 | continue
347 | if number % 768 == 0:
348 | length_embed = 768 #sd1.x
349 | num_embeds = number // length_embed
350 | embed = torch.frombuffer(data, dtype=torch.float)
351 | out = embed.reshape((num_embeds, length_embed)).clone()
352 | del embed
353 | return out
354 |
355 |
356 | # Taken from https://github.com/comfyanonymous/ComfyUI/blob/master/comfy/sd1_clip.py
357 | # This function is only for reference, and not used in the backend or runtime.
358 | def expand_directory_list(directories):
359 | dirs = set()
360 | for x in directories:
361 | dirs.add(x)
362 | for root, subdir, file in os.walk(x, followlinks=True):
363 | dirs.add(root)
364 | return list(dirs)
365 |
366 |
367 | # Taken from https://github.com/comfyanonymous/ComfyUI/blob/master/comfy/sd1_clip.py
368 | # This function is only for reference, and not used in the backend or runtime.
369 | def load_embed(embedding_name, embedding_directory, embedding_size, embed_key=None):
370 | if isinstance(embedding_directory, str):
371 | embedding_directory = [embedding_directory]
372 |
373 | embedding_directory = expand_directory_list(embedding_directory)
374 |
375 | valid_file = None
376 | for embed_dir in embedding_directory:
377 | embed_path = os.path.abspath(os.path.join(embed_dir, embedding_name))
378 | embed_dir = os.path.abspath(embed_dir)
379 | try:
380 | if os.path.commonpath((embed_dir, embed_path)) != embed_dir:
381 | continue
382 | except:
383 | continue
384 | if not os.path.isfile(embed_path):
385 | extensions = ['.safetensors', '.pt', '.bin']
386 | for x in extensions:
387 | t = embed_path + x
388 | if os.path.isfile(t):
389 | valid_file = t
390 | break
391 | else:
392 | valid_file = embed_path
393 | if valid_file is not None:
394 | break
395 |
396 | if valid_file is None:
397 | return None
398 |
399 | embed_path = valid_file
400 |
401 | embed_out = None
402 |
403 | try:
404 | if embed_path.lower().endswith(".safetensors"):
405 | import safetensors.torch
406 | embed = safetensors.torch.load_file(embed_path, device="cpu")
407 | else:
408 | if 'weights_only' in torch.load.__code__.co_varnames:
409 | try:
410 | embed = torch.load(embed_path, weights_only=True, map_location="cpu")
411 | except:
412 | embed_out = safe_load_embed_zip(embed_path)
413 | else:
414 | embed = torch.load(embed_path, map_location="cpu")
415 | except Exception as e:
416 | print(traceback.format_exc())
417 | print()
418 | print("error loading embedding, skipping loading:", embedding_name)
419 | return None
420 |
421 | if embed_out is None:
422 | if 'string_to_param' in embed:
423 | values = embed['string_to_param'].values()
424 | embed_out = next(iter(values))
425 | elif isinstance(embed, list):
426 | out_list = []
427 | for x in range(len(embed)):
428 | for k in embed[x]:
429 | t = embed[x][k]
430 | if t.shape[-1] != embedding_size:
431 | continue
432 | out_list.append(t.reshape(-1, t.shape[-1]))
433 | embed_out = torch.cat(out_list, dim=0)
434 | elif embed_key is not None and embed_key in embed:
435 | embed_out = embed[embed_key]
436 | else:
437 | values = embed.values()
438 | embed_out = next(iter(values))
439 | return embed_out
440 |
441 | class SDTokenizer:
442 | def __init__(self, tokenizer_path=None, max_length=77, pad_with_end=True, embedding_directory=None, embedding_size=768, embedding_key='clip_l', tokenizer_class=CLIPTokenizer, has_start_token=True, pad_to_max_length=True):
443 | if tokenizer_path is None:
444 | tokenizer_path = os.path.join(appropriate_file_path, "sd1_tokenizer")
445 | self.tokenizer = tokenizer_class.from_pretrained(tokenizer_path)
446 | self.max_length = max_length
447 |
448 | empty = self.tokenizer('')["input_ids"]
449 | if has_start_token:
450 | self.tokens_start = 1
451 | self.start_token = empty[0]
452 | self.end_token = empty[1]
453 | else:
454 | self.tokens_start = 0
455 | self.start_token = None
456 | self.end_token = empty[0]
457 | self.pad_with_end = pad_with_end
458 | self.pad_to_max_length = pad_to_max_length
459 |
460 | vocab = self.tokenizer.get_vocab()
461 | self.inv_vocab = {v: k for k, v in vocab.items()}
462 | self.embedding_directory = embedding_directory
463 | self.max_word_length = 8
464 | self.embedding_identifier = "embedding:"
465 | self.embedding_size = embedding_size
466 | self.embedding_key = embedding_key
467 |
468 | # Taken from https://github.com/comfyanonymous/ComfyUI/blob/master/comfy/sd1_clip.py
469 | # This function is only for reference, and not used in the backend or runtime.
470 | def _try_get_embedding(self, embedding_name:str):
471 | '''
472 | Takes a potential embedding name and tries to retrieve it.
473 | Returns a Tuple consisting of the embedding and any leftover string, embedding can be None.
474 | '''
475 | embed = load_embed(embedding_name, self.embedding_directory, self.embedding_size, self.embedding_key)
476 | if embed is None:
477 | stripped = embedding_name.strip(',')
478 | if len(stripped) < len(embedding_name):
479 | embed = load_embed(stripped, self.embedding_directory, self.embedding_size, self.embedding_key)
480 | return (embed, embedding_name[len(stripped):])
481 | return (embed, "")
482 |
483 | # Taken from https://github.com/comfyanonymous/ComfyUI/blob/master/comfy/sd1_clip.py
484 | # This function is only for reference, and not used in the backend or runtime.
485 | def tokenize_with_weights(self, text:str, return_word_ids=False):
486 | '''
487 | Takes a prompt and converts it to a list of (token, weight, word id) elements.
488 | Tokens can both be integer tokens and pre computed CLIP tensors.
489 | Word id values are unique per word and embedding, where the id 0 is reserved for non word tokens.
490 | Returned list has the dimensions NxM where M is the input size of CLIP
491 | '''
492 | if self.pad_with_end:
493 | pad_token = self.end_token
494 | else:
495 | pad_token = 0
496 |
497 | text = escape_important(text)
498 | parsed_weights = token_weights(text, 1.0)
499 |
500 | #tokenize words
501 | tokens = []
502 | for weighted_segment, weight in parsed_weights:
503 | to_tokenize = unescape_important(weighted_segment).replace("\n", " ").split(' ')
504 | to_tokenize = [x for x in to_tokenize if x != ""]
505 | for word in to_tokenize:
506 | #if we find an embedding, deal with the embedding
507 | if word.startswith(self.embedding_identifier) and self.embedding_directory is not None:
508 | embedding_name = word[len(self.embedding_identifier):].strip('\n')
509 | embed, leftover = self._try_get_embedding(embedding_name)
510 | if embed is None:
511 | print(f"warning, embedding:{embedding_name} does not exist, ignoring")
512 | else:
513 | if len(embed.shape) == 1:
514 | tokens.append([(embed, weight)])
515 | else:
516 | tokens.append([(embed[x], weight) for x in range(embed.shape[0])])
517 | #if we accidentally have leftover text, continue parsing using leftover, else move on to next word
518 | if leftover != "":
519 | word = leftover
520 | else:
521 | continue
522 | #parse word
523 | tokens.append([(t, weight) for t in self.tokenizer(word)["input_ids"][self.tokens_start:-1]])
524 |
525 | #reshape token array to CLIP input size
526 | batched_tokens = []
527 | batch = []
528 | if self.start_token is not None:
529 | batch.append((self.start_token, 1.0, 0))
530 | batched_tokens.append(batch)
531 | for i, t_group in enumerate(tokens):
532 | #determine if we're going to try and keep the tokens in a single batch
533 | is_large = len(t_group) >= self.max_word_length
534 |
535 | while len(t_group) > 0:
536 | if len(t_group) + len(batch) > self.max_length - 1:
537 | remaining_length = self.max_length - len(batch) - 1
538 | #break word in two and add end token
539 | if is_large:
540 | batch.extend([(t,w,i+1) for t,w in t_group[:remaining_length]])
541 | batch.append((self.end_token, 1.0, 0))
542 | t_group = t_group[remaining_length:]
543 | #add end token and pad
544 | else:
545 | batch.append((self.end_token, 1.0, 0))
546 | if self.pad_to_max_length:
547 | batch.extend([(pad_token, 1.0, 0)] * (remaining_length))
548 | #start new batch
549 | batch = []
550 | if self.start_token is not None:
551 | batch.append((self.start_token, 1.0, 0))
552 | batched_tokens.append(batch)
553 | else:
554 | batch.extend([(t,w,i+1) for t,w in t_group])
555 | t_group = []
556 |
557 | #fill last batch
558 | batch.append((self.end_token, 1.0, 0))
559 | if self.pad_to_max_length:
560 | batch.extend([(pad_token, 1.0, 0)] * (self.max_length - len(batch)))
561 |
562 | if not return_word_ids:
563 | batched_tokens = [[(t, w) for t, w,_ in x] for x in batched_tokens]
564 |
565 | return batched_tokens
566 |
567 | # Taken from https://github.com/comfyanonymous/ComfyUI/blob/master/comfy/sd1_clip.py
568 | # This function is only for reference, and not used in the backend or runtime.
569 | def untokenize(self, token_weight_pair):
570 | return list(map(lambda a: (a, self.inv_vocab[a[0]]), token_weight_pair))
571 |
572 |
573 | class SD1Tokenizer:
574 | def __init__(self, embedding_directory=None, clip_name="l", tokenizer=SDTokenizer):
575 | self.clip_name = clip_name
576 | self.clip = "clip_{}".format(self.clip_name)
577 | setattr(self, self.clip, tokenizer(embedding_directory=embedding_directory))
578 |
579 | # Taken from https://github.com/comfyanonymous/ComfyUI/blob/master/comfy/sd1_clip.py
580 | # This function is only for reference, and not used in the backend or runtime.
581 | def tokenize_with_weights(self, text:str, return_word_ids=False):
582 | out = {}
583 | out[self.clip_name] = getattr(self, self.clip).tokenize_with_weights(text, return_word_ids)
584 | return out
585 |
586 | # Taken from https://github.com/comfyanonymous/ComfyUI/blob/master/comfy/sd1_clip.py
587 | # This function is only for reference, and not used in the backend or runtime.
588 | def untokenize(self, token_weight_pair):
589 | return getattr(self, self.clip).untokenize(token_weight_pair)
590 |
591 |
592 | class SD1ClipModel(torch.nn.Module):
593 | def __init__(self, device="cpu", dtype=None, clip_name="l", clip_model=SDClipModel, **kwargs):
594 | super().__init__()
595 | self.clip_name = clip_name
596 | self.clip = "clip_{}".format(self.clip_name)
597 | setattr(self, self.clip, clip_model(device=device, dtype=dtype, **kwargs))
598 |
599 | # Taken from https://github.com/comfyanonymous/ComfyUI/blob/master/comfy/sd1_clip.py
600 | # This function is only for reference, and not used in the backend or runtime.
601 | def clip_layer(self, layer_idx):
602 | getattr(self, self.clip).clip_layer(layer_idx)
603 |
604 | # Taken from https://github.com/comfyanonymous/ComfyUI/blob/master/comfy/sd1_clip.py
605 | # This function is only for reference, and not used in the backend or runtime.
606 | def reset_clip_layer(self):
607 | getattr(self, self.clip).reset_clip_layer()
608 |
609 | # Taken from https://github.com/comfyanonymous/ComfyUI/blob/master/comfy/sd1_clip.py
610 | # This function is only for reference, and not used in the backend or runtime.
611 | def encode_token_weights(self, token_weight_pairs):
612 | token_weight_pairs = token_weight_pairs[self.clip_name]
613 | out, pooled = getattr(self, self.clip).encode_token_weights(token_weight_pairs)
614 | return out, pooled
615 |
616 | # Taken from https://github.com/comfyanonymous/ComfyUI/blob/master/comfy/sd1_clip.py
617 | # This function is only for reference, and not used in the backend or runtime.
618 | def load_sd(self, sd):
619 | return getattr(self, self.clip).load_sd(sd)
620 |
--------------------------------------------------------------------------------
/Line2Normalmap_modules/sd2_clip.py:
--------------------------------------------------------------------------------
1 | import sys
2 | # 'frozen' 状態に応じて適切なファイルパスを取得する関数
3 | def get_appropriate_file_path():
4 | if getattr(sys, 'frozen', False):
5 | return sys.executable + "/Line2Normalmap/"
6 | else:
7 | return __file__
8 | appropriate_file_path = get_appropriate_file_path()
9 | import sys
10 | # 'frozen'状態に応じて適切なファイルパスを取得する関数
11 | def get_appropriate_file_path():
12 | if getattr(sys, 'frozen', False):
13 | # ビルドされたアプリケーションの場合、sys.executableのパスを使用
14 | return sys.executable + "/Line2Normalmap/"
15 | else:
16 | # そうでない場合は、従来通りappropriate_file_pathを使用
17 | return appropriate_file_path
18 |
19 | # 適切なファイルパスを取得
20 | appropriate_file_path = get_appropriate_file_path()
21 |
22 | import sys
23 | import os
24 |
25 | # 'frozen'状態に応じて適切なファイルパスを取得する関数
26 | def get_appropriate_file_path():
27 | if getattr(sys, 'frozen', False):
28 | # ビルドされたアプリケーションの場合、os.path.dirname(sys.executable)のパスを使用
29 | return os.path.dirname(sys.executable) + "/ldm_patched/modules"
30 | else:
31 | # そうでない場合は、従来通りappropriate_file_pathを使用
32 | return os.path.dirname(appropriate_file_path)
33 |
34 | # 適切なファイルパスを取得
35 | appropriate_file_path = get_appropriate_file_path()
36 |
37 | # Taken from https://github.com/comfyanonymous/ComfyUI
38 | # This file is only for reference, and not used in the backend or runtime.
39 |
40 |
41 | from ldm_patched.modules import sd1_clip
42 | import torch
43 | import os
44 |
45 | class SD2ClipHModel(sd1_clip.SDClipModel):
46 | def __init__(self, arch="ViT-H-14", device="cpu", max_length=77, freeze=True, layer="penultimate", layer_idx=None, dtype=None):
47 | if layer == "penultimate":
48 | layer="hidden"
49 | layer_idx=-2
50 |
51 | textmodel_json_config = os.path.join(appropriate_file_path, "sd2_clip_config.json")
52 | super().__init__(device=device, freeze=freeze, layer=layer, layer_idx=layer_idx, textmodel_json_config=textmodel_json_config, dtype=dtype, special_tokens={"start": 49406, "end": 49407, "pad": 0})
53 |
54 | class SD2ClipHTokenizer(sd1_clip.SDTokenizer):
55 | def __init__(self, tokenizer_path=None, embedding_directory=None):
56 | super().__init__(tokenizer_path, pad_with_end=False, embedding_directory=embedding_directory, embedding_size=1024)
57 |
58 | class SD2Tokenizer(sd1_clip.SD1Tokenizer):
59 | def __init__(self, embedding_directory=None):
60 | super().__init__(embedding_directory=embedding_directory, clip_name="h", tokenizer=SD2ClipHTokenizer)
61 |
62 | class SD2ClipModel(sd1_clip.SD1ClipModel):
63 | def __init__(self, device="cpu", dtype=None, **kwargs):
64 | super().__init__(device=device, dtype=dtype, clip_name="h", clip_model=SD2ClipHModel, **kwargs)
65 |
--------------------------------------------------------------------------------
/Line2Normalmap_modules/sdxl_clip.py:
--------------------------------------------------------------------------------
1 | import sys
2 | # 'frozen' 状態に応じて適切なファイルパスを取得する関数
3 | def get_appropriate_file_path():
4 | if getattr(sys, 'frozen', False):
5 | return sys.executable + "/Line2Normalmap/"
6 | else:
7 | return __file__
8 | appropriate_file_path = get_appropriate_file_path()
9 | import sys
10 | # 'frozen'状態に応じて適切なファイルパスを取得する関数
11 | def get_appropriate_file_path():
12 | if getattr(sys, 'frozen', False):
13 | # ビルドされたアプリケーションの場合、sys.executableのパスを使用
14 | return sys.executable + "/Line2Normalmap/"
15 | else:
16 | # そうでない場合は、従来通りappropriate_file_pathを使用
17 | return appropriate_file_path
18 |
19 | # 適切なファイルパスを取得
20 | appropriate_file_path = get_appropriate_file_path()
21 |
22 | import sys
23 | import os
24 |
25 | # 'frozen'状態に応じて適切なファイルパスを取得する関数
26 | def get_appropriate_file_path():
27 | if getattr(sys, 'frozen', False):
28 | # ビルドされたアプリケーションの場合、os.path.dirname(sys.executable)のパスを使用
29 | return os.path.dirname(sys.executable) + "/ldm_patched/modules"
30 | else:
31 | # そうでない場合は、従来通りappropriate_file_pathを使用
32 | return os.path.dirname(appropriate_file_path)
33 |
34 |
35 | # 適切なファイルパスを取得
36 | appropriate_file_path = get_appropriate_file_path()
37 |
38 |
39 | # Taken from https://github.com/comfyanonymous/ComfyUI
40 | # This file is only for reference, and not used in the backend or runtime.
41 |
42 |
43 | from ldm_patched.modules import sd1_clip
44 | import torch
45 | import os
46 |
47 | class SDXLClipG(sd1_clip.SDClipModel):
48 | def __init__(self, device="cpu", max_length=77, freeze=True, layer="penultimate", layer_idx=None, dtype=None):
49 | if layer == "penultimate":
50 | layer="hidden"
51 | layer_idx=-2
52 |
53 | textmodel_json_config = os.path.join(appropriate_file_path, "clip_config_bigg.json")
54 | super().__init__(device=device, freeze=freeze, layer=layer, layer_idx=layer_idx, textmodel_json_config=textmodel_json_config, dtype=dtype,
55 | special_tokens={"start": 49406, "end": 49407, "pad": 0}, layer_norm_hidden_state=False)
56 |
57 | def load_sd(self, sd):
58 | return super().load_sd(sd)
59 |
60 | class SDXLClipGTokenizer(sd1_clip.SDTokenizer):
61 | def __init__(self, tokenizer_path=None, embedding_directory=None):
62 | super().__init__(tokenizer_path, pad_with_end=False, embedding_directory=embedding_directory, embedding_size=1280, embedding_key='clip_g')
63 |
64 |
65 | class SDXLTokenizer:
66 | def __init__(self, embedding_directory=None):
67 | self.clip_l = sd1_clip.SDTokenizer(embedding_directory=embedding_directory)
68 | self.clip_g = SDXLClipGTokenizer(embedding_directory=embedding_directory)
69 |
70 | def tokenize_with_weights(self, text:str, return_word_ids=False):
71 | out = {}
72 | out["g"] = self.clip_g.tokenize_with_weights(text, return_word_ids)
73 | out["l"] = self.clip_l.tokenize_with_weights(text, return_word_ids)
74 | return out
75 |
76 | def untokenize(self, token_weight_pair):
77 | return self.clip_g.untokenize(token_weight_pair)
78 |
79 | class SDXLClipModel(torch.nn.Module):
80 | def __init__(self, device="cpu", dtype=None):
81 | super().__init__()
82 | self.clip_l = sd1_clip.SDClipModel(layer="hidden", layer_idx=-2, device=device, dtype=dtype, layer_norm_hidden_state=False)
83 | self.clip_g = SDXLClipG(device=device, dtype=dtype)
84 |
85 | def clip_layer(self, layer_idx):
86 | self.clip_l.clip_layer(layer_idx)
87 | self.clip_g.clip_layer(layer_idx)
88 |
89 | def reset_clip_layer(self):
90 | self.clip_g.reset_clip_layer()
91 | self.clip_l.reset_clip_layer()
92 |
93 | def encode_token_weights(self, token_weight_pairs):
94 | token_weight_pairs_g = token_weight_pairs["g"]
95 | token_weight_pairs_l = token_weight_pairs["l"]
96 | g_out, g_pooled = self.clip_g.encode_token_weights(token_weight_pairs_g)
97 | l_out, l_pooled = self.clip_l.encode_token_weights(token_weight_pairs_l)
98 | return torch.cat([l_out, g_out], dim=-1), g_pooled
99 |
100 | def load_sd(self, sd):
101 | if "text_model.encoder.layers.30.mlp.fc1.weight" in sd:
102 | return self.clip_g.load_sd(sd)
103 | else:
104 | return self.clip_l.load_sd(sd)
105 |
106 | class SDXLRefinerClipModel(sd1_clip.SD1ClipModel):
107 | def __init__(self, device="cpu", dtype=None):
108 | super().__init__(device=device, dtype=dtype, clip_name="g", clip_model=SDXLClipG)
109 |
--------------------------------------------------------------------------------
/Line2Normalmap_modules/shared_cmd_options.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tori29umai0123/Line2Normalmap/cbb6a109eddcfe02225e1a2ef1278f6fd9b665e3/Line2Normalmap_modules/shared_cmd_options.py
--------------------------------------------------------------------------------
/Line2Normalmap_modules/ui_extensions.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tori29umai0123/Line2Normalmap/cbb6a109eddcfe02225e1a2ef1278f6fd9b665e3/Line2Normalmap_modules/ui_extensions.py
--------------------------------------------------------------------------------
/Line2Normalmap_setup.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 | import shutil
4 |
5 | # 絶対パスで作業ディレクトリを指定
6 | script_path = os.path.abspath(__file__)
7 | target_directory = os.path.dirname(script_path)
8 |
9 | # 追加するコードの定義
10 | prepend_code = """import sys
11 | # 'frozen' 状態に応じて適切なファイルパスを取得する関数
12 | def get_appropriate_file_path():
13 | if getattr(sys, 'frozen', False):
14 | return sys.executable + "/Line2Normalmap/"
15 | else:
16 | return __file__
17 | appropriate_file_path = get_appropriate_file_path()
18 | """
19 |
20 | ldm_special_prepend_code = """import sys
21 | import os
22 | def get_appropriate_file_path():
23 | if getattr(sys, 'frozen', False):
24 | return os.path.dirname(sys.executable) + "/ldm_patched/modules"
25 | else:
26 | return os.path.dirname(__file__)
27 | appropriate_file_path = get_appropriate_file_path()
28 | """
29 |
30 | exclude_files = []
31 |
32 | ldm_patched_files = [
33 | os.path.join(target_directory, "ldm_patched/modules/sd1_clip.py"),
34 | os.path.join(target_directory, "ldm_patched/modules/sd2_clip.py"),
35 | os.path.join(target_directory, "ldm_patched/modules/sdxl_clip.py"),
36 | ]
37 |
38 | exclude_folders = [
39 | os.path.join(target_directory, "venv"),
40 | os.path.join(target_directory, "Line2Normalmap_modules"),
41 | os.path.join(target_directory, "utils"),
42 | ]
43 |
44 | # 新しいファイルの追加に加えて既存のコピー機能
45 | files_to_copy = {
46 | os.path.join(target_directory, "Line2Normalmap_modules/config_states.py"): os.path.join(target_directory, "modules/config_states.py"),
47 | os.path.join(target_directory, "Line2Normalmap_modules/gitpython_hack.py"): os.path.join(target_directory, "modules/gitpython_hack.py"),
48 | os.path.join(target_directory, "Line2Normalmap_modules/launch_utils_Line2Normalmap.py"): os.path.join(target_directory, "modules/launch_utils_Line2Normalmap.py"),
49 | os.path.join(target_directory, "Line2Normalmap_modules/sdxl_clip.py"): os.path.join(target_directory, "ldm_patched/modules/sdxl_clip.py"),
50 | os.path.join(target_directory, "Line2Normalmap_modules/sd1_clip.py"): os.path.join(target_directory, "ldm_patched/modules/sd1_clip.py"),
51 | os.path.join(target_directory, "Line2Normalmap_modules/sd2_clip.py"): os.path.join(target_directory, "ldm_patched/modules/sd2_clip.py"),
52 | os.path.join(target_directory, "Line2Normalmap_modules/shared_cmd_options.py"): os.path.join(target_directory, "modules/shared_cmd_options.py"),
53 | os.path.join(target_directory, "Line2Normalmap_modules/ui_extensions.py"): os.path.join(target_directory, "modules/ui_extensions.py"),
54 | }
55 |
56 | def file_needs_update(filepath):
57 | try:
58 | with open(filepath, 'r', encoding='utf-8') as file:
59 | content = file.read()
60 | return re.search(r'(?
9 | ②セキュリティーソフトの設定で、フォルダと実行ファイル名を除外リストに追加する。
10 | 例:Windows Defenderの場合、Windows セキュリティ→ウイルスと脅威の防止→ウイルスと脅威の防止の設定→設定の管理→除外
11 | Line2Normalmap.exe(プロセス)
12 | C:\Line2Normalmap(フォルダ)
13 | のように指定する。
14 | ③venv.cmdを実行。
15 | ```
16 | pyinstaller "C:\Line2Normalmap\Line2Normalmap.py" ^
17 | --clean ^
18 | --collect-data tkinterdnd2 ^
19 | --add-data "C:\Line2Normalmap\javascript;.\javascript" ^
20 | --add-data "C:\Line2Normalmap\ldm_patched;.\ldm_patched" ^
21 | --add-data "C:\Line2Normalmap\localizations;.\localizations" ^
22 | --add-data "C:\Line2Normalmap\modules;.\modules" ^
23 | --add-data "C:\Line2Normalmap\modules_forge;.\modules_forge" ^
24 | --add-data "C:\Line2Normalmap\repositories;.\repositories" ^
25 | --add-data "C:\Line2Normalmap\cache.json;." ^
26 | --add-data "C:\Line2Normalmap\script.js;." ^
27 | --add-data "C:\Line2Normalmap\ui-config.json;." ^
28 | --add-data "C:\Line2Normalmap\config_states;.\config_states" ^
29 | --add-data "C:\Line2Normalmap\configs;.\configs" ^
30 | --add-data "C:\Line2Normalmap\extensions-builtin;.\extensions-builtin" ^
31 | --add-data "C:\Line2Normalmap\html;.\html"
32 |
33 | xcopy /E /I /Y venv\Lib\site-packages\xformers dist\Line2Normalmap\_internal\xformers
34 | xcopy /E /I /Y venv\Lib\site-packages\pytorch_lightning dist\Line2Normalmap\_internal\pytorch_lightning
35 | xcopy /E /I /Y venv\Lib\site-packages\lightning_fabric dist\Line2Normalmap\_internal\lightning_fabric
36 | xcopy /E /I /Y venv\Lib\site-packages\gradio dist\Line2Normalmap\_internal\gradio
37 | xcopy /E /I /Y venv\Lib\site-packages\gradio_client dist\Line2Normalmap\_internal\gradio_client
38 | xcopy /E /I /Y venv\Lib\site-packages\kornia dist\Line2Normalmap\_internal\kornia
39 | xcopy /E /I /Y venv\Lib\site-packages\open_clip dist\Line2Normalmap\_internal\open_clip
40 | xcopy /E /I /Y venv\Lib\site-packages\jsonmerge dist\Line2Normalmap\_internal\jsonmerge
41 | xcopy /E /I /Y venv\Lib\site-packages\torchdiffeq dist\Line2Normalmap\_internal\torchdiffeq
42 | xcopy /E /I /Y venv\Lib\site-packages\cleanfid dist\Line2Normalmap\_internal\cleanfid
43 | xcopy /E /I /Y venv\Lib\site-packages\clip dist\Line2Normalmap\_internal\clip
44 | xcopy /E /I /Y venv\Lib\site-packages\resize_right dist\Line2Normalmap\_internal\resize_right
45 | xcopy /E /I /Y venv\Lib\site-packages\diffusers dist\Line2Normalmap\_internal\diffusers
46 | xcopy /E /I /Y venv\Lib\site-packages\onnx dist\Line2Normalmap\_internal\onnx
47 | xcopy /E /I /Y venv\Lib\site-packages\onnxruntime dist\Line2Normalmap\_internal\onnxruntime
48 | xcopy /E /I /Y venv\Lib\site-packages\scipy dist\Line2Normalmap\_internal\scipy
49 | xcopy /E /I /Y config_states dist\Line2Normalmap\config_states
50 | xcopy /E /I /Y configs dist\Line2Normalmap\configs
51 | xcopy /E /I /Y embeddings dist\Line2Normalmap\embeddings
52 | xcopy /E /I /Y extensions-builtin dist\Line2Normalmap\extensions-builtin
53 | xcopy /E /I /Y html dist\Line2Normalmap\html
54 | xcopy /E /I /Y javascript dist\Line2Normalmap\javascript
55 | xcopy /E /I /Y ldm_patched dist\Line2Normalmap\ldm_patched
56 | xcopy /E /I /Y localizations dist\Line2Normalmap\localizations
57 | xcopy /E /I /Y modules dist\Line2Normalmap\modules
58 | xcopy /E /I /Y modules_forge dist\Line2Normalmap\modules_forge
59 | xcopy /E /I /Y repositories dist\Line2Normalmap\repositories
60 | xcopy /E /I /Y scripts dist\Line2Normalmap\scripts
61 | copy script.js dist\Line2Normalmap\script.js
62 | copy Line2Normalmap_model_DL.cmd dist\Line2Normalmap\Line2Normalmap_model_DL.cmd
63 | copy Line2Normalmap_ReadMe.txt dist\Line2Normalmap\Line2Normalmap_ReadMe.txt
64 | ```
65 |
--------------------------------------------------------------------------------
/utils/application.py:
--------------------------------------------------------------------------------
1 | import tkinter as tk
2 | from tkinter import ttk
3 | from tkinterdnd2 import DND_FILES, TkinterDnD
4 | import asyncio
5 | import cv2
6 | from PIL import Image, ImageTk
7 | import numpy as np
8 | import os
9 | import sys
10 | import datetime
11 | from utils.tagger import modelLoad, analysis
12 | from utils.request_api import create_and_save_images, get_model, set_model, get_controlnet_model
13 |
14 |
15 | if getattr(sys, 'frozen', False):
16 | # PyInstaller でビルドされた場合
17 | dpath = os.path.dirname(sys.executable)
18 | else:
19 | # 通常の Python スクリプトとして実行された場合
20 | dpath = os.path.dirname(sys.argv[0])
21 |
22 | model = None
23 | fastapi_url = None
24 |
25 |
26 | def canny_process(image_path, threshold1, threshold2):
27 | img_pil = Image.open(image_path).convert('RGBA')
28 | img = np.array(img_pil) # PIL画像をNumPy配列に変換
29 |
30 | alpha_channel = img[:, :, 3]
31 | # RGBチャンネルを取得
32 | rgb_channels = img[:, :, :3]
33 |
34 | # アルファチャンネルで透明部分を白にする
35 | # アルファチャンネルが0の部分は白に、それ以外は元の色を使う
36 | white_background = np.ones_like(rgb_channels, dtype=np.uint8) * 255
37 | # アルファチャンネルを基に背景を合成
38 | white_background = cv2.bitwise_not(white_background, mask=alpha_channel)
39 | background = cv2.bitwise_or(white_background, white_background, mask=alpha_channel)
40 | foreground = cv2.bitwise_and(rgb_channels, rgb_channels, mask=alpha_channel)
41 | combined = cv2.add(foreground, background)
42 |
43 | # RGBA形式からRGB形式に変換
44 | combined = cv2.cvtColor(combined, cv2.COLOR_RGBA2RGB)
45 |
46 | # グレースケール変換
47 | gray = cv2.cvtColor(combined, cv2.COLOR_RGB2GRAY)
48 | # Cannyエッジ検出
49 | edges = cv2.Canny(gray, threshold1, threshold2)
50 |
51 | return edges
52 |
53 | def resize_image_aspect_ratio(image, max_length=1200):
54 | # 元の画像サイズを取得
55 | original_width, original_height = image.size
56 |
57 | # アスペクト比を計算
58 | aspect_ratio = original_width / original_height
59 |
60 | # 長辺がmax_lengthになるように新しいサイズを計算
61 | if original_width > original_height:
62 | new_width = max_length
63 | new_height = round(max_length / aspect_ratio)
64 | else:
65 | new_height = max_length
66 | new_width = round(max_length * aspect_ratio)
67 |
68 | # 新しいサイズで画像をリサイズ
69 | resized_image = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
70 | return resized_image
71 |
72 |
73 | class Application(TkinterDnD.Tk):
74 | def __init__(self, fastapi_url=None):
75 | super().__init__()
76 | self.fastapi_url = fastapi_url
77 | self.title("Line2Normalmap")
78 | self.geometry("600x600")
79 | self.image_path = None
80 | self.canny_pil = None
81 | self.tab_control = ttk.Notebook(self)
82 | self.line_input_tab = tk.Frame(self.tab_control)
83 | self.canny_input_tab = tk.Frame(self.tab_control)
84 | self.image_output_tab = tk.Frame(self.tab_control)
85 | self.tab_control.add(self.line_input_tab, text='①線画入力')
86 | self.tab_control.add(self.canny_input_tab, text='②canny入力')
87 | self.tab_control.add(self.image_output_tab, text='③画像出力')
88 | self.tab_control.pack(expand=1, fill="both")
89 | self.setup_line_input_tab()
90 | self.setup_canny_input_tab()
91 | self.setup_image_output_tab()
92 | self.setup_drag_and_drop()
93 | self.sd_model_names = None
94 | self.sd_current_model_name = None
95 |
96 | def setup_line_input_tab(self):
97 | self.prompt_label = tk.Label(self.line_input_tab, text="Prompt:")
98 | self.prompt_label.pack()
99 | self.prompt_text = tk.Text(self.line_input_tab, width=60, height=3, wrap=tk.WORD)
100 | self.prompt_text.pack()
101 | self.analyze_prompt_button = tk.Button(self.line_input_tab, text="Prompt解析", command=self.analyze_prompt)
102 | self.analyze_prompt_button.pack()
103 |
104 | def setup_canny_input_tab(self):
105 | self.threshold1_slider = tk.Scale(self.canny_input_tab, from_=0, to=255, label="Threshold1", orient="horizontal")
106 | self.threshold1_slider.set(20)
107 | self.threshold1_slider.pack()
108 | self.threshold2_slider = tk.Scale(self.canny_input_tab, from_=0, to=255, label="Threshold2", orient="horizontal")
109 | self.threshold2_slider.set(120)
110 | self.threshold2_slider.pack()
111 | tk.Button(self.canny_input_tab, text="Canny加工", command=self.apply_canny).pack()
112 | self.clear_canny_button = tk.Button(self.canny_input_tab, text="Canny画像消去", command=self.clear_canny)
113 | self.clear_canny_button.pack()
114 |
115 | def setup_image_output_tab(self):
116 | self.sd_model_names, self.sd_current_model_name = get_model(self.fastapi_url)
117 | self.model_label = tk.Label(self.image_output_tab, text="モデル選択:")
118 | self.model_label.pack()
119 | # モデル選択プルダウンメニューを作成
120 | self.model_variable = tk.StringVar(self.image_output_tab)
121 | self.model_dropdown = tk.OptionMenu(self.image_output_tab, self.model_variable, *self.sd_model_names)
122 | self.model_dropdown.pack()
123 | # プルダウンメニューの選択が変更された時に呼び出される関数を登録
124 | self.model_variable.trace("w", self.on_model_selected)
125 |
126 | self.prompt_label = tk.Label(self.image_output_tab, text="Prompt:")
127 | self.prompt_label.pack()
128 | self.prompt_entry = tk.Text(self.image_output_tab, width=60, height=3, wrap=tk.WORD)
129 | self.prompt_entry.pack()
130 | self.negative_prompt_label = tk.Label(self.image_output_tab, text="Negative Prompt:")
131 | self.negative_prompt_label.pack()
132 | self.negative_prompt_entry = tk.Text(self.image_output_tab, width=60, height=3, wrap=tk.WORD)
133 | self.negative_prompt_entry.pack()
134 | negative = "lowres, error, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, blurry"
135 | self.negative_prompt_entry.insert(tk.END, negative)
136 | self.lineart_fidelity_label = tk.Label(self.image_output_tab, text="線画忠実度(1.0-1.5):")
137 | self.lineart_fidelity_label.pack()
138 | self.lineart_fidelity = tk.DoubleVar(value=1.0)
139 | self.lineart_fidelity_slider = tk.Scale(self.image_output_tab, from_=1.0, to=1.5, resolution=0.05, orient=tk.HORIZONTAL, variable=self.lineart_fidelity)
140 | self.lineart_fidelity_slider.pack()
141 | self.generate_image_button = tk.Button(self.image_output_tab, text="画像生成", command=self.generate_image)
142 | self.generate_image_button.pack()
143 |
144 |
145 | def on_model_selected(self, *args):
146 | self.sd_current_model_name = get_model(self.fastapi_url)
147 | self.selected_model = self.model_variable.get()
148 |
149 | def show_processed_image(self, img):
150 | # PILライブラリを使用してNumPy配列から画像を作成
151 | image = Image.fromarray(img)
152 |
153 | # アスペクト比を保ちつつ、長辺が400ピクセルになるようにリサイズする処理
154 | original_width, original_height = image.size
155 | max_length = 400
156 |
157 | # アスペクト比を計算
158 | aspect_ratio = original_width / original_height
159 |
160 | # 長辺が400ピクセルになるように新しいサイズを計算
161 | if original_width > original_height:
162 | new_width = max_length
163 | new_height = round(max_length / aspect_ratio)
164 | else:
165 | new_height = max_length
166 | new_width = round(max_length * aspect_ratio)
167 |
168 | # 新しいサイズで画像をリサイズ
169 | image_resized = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
170 |
171 | # リサイズした画像をTkinter用のPhotoImageに変換
172 | photo = ImageTk.PhotoImage(image_resized)
173 |
174 | # 現在選択されているタブを取得
175 | current_tab = self.tab_control.nametowidget(self.tab_control.select())
176 |
177 | # 以前に表示した画像があれば削除
178 | if hasattr(current_tab, 'image_label'):
179 | current_tab.image_label.pack_forget()
180 | current_tab.image_label.destroy()
181 |
182 | # 新しい画像を表示するLabelを作成して配置
183 | current_tab.image_label = tk.Label(current_tab, image=photo)
184 | current_tab.image_label.image = photo # 参照を保持しておかないと画像が表示されなくなる
185 | current_tab.image_label.pack()
186 |
187 |
188 | def clear_processed_image(self):
189 | current_tab = self.tab_control.nametowidget(self.tab_control.select())
190 | if hasattr(current_tab, 'image_label'):
191 | current_tab.image_label.pack_forget()
192 | current_tab.image_label.destroy()
193 | delattr(current_tab, 'image_label') # この行を追加
194 |
195 | def setup_drag_and_drop(self):
196 | for tab in [self.line_input_tab, self.canny_input_tab]:
197 | tab.drop_target_register(DND_FILES)
198 | tab.dnd_bind('<>', self.on_drop)
199 |
200 | def on_drop(self, event):
201 | files = self.parse_dropped_files(event.data)
202 | if files:
203 | try:
204 | self.image_path = files[0].encode('utf-8').decode(sys.getfilesystemencoding())
205 | except UnicodeEncodeError as e:
206 | print(f"Error processing file name: {e}")
207 | return
208 | self.load_image(self.image_path)
209 |
210 | def parse_dropped_files(self, data):
211 | files = data.split()
212 | return [file.replace('{', '').replace('}', '') for file in files]
213 |
214 | def load_image(self, image_path):
215 | self.image_path = image_path # 画像パスを更新する
216 |
217 | img = Image.open(self.image_path).convert("RGBA")
218 | canvas = Image.new('RGBA', img.size, (255, 255, 255, 255)) # 白背景のキャンバスを作成
219 | img = Image.alpha_composite(canvas, img)
220 | img = img.convert("RGB") # 最終的な画像をRGB形式に変換
221 |
222 | # アスペクト比を保ちつつ、長辺が400ピクセルになるようにリサイズ
223 | max_size = (400, 400)
224 | img.thumbnail(max_size, Image.Resampling.LANCZOS)
225 |
226 | # 長辺を400にするためのリサイズ処理
227 | original_size = img.size
228 | ratio = float(max_size[0]) / max(original_size)
229 | new_size = tuple([int(x * ratio) for x in original_size])
230 | photo_img = img.resize(new_size, Image.Resampling.LANCZOS)
231 |
232 | photo = ImageTk.PhotoImage(photo_img)
233 |
234 | current_tab = self.tab_control.nametowidget(self.tab_control.select())
235 |
236 | # 既存のimage_labelがある場合、新しい画像で更新する
237 | if hasattr(current_tab, 'image_label'):
238 | current_tab.image_label.configure(image=photo)
239 | current_tab.image_label.image = photo
240 | else:
241 | current_tab.image_label = tk.Label(current_tab, image=photo)
242 | current_tab.image_label.image = photo
243 | current_tab.image_label.pack()
244 |
245 | # 画像の種類に応じて適切な変数を更新
246 | if current_tab == self.canny_input_tab:
247 | self.canny_pil = img # Canny用PIL画像を更新
248 |
249 | def analyze_prompt(self):
250 | global model
251 | model_dir = os.path.join(dpath, 'models/tagger')
252 | if model is None:
253 | model = modelLoad(model_dir)
254 | image_path = self.image_path
255 | tag = analysis(image_path, model_dir, model)
256 | execute_tags = ["monochrome", "greyscale", "lineart", "white background"]
257 | tag_list = tag.split(", ")
258 | filtered_tags = [t for t in tag_list if t not in execute_tags]
259 | new_tag = ", ".join(filtered_tags)
260 | self.prompt_text.delete("1.0", tk.END)
261 | self.prompt_entry.delete("1.0", tk.END)
262 | self.prompt_text.insert("1.0", new_tag)
263 | self.prompt_entry.insert("1.0", new_tag)
264 |
265 | def apply_canny(self):
266 | if self.image_path is None:
267 | return
268 | threshold1 = self.threshold1_slider.get()
269 | threshold2 = self.threshold2_slider.get()
270 | canny = canny_process(self.image_path, threshold1, threshold2)
271 | self.canny_pil = Image.fromarray(cv2.cvtColor(canny, cv2.COLOR_GRAY2RGB)) # Canny結果をPIL画像として保存
272 | self.show_processed_image(canny)
273 |
274 | def clear_canny(self):
275 | self.clear_processed_image()
276 | self.canny_pil = None
277 |
278 | def generate_image(self):
279 | output_dir = os.path.join(dpath, "output/")
280 | if not os.path.exists(output_dir):
281 | os.makedirs(output_dir)
282 | # self.image_path.splitから拡張子なしの画像の名前部分を抽出
283 | img_name = os.path.splitext(os.path.basename(self.image_path))[0]
284 | # 日時の文字列からファイル名として無効な文字を置換
285 | dt_now = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
286 | output_path = os.path.join(output_dir, img_name + "_" + dt_now + ".png")
287 | prompt = "normal map, " + self.prompt_entry.get("1.0", tk.END).strip()
288 | nega = self.negative_prompt_entry.get("1.0", tk.END).strip()
289 | lineart_fidelity = float(self.lineart_fidelity_slider.get())
290 |
291 | if self.canny_pil is None:
292 | self.canny_pil = Image.fromarray(cv2.cvtColor(canny_process(self.image_path, 20, 120), cv2.COLOR_GRAY2RGB))
293 | self.canny_pil = resize_image_aspect_ratio(self.canny_pil, 1200)
294 | output_pil = create_and_save_images(self.fastapi_url, prompt, nega, self.canny_pil, lineart_fidelity, output_path)
295 | output_np = np.array(output_pil)
296 | self.show_processed_image(output_np)
297 |
298 | def start(fastapi_url):
299 | app = Application(fastapi_url)
300 | app.mainloop()
--------------------------------------------------------------------------------
/utils/request_api.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import json
3 | import base64
4 | from datetime import datetime
5 | import os
6 | import itertools
7 | import random
8 | import re
9 | from PIL import Image, PngImagePlugin, ImageEnhance, ImageFilter, ImageOps
10 | import io
11 | import glob
12 | import cv2
13 |
14 |
15 | def build_payload(prompt, nega, w, h, unit1):
16 | return {
17 | "prompt": prompt,
18 | "negative_prompt": nega,
19 | "seed": -1,
20 | "sampler_name": "Euler a",
21 | "steps": 20,
22 | "cfg_scale": 7,
23 | "width": w,
24 | "height": h,
25 | "alwayson_scripts": {"ControlNet": {"args": [unit1]}},
26 | }
27 |
28 | def send_post_request(url, payload):
29 | headers = {
30 | "Content-Type": "application/json"
31 | }
32 | response = requests.post(url, data=json.dumps(payload), headers=headers)
33 | return response
34 |
35 |
36 | def save_image(data, url, file_name):
37 | image_string = data["images"][0]
38 | image_bytes = base64.b64decode(image_string)
39 |
40 | png_payload = {
41 | "image": "data:image/png;base64," + image_string
42 | }
43 | response2 = requests.post(url=f'{url}/sdapi/v1/png-info', json=png_payload)
44 | image_info = response2.json().get("info")
45 |
46 | image = Image.open(io.BytesIO(image_bytes))
47 | pnginfo = PngImagePlugin.PngInfo()
48 | if image_info: # Ensure image_info is not None
49 | pnginfo.add_text("parameters", image_info)
50 |
51 | image.save(file_name, pnginfo=pnginfo)
52 | return image
53 |
54 |
55 | def create_and_save_images(input_url, prompt, nega, canny_pil, lineart_fidelity, output_path):
56 | url = f"{input_url}/sdapi/v1/txt2img"
57 | w, h = canny_pil.size
58 | canny_bytes = io.BytesIO()
59 | canny_pil.save(canny_bytes, format='PNG')
60 | encoded_canny = base64.b64encode(canny_bytes.getvalue()).decode('utf-8')
61 |
62 | prompt = "masterpiece, best quality, SimplepositiveXLv1 , " + prompt
63 | unit1 = {
64 | "image": encoded_canny,
65 | "mask_image": None,
66 | "control_mode": "Balanced",
67 | "enabled": True,
68 | "guidance_end": 1,
69 | "guidance_start": 0,
70 | "pixel_perfect": True,
71 | "processor_res": 1200,
72 | "resize_mode": "Just Resize", # "Just Resize", "Crop and Resize", "Resize and Fill"
73 | "threshold_a": 64,
74 | "threshold_b": 64,
75 | "weight": lineart_fidelity,
76 | "module": "canny",
77 | "model": "control-lora-canny-rank256 [ec2dbbe4]",
78 | "save_detected_map": None,
79 | "hr_option": "Both"
80 | }
81 |
82 |
83 | payload = build_payload(prompt, nega, w, h, unit1)
84 | response = send_post_request(url, payload)
85 | image_data = response.json()
86 |
87 | if "images" in image_data and image_data["images"]:
88 | output_pil = save_image(image_data, input_url, output_path)
89 | print(f"Downloaded {output_path} to local")
90 | return output_pil
91 | else:
92 | print("Failed to generate image. 'images' key not found in the response.")
93 |
94 | def get_model(url):
95 | sd_models = requests.get(f"{url}/sdapi/v1/sd-models").json()
96 | sd_model_names = [i["title"] for i in sd_models]
97 | current_model_name = requests.get(f"{url}/sdapi/v1/options").json()["sd_model_checkpoint"]
98 | return sd_model_names, current_model_name
99 |
100 | def get_controlnet_model(url):
101 | cn_models = requests.get(f"{url}/controlnet/model_list").json()
102 | return cn_models
103 |
104 | def set_model(url, sd_model_name):
105 | option_payload = {
106 | "sd_model_checkpoint":sd_model_name,
107 | }
108 | response = requests.post(url=f'{url}/sdapi/v1/options', json=option_payload)
--------------------------------------------------------------------------------
/utils/tagger.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # https://github.com/kohya-ss/sd-scripts/blob/main/finetune/tag_images_by_wd14_tagger.py
3 |
4 | import csv
5 | import os
6 | os.environ['TF_FORCE_GPU_ALLOW_GROWTH'] = 'true'
7 |
8 | from PIL import Image
9 | import cv2
10 | import numpy as np
11 | from pathlib import Path
12 | import onnx
13 | import onnxruntime as ort
14 |
15 | # from wd14 tagger
16 | IMAGE_SIZE = 448
17 |
18 | model = None # Initialize model variable
19 |
20 |
21 | def convert_array_to_bgr(array):
22 | """
23 | Convert a NumPy array image to BGR format regardless of its original format.
24 |
25 | Parameters:
26 | - array: NumPy array of the image.
27 |
28 | Returns:
29 | - A NumPy array representing the image in BGR format.
30 | """
31 | # グレースケール画像(2次元配列)
32 | if array.ndim == 2:
33 | # グレースケールをBGRに変換(3チャンネルに拡張)
34 | bgr_array = np.stack((array,) * 3, axis=-1)
35 | # RGBAまたはRGB画像(3次元配列)
36 | elif array.ndim == 3:
37 | # RGBA画像の場合、アルファチャンネルを削除
38 | if array.shape[2] == 4:
39 | array = array[:, :, :3]
40 | # RGBをBGRに変換
41 | bgr_array = array[:, :, ::-1]
42 | else:
43 | raise ValueError("Unsupported array shape.")
44 |
45 | return bgr_array
46 |
47 |
48 | def preprocess_image(image):
49 | image = np.array(image)
50 | image = convert_array_to_bgr(image)
51 |
52 | size = max(image.shape[0:2])
53 | pad_x = size - image.shape[1]
54 | pad_y = size - image.shape[0]
55 | pad_l = pad_x // 2
56 | pad_t = pad_y // 2
57 | image = np.pad(image, ((pad_t, pad_y - pad_t), (pad_l, pad_x - pad_l), (0, 0)), mode="constant", constant_values=255)
58 |
59 | interp = cv2.INTER_AREA if size > IMAGE_SIZE else cv2.INTER_LANCZOS4
60 | image = cv2.resize(image, (IMAGE_SIZE, IMAGE_SIZE), interpolation=interp)
61 |
62 | image = image.astype(np.float32)
63 | return image
64 |
65 | def modelLoad(model_dir):
66 | onnx_path = os.path.join(model_dir, "model.onnx")
67 | # 実行プロバイダーをCPUのみに指定
68 | providers = ['CPUExecutionProvider']
69 | # InferenceSessionの作成時にプロバイダーのリストを指定
70 | ort_session = ort.InferenceSession(onnx_path, providers=providers)
71 | input_name = ort_session.get_inputs()[0].name
72 |
73 | # 実際に使用されているプロバイダーを取得して表示
74 | actual_provider = ort_session.get_providers()[0] # 使用されているプロバイダー
75 | print(f"Using provider: {actual_provider}")
76 |
77 | return [ort_session, input_name]
78 |
79 | def analysis(image_path, model_dir, model):
80 | ort_session = model[0]
81 | input_name = model[1]
82 |
83 | with open(os.path.join(model_dir, "selected_tags.csv"), "r", encoding="utf-8") as f:
84 | reader = csv.reader(f)
85 | l = [row for row in reader]
86 | header = l[0] # tag_id,name,category,count
87 | rows = l[1:]
88 | assert header[0] == "tag_id" and header[1] == "name" and header[2] == "category", f"unexpected csv format: {header}"
89 |
90 | general_tags = [row[1] for row in rows[1:] if row[2] == "0"]
91 | character_tags = [row[1] for row in rows[1:] if row[2] == "4"]
92 |
93 | tag_freq = {}
94 | undesired_tags = []
95 |
96 | # 画像をロードして前処理する
97 | image_pil = Image.open(image_path).convert("RGB")
98 |
99 | image_preprocessed = preprocess_image(image_pil)
100 | image_preprocessed = np.expand_dims(image_preprocessed, axis=0)
101 |
102 | # 推論を実行
103 | prob = ort_session.run(None, {input_name: image_preprocessed})[0][0]
104 | # タグを生成
105 | combined_tags = []
106 | general_tag_text = ""
107 | character_tag_text = ""
108 | remove_underscore = True
109 | caption_separator = ", "
110 | general_threshold = 0.35
111 | character_threshold = 0.35
112 |
113 | for i, p in enumerate(prob[4:]):
114 | if i < len(general_tags) and p >= general_threshold:
115 | tag_name = general_tags[i]
116 | if remove_underscore and len(tag_name) > 3: # ignore emoji tags like >_< and ^_^
117 | tag_name = tag_name.replace("_", " ")
118 |
119 | if tag_name not in undesired_tags:
120 | tag_freq[tag_name] = tag_freq.get(tag_name, 0) + 1
121 | general_tag_text += caption_separator + tag_name
122 | combined_tags.append(tag_name)
123 | elif i >= len(general_tags) and p >= character_threshold:
124 | tag_name = character_tags[i - len(general_tags)]
125 | if remove_underscore and len(tag_name) > 3:
126 | tag_name = tag_name.replace("_", " ")
127 |
128 | if tag_name not in undesired_tags:
129 | tag_freq[tag_name] = tag_freq.get(tag_name, 0) + 1
130 | character_tag_text += caption_separator + tag_name
131 | combined_tags.append(tag_name)
132 |
133 | # 先頭のカンマを取る
134 | if len(general_tag_text) > 0:
135 | general_tag_text = general_tag_text[len(caption_separator) :]
136 | if len(character_tag_text) > 0:
137 | character_tag_text = character_tag_text[len(caption_separator) :]
138 | tag_text = caption_separator.join(combined_tags)
139 | return tag_text
--------------------------------------------------------------------------------
/venv.cmd:
--------------------------------------------------------------------------------
1 | cmd /k venv\Scripts\activate
--------------------------------------------------------------------------------