Understanding the Waifuc Workflow

Let’s Whip Up Some Donuts

Now, you might find this title a tad surprising, but hang tight! Picture this scenario: you’ve got a charming little donut shop, and from time to time, customers with various taste preferences swing by to buy some delectable donuts. So, what’s on your to-do list?

The answer is pretty simple, broken down into three key tasks:

  1. Get Your Undecorated Donuts: Before your official opening each day, getting your hands on some ready-to-fry donuts is undoubtedly your top priority. This typically involves coordinating with your ingredient supplier or prepping them yourself.

  2. Artisanal Crafting Services: Before welcoming customers, you’ve got to pick the donuts, sift out any flawed ones, and keep track of losses. And then comes the wide array of customer requests: hollowed or intricately carved shapes, smearing icing, sprinkling toppings, and much more, making your day “eventful.”

  3. Wrap Up the Tempting Delights: When it’s time to hand them over to customers, don’t forget the elegant packaging and a cheerful message. Remember to wish them a delightful meal!

“Phew, that’s quite a hassle!”😅

For waifuc, although the workflow matches these steps—corresponding to Data Source, Action, and Exporter— it handles everything from acquiring the donuts to packaging and delivery within its realm. In a nutshell, waifuc can transform you into the proud owner of an all-in-one donut vending machine.

Note

To make upcoming reading more convenient, we’ve drawn some comparisons between concepts:

  • Donut Vending Production Line — Waifuc Workflow

  • Getting Undecorated Donuts — Source

  • Crafting Donuts — Action

  • Donut Packaging — Exporter

Source

Before you whip up those delectable donuts, you need to get your hands on some pre-fried donuts. Depending on your specific situation, you might have various options:

  • Mix and make the dough on-site, then fry them into golden-brown donuts (undecorated donuts).

  • Leftover donuts? Not bad! always the refrigerator works well.

  • Still not enough? Well, you could always “grab” some from your neighbor… like how Jerry often do😏

../../_images/jerry_and_donuts.png

Regardless of the scenario, all you need are the fried donuts—how the donuts came to be? Well, that’s not really important.

If you’ve grasped the above process, congratulations — you’ve also understood the concept of Source in waifuc. If we were to draw a parallel between this logic and the waifuc paradigm, it might look something like this:

Note

Please note that the following code is not executable; it’s for illustration and analogy purposes only

 1from mydonutsstore.source import FryingSource, RefrigeratorSource, NeighborSource
 2
 3# fry it now
 4process_source = FryingSource()
 5
 6# take from the 2nd compartment of your refrigerator
 7local_source = RefrigeratorSource(compartment_no=2)
 8
 9# *borrow* from your neighbor
10grab_source = NeighborSource(from="Crazy Dave")

In a nutshell, you might acquire images from various sources, like grabing from Danbooru, reading from your hard drive, or extracting frames from videos, and so on. However, they will all be output in a uniform format for downstream processing. For example, the following code illustrates different image data sources, but they are used in the same way with no differences:

 1from waifuc.source import DanbooruSource, LocalSource, VideoSource
 2
 3# Crawl images from Danbooru
 4danbooru_source = DanbooruSource(['1girl'])
 5
 6# Local images from directory '/your/directory'
 7local_source = LocalSource('/your/directory')
 8
 9# Extract frames from videos in '/your/anime/directory'
10video_source = VideoSource.from_directory('/your/anime/directory')

So, no matter where your images come from, they all play nicely together! 😄

Actions

Once you have enough undecorated donuts, the next step is the actual donut-making process. Depending on various requirements, you might perform the following actions:

  • Filtering:
    • Discard donuts that have gone bad overnight.

    • Get rid of donuts that were poorly made on the spot.

    • Toss out the neighbor’s subpar donuts with no regrets.

  • Processing:
    • Satisfy picky customers by carving suitable patterns on the donuts.

    • Spread the desired icing evenly over the donuts.

    • Sprinkle toppings like honey, sugar, nut sprinkles, jimmies, or other condiments as needed.

    • Special decorations might bring novelty during festivities.

    • If necessary, cut the donuts into bite-sized pieces for easy consumption.

After going through these steps, you’ll have finished donuts.

If you understood the process above, congratulations — you’ve also grasped the concept of Actions in waifuc. To draw a parallel with waifuc’s paradigm, the following code snippets provide a similar analogy:

Note

Please note that the code below is not meant to run, it’s for illustrative purposes only.

  • Producing some plain chocolate donuts

 1from mydonutsstore.action import IcingAction
 2from mydonutsstore.source import FryingSource
 3
 4# fry it by myself
 5source = FryingSource()
 6
 7# Icing with chocolate, and that is all
 8source = source.attach(
 9    IcingAction(flavor='chocolate'),
10)
11

The final product should look like this (image provided by DALL-E):

../../_images/donut_choco.png
  • Producing some strawberry-flavored donuts with jimmies

 1from mydonutsstore.action import IcingAction, JimmiesAction
 2from mydonutsstore.source import FryingSource
 3
 4# fry it by myself
 5source = FryingSource()
 6
 7# Icing with strawberry, and put some jimmies on it
 8source = source.attach(
 9    IcingAction(flavor='strawberry'),
10    JimmiesAction(),
11)

The final product will look something like this (image provided by DALL-E):

../../_images/donut_strawberry.png

In simple terms, in waifuc, after going through a series of actions (Action) such as filtering, processing, and slicing for images, you will get a collection of processed images that make up your training dataset. For example, in the following waifuc code, after crawling images, monochrome images will first be removed. Following that, if an image is too large, it will be resized to a smaller size. Finally, each image will be tagged. The resulting image data will go through these three processes sequentially.

 1from waifuc.action import NoMonochromeAction, AlignMinSizeAction, TaggingAction
 2from waifuc.source import DanbooruSource
 3
 4# crawl Surtr's sexy images from Danbooru
 5source = DanbooruSource(['surtr_(arknights)'])
 6
 7# 1. Drop the monochrome images.
 8# 2. If the image is too large, resize it to a smaller size.
 9# 3. Tag the images.
10source = source.attach(
11    NoMonochromeAction(),
12    AlignMinSizeAction(640),
13    TaggingAction()
14)

Order Matters

After seeing the above code, you might have a question – what is the relationship between the actions used in the attach function? Is their order important?

To answer this question, let’s continue with the analogy of the donut shop. In the previous content, we tried to make strawberry-flavored donuts with jimmies. In the analogous code, we used IcingAction first and then JimmiesAction, indicating that for donuts, we first apply icing and then sprinkle sugar candies.

But what if we reverse the order, first sprinkle sugar candies, and then apply icing – obviously, sugar candies will have a hard time sticking to the donut and will often end up in a mess. This means that JimmiesAction did not work as expected; afterward, we apply strawberry-flavored icing.

Here’s the code:

 1from mydonutsstore.action import IcingAction, JimmiesAction
 2from mydonutsstore.source import FryingSource
 3
 4# fry it by myself
 5source = FryingSource()
 6
 7# put jimmies before icing
 8source = source.attach(
 9    JimmiesAction(),  # jimmies action will fail
10    IcingAction(flavor='strawberry'),
11)

So, the donut you will end up with will look like this (image provided by DALL-E, please ignore the strawberry above because it seems to not quite understand what I meant):

../../_images/donut_strawberry_failed.png

In simple terms, the attach method represents the order of operations, and changing the order means a significant change in the processing flow – we shouldn’t understand the relationship between operations in the attach method as a simple unordered stack. For example, in the waifuc code example above, if we move NoMonochromeAction to the end, the entire process will become – first, scaling and tagging, and then filtering. The consequence of this is that a large number of monochrome images will be tagged first and then deleted, resulting in significantly slower program execution and unnecessary waste of computing resources.

 1from waifuc.action import NoMonochromeAction, AlignMinSizeAction, TaggingAction
 2from waifuc.source import DanbooruSource
 3
 4# crawl images from Danbooru
 5source = DanbooruSource(['surtr_(arknights)'])
 6source = source.attach(
 7    AlignMinSizeAction(640),
 8    # all the images will be tagged!!!!!
 9    # no matter if it is colorful or not
10    TaggingAction(),
11
12    # monochrome images dropped
13    # there is NO need to tag them actually
14    NoMonochromeAction(),
15)

Semi-Finished Products?

Imagine a scenario where you are preparing to make a batch of donuts with jimmies, and you have already applied the icing. However, you suddenly have to leave for a while due to an unexpected event. In a situation like this, what should you do when you come back? It’s similar to the following code, so what should you do next?

1from mydonutsstore.action import IcingAction
2from mydonutsstore.source import FryingSource
3
4# fry it by myself
5source = FryingSource()
6
7# just icing, not completed!
8process_source = source.attach(IcingAction(flavor='strawberry'))

It’s quite simple; you just need to heat it up a bit to melt the icing slightly and then sprinkle the sugar candies, just like this:

 1from mydonutsstore.action import IcingAction, JimmiesAction
 2from mydonutsstore.source import FryingSource
 3
 4# fry it by myself
 5source = FryingSource()
 6
 7# just icing, not completed!
 8process_source = source.attach(IcingAction(flavor='strawberry'))
 9
10# heating, and then put jimmies on donuts
11source = source.attach(
12    HeatingAction(),  # melting down the ice
13    JimmiesAction(),
14)

In reality, when the first attach is called, a new secondary data source (Secondary Source, corresponding to the concept of Primary Source, such as the DanbooruSource at the beginning) has already been generated. This data source is exactly the same as the others, except it produces donuts with the previous icing. We just need to process this new data source as usual. In waifuc, this concept is similar. For example, the following two code snippets are completely equivalent:

  • Code 1 (using a single attach):

 1from waifuc.action import NoMonochromeAction, AlignMinSizeAction, TaggingAction
 2from waifuc.source import DanbooruSource
 3
 4# crawl images from danbooru
 5source = DanbooruSource(['surtr_(arknights)'])
 6source = source.attach(
 7    NoMonochromeAction(),
 8    AlignMinSizeAction(640),
 9    TaggingAction(),
10)
  • Code 2 (first using attach to generate a new source, then attaching operations to that source):

 1from waifuc.action import NoMonochromeAction, AlignMinSizeAction, TaggingAction
 2from waifuc.source import DanbooruSource
 3
 4# crawl images from danbooru
 5source = DanbooruSource(['surtr_(arknights)'])
 6source = source.attach(NoMonochromeAction())  # just drop the monochrome images
 7
 8# process the colorful images
 9source = source.attach(
10    AlignMinSizeAction(640),
11    TaggingAction(),
12)

Exporter

When we have finished making donuts, we need to deliver them to the customers. Specifically, you may encounter the following scenarios:

  • During tea time, hungry customers want to have hot donuts right away. You need to put the freshly made donuts into paper bags and hand them over to the customers.

  • Customers want to buy a dozen donuts as a snack for late-night coding. You need to neatly pack the donuts in a box and help the customer wrap it for carrying back home.

  • Customers who have heard about your donuts want to buy a whole cartload of them for a party. You need to put the donuts in a fresh-keeping bag, load them into a van, secure them with tie-down straps to prevent them from falling off during transport.

In any of these cases, what you need to do is deliver the finished donuts to the customers. You need to deliver in different forms according to the actual needs of the customers, but you don’t care about how the donuts were made before or how the customers will use them.

If you understand the above process, congratulations - you have also understood the concept of Exporter in waifuc. If we compare this logic to the paradigm of waifuc, it should be something like the following code snippets

Note

Please note that the following code cannot actually run, it is for analogy and illustration purposes.

  • Hand over the donuts directly to the customer

1# NOTE: you already have a source that can provide some donuts
2from mydonutsstore.exporter import HandOverExporter
3
4# hand it over the customer
5source.export(HandOverExporter())
  • Package a dozen donuts and hand them over to the customer

1# NOTE: you already have a source that can provide some donuts
2from mydonutsstore.exporter import DozenPackExporter
3
4# hand the dozen of donuts over the customer
5source.export(DozenPackExporter())
  • Load a large quantity of donuts into boxes and onto a pickup truck

1# NOTE: you already have a source that can provide some donuts
2from mydonutsstore.exporter import TruckExporter
3
4# place the donuts onto the pickup truck
5# which has a bed length of 5.5 feet, width of 5 feet, and height of 1.5 feet.
6source.export(TruckExporter(type='pickup', length=5.5, width=5, height=1.5))

With this, your donut shop is officially up and running. Congratulations! 🎉👏🎊

In simple terms, a Data Exporter retrieves data from the data source you provide (it could be a native data source or a secondary data source) and exports the data in the desired format according to a preset process. For example, in the following waifuc code, using different Data Exporters with the same data source results in completely different data formats:

  • Export in the format of images + JSON metadata

1# NOTE: you already have a source that can provide image items
2from waifuc.export import SaveExporter
3
4# save to /my/dataset with images and JSON metadata files
5source.exporter(SaveExporter('/my/dataset'))
  • Export only images, without JSON metadata

1# NOTE: you already have a source that can provide image items
2from waifuc.export import SaveExporter
3
4# save to /my/dataset with images only, no JSON files
5source.exporter(SaveExporter('/my/dataset', no_meta=True))
  • Export in the format of images + TXT, which is suitable for training most neural network models

1# NOTE: you already have a source that can provide image items
2from waifuc.export import TextualInversionExporter
3
4# save to /my/lora/dataset with images and TXTs
5# which is ready for LoRA Training
6source.exporter(TextualInversionExporter('/my/lora/dataset'))

With this, a complete waifuc image data processing workflow is set up.