ingredients¶
from pierogis.ingredients import Pierogi, SpatialQuantize, Sort, Threshold, Dish, Recipe...
A processing factory, called an Ingredient,
has a prep() method for receiving parameters,
and a cook() method for operating
on a numpy array to produce a programmatic output.
These two methods are usually called implicitly,
prep through Ingredient()
and cook obfuscated in the typical usage flow.
prep can be seen as parameterizing the manipulation
while cook applies it (to an array).
Here are some examples of Ingredient subtypes.
pierogi¶
Ingredient is one of the simplest Ingredient types.
It stores pixels, usually loaded from an image.
Pierogi is unique in that the array it returns from its cook function
is not based on the input
pierogi = Pierogi(file="/Users/kyle/Desktop/image.jpg")
pierogi = Pierogi(pixels=np.array(
[[[0, 0, 0], [0, 0, 0]], [[255, 255, 255], [255, 255, 255]]]
)
quantize¶
Quantize is another Ingredient.
When cooked, it will process an incoming numpy array and return an array
where every pixel has been quantized to the closest color in the palette.
There is also the SpatialQuantize variant which is used by the cli tool.
palette = [
[0, 0, 0],
[127, 127, 127],
[255, 255, 255]
]
quantize = Quantize(palette=palette)
quantized_pixels = quantize.cook(pierogi.pixels)
This should produce a pixel for pixel quantized version of the input array.
As you can see above, a Pierogi has a pixels member.
This is the internal numpy pixel array of that Pierogi
with shape (width, height, 3).
Some other Ingredient types include:
Threshold, Flip, and Rotate.
recipe¶
A typical flow allows you to create a pipeline of Ingredient types
that sequentially apply their cook method on to
the previous array of pixels.
A pipeline in pierogis is called a Recipe.
It is an Ingredient itself.
recipe = Recipe(ingredients=[pierogi, quantize])
recipe.cook()
recipe = Recipe(ingredients=[quantize])
recipe.cook(pierogi.pixels)
The two will produce the same result. But there’s a better way.
dish¶
“get to the point already”
a wiser man
We could also use a Dish to serve this recipe.
This is the recommended way to use Recipe.
dish = Dish(recipe=recipe, pierogi=pierogi)
cooked_dish = dish.serve()
The recipe gets cooked sequentially for each pierogi in pierogis.
The output cooked_dish has pierogi member set with cooked pixels.
seasoning¶
There is also a concept of seasonings. They can be used to apply something like a mask to other ingredients that affect the pixels they act on.
sort = Sort()
threshold = Threshold()
# season sort with threshold
sort.season(threshold)
cook() outputs a black and white array.
That’s what makes it a seasoning.
Now that sort is seasoned with the Threshold,
it will only sort pixels that have been “colored”
white by the Threshold.
extending¶
To create a custom Ingredient type,
it must subclass Ingredient and override the
cook() and prep() methods.
class Custom(Ingredient):
def prep(self, brighten: int, scale: int, **kwargs):
self.brighten = brighten
self.scale = scale
def cook(self, pixels: np.ndarray):
return (self.pixels + self.brighten) /*self.scale
prep¶
Override to parameterize your manipulation
This means any settings, constants,
or inputs that configure the new functionality.
Think about the palette used with
quantization.
def prep(self, brighten: int, scale: int, *args, **kwargs):
self.brighten = brighten
self.scale = scale
cook¶
Override to perform the manipulation
This is the function that you acts on an input pixel grid.
More specifically, this function receives
a (width, height, 3) ndarray
and should return a 3d array that is also size 3 in the last dimension.
def cook(self, pixels: np.ndarray):
return (self.pixels + self.brighten) * self.scale
This function increases the r, g, and b of every pixel by self.brighten
then multiplies that sum for each by self.scale.
Numpy operations can be pretty fast if you can keep them vectorized. This means try to avoid looping over the columns and rows of an array.