pyrogis API documentation

# pyrogis

## ingredients

> "`pierogis` is the name of the framework;

> `pyrogis` is the name of the python package and cli tool"

> - a wise man

    from pyrogis 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` on __init__ and `cook` obfuscated in the typical usage flow. `prep` can be seen as parameterizing the manipulation while `cook` applies it (to an array).

### pierogi

`Pierogi` 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 for 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, an `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, pierogis=[pierogi])
    cooked_dish = dish.serve()

The recipe gets cooked sequentially for each pierogi in `pierogis`. The output dish has `pierogis` member set with cooked pierogis. The `save` method of `dish` can be used as well.

### 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()
    seasoning = Threshold()

    sort.season(seasoning)

`Threshold.cook` outputs a black and white array. 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 `prep` 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 `cook` 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.

View Source

    """
    <a name="ingredients"></a>
    ## ingredients

    >*"`pierogis` is the name of the framework;*
    >
    >*`pyrogis` is the name of the python package and cli tool"*
    >
    >\- a wise man

    ```python
    from pyrogis 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` on __init__ and `cook` obfuscated in the typical usage flow.
    `prep` can be seen as parameterizing the manipulation
    while `cook` applies it (to an array).

    ### pierogi

    `Pierogi` 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

    ```python
    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 for the cli tool.

    ```python
    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, an `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.

    ```python
    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`.

    ```python
    dish = Dish(recipe=recipe, pierogis=[pierogi])
    cooked_dish = dish.serve()
    ```

    The recipe gets cooked sequentially for each pierogi in `pierogis`.
    The output dish has `pierogis` member set with cooked pierogis.
    The `save` method of `dish` can be used as well.

    ### 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.

    ```python
    sort = Sort()
    seasoning = Threshold()

    sort.season(seasoning)
    ```

    `Threshold.cook` outputs a black and white array.
    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.

    ```python
    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 `prep` to parameterize your manipulation.*

    This means any settings, constants,
    or inputs that configure the new functionality.
    Think about the `palette` used with
    quantization.

    ```python
    def prep(self, brighten: int, scale: int, *args, **kwargs):
        self.brighten = brighten
        self.scale = scale
    ```

    ### cook

    *Override `cook` 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.

    ```python
    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.
    """

    from .chef import Chef
    from .ingredients import Dish
    from .ingredients import Ingredient
    from .ingredients import Pierogi
    from .ingredients import Quantize
    from .ingredients import Recipe
    from .ingredients import Sort
    from .ingredients import SpatialQuantize
    from .ingredients import Threshold

    __version__ = '0.2.0'

    __all__ = [
        'Pierogi',
        'Dish',
        'Ingredient',
        'Recipe',
        'Pierogi',
        'Quantize',
        'SpatialQuantize',
        'Sort',
        'Threshold',
        'Chef'
    ]

class Pierogi(pyrogis.ingredients.ingredient.Ingredient):
View Source

    class Pierogi(Ingredient):
        """
        image container for iterative pixel manipulation

        uses PIL.Image to load to self.pixels
        """

        RESAMPLE = Image.NEAREST
        """default resize algorithm is nearest neighbor"""

        pixels: np.ndarray
        """underlying numpy pixels array"""

        @property
        def image(self) -> Image.Image:
            """
            turn the numpy array into a PIL Image
            """
            image = Image.fromarray(np.rot90(self.pixels), 'RGB')
            return image

        @property
        def width(self) -> int:
            """
            1st dimension of the underlying pixel array
            """
            return self.pixels.shape[0]

        @property
        def height(self) -> int:
            """
            2nd dimension of the underlying pixel array
            """
            return self.pixels.shape[1]

        def prep(
                self,
                pixels: np.ndarray = None,
                shape: tuple = (0, 0),
                image: Image.Image = None,
                file: str = None,
                **kwargs
        ) -> None:
            """
            provide the source image in a number of ways

            :param pixels: numpy array

            :param shape: shape to make simple pixels array

            :param image: PIL Image that has already been loaded

            :param file: file path to load from
            """

            if pixels is None:
                if image is not None:
                    # rotate the image array on receipt so that
                    # array dimensions are (width, height, 3)
                    pixels = np.rot90(np.array(image.convert('RGB')), axes=(1, 0))
                elif file is not None:
                    self.file = file
                    pixels = np.rot90(np.array(imageio.imread(file)), axes=(1, 0))
                elif shape is not None:
                    pixels = np.full((*shape, 3), self._default_pixel)
                else:
                    raise Exception("one of image, file, or shape must be provided")

            self.pixels = pixels

        def cook(self, pixels: np.ndarray) -> np.ndarray:
            """
            performs actions on a pixel array and returns a cooked array
            """
            return self.pixels

        def show(self) -> None:
            """
            open an image viewer to display the array
            """
            self.image.show()

        def save(self, path: str, optimize: bool =False) -> None:
            """
            save the image to the given path
            """

            output_filename = path
            if os.path.isdir(path):
                output_filename = os.path.join(
                    path, os.path.split(self.file)[1]
                )

            self.image.save(output_filename, optimize=optimize)

        def resize(self, width: int, height: int, resample: int = RESAMPLE):
            """
            resize pixels to new width and height

            :param width: width to resize to

            :param height: height to resize to

            :param resample: resample method to use in Image.resize.
            PIL documentation:

            >    "An optional resampling filter.
            >    This can be one of PIL.Image.NEAREST (use nearest neighbour),
            >    PIL.Image.BILINEAR (linear interpolation),
            >    PIL.Image.BICUBIC (cubic spline interpolation),
            >    or PIL.Image.LANCZOS (a high-quality downsampling filter).
            >    If omitted, or if the image has mode “1” or “P”, it is set PIL.Image.NEAREST."
            """

            self.pixels = np.array(
                Image.fromarray(
                    self.pixels
                ).resize(
                    (height, width), resample
                )
            )

image container for iterative pixel manipulation

uses PIL.Image to load to self.pixels

RESAMPLE = 0

default resize algorithm is nearest neighbor

pixels: numpy.ndarray

underlying numpy pixels array

image: PIL.Image.Image

turn the numpy array into a PIL Image

width: int

1st dimension of the underlying pixel array

height: int

2nd dimension of the underlying pixel array

def prep( self, pixels: numpy.ndarray = None, shape: tuple = (0, 0), image: PIL.Image.Image = None, file: str = None, **kwargs ) -> None:
View Source

        def prep(
                self,
                pixels: np.ndarray = None,
                shape: tuple = (0, 0),
                image: Image.Image = None,
                file: str = None,
                **kwargs
        ) -> None:
            """
            provide the source image in a number of ways

            :param pixels: numpy array

            :param shape: shape to make simple pixels array

            :param image: PIL Image that has already been loaded

            :param file: file path to load from
            """

            if pixels is None:
                if image is not None:
                    # rotate the image array on receipt so that
                    # array dimensions are (width, height, 3)
                    pixels = np.rot90(np.array(image.convert('RGB')), axes=(1, 0))
                elif file is not None:
                    self.file = file
                    pixels = np.rot90(np.array(imageio.imread(file)), axes=(1, 0))
                elif shape is not None:
                    pixels = np.full((*shape, 3), self._default_pixel)
                else:
                    raise Exception("one of image, file, or shape must be provided")

            self.pixels = pixels

provide the source image in a number of ways

:param pixels: numpy array

:param shape: shape to make simple pixels array

:param image: PIL Image that has already been loaded

:param file: file path to load from

def cook(self, pixels: numpy.ndarray) -> numpy.ndarray:
View Source

        def cook(self, pixels: np.ndarray) -> np.ndarray:
            """
            performs actions on a pixel array and returns a cooked array
            """
            return self.pixels

performs actions on a pixel array and returns a cooked array

def show(self) -> None:
View Source

        def show(self) -> None:
            """
            open an image viewer to display the array
            """
            self.image.show()

open an image viewer to display the array

def save(self, path: str, optimize: bool = False) -> None:
View Source

        def save(self, path: str, optimize: bool =False) -> None:
            """
            save the image to the given path
            """

            output_filename = path
            if os.path.isdir(path):
                output_filename = os.path.join(
                    path, os.path.split(self.file)[1]
                )

            self.image.save(output_filename, optimize=optimize)

save the image to the given path

def resize(self, width: int, height: int, resample: int = 0):
View Source

        def resize(self, width: int, height: int, resample: int = RESAMPLE):
            """
            resize pixels to new width and height

            :param width: width to resize to

            :param height: height to resize to

            :param resample: resample method to use in Image.resize.
            PIL documentation:

            >    "An optional resampling filter.
            >    This can be one of PIL.Image.NEAREST (use nearest neighbour),
            >    PIL.Image.BILINEAR (linear interpolation),
            >    PIL.Image.BICUBIC (cubic spline interpolation),
            >    or PIL.Image.LANCZOS (a high-quality downsampling filter).
            >    If omitted, or if the image has mode “1” or “P”, it is set PIL.Image.NEAREST."
            """

            self.pixels = np.array(
                Image.fromarray(
                    self.pixels
                ).resize(
                    (height, width), resample
                )
            )

resize pixels to new width and height

:param width: width to resize to

:param height: height to resize to

:param resample: resample method to use in Image.resize. PIL documentation:

> "An optional resampling filter. This can be one of PIL.Image.NEAREST (use nearest neighbour), PIL.Image.BILINEAR (linear interpolation), PIL.Image.BICUBIC (cubic spline interpolation), or PIL.Image.LANCZOS (a high-quality downsampling filter). If omitted, or if the image has mode “1” or “P”, it is set PIL.Image.NEAREST."

##### Inherited Members

pyrogis.ingredients.ingredient.Ingredient
  Ingredient
  opacity
  mask
  mask_pixels
  season

class Dish(pyrogis.ingredients.ingredient.Ingredient):
View Source

    class Dish(Ingredient):
        """
        crop and cook an entire recipe for all pixels
        """

        @property
        def frames(self):
            return len(self.pierogis)

        def prep(
                self, recipe=None,
                pierogis: List[Pierogi] = None,
                file=None,
                path=None,
                **kwargs
        ):
            """
            set the recipe to cook for this dish

            :param recipe: something callable that returns a
            cook(pixels) method.
            Any Ingredient (including recipe) is an example of this

            :param pierogis: a list of Pierogi to cook

            :param file: a file to use as input to

            :param path: a list of Pierogi to cook
            """

            if pierogis is None:
                pierogis = []

                if file is not None:
                    try:
                        # first try to load as video/animation
                        images = imageio.mimread(file, memtest=False)
                        for image in images:
                            pierogis.append(
                                Pierogi(pixels=np.rot90(
                                    np.asarray(image), axes=(1, 0)
                                ))
                            )

                    except ValueError:
                        # then load as a single image
                        pierogis = [Pierogi(file=file)]

                elif path is not None:
                    pierogis = self.get_path_pierogis(path)

                else:
                    raise ValueError("Could not create pierogis")

            self.pierogis = pierogis

            if recipe is None:
                Recipe()
            self.recipe = recipe

        @staticmethod
        def get_path_pierogis(path: str) -> List[Pierogi]:
            pierogis = []

            for file in os.listdir(path):
                if not os.path.isfile(file):
                    continue

                pierogis.append(Pierogi(file=file))

            return pierogis

        def cook(self, pixels: np.ndarray) -> np.ndarray:
            return self.recipe(0, 0).cook(self.pierogis[0].pixels)

        def serve(self) -> 'Dish':
            """
            cook the recipe and set the output to this object's pixel array
            """

            cooked_pierogis = []

            for frame in range(self.frames):
                pierogi = self.pierogis[frame]
                # cook with these pixels as first input
                recipe = self.recipe(frame + 1, self.frames)
                cooked_pixels = recipe.cook(pierogi.pixels)
                # ensure that the cooked pixels do not overflow 0-255
                clipped_pixels = np.clip(cooked_pixels, 0, 255)
                # # set the objects own pixels to the result of cooking
                cooked_pierogi = Pierogi(pixels=clipped_pixels)

                cooked_pierogis.append(cooked_pierogi)

            return Dish(pierogis=cooked_pierogis)

        def save(
                self, path, optimize: bool = True, duration: float = None, fps: int = 25
        ) -> None:
            """
            :param duration: s duration between frames
            """
            if len(self.pierogis) > 1:
                ims = [np.asarray(pierogi.image) for pierogi in self.pierogis]
                if duration is not None:
                    imageio.mimwrite(
                        path,
                        ims=ims,
                        duration=duration,
                        fps=fps
                    )
                else:
                    imageio.mimwrite(
                        path,
                        ims=ims,
                        fps=fps
                    )

                if optimize and os.path.splitext(path)[1] == ".gif":
                    try:
                        import pygifsicle
                        pygifsicle.optimize(path)
                    except FileNotFoundError as err:
                        print(err)

            elif len(self.pierogis) == 1:
                self.pierogis[0].save(path)

            else:
                raise Exception("Dish has no pierogis")

crop and cook an entire recipe for all pixels

frames
def prep( self, recipe=None, pierogis: List[pyrogis.ingredients.pierogi.Pierogi] = None, file=None, path=None, **kwargs ):
View Source

        def prep(
                self, recipe=None,
                pierogis: List[Pierogi] = None,
                file=None,
                path=None,
                **kwargs
        ):
            """
            set the recipe to cook for this dish

            :param recipe: something callable that returns a
            cook(pixels) method.
            Any Ingredient (including recipe) is an example of this

            :param pierogis: a list of Pierogi to cook

            :param file: a file to use as input to

            :param path: a list of Pierogi to cook
            """

            if pierogis is None:
                pierogis = []

                if file is not None:
                    try:
                        # first try to load as video/animation
                        images = imageio.mimread(file, memtest=False)
                        for image in images:
                            pierogis.append(
                                Pierogi(pixels=np.rot90(
                                    np.asarray(image), axes=(1, 0)
                                ))
                            )

                    except ValueError:
                        # then load as a single image
                        pierogis = [Pierogi(file=file)]

                elif path is not None:
                    pierogis = self.get_path_pierogis(path)

                else:
                    raise ValueError("Could not create pierogis")

            self.pierogis = pierogis

            if recipe is None:
                Recipe()
            self.recipe = recipe

set the recipe to cook for this dish

:param recipe: something callable that returns a cook(pixels) method. Any Ingredient (including recipe) is an example of this

:param pierogis: a list of Pierogi to cook

:param file: a file to use as input to

:param path: a list of Pierogi to cook

@staticmethod
def get_path_pierogis(path: str) -> List[pyrogis.ingredients.pierogi.Pierogi]:
View Source

        @staticmethod
        def get_path_pierogis(path: str) -> List[Pierogi]:
            pierogis = []

            for file in os.listdir(path):
                if not os.path.isfile(file):
                    continue

                pierogis.append(Pierogi(file=file))

            return pierogis

def cook(self, pixels: numpy.ndarray) -> numpy.ndarray:
View Source

        def cook(self, pixels: np.ndarray) -> np.ndarray:
            return self.recipe(0, 0).cook(self.pierogis[0].pixels)

performs actions on a pixel array and returns a cooked array

def serve(self) -> pyrogis.ingredients.dish.Dish:
View Source

        def serve(self) -> 'Dish':
            """
            cook the recipe and set the output to this object's pixel array
            """

            cooked_pierogis = []

            for frame in range(self.frames):
                pierogi = self.pierogis[frame]
                # cook with these pixels as first input
                recipe = self.recipe(frame + 1, self.frames)
                cooked_pixels = recipe.cook(pierogi.pixels)
                # ensure that the cooked pixels do not overflow 0-255
                clipped_pixels = np.clip(cooked_pixels, 0, 255)
                # # set the objects own pixels to the result of cooking
                cooked_pierogi = Pierogi(pixels=clipped_pixels)

                cooked_pierogis.append(cooked_pierogi)

            return Dish(pierogis=cooked_pierogis)

cook the recipe and set the output to this object's pixel array

def save( self, path, optimize: bool = True, duration: float = None, fps: int = 25 ) -> None:
View Source

        def save(
                self, path, optimize: bool = True, duration: float = None, fps: int = 25
        ) -> None:
            """
            :param duration: s duration between frames
            """
            if len(self.pierogis) > 1:
                ims = [np.asarray(pierogi.image) for pierogi in self.pierogis]
                if duration is not None:
                    imageio.mimwrite(
                        path,
                        ims=ims,
                        duration=duration,
                        fps=fps
                    )
                else:
                    imageio.mimwrite(
                        path,
                        ims=ims,
                        fps=fps
                    )

                if optimize and os.path.splitext(path)[1] == ".gif":
                    try:
                        import pygifsicle
                        pygifsicle.optimize(path)
                    except FileNotFoundError as err:
                        print(err)

            elif len(self.pierogis) == 1:
                self.pierogis[0].save(path)

            else:
                raise Exception("Dish has no pierogis")

:param duration: s duration between frames

##### Inherited Members

pyrogis.ingredients.ingredient.Ingredient
  Ingredient
  opacity
  mask
  mask_pixels
  season

class Ingredient:
View Source

    class Ingredient:
        """
        wrapper for a function to be applied to a grid of pixels.

        this base class defines some basic methods to be inherited and built upon,
        as well as a container for media represented as a numpy array.

        the two methods to be inherited and overridden are prep and cook.

        prep defines and sets the parameters of this image manipulation,
        and cook performs the manipulation of the array.

        in this base class, cook and prep do nothing
        """

        # used to fill in empty spots when cooked
        _black_pixel = np.array([0, 0, 0])
        _white_pixel = np.array([255, 255, 255])

        _default_pixel = _white_pixel

        def __init__(self, opacity: int = 100, mask: np.ndarray = None, **kwargs):
            """
            :param opacity: cook will overlay this % on input pixels

            :param mask: only cook with the pixels that are white in this mask

            :param kwargs: extra arguments to be passed to prep
            """
            self.opacity = opacity
            """opacity when overlaying on previous output"""
            self.mask = mask
            """opacity when overlaying"""
            self.prep(**kwargs)
            self.seasonings = []

        def __call__(self, frame: int, frames: int):
            return self

        def prep(self, **kwargs) -> None:
            """
            parameterize the cook function
            """
            pass

        def cook(self, pixels: np.ndarray) -> np.ndarray:
            """
            performs actions on a pixel array and returns a cooked array
            """
            return pixels

        def mask_pixels(self, pixels):
            """
            create a black and white mask from pixels
            and the seasonings attached to this ingredient

            :param pixels: pixels to create a mask from
            """
            white_pixels = np.resize(self._white_pixel, pixels.shape)

            base_mask = self.mask
            if base_mask is None:
                base_mask = white_pixels

            masks = [base_mask]

            for seasoning in self.seasonings:
                masks.append(seasoning.cook(pixels))

            binary_array = np.all(np.asarray(masks) == 255, axis=0)

            black_pixels = np.resize(self._black_pixel, pixels.shape)

            mask = black_pixels

            mask[binary_array] = white_pixels[binary_array]

            return mask

        def season(self, seasoning: 'Ingredient'):
            """
            add an ingredient whose cook function is
            used to produce this ingredients mask
            """
            self.seasonings.append(seasoning)

wrapper for a function to be applied to a grid of pixels.

this base class defines some basic methods to be inherited and built upon, as well as a container for media represented as a numpy array.

the two methods to be inherited and overridden are prep and cook.

prep defines and sets the parameters of this image manipulation, and cook performs the manipulation of the array.

in this base class, cook and prep do nothing

Ingredient(opacity: int = 100, mask: numpy.ndarray = None, **kwargs)
View Source

        def __init__(self, opacity: int = 100, mask: np.ndarray = None, **kwargs):
            """
            :param opacity: cook will overlay this % on input pixels

            :param mask: only cook with the pixels that are white in this mask

            :param kwargs: extra arguments to be passed to prep
            """
            self.opacity = opacity
            """opacity when overlaying on previous output"""
            self.mask = mask
            """opacity when overlaying"""
            self.prep(**kwargs)
            self.seasonings = []

:param opacity: cook will overlay this % on input pixels

:param mask: only cook with the pixels that are white in this mask

:param kwargs: extra arguments to be passed to prep

opacity

opacity when overlaying on previous output

mask

opacity when overlaying

def prep(self, **kwargs) -> None:
View Source

        def prep(self, **kwargs) -> None:
            """
            parameterize the cook function
            """
            pass

parameterize the cook function

def cook(self, pixels: numpy.ndarray) -> numpy.ndarray:
View Source

        def cook(self, pixels: np.ndarray) -> np.ndarray:
            """
            performs actions on a pixel array and returns a cooked array
            """
            return pixels

performs actions on a pixel array and returns a cooked array

def mask_pixels(self, pixels):
View Source

        def mask_pixels(self, pixels):
            """
            create a black and white mask from pixels
            and the seasonings attached to this ingredient

            :param pixels: pixels to create a mask from
            """
            white_pixels = np.resize(self._white_pixel, pixels.shape)

            base_mask = self.mask
            if base_mask is None:
                base_mask = white_pixels

            masks = [base_mask]

            for seasoning in self.seasonings:
                masks.append(seasoning.cook(pixels))

            binary_array = np.all(np.asarray(masks) == 255, axis=0)

            black_pixels = np.resize(self._black_pixel, pixels.shape)

            mask = black_pixels

            mask[binary_array] = white_pixels[binary_array]

            return mask

create a black and white mask from pixels and the seasonings attached to this ingredient

:param pixels: pixels to create a mask from

def season(self, seasoning: pyrogis.ingredients.ingredient.Ingredient):
View Source

        def season(self, seasoning: 'Ingredient'):
            """
            add an ingredient whose cook function is
            used to produce this ingredients mask
            """
            self.seasonings.append(seasoning)

add an ingredient whose cook function is used to produce this ingredients mask

class Recipe(pyrogis.ingredients.ingredient.Ingredient):
View Source

    class Recipe(Ingredient):
        """
        ingredient used to coordinate cooking of several ingredients

        when a recipe is cooked, its ingredients are cooked in order
        """

        def prep(self, ingredients: list = None, **kwargs):
            """
            provide a list of ingredients to cook in sequence

            :param ingredients: list of Ingredient objects
            """
            if ingredients is None:
                ingredients = []
            if isinstance(ingredients, list):
                self.ingredients = ingredients
            else:
                raise TypeError("kwarg 'ingredients' must be of type list")

        def cook(self, pixels: np.ndarray):
            """
            sequentially cooks each ingredient
            uses the pixels resulting from the previous cook
            """
            # input array used to select the
            under_pixels = pixels

            for ingredient in self.ingredients:
                # cook the lower layer
                cooked_pixels = ingredient.cook(under_pixels)
                mask = ingredient.mask_pixels(cooked_pixels)
                binary_array = np.all(mask == self._white_pixel, axis=2)

                # resize under array to cooked array
                cooked_width = cooked_pixels.shape[0]
                cooked_height = cooked_pixels.shape[1]
                resized_pixels = np.resize(under_pixels, (cooked_width, cooked_height, 3))

                masked_pixels = resized_pixels

                # layer cooked pixels over uncooked for true pixels (white in mask)
                masked_pixels[binary_array] = cooked_pixels[binary_array]

                # mix them based on the overlaying opacity
                mixed_pixels = (masked_pixels.astype(np.dtype(float))
                                * ingredient.opacity
                                +
                                resized_pixels.astype(np.dtype(float))
                                * (100 - ingredient.opacity)) / 100
                # reset for loop
                under_pixels = mixed_pixels.astype('uint8')

            # keep in range
            clipped_pixels = np.clip(under_pixels, 0, 255)

            return clipped_pixels

        def add(self, ingredient: Ingredient):
            """
            Add an ingredient
            """
            self.ingredients.append(ingredient)

ingredient used to coordinate cooking of several ingredients

when a recipe is cooked, its ingredients are cooked in order

def prep(self, ingredients: list = None, **kwargs):
View Source

        def prep(self, ingredients: list = None, **kwargs):
            """
            provide a list of ingredients to cook in sequence

            :param ingredients: list of Ingredient objects
            """
            if ingredients is None:
                ingredients = []
            if isinstance(ingredients, list):
                self.ingredients = ingredients
            else:
                raise TypeError("kwarg 'ingredients' must be of type list")

provide a list of ingredients to cook in sequence

:param ingredients: list of Ingredient objects

def cook(self, pixels: numpy.ndarray):
View Source

        def cook(self, pixels: np.ndarray):
            """
            sequentially cooks each ingredient
            uses the pixels resulting from the previous cook
            """
            # input array used to select the
            under_pixels = pixels

            for ingredient in self.ingredients:
                # cook the lower layer
                cooked_pixels = ingredient.cook(under_pixels)
                mask = ingredient.mask_pixels(cooked_pixels)
                binary_array = np.all(mask == self._white_pixel, axis=2)

                # resize under array to cooked array
                cooked_width = cooked_pixels.shape[0]
                cooked_height = cooked_pixels.shape[1]
                resized_pixels = np.resize(under_pixels, (cooked_width, cooked_height, 3))

                masked_pixels = resized_pixels

                # layer cooked pixels over uncooked for true pixels (white in mask)
                masked_pixels[binary_array] = cooked_pixels[binary_array]

                # mix them based on the overlaying opacity
                mixed_pixels = (masked_pixels.astype(np.dtype(float))
                                * ingredient.opacity
                                +
                                resized_pixels.astype(np.dtype(float))
                                * (100 - ingredient.opacity)) / 100
                # reset for loop
                under_pixels = mixed_pixels.astype('uint8')

            # keep in range
            clipped_pixels = np.clip(under_pixels, 0, 255)

            return clipped_pixels

sequentially cooks each ingredient uses the pixels resulting from the previous cook

def add(self, ingredient: pyrogis.ingredients.ingredient.Ingredient):
View Source

        def add(self, ingredient: Ingredient):
            """
            Add an ingredient
            """
            self.ingredients.append(ingredient)

Add an ingredient

##### Inherited Members

pyrogis.ingredients.ingredient.Ingredient
  Ingredient
  opacity
  mask
  mask_pixels
  season

class Quantize(pyrogis.ingredients.ingredient.Ingredient):
View Source

    class Quantize(Ingredient):
        """
        quantize reduces the color palette of the input pixels to a smaller set.
        """

        def prep(self,
                 colors=None, **kwargs):
            """
            parameters for spatial color quantization

            :param colors: colors to use. can be a list of str
            or pixel array likes
            """

            if colors is None:
                colors = np.asarray([[]])
            elif type(colors) is list:
                rgb_colors = []
                for color in colors:
                    if type(color) is str:
                        if color[0] != '#':
                            color = '#' + color
                        rgb_colors.append(ImageColor.getcolor(color, "RGB"))

                colors = np.asarray(rgb_colors)

            else:
                colors = np.array(colors)

            self.palette = colors.astype(np.dtype('uint8'))
            """palette to use"""

        def cook(self, pixels: np.ndarray):
            """
            get the closest rgb color in the palette to each pixel rgb
            "snap" to the colors in the palette
            """
            # pixels -> (width, height, 1, 3)
            # palette -> (1, 1, n, 3)
            # subtract -> (width, height, n, 3)

            # the difference between pixel r, g, b (3) and color
            # for each pixel (width, height),
            # for each color in the palette (n, 3)
            differences = (
                    np.expand_dims(pixels, axis=2)
                    - np.expand_dims(self.palette, axis=(0, 1))
            )

            # sum up the last axis (r + g + b)
            # and sqrt that sum
            # -> (width, height, n)
            distances = np.sqrt(np.sum(differences ** 2, axis=3))

            # get the minimum among the n color
            # smallest value in each n group last dimension (smallest sqrt sum)
            # -> (width, height, 1)
            nearest_palette_index = np.argmin(distances, axis=2)

            # replace the min index identified with the corresponding color
            # -> (width, height, 3)
            return self.palette[nearest_palette_index]

quantize reduces the color palette of the input pixels to a smaller set.

def prep(self, colors=None, **kwargs):
View Source

        def prep(self,
                 colors=None, **kwargs):
            """
            parameters for spatial color quantization

            :param colors: colors to use. can be a list of str
            or pixel array likes
            """

            if colors is None:
                colors = np.asarray([[]])
            elif type(colors) is list:
                rgb_colors = []
                for color in colors:
                    if type(color) is str:
                        if color[0] != '#':
                            color = '#' + color
                        rgb_colors.append(ImageColor.getcolor(color, "RGB"))

                colors = np.asarray(rgb_colors)

            else:
                colors = np.array(colors)

            self.palette = colors.astype(np.dtype('uint8'))
            """palette to use"""

parameters for spatial color quantization

:param colors: colors to use. can be a list of str or pixel array likes

def cook(self, pixels: numpy.ndarray):
View Source

        def cook(self, pixels: np.ndarray):
            """
            get the closest rgb color in the palette to each pixel rgb
            "snap" to the colors in the palette
            """
            # pixels -> (width, height, 1, 3)
            # palette -> (1, 1, n, 3)
            # subtract -> (width, height, n, 3)

            # the difference between pixel r, g, b (3) and color
            # for each pixel (width, height),
            # for each color in the palette (n, 3)
            differences = (
                    np.expand_dims(pixels, axis=2)
                    - np.expand_dims(self.palette, axis=(0, 1))
            )

            # sum up the last axis (r + g + b)
            # and sqrt that sum
            # -> (width, height, n)
            distances = np.sqrt(np.sum(differences ** 2, axis=3))

            # get the minimum among the n color
            # smallest value in each n group last dimension (smallest sqrt sum)
            # -> (width, height, 1)
            nearest_palette_index = np.argmin(distances, axis=2)

            # replace the min index identified with the corresponding color
            # -> (width, height, 3)
            return self.palette[nearest_palette_index]

get the closest rgb color in the palette to each pixel rgb "snap" to the colors in the palette

##### Inherited Members

pyrogis.ingredients.ingredient.Ingredient
  Ingredient
  opacity
  mask
  mask_pixels
  season

class SpatialQuantize(pyrogis.ingredients.quantize.Quantize):
View Source

    class SpatialQuantize(Quantize):
        """
        use the Spatial Color Quantization algorithm
        implemented in rust with rscolorq

        also performs dithering to make the palette appear richer.
        """
        PALETTE_SIZE = 8
        ITERATIONS = 3
        REPEATS = 1
        INITIAL_TEMP = 1
        FINAL_TEMP = .001
        FILTER_SIZE = 3
        DITHERING_LEVEL = .8

        palette_size: int
        """number of colors"""
        iterations: int
        """number of iterations to do at each coarseness level"""
        repeats: int
        """number of repeats to do of each annealing temp"""
        initial_temp: float
        """starting annealing temp (around 1)"""
        final_temp: float
        """final annealing temp (decimal near but above 0)"""
        filter_size: int
        """filter size for dithering"""
        dithering_level: float
        """relative amount of dithering (.5-1.5)"""
        seed: int
        """seed for rng"""

        def prep(
                self, palette_size=PALETTE_SIZE,
                iterations=ITERATIONS, repeats=REPEATS,
                initial_temp=INITIAL_TEMP, final_temp=FINAL_TEMP,
                dithering_level=DITHERING_LEVEL, seed=0, **kwargs
        ):
            """

            """
            super().prep(**kwargs)

            self.palette_size = palette_size
            """number of colors"""
            self.iterations = iterations
            """number of iterations to do at each coarseness level"""
            self.repeats = repeats
            """number of repeats to do of each annealing temp"""
            self.initial_temp = initial_temp
            """starting annealing temp (around 1)"""
            self.final_temp = final_temp
            """final annealing temp (decimal near but above 0)"""
            self.filter_size = self.FILTER_SIZE
            self.dithering_level = dithering_level
            """relative amount of dithering (.5-1.5)"""
            self.seed = seed
            """seed for rng"""

        def cook(self, pixels: np.ndarray):
            """
            use the binding to the rscolorq package in rust
            to perform an optimization in quantizing and dithering
            """

            # rotating and unrotating because different orientation is expected
            cooked_pixels = np.rot90(algorithms.quantize(
                np.ascontiguousarray(np.rot90(pixels), dtype=np.dtype('uint8')),
                self.palette,
                palette_size=self.palette_size,
                iters_per_level=self.iterations,
                repeats_per_temp=self.repeats,
                initial_temp=self.initial_temp,
                final_temp=self.final_temp,
                filter_size=self.filter_size,
                dithering_level=self.dithering_level,
                seed=self.seed
            ), axes=(1, 0))

            return cooked_pixels

use the Spatial Color Quantization algorithm implemented in rust with rscolorq

also performs dithering to make the palette appear richer.

PALETTE_SIZE = 8
ITERATIONS = 3
REPEATS = 1
INITIAL_TEMP = 1
FINAL_TEMP = 0.001
FILTER_SIZE = 3
DITHERING_LEVEL = 0.8
palette_size: int

number of colors

iterations: int

number of iterations to do at each coarseness level

repeats: int

number of repeats to do of each annealing temp

initial_temp: float

starting annealing temp (around 1)

final_temp: float

final annealing temp (decimal near but above 0)

filter_size: int

filter size for dithering

dithering_level: float

relative amount of dithering (.5-1.5)

seed: int

seed for rng

def prep( self, palette_size=8, iterations=3, repeats=1, initial_temp=1, final_temp=0.001, dithering_level=0.8, seed=0, **kwargs ):
View Source

        def prep(
                self, palette_size=PALETTE_SIZE,
                iterations=ITERATIONS, repeats=REPEATS,
                initial_temp=INITIAL_TEMP, final_temp=FINAL_TEMP,
                dithering_level=DITHERING_LEVEL, seed=0, **kwargs
        ):
            """

            """
            super().prep(**kwargs)

            self.palette_size = palette_size
            """number of colors"""
            self.iterations = iterations
            """number of iterations to do at each coarseness level"""
            self.repeats = repeats
            """number of repeats to do of each annealing temp"""
            self.initial_temp = initial_temp
            """starting annealing temp (around 1)"""
            self.final_temp = final_temp
            """final annealing temp (decimal near but above 0)"""
            self.filter_size = self.FILTER_SIZE
            self.dithering_level = dithering_level
            """relative amount of dithering (.5-1.5)"""
            self.seed = seed
            """seed for rng"""

def cook(self, pixels: numpy.ndarray):
View Source

        def cook(self, pixels: np.ndarray):
            """
            use the binding to the rscolorq package in rust
            to perform an optimization in quantizing and dithering
            """

            # rotating and unrotating because different orientation is expected
            cooked_pixels = np.rot90(algorithms.quantize(
                np.ascontiguousarray(np.rot90(pixels), dtype=np.dtype('uint8')),
                self.palette,
                palette_size=self.palette_size,
                iters_per_level=self.iterations,
                repeats_per_temp=self.repeats,
                initial_temp=self.initial_temp,
                final_temp=self.final_temp,
                filter_size=self.filter_size,
                dithering_level=self.dithering_level,
                seed=self.seed
            ), axes=(1, 0))

            return cooked_pixels

use the binding to the rscolorq package in rust to perform an optimization in quantizing and dithering

##### Inherited Members

pyrogis.ingredients.ingredient.Ingredient
  Ingredient
  opacity
  mask
  mask_pixels
  season

class Sort(pyrogis.ingredients.ingredient.Ingredient):
View Source

    class Sort(Ingredient):
        """
        sort a pixel array

        uses its mask to determine which groups of pixels to sort
        (white pixels get sorted)

        can use a seasoning to create that mask when cooking,
        or have it preloaded using a season method
        """

        delimiter: np.ndarray
        """pixel used as the sort subgroup delimiter"""
        rotate: Rotate
        """define the direction to rotate on"""

        def prep(
                self, rotate: Rotate = None,
                delimiter: np.ndarray = np.asarray([255, 255, 255]),
                **kwargs
        ):
            """
            extra kwargs get passed to the Rotate if one is not provided
            """
            self.delimiter = delimiter

            if rotate is None:
                rotate = Rotate(turns=0)

            self.rotate = rotate

        def cook(self, pixels: np.ndarray):
            """
            cook sorts from bottom to top after rotation, then unrotates.
            sort within each sequence group of contiguous white pixels
            in the mask (may be all white)
            """
            # rotate self.mask and pixels to correspond to self.angle
            rotate = self.rotate

            mask = self.mask_pixels(pixels)

            rotated_mask = rotate.cook(mask)
            rotated_pixels = rotate.cook(pixels)

            # false indicates that the pixel should not be sorted
            boolean_array = np.all(rotated_mask == self._white_pixel, axis=2)

            sorted_pixels = rotated_pixels
            # loop through one axis
            for i in range(rotated_pixels.shape[0]):
                # get that axis
                axis = rotated_pixels[i]
                # and the axis for the mask-truth
                boolean_axis = boolean_array[i]
                # get the indices for this row on the mask that are false
                masked_indices_axis = np.nonzero(np.invert(boolean_axis))[0]
                # split up the axis into sub groups at indices where mask is black (false)
                sort_groups = np.split(axis, masked_indices_axis)

                sorted_groups = []
                # loop through the groups
                for group in sort_groups:
                    # np.sort(group)
                    # if the subgroup to be sorted contains 0 or 1 pixels, ignore
                    if group.size > 3:
                        # intensity as the sorting criterion
                        intensities = np.sum(
                            group * np.asarray([0.299, 0.587, 0.114]), axis=1
                        )
                        # get "sort order" indices of the intensities of this group
                        indices = np.argsort(intensities)
                        # sort the group by these indices
                        group = group[indices]
                    sorted_groups.append(group)

                # concatenate the row back together, sorted in the mask
                sorted_pixels[i] = np.concatenate(sorted_groups)

            # unrotate sorted_pixels to return to correct orientation
            unrotate = Rotate.unrotate(rotate)
            sorted_pixels = unrotate.cook(sorted_pixels)

            return sorted_pixels

sort a pixel array

uses its mask to determine which groups of pixels to sort (white pixels get sorted)

can use a seasoning to create that mask when cooking, or have it preloaded using a season method

delimiter: numpy.ndarray

pixel used as the sort subgroup delimiter

rotate: pyrogis.ingredients.rotate.Rotate

define the direction to rotate on

def prep( self, rotate: pyrogis.ingredients.rotate.Rotate = None, delimiter: numpy.ndarray = array([255, 255, 255]), **kwargs ):
View Source

        def prep(
                self, rotate: Rotate = None,
                delimiter: np.ndarray = np.asarray([255, 255, 255]),
                **kwargs
        ):
            """
            extra kwargs get passed to the Rotate if one is not provided
            """
            self.delimiter = delimiter

            if rotate is None:
                rotate = Rotate(turns=0)

            self.rotate = rotate

extra kwargs get passed to the Rotate if one is not provided

def cook(self, pixels: numpy.ndarray):
View Source

        def cook(self, pixels: np.ndarray):
            """
            cook sorts from bottom to top after rotation, then unrotates.
            sort within each sequence group of contiguous white pixels
            in the mask (may be all white)
            """
            # rotate self.mask and pixels to correspond to self.angle
            rotate = self.rotate

            mask = self.mask_pixels(pixels)

            rotated_mask = rotate.cook(mask)
            rotated_pixels = rotate.cook(pixels)

            # false indicates that the pixel should not be sorted
            boolean_array = np.all(rotated_mask == self._white_pixel, axis=2)

            sorted_pixels = rotated_pixels
            # loop through one axis
            for i in range(rotated_pixels.shape[0]):
                # get that axis
                axis = rotated_pixels[i]
                # and the axis for the mask-truth
                boolean_axis = boolean_array[i]
                # get the indices for this row on the mask that are false
                masked_indices_axis = np.nonzero(np.invert(boolean_axis))[0]
                # split up the axis into sub groups at indices where mask is black (false)
                sort_groups = np.split(axis, masked_indices_axis)

                sorted_groups = []
                # loop through the groups
                for group in sort_groups:
                    # np.sort(group)
                    # if the subgroup to be sorted contains 0 or 1 pixels, ignore
                    if group.size > 3:
                        # intensity as the sorting criterion
                        intensities = np.sum(
                            group * np.asarray([0.299, 0.587, 0.114]), axis=1
                        )
                        # get "sort order" indices of the intensities of this group
                        indices = np.argsort(intensities)
                        # sort the group by these indices
                        group = group[indices]
                    sorted_groups.append(group)

                # concatenate the row back together, sorted in the mask
                sorted_pixels[i] = np.concatenate(sorted_groups)

            # unrotate sorted_pixels to return to correct orientation
            unrotate = Rotate.unrotate(rotate)
            sorted_pixels = unrotate.cook(sorted_pixels)

            return sorted_pixels

cook sorts from bottom to top after rotation, then unrotates. sort within each sequence group of contiguous white pixels in the mask (may be all white)

##### Inherited Members

pyrogis.ingredients.ingredient.Ingredient
  Ingredient
  opacity
  mask
  mask_pixels
  season

class Threshold(pyrogis.ingredients.seasonings.seasoning.Seasoning):
View Source

    class Threshold(Seasoning):
        """
        a seasoning that compares the brightness value of each pixel
        in the :param target pixel array.

        when used in a mix, the threshold will target the pixel array below it
        if it has not been initialized with target.

        as it is a subclass of seasoning,
        a Threshold instance has a season method to work with or without a target
        """

        LOWER_THRESHOLD = 64
        UPPER_THRESHOLD = 180

        lower_threshold: int
        """pixels below are `True`"""
        upper_threshold: int
        """pixels above are `True`"""

        def prep(
                self,
                lower_threshold: int = None,
                upper_threshold: int = None,
                **kwargs
        ):
            """
            set the threshold intensity levels

            calls Seasoning.prep with leftover kwargs

            pixels lower than lower_threshold
            or higher that upper_threshold
            are true (include_pixel)
            """
            # set include/exclude_pixel and target, if provided
            super().prep(**kwargs)

            if lower_threshold is None and upper_threshold is None:
                lower_threshold = self.LOWER_THRESHOLD
                upper_threshold = self.UPPER_THRESHOLD

            elif lower_threshold is None:
                lower_threshold = 0

            elif upper_threshold is None:
                upper_threshold = 255

            self.lower_threshold = lower_threshold
            self.upper_threshold = upper_threshold

        def cook(self, pixels: np.ndarray):
            """
            pixels with brightness >= upper_threshold
            or <= lower_threshold are replaced by include pixel (white)

            brightness = r * 0.299 + g * 0.587 + b * 0.114

            parallel computation in rust is 10x speedup
            """
            cooked_pixels = pixels.copy()

            # cook using the rust function
            cooked_pixels = algorithms.threshold(
                cooked_pixels.astype(np.dtype('uint8')),
                self.lower_threshold, self.upper_threshold,
                self.include_pixel.astype(np.dtype('uint8')),
                self.exclude_pixel.astype(np.dtype('uint8'))
            )

            return cooked_pixels

        def cook_np(self, pixels: np.ndarray):
            """
            perform the same operation as Threshold.cook, but only in numpy
            """

            include_pixels = np.resize(self.include_pixel, pixels.shape)
            exclude_pixels = np.resize(self.exclude_pixel, pixels.shape)

            # use exclude_pixels as the base
            cooked_pixels = exclude_pixels
            # get intensities from average of rgb
            intensities_array = np.sum(
                pixels * np.asarray([0.299, 0.587, 0.114]), axis=2
            )
            # if intensity <= lower or >= upper, True
            boolean_array = np.logical_or(
                intensities_array >= self.upper_threshold,
                intensities_array <= self.lower_threshold
            )

            # set True values in boolean_array to include_pixel
            cooked_pixels[boolean_array] = include_pixels[boolean_array]

            return cooked_pixels

a seasoning that compares the brightness value of each pixel in the :param target pixel array.

when used in a mix, the threshold will target the pixel array below it if it has not been initialized with target.

as it is a subclass of seasoning, a Threshold instance has a season method to work with or without a target

LOWER_THRESHOLD = 64
UPPER_THRESHOLD = 180
lower_threshold: int

pixels below are `True`

upper_threshold: int

pixels above are `True`

def prep( self, lower_threshold: int = None, upper_threshold: int = None, **kwargs ):
View Source

        def prep(
                self,
                lower_threshold: int = None,
                upper_threshold: int = None,
                **kwargs
        ):
            """
            set the threshold intensity levels

            calls Seasoning.prep with leftover kwargs

            pixels lower than lower_threshold
            or higher that upper_threshold
            are true (include_pixel)
            """
            # set include/exclude_pixel and target, if provided
            super().prep(**kwargs)

            if lower_threshold is None and upper_threshold is None:
                lower_threshold = self.LOWER_THRESHOLD
                upper_threshold = self.UPPER_THRESHOLD

            elif lower_threshold is None:
                lower_threshold = 0

            elif upper_threshold is None:
                upper_threshold = 255

            self.lower_threshold = lower_threshold
            self.upper_threshold = upper_threshold

set the threshold intensity levels

calls Seasoning.prep with leftover kwargs

pixels lower than lower_threshold or higher that upper_threshold are true (include_pixel)

def cook(self, pixels: numpy.ndarray):
View Source

        def cook(self, pixels: np.ndarray):
            """
            pixels with brightness >= upper_threshold
            or <= lower_threshold are replaced by include pixel (white)

            brightness = r * 0.299 + g * 0.587 + b * 0.114

            parallel computation in rust is 10x speedup
            """
            cooked_pixels = pixels.copy()

            # cook using the rust function
            cooked_pixels = algorithms.threshold(
                cooked_pixels.astype(np.dtype('uint8')),
                self.lower_threshold, self.upper_threshold,
                self.include_pixel.astype(np.dtype('uint8')),
                self.exclude_pixel.astype(np.dtype('uint8'))
            )

            return cooked_pixels

pixels with brightness >= upper_threshold or <= lower_threshold are replaced by include pixel (white)

brightness = r * 0.299 + g * 0.587 + b * 0.114

parallel computation in rust is 10x speedup

def cook_np(self, pixels: numpy.ndarray):
View Source

        def cook_np(self, pixels: np.ndarray):
            """
            perform the same operation as Threshold.cook, but only in numpy
            """

            include_pixels = np.resize(self.include_pixel, pixels.shape)
            exclude_pixels = np.resize(self.exclude_pixel, pixels.shape)

            # use exclude_pixels as the base
            cooked_pixels = exclude_pixels
            # get intensities from average of rgb
            intensities_array = np.sum(
                pixels * np.asarray([0.299, 0.587, 0.114]), axis=2
            )
            # if intensity <= lower or >= upper, True
            boolean_array = np.logical_or(
                intensities_array >= self.upper_threshold,
                intensities_array <= self.lower_threshold
            )

            # set True values in boolean_array to include_pixel
            cooked_pixels[boolean_array] = include_pixels[boolean_array]

            return cooked_pixels

perform the same operation as Threshold.cook, but only in numpy

##### Inherited Members

pyrogis.ingredients.ingredient.Ingredient
  Ingredient
  opacity
  mask
  mask_pixels
  season

class Chef:
View Source

    class Chef:
        """
        handles text and json representations of pierogis constructs

        implements parsing into a standard representation
        and cooking a parsed representation
        """
        ingredient_classes = {
            'sort': Sort,
            'quantize': SpatialQuantize,
            'threshold': Threshold,
            'rotate': Rotate,
            'resize': Resize
        }

        menu = menu

        def create_pierogi_objects(self, pierogi_descs: dict, file_links: dict):
            pierogi_objects = {}

            for pierogi_key, pierogi_desc in pierogi_descs.items():
                file = file_links[pierogi_desc.files_key]
                pierogi = Pierogi(file=file)

                pierogi_objects[pierogi_key] = pierogi

            return pierogi_objects

        def create_ingredient_objects(
                self,
                ingredient_descs: dict,
                pierogis: dict
        ):
            """
            create a dict of uuid->Ingredient
            from an ingredient description and a lookup for file paths

            :param ingredient_descs: map of uuid keys to inner dict
            with type, args, and kwargs keys
            :param file_links: map of uuid keys to file paths,
            usually for Pierogi
            """
            ingredients: Dict[str, Ingredient] = {}

            for ingredient_name, ingredient_desc in ingredient_descs.items():
                # if path is one of the kwargs
                # look it up in the linking paths dictionary
                pierogi_name = ingredient_desc.kwargs.get('pierogi')
                if pierogi_name is not None:
                    pierogi = pierogis[pierogi_name]
                    ingredient_desc.kwargs['pierogi'] = pierogi

                ingredient = self.get_or_create_ingredient(
                    ingredients, ingredient_descs, ingredient_name
                )

                ingredients[ingredient_name] = ingredient

            return ingredients

        def get_or_create_ingredient(
                self,
                ingredients: dict,
                ingredient_descs: dict,
                ingredient_name
        ):
            """
            look to see if an ingredient object has already been created
            otherwise create it and swap it in the dictionary
            """
            ingredient = ingredients.get(ingredient_name)

            if ingredient is None:
                ingredient_desc = ingredient_descs[ingredient_name]

                # search kwargs for keys that are type names
                # swap the value of that kwarg (reference key)
                # for the created ingredient obj
                for ingredient_type_name in self.ingredient_classes.keys():
                    ingredient_name = ingredient_desc.kwargs.get(
                        ingredient_type_name
                    )

                    if ingredient_name is not None:
                        ingredient = self.get_or_create_ingredient(
                            ingredients, ingredient_descs, ingredient_name
                        )
                        ingredient_desc.kwargs[ingredient_type_name] = ingredient

                # now create an ingredient as specified in the description
                ingredient = ingredient_desc.create(self.ingredient_classes)

            return ingredient

        def apply_seasonings(self, ingredients, seasoning_links):
            for seasoning, recipient in seasoning_links.items():
                seasoning = ingredients[seasoning]
                recipient = ingredients[recipient]

                recipient.season(seasoning)

        def create_recipe_object(
                self,
                ingredients: Dict[str, Ingredient],
                recipe_order: List[str]
        ):
            """
            create a recipe from:
            ingredients map
            seasoning name map
            order of ingredients

            :param ingredients: map of uuid key to ingredient object value
            :param seasoning_links: map of "recipient ingredient" uuid keys
            to seasoning uuid values
            :param recipe_order: order of ingredients by uuid
            """

            recipe = Recipe(ingredients=[])
            # loop through the ingredient keys specified by the recipe
            for ingredient_key in recipe_order:
                # there might be a bug here with the ingredient being out
                # of sync with another reference to that ingredient
                ingredient = ingredients[ingredient_key]

                # add this created ingredient to the dish recipe for return
                recipe.add(ingredient)

            return recipe

        def cook_dish_desc(self, dish_description: DishDescription):
            """
            cook a dish from a series of descriptive dicts
            """
            pierogi_descs = dish_description.pierogis
            ingredient_descs = dish_description.ingredients
            files = dish_description.files
            dish_kwargs = dish_description.dish
            seasoning_links = dish_description.seasoning_links

            pierogis = self.create_pierogi_objects(
                pierogi_descs,
                files
            )

            ingredients = self.create_ingredient_objects(
                ingredient_descs,
                pierogis
            )

            self.apply_seasonings(
                ingredients,
                seasoning_links
            )

            recipe = self.create_recipe_object(
                ingredients,
                dish_kwargs['recipe']
            )

            dish = Dish(
                pierogis=[pierogis[dish_kwargs['pierogi']]],
                recipe=recipe
            )

            return dish.serve()

handles text and json representations of pierogis constructs

implements parsing into a standard representation and cooking a parsed representation

Chef()
ingredient_classes = {'sort': <class 'pyrogis.ingredients.sort.Sort'>, 'quantize': <class 'pyrogis.ingredients.quantize.SpatialQuantize'>, 'threshold': <class 'pyrogis.ingredients.seasonings.threshold.Threshold'>, 'rotate': <class 'pyrogis.ingredients.rotate.Rotate'>, 'resize': <class 'pyrogis.ingredients.resize.Resize'>}
menu = {'sort': <class 'pyrogis.chef.menu.sort_order.SortOrder'>, 'quantize': <class 'pyrogis.chef.menu.quantize_order.QuantizeOrder'>, 'chef': <class 'pyrogis.chef.menu.custom_order.CustomOrder'>, 'threshold': <class 'pyrogis.chef.menu.threshold_order.ThresholdOrder'>, 'resize': <class 'pyrogis.chef.menu.resize_order.ResizeOrder'>}
def create_pierogi_objects(self, pierogi_descs: dict, file_links: dict):
View Source

        def create_pierogi_objects(self, pierogi_descs: dict, file_links: dict):
            pierogi_objects = {}

            for pierogi_key, pierogi_desc in pierogi_descs.items():
                file = file_links[pierogi_desc.files_key]
                pierogi = Pierogi(file=file)

                pierogi_objects[pierogi_key] = pierogi

            return pierogi_objects

def create_ingredient_objects(self, ingredient_descs: dict, pierogis: dict):
View Source

        def create_ingredient_objects(
                self,
                ingredient_descs: dict,
                pierogis: dict
        ):
            """
            create a dict of uuid->Ingredient
            from an ingredient description and a lookup for file paths

            :param ingredient_descs: map of uuid keys to inner dict
            with type, args, and kwargs keys
            :param file_links: map of uuid keys to file paths,
            usually for Pierogi
            """
            ingredients: Dict[str, Ingredient] = {}

            for ingredient_name, ingredient_desc in ingredient_descs.items():
                # if path is one of the kwargs
                # look it up in the linking paths dictionary
                pierogi_name = ingredient_desc.kwargs.get('pierogi')
                if pierogi_name is not None:
                    pierogi = pierogis[pierogi_name]
                    ingredient_desc.kwargs['pierogi'] = pierogi

                ingredient = self.get_or_create_ingredient(
                    ingredients, ingredient_descs, ingredient_name
                )

                ingredients[ingredient_name] = ingredient

            return ingredients

create a dict of uuid->Ingredient from an ingredient description and a lookup for file paths

:param ingredient_descs: map of uuid keys to inner dict with type, args, and kwargs keys :param file_links: map of uuid keys to file paths, usually for Pierogi

def get_or_create_ingredient(self, ingredients: dict, ingredient_descs: dict, ingredient_name):
View Source

        def get_or_create_ingredient(
                self,
                ingredients: dict,
                ingredient_descs: dict,
                ingredient_name
        ):
            """
            look to see if an ingredient object has already been created
            otherwise create it and swap it in the dictionary
            """
            ingredient = ingredients.get(ingredient_name)

            if ingredient is None:
                ingredient_desc = ingredient_descs[ingredient_name]

                # search kwargs for keys that are type names
                # swap the value of that kwarg (reference key)
                # for the created ingredient obj
                for ingredient_type_name in self.ingredient_classes.keys():
                    ingredient_name = ingredient_desc.kwargs.get(
                        ingredient_type_name
                    )

                    if ingredient_name is not None:
                        ingredient = self.get_or_create_ingredient(
                            ingredients, ingredient_descs, ingredient_name
                        )
                        ingredient_desc.kwargs[ingredient_type_name] = ingredient

                # now create an ingredient as specified in the description
                ingredient = ingredient_desc.create(self.ingredient_classes)

            return ingredient

look to see if an ingredient object has already been created otherwise create it and swap it in the dictionary

def apply_seasonings(self, ingredients, seasoning_links):
View Source

        def apply_seasonings(self, ingredients, seasoning_links):
            for seasoning, recipient in seasoning_links.items():
                seasoning = ingredients[seasoning]
                recipient = ingredients[recipient]

                recipient.season(seasoning)

def create_recipe_object( self, ingredients: Dict[str, pyrogis.ingredients.ingredient.Ingredient], recipe_order: List[str] ):
View Source

        def create_recipe_object(
                self,
                ingredients: Dict[str, Ingredient],
                recipe_order: List[str]
        ):
            """
            create a recipe from:
            ingredients map
            seasoning name map
            order of ingredients

            :param ingredients: map of uuid key to ingredient object value
            :param seasoning_links: map of "recipient ingredient" uuid keys
            to seasoning uuid values
            :param recipe_order: order of ingredients by uuid
            """

            recipe = Recipe(ingredients=[])
            # loop through the ingredient keys specified by the recipe
            for ingredient_key in recipe_order:
                # there might be a bug here with the ingredient being out
                # of sync with another reference to that ingredient
                ingredient = ingredients[ingredient_key]

                # add this created ingredient to the dish recipe for return
                recipe.add(ingredient)

            return recipe

create a recipe from: ingredients map seasoning name map order of ingredients

:param ingredients: map of uuid key to ingredient object value :param seasoning_links: map of "recipient ingredient" uuid keys to seasoning uuid values :param recipe_order: order of ingredients by uuid

def cook_dish_desc( self, dish_description: pyrogis.chef.dish_description.DishDescription ):
View Source

        def cook_dish_desc(self, dish_description: DishDescription):
            """
            cook a dish from a series of descriptive dicts
            """
            pierogi_descs = dish_description.pierogis
            ingredient_descs = dish_description.ingredients
            files = dish_description.files
            dish_kwargs = dish_description.dish
            seasoning_links = dish_description.seasoning_links

            pierogis = self.create_pierogi_objects(
                pierogi_descs,
                files
            )

            ingredients = self.create_ingredient_objects(
                ingredient_descs,
                pierogis
            )

            self.apply_seasonings(
                ingredients,
                seasoning_links
            )

            recipe = self.create_recipe_object(
                ingredients,
                dish_kwargs['recipe']
            )

            dish = Dish(
                pierogis=[pierogis[dish_kwargs['pierogi']]],
                recipe=recipe
            )

            return dish.serve()

cook a dish from a series of descriptive dicts