Repository: pierogis/pierogis (language: python)

Issue:
trim animation
`Trim` a `Dish` using new `pierogi` based technology.

~~FFmpeg is awesome and it is behind some of the greatest features (saving and loading files) in `pierogis`. However I don't find it intuitive to use from command line if I haven't done so in the past week because it is so beefy. Specifically, I want to be able to trim a video file to the frame or (milli)second with a nice interface for just making it happen while keeping my dignity intact (read: not googling "trim video ffmpeg"). I've been replaced with a doppelganger if you catch me uploading a 2 gb file to the www.quickimagetrim.ads neural net.~~ This feature would be useful.

It would be super easy to just make a `Dish` or `Order` leave out some frames, but I want this to be an object (preferably an `Ingredient`) that can be in a pipelined like the rest of them.

Trouble is, every `Ingredient` operation so far works on a single frame. `Dish`, the animation wrapper, is in theory an `Ingredient` (when used as one it only supplies a single frame). It would be sweet if a `Trim` ingredient could be worked into a pipeline and treated the same way as `Sort` or `Quantize` without having to come up with some other bogus construct/exception in the json "rpc" model: describing transformation as sequentially applied Ingredients in json-friendly Ticket for each frame -> `Recipe` with python objects -> cook. 

May be silly to use trim to tell every single frame which frames to not cook. May require an overhaul or at least extension of the RPC/`Chef`/`Ticket`/`Server` system.

It would be cool for this (or through subclassing) to be a general frame-leaver-outer tool so that you could half your frame rate without losing playback speed, for example.

If done correctly, this could be a good baseline for making windowed transformations ie. one frame is aware of its surroundings. Sentient `pierogis` through a windowing API in the spec... Probably call it `CuttingBoard` or something equally ridiculous.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4d6677c..200ba30 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,7 +1,7 @@
 # This workflow will upload a Python Package using Twine when a release is created
 # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
 
-name: pyrogis ci
+name: pierogis ci
 
 on:
   push:
@@ -49,7 +49,7 @@ jobs:
           command: clippy
           args: --manifest-path ./Cargo.toml --all-targets --all-features -- -D warnings
 
-      - name: create pyrogis environment
+      - name: create pierogis environment
         run: |
           python -m venv .env
           source .env/bin/activate
@@ -62,21 +62,21 @@ jobs:
         run: |
           source .env/bin/activate
           pip install flake8
-          flake8 src/pyrogis
+          flake8 src/pierogis
 
       - name: pylint python
         continue-on-error: true
         run: |
           source .env/bin/activate
           pip install pylint
-          pylint src/pyrogis
+          pylint src/pierogis
 
       - name: run mypy
         continue-on-error: true
         run: |
           source .env/bin/activate
           pip install mypy
-          mypy src/pyrogis
+          mypy src/pierogis
 
       - name: run tests
         run: |
@@ -113,10 +113,6 @@ jobs:
           pip install -r requirements.txt
           python -m pip install cibuildwheel==1.10.0
 
-      - name: Install Visual C++ for Python 2.7
-        if: runner.os == 'Windows'
-        run: choco install vcpython27 -f -y
-
       - name: build wheels
         run: python -m cibuildwheel --output-dir wheelhouse
         env:
@@ -142,11 +138,11 @@ jobs:
           git fetch --depth=1 origin +refs/tags/*:refs/tags/*
 
       - name: build wheels
-        run: 'arch -arm64 sh build_wheels_mac_arm.sh'
+        run: 'zsh ./build_wheels_mac_arm.sh'
 
       - uses: actions/upload-artifact@v2
         with:
-          path: ./dist/*.whl
+          path: dist/*.whl
 
   sdist-build:
     name: sdist build
@@ -180,7 +176,7 @@ jobs:
   publish:
     if: startsWith(github.ref, 'refs/tags')
     runs-on: ubuntu-latest
-    needs: [ wheel-build, test, sdist-build ]
+    needs: [ wheel-build, test, sdist-build, macos-11-build ]
     steps:
       - name: install python
         uses: actions/setup-python@v2
diff --git a/Cargo.lock b/Cargo.lock
index 5ddc96f..7ec4bcd 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -12,6 +12,15 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
 
+[[package]]
+name = "ansi_term"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
+dependencies = [
+ "winapi",
+]
+
 [[package]]
 name = "approx"
 version = "0.3.2"
@@ -21,6 +30,17 @@ dependencies = [
  "num-traits",
 ]
 
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
 [[package]]
 name = "autocfg"
 version = "1.0.1"
@@ -63,9 +83,13 @@ version = "2.33.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
 dependencies = [
+ "ansi_term",
+ "atty",
  "bitflags",
+ "strsim",
  "textwrap",
  "unicode-width",
+ "vec_map",
 ]
 
 [[package]]
@@ -338,6 +362,17 @@ dependencies = [
  "autocfg",
 ]
 
+[[package]]
+name = "mmpx"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd6f04bada27c83a947e72fd4d4f724fe1fb19a121d6888e786ac552d3f12a57"
+dependencies = [
+ "image",
+ "rayon",
+ "structopt",
+]
+
 [[package]]
 name = "ndarray"
 version = "0.14.0"
@@ -480,10 +515,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c5d65c4d95931acda4498f675e332fcbdc9a06705cd07086c510e9b6009cd1c1"
 
 [[package]]
-name = "pierogis_rs"
+name = "pierogis"
 version = "0.1.0"
 dependencies = [
  "image",
+ "mmpx",
  "ndarray",
  "numpy",
  "pyo3",
@@ -697,6 +733,12 @@ version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1a55ca5f3b68e41c979bf8c46a6f1da892ca4db8f94023ce0bd32407573b1ac0"
 
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
 [[package]]
 name = "structopt"
 version = "0.3.21"
@@ -776,6 +818,12 @@ version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7"
 
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
 [[package]]
 name = "version_check"
 version = "0.9.2"
diff --git a/Cargo.toml b/Cargo.toml
index 5160e58..c83a149 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,18 +1,19 @@
 [package]
-name = "pierogis_rs"
+name = "pierogis"
 version = "0.1.0"
 authors = ["Kyle Moore <moore.kyle39@gmail.com>"]
 edition = "2018"
 
 [lib]
 name = "pierogis"
-crate-type = ["cdylib"]
+crate-type = ["lib", "cdylib"]
 
 [dependencies]
 numpy = "0.13"
 ndarray = { version = "0.14.0", features = ["rayon"] }
 rayon = "1.5"
 image = "0.23.12"
+mmpx = "0.1.0"
 
 [dependencies.pyo3]
 version = "0.13"
diff --git a/README.md b/README.md
index c559a00..d90a6d7 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,19 @@
 # pierogis
 
+[![version](https://img.shields.io/pypi/v/pierogis)](https://pypi.org/project/pierogis/)
+[![docs](https://img.shields.io/readthedocs/pierogis/stable)](https://docs.pierogis.live)
+
+[![discord](https://img.shields.io/badge/discord-flat?label=&logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/9XpEjMw3Rx)
+[![twitter](https://img.shields.io/badge/twitter-flat?&logo=twitter&logoColor=ffffff&color=1DA1F2)](https://twitter.com/pierogis_chef)
+
 `pierogis` is a framework for image and animation processing. Ingredients that describe image processing functions can be assembled
 into recipes and used to cook an image or animation.
 
-`pyrogis` is a python library and cli tool implementing this framework.
-
 ```bash
-pip install pyrogis
-pyrogis chef teton.png "resize --width 768 --height 768; sort; quantize; resize --scale 4" -o output.png
+pip install pierogis
+pierogis custom teton.png "resize --width 768 --height 768; sort; quantize; resize --scale 4" -o output.png
 # or
-pyrogis chef teton.png recipe.txt -o output.png
+pierogis custom teton.png recipe.txt -o output.png
 ```
 
 *recipe.txt*
@@ -34,7 +38,7 @@ resize -s 4;
 **install from a wheel with pip**
 
 ```sh
-pip install pyrogis
+pip install pierogis
 ```
 
 Depends on `numpy` and `PIL`. PIL requires some external C libraries for handling image files. You probably don't have
@@ -49,13 +53,22 @@ pip install -r requirements.txt
 pip install .
 ```
 
+Note that the python package was previously called `pyrogis`.
+That was supposed to denote the difference between the package
+and the Rust algorithms (`pierogis_rs`) that it relied on.
+
+These two parts were combined into one namespace in the `0.4.0` release,
+and the split naming became redundant.
+
+Still, `pip install pyrogis==0.4.0` will install `pierogis`.
+
 <a name="features"></a>
 ## features
 
-- **CLI** - Use a `rich` cli to cook à la carte recipes, or provide a recipe in a document
+- **CLI** - Use a `rich` cli to cook à la carte recipes, or provide a recipe in a document (see [docs](https://docs.pierogis.live/en/stable/cli.html))
 - **Animations** - Animations (gifs and movies) can be cooked in one command
-- **Extendable** - Easy to create custom manipulations (see [docs](https://docs.pierogis.live/))
-- **Lazy Rendering** - Render a manipulation after constructing your pipeline (see [docs](https://docs.pierogis.live/))
+- **Extendable** - Easy to create custom manipulations (see [docs](https://docs.pierogis.live/en/stable/ingredients.html#extending))
+- **Lazy Rendering** - Render a manipulation after constructing your pipeline (see [docs](https://docs.pierogis.live/en/stable/ingredients.html#dish))
 - **Numpy or Rust backend** - Image processing functions use Numpy for (python relative) fast operations. Some
   ingredients use compiled `Rust` for more speed.
   
@@ -79,8 +92,13 @@ underlying [algorithm](https://github.com/kimasendorf/ASDFPixelSort) of that pac
 supposed to be functionally the same, details of the implementation may differ.
 
 A quantization algorithm used in this package uses [`rscolorq`](https://github.com/okaneco/rscolorq), which
-is a port of [`scolorq`](http://people.eecs.berkeley.edu/~dcoetzee/downloads/scolorq/), itself an implementation of
-Spatial Color Quantization.
+is a Rust port of [`scolorq`](http://people.eecs.berkeley.edu/~dcoetzee/downloads/scolorq/),
+itself an implementation of Spatial Color Quantization.
+
+An algorithm called
+[`MMPX`](https://casual-effects.com/research/McGuire2021PixelArt/index.html)
+is used in this package to do 2x image magnification.
+It is implemented in a separate [Rust package](https://github.com/pierogis/mmpx-rs).
 
 <a name="issues-and-contributing"></a>
 ## issues and contributing
@@ -88,9 +106,9 @@ Spatial Color Quantization.
 When you encounter an error, there are some guidelines that will make it easier to help you:
 
 - Ensure that you are using the latest version of the package. It's early days so errors and updates will be frequent.
-  Use `pip uninstall pyrogis` then `pip install pyrogis --no-cache-dir` to reinstall.
-- Provide the version of `pyrogis` that you are using in issues to rule that out.
-  `pip list` -> pyrogis \_.\_.\_
+  Use `pip uninstall pierogis` then `pip install pierogis --no-cache-dir` to reinstall.
+- Provide the version of `pierogis` that you are using in issues to rule that out.
+  `pip list` -> pierogis \_.\_.\_
 - Provide the traceback or error message if relevant.
 - Provide your os and any other specific information about how you are trying to use the package.
 - Provide the code or the cli command that triggered the error.
@@ -137,8 +155,3 @@ at least modules related to this package.
 
 If your paid derivative work adds marginal value to what is included in this package,
 the author reserves the right to go to great lengths to make a free (and better) alternative to your derivative work.
-
-Please think twice about minting NFTs for works made with this package, especially if they are ugly.
-Consider that you do
-[communal damage](https://memoakten.medium.com/the-unreasonable-ecological-cost-of-cryptoart-2221d3eb2053)
-by trying to profit individually.
diff --git a/demo/examples.py b/demo/examples.py
index 52a157e..4bdb6fc 100644
--- a/demo/examples.py
+++ b/demo/examples.py
@@ -1,6 +1,6 @@
 import numpy as np
 
-from pyrogis.ingredients import (
+from pierogis.ingredients import (
     Dish, Ingredient, Pierogi,
     Quantize, Recipe, Sort, Threshold
 )
diff --git a/demo/out/gnome_mmpx.png b/demo/out/gnome_mmpx.png
new file mode 100644
index 0000000..59d3587
--- /dev/null
+++ b/demo/out/gnome_mmpx.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:93f8a0ad8aa49084bff329c2d67a0130c5f9c09f15c1e2e40abaafc4542e8672
+size 32575
diff --git a/docs/cli.rst b/docs/cli.rst
index 4b56800..b57e0a6 100644
--- a/docs/cli.rst
+++ b/docs/cli.rst
@@ -5,10 +5,10 @@ All of the cli commands look like this.
 
 .. code-block:: console
 
-   $ pyrogis {filling/subcommand} {path} [-o output] [..common options] [..order options] [..togo options]
+   $ pierogis {filling/subcommand} {path} [-o output] [..common options] [..order options] [..togo options]
 
 A ``filling`` is a set of ingredients that will be used to cook.
-Often it just represents the named :py:class:`~pyrogis.ingredients.ingredient.Ingredient`.
+Often it just represents the named :py:class:`~pierogis.ingredients.ingredient.Ingredient`.
 A directory can be used for ``path``, in which case the program will try to cook each file in the directory.
 If an ``output`` filename or dir is provided, it should match the expected output.
 
@@ -28,6 +28,28 @@ arg                  description                                   default    va
 ``filling``          define filling/ingredients to cook with       required   see menu
 ``path``             path to input media                           required   dir, image, animation
 ``-o``, ``--output`` name of the output file                       depends    ``int``
+``--frames-filter``  provide a python expression to identify       ``True``   ``str``
+                     frames to be cooked
+==================== ============================================= ========== =======
+
+If the input file is a directory or a movie file (anything animated),
+the output will be an animation as well. Artifact "cooked" folder will contain frames.
+If you don't understand what output type to expect from your command, don't provide ``output``.
+
+frames filtering
+""""""""""""""""
+
+The variables ``i`` and ``frames`` can be used in the ``frames-filter`` expression.
+They represent the **index** of the frame in question and the total number of frames.
+The string provided to ``--frames-filter`` should evaluate to ``True`` or ``False``.
+Where ``True`` means the frame in question will be cooked
+
+*other options*
+~~~~~~~~~~~~~~~
+
+==================== ============================================= ========== =======
+arg                  description                                   default    valid
+==================== ============================================= ========== =======
 ``--presave``        flag to indicate frames should be saved from  ``False``  flag
                      animations
 ``--async``          flag to indicate frames should be cooked      ``False``  flag
@@ -36,14 +58,30 @@ arg                  description                                   default    va
 ``--resume``         skip cooked frames to finish a cook task      ``False``  flag
 ==================== ============================================= ========== =======
 
-If the input file is a directory or a movie file (anything animated),
-the output will be an animation as well. Artifact "cooked" folder will contain frames.
-If you don't understand what output type to expect from your command, don't provide ``output``.
+These don't apply to ``togo``.
+
+auto pilot
+""""""""""
+
+There is an "auto pilot" preprocessing step where the program will try to determine an
+effective way to cook the order.
+By default, it will cook a batch of frames using ``multiprocessing``
+and a batch where each frame is first saved as it's own png for input to cooking.
+It compares the time these batches took to the time it took to cook without tricks.
+
+Usually, animations will find multiprocessing to be beneficial.
+In some cases, presave is used;
+the file has to take a long time to open for it to be worth presaving frames
+
+Provide ``presave``, and/or ``async`` to skip their respective tests.
 
 ``presave`` will be ignored if dir ``path``.
 If ``processes`` is provided, ``async`` is set to ``True``.
 If ``async`` is provided without ``processes``, ``processes`` wil be ``os.cpu_count()``.
 
+resume
+""""""
+
 With ``resume`` present, frames that are already in the cooked directory
 and generated from the given input filename
 will not be overwritten by the outputs of this command.
@@ -53,6 +91,19 @@ Three uses:
 - Change the ``filling`` in the animation for any frames that weren't finished
 - Recover errors if a frame failed to cook.
 
+.. code-block:: python
+
+   # only the frame with index 5
+   i==5
+   # frames indices greater than 50
+   i>50
+   # only even indexed frames
+   i%2==0
+   # frames beyond the halfway point
+   i>=frames/2
+
+Keep in mind python is 0 indexed -> frame index 5 is frame 6 in the sequence.
+
 *togo options*
 ~~~~~~~~~~~~~~
 
@@ -78,7 +129,7 @@ togo
 
 .. code-block:: console
 
-   $ pyrogis togo ./cooked --fps 50
+   $ pierogis togo ./cooked --fps 50
 
 ``togo`` can be used to take this input directory and compile into a movie file.
 This happens automatically for other subcommands
diff --git a/docs/conf.py b/docs/conf.py
index cfb2493..b7ffc38 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -10,7 +10,7 @@
 project = "pierogis"
 copyright = "2021 pierogis"
 author = "pierogis-live"
-version = version('pyrogis')
+version = version('pierogis')
 
 # General --------------------------------------------------------------
 
@@ -29,7 +29,7 @@
     'python': ('https://docs.python.org/3', None),
 }
 issues_github_path = "pierogis/pierogis"
-apidoc_module_dir = '../src/pyrogis'
+apidoc_module_dir = '../src/pierogis'
 apidoc_output_dir = 'source'
 apidoc_separate_modules = True
 
diff --git a/docs/index.rst b/docs/index.rst
index c495df2..89d36d4 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,15 +1,14 @@
 pierogis
 ========
 
-*image and animation processing framework*
+**image and animation processing framework**
 
-   "``pierogis`` *is the name of the framework;*
-
-   ``pyrogis`` *is the name of the python package and cli tool"*
+   *"ingredients that describe image processing functions can be assembled
+   into recipes and used to cook an image or animation."*
 
    \- a wise man
 
-.. currentmodule:: pyrogis.ingredients
+.. py:currentmodule:: pierogis.ingredients
 
 Pixel arrays (like images) stored as a :py:class:`~pierogi.Pierogi` object can be cooked
 with an :py:class:`~ingredient.Ingredient`
@@ -20,14 +19,14 @@ A :py:class:`~dish.Dish` can be made with a :py:class:`~recipe.Recipe` containin
 several :py:class:`~ingredient.Ingredient` objects describing a pipeline
 to be applied to a :py:class:`~pierogi.Pierogi`.
 
-A :py:class:`~pyrogis.course.Course` can be made to cook a set of
+A :py:class:`~pierogis.course.Course` can be made to cook a set of
 :py:class:`~dish.Dish` objects representing frames
 that compile to a cooked animation.
 
-.. currentmodule:: pyrogis.kitchen.menu
+.. py:currentmodule:: pierogis.kitchen.menu
 
 A :doc:`rich <rich:introduction>` cli uses :py:class:`~filling.Filling`
-objects to parse orders into cook tasks as a :py:class:`~pyrogis.restaurant.Restaurant`.
+objects to parse orders into cook tasks as a :py:class:`~pierogis.restaurant.Restaurant`.
 These can be found on the :doc:`menu/index`.
 
 In this realm, a single `pierogi` means a pixel, many `pierogi` means an image, and `pierogis` means many images.
diff --git a/docs/ingredients.rst b/docs/ingredients.rst
index 9c1bce4..e9a1aa2 100644
--- a/docs/ingredients.rst
+++ b/docs/ingredients.rst
@@ -1,11 +1,11 @@
 ingredients
 ===========
 
-.. currentmodule:: pyrogis.ingredients
+.. py:currentmodule:: pierogis.ingredients
 
 .. code-block:: python
 
-   from pyrogis.ingredients import Pierogi, SpatialQuantize, Sort, Threshold, Dish, Recipe...
+   from pierogis.ingredients import Pierogi, SpatialQuantize, Sort, Threshold, Dish, Recipe...
 
 A processing factory, called an :py:class:`~ingredient.Ingredient`,
 has a :py:meth:`~ingredient.Ingredient.prep` method for receiving parameters,
@@ -104,7 +104,7 @@ 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
@@ -119,12 +119,13 @@ to other ingredients that affect the pixels they act on.
    sort.season(threshold)
 
 :py:meth:`~threshold.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 :py:class:`~ingredient.Ingredient` type,
 it must subclass ``Ingredient`` and override the
@@ -140,7 +141,7 @@ it must subclass ``Ingredient`` and override the
            return (self.pixels + self.brighten) /*self.scale
 
 prep
-----
+~~~~
 
 *Override to parameterize your manipulation*
 
@@ -156,7 +157,7 @@ quantization.
        self.scale = scale
 
 cook
-----
+~~~~
 
 *Override to perform the manipulation*
 
diff --git a/docs/kitchen.rst b/docs/kitchen.rst
index 233d236..60e07d7 100644
--- a/docs/kitchen.rst
+++ b/docs/kitchen.rst
@@ -9,10 +9,7 @@ kitchen
 
    -- weezy
 
-.. py:currentmodule::
-
-   pyrogis.kitchen
-
+.. py:currentmodule:: pierogis.kitchen
 
 This submodule defines a model of a kitchen for cooking en masse.
 The goal of this is to create an idiom for distributing cooking tasks.
@@ -29,12 +26,12 @@ media to be cooked. The :py:class:`~server.OrderTaker` should transform this int
 with :py:class:`~ticket.Ticket` objects to describe each frame. The :py:class:`~server.Server` passes this off to
 a :py:class:`~kitchen.Kitchen` which decides how to cook the :py:class:`~order.Order`. Ultimately, a
 :py:class:`~chef.Cooker` (implemented with :py:class:`~chef.Chef`) cooks the :py:class:`~ticket.Ticket`
-and a cooked :py:class:`~pyrogis.ingredients.pierogi.Pierogi` is produced.
+and a cooked :py:class:`~pierogis.ingredients.pierogi.Pierogi` is produced.
 
 - :py:class:`~ticket.Ticket`
   - Describes in a json-friendly format the input media, Ingredient objects, and instructions for a Recipe
 - :py:class:`~order.Order`
-  - A collection of many tickets and a desciption for handling the :py:class:`~pyrogis.course.Course` output as a whole
+  - A collection of many tickets and a desciption for handling the :py:class:`~pierogis.course.Course` output as a whole
 - :py:class:`~server.Server`
   - Interfaces with instructions provided in a parsable format, creating an :py:class:`~order.Order` for a :py:class:`~kitchen.Kitchen` to process and checking its status
 - :py:class:`~chef.Chef`
diff --git a/docs/menu/crop.rst b/docs/menu/crop.rst
index 933cf38..eedff71 100644
--- a/docs/menu/crop.rst
+++ b/docs/menu/crop.rst
@@ -1,6 +1,6 @@
 .. _crop:
 
-.. currentmodule:: pyrogis.ingredients
+.. py:currentmodule:: pierogis.ingredients
 
 crop
 ~~~~
@@ -8,7 +8,7 @@ crop
 
 .. code-block:: console
 
-   $ pyrogis crop ./input.jpg -x 20 --height 400 --aspect 1 --origin c
+   $ pierogis crop ./input.jpg -x 20 --height 400 --aspect 1 --origin c
 
 .. figure:: https://media.githubusercontent.com/media/pierogis/pierogis/develop/demo/out/gnome_crop.png
    :alt: cropped gnome
@@ -47,8 +47,8 @@ and the direction of the selection
 
 ``--aspect`` will crop the image to the given aspect ratio.
 It achieves this in different ways depending on the other options provided.
-- If neither ``--width`` or ``--height`` are provided,
-it will preserve one dimension and reduce another so as to not increase in size. Same treatment as resize.
+
+- If neither ``--width`` nor ``--height`` are provided, it will preserve one dimension and reduce another so as to not increase in size. Same treatment as resize.
 - If both ``--width`` and ``--height`` are provided, the ``--aspect`` is ignored.
 - If one of ``--width`` or ``--height`` are provided, the ``--aspect`` is applied to the missing dimension.
 
@@ -77,4 +77,4 @@ If you are reading this you are a champion.
    :file: crop_table.csv
    :header-rows: 1
 
-See: :py:class:`~pyrogis.kitchen.menu.crop_filling.CropFilling`
+See: :py:class:`~pierogis.kitchen.menu.crop_filling.CropFilling`, :py:class:`~crop.Crop`
diff --git a/docs/menu/custom.rst b/docs/menu/custom.rst
index 096c758..369f86a 100644
--- a/docs/menu/custom.rst
+++ b/docs/menu/custom.rst
@@ -1,6 +1,6 @@
 .. _custom:
 
-.. currentmodule:: pyrogis.ingredients
+.. py:currentmodule:: pierogis.ingredients
 
 custom
 ~~~~~~
@@ -9,9 +9,9 @@ custom
 
 .. code-block:: console
 
-   $ pyrogis custom ./input.jpg "sort -u 100; quantize"
+   $ pierogis custom ./input.jpg "sort -u 100; quantize"
    $ # or
-   $ pyrogis custom ./input.jpg recipe.txt
+   $ pierogis custom ./input.jpg recipe.txt
 
 *recipe.txt*
 
@@ -33,4 +33,4 @@ arg        description                                 default    valid
 ``recipe`` path to json or txt file to use as a recipe recipe.txt `str`
 ========== =========================================== ========== =====
 
-See: :py:class:`~pyrogis.kitchen.menu.custom_filling.CustomFilling`
+See: :py:class:`~pierogis.kitchen.menu.custom_filling.CustomFilling`
diff --git a/docs/menu/index.rst b/docs/menu/index.rst
index 56f7d60..58e1f53 100644
--- a/docs/menu/index.rst
+++ b/docs/menu/index.rst
@@ -11,4 +11,5 @@ Here lie a bunch of commands for creating à la carte recipes and a command for
     crop
     custom
     threshold
-    rotate
\ No newline at end of file
+    rotate
+    mmpx
\ No newline at end of file
diff --git a/docs/menu/mmpx.rst b/docs/menu/mmpx.rst
new file mode 100644
index 0000000..ecc2d1a
--- /dev/null
+++ b/docs/menu/mmpx.rst
@@ -0,0 +1,37 @@
+.. _mmpx:
+
+.. py:currentmodule:: pierogis.ingredients
+
+mmpx
+~~~~
+*2x image scaling*
+
+.. code-block:: console
+
+   $ pierogis custom ./input.jpg "resize -s .5; quantize; mmpx"
+
+.. figure:: https://media.githubusercontent.com/media/pierogis/pierogis/develop/demo/out/gnome_mmpx.png
+   :alt: mmpxed gnome
+   :align: center
+
+   *very chill.*
+
+Results in an image with size 2 x width and 2 x height using :py:class:`~mmpx.MMPX`.
+This algorithm uses a series of rules based on nearby pixels to determine how to map
+a single pixel to 4 corresponding (2*2) output pixels.
+
+You can read about the
+`algorithm <https://casual-effects.com/research/McGuire2021PixelArt/index.html>`_,
+and check out its
+`implementation <https://github.com/pierogis/mmpx-rs>`_
+done in Rust (like :ref:`quantize`).
+
+Thank you to the authors of that algorithm, it's very cool and performant.
+
+A recipe with ``resize -s .5``, a "style" ingredient/filling (``quantize`` for example), then ``mmpx`` creates a nice effect.
+This is because MMPX is designed for pixel art (small color palette and large pixels),
+meaning pixels in normal images will typically be scaled using nearest neighbor.
+
+There are no options specific to this ingredient/filling.
+
+See: :py:class:`~pierogis.kitchen.menu.mmpx_filling.MMPXFilling`
diff --git a/docs/menu/quantize.rst b/docs/menu/quantize.rst
index d5bc40b..515c3f8 100644
--- a/docs/menu/quantize.rst
+++ b/docs/menu/quantize.rst
@@ -1,6 +1,6 @@
 .. _quantize:
 
-.. currentmodule:: pyrogis.ingredients
+.. py:currentmodule:: pierogis.ingredients
 
 quantize
 ~~~~~~~~
@@ -9,9 +9,9 @@ quantize
 
 .. code-block:: console
 
-   $ pyrogis quantize input.jpg -c aaaaaa 43ad32 696969 --repeats 3 --iterations 3
+   $ pierogis quantize input.jpg -c aaaaaa 43ad32 696969 --repeats 3 --iterations 3
    $ # or
-   $ pyrogis quantize input.jpg -n 16 --repeats 3 --iterations 3
+   $ pierogis quantize input.jpg -n 16 --repeats 3 --iterations 3
 
 .. figure:: https://media.githubusercontent.com/media/pierogis/pierogis/develop/demo/out/gnome_magic.png
    :alt: quantized gnome
@@ -34,4 +34,4 @@ arg                        description
 ``--dithering-level``      relative dithering level (use .5-1.5)                ``0.8``   ``float``
 ========================== ==================================================== ========= =========
 
-See: :py:class:`~pyrogis.kitchen.menu.quantize_filling.QuantizeFilling`
+See: :py:class:`~pierogis.kitchen.menu.quantize_filling.QuantizeFilling`
diff --git a/docs/menu/resize.rst b/docs/menu/resize.rst
index 33d6dc2..d0508d0 100644
--- a/docs/menu/resize.rst
+++ b/docs/menu/resize.rst
@@ -1,6 +1,6 @@
 .. _resize:
 
-.. currentmodule:: pyrogis.ingredients
+.. py:currentmodule:: pierogis.ingredients
 
 resize
 ~~~~~~
@@ -8,12 +8,12 @@ resize
 
 .. code-block:: console
 
-   $ pyrogis resize ./input.jpg --scale .25
-   $ pyrogis resize ./input.jpg --scale 4
+   $ pierogis resize ./input.jpg --scale .25
+   $ pierogis resize ./input.jpg --scale 4
 
    $ # or using exact dimension (aspect ratio maintained)
-   $ pyrogis resize ./input.jpg --height 200
-   $ pyrogis resize ./input.jpg --height 800
+   $ pierogis resize ./input.jpg --height 200
+   $ pierogis resize ./input.jpg --height 800
 
 .. figure:: https://media.githubusercontent.com/media/pierogis/pierogis/develop/demo/out/gnome_resize.png
    :alt: resized gnome
@@ -35,16 +35,30 @@ if ``--scale`` is a whole number
 See `PIL documentation <https://pillow.readthedocs.io/en/stable/handbook/concepts.html#concept-filters>`_
 on filters.
 
+.. code-block:: python
+
+   choices = {
+       'default': Image.NEAREST,
+       'nearest': Image.NEAREST,
+       'box': Image.BOX,
+       'bicubic': Image.BICUBIC,
+       'bilinear': Image.BILINEAR,
+       'hamming': Image.HAMMING,
+       'lanczos': Image.LANCZOS,
+   }
+
 When used in a :ref:`custom` recipe, scaling down at the beginning of a recipe
 and up at the end can lead to cool (and faster) results.
 
-===================== ===================================== ============= =======
+Check out :ref:`mmpx` for an alternative resizing algorithm (only 2x scale).
+
+===================== ===================================== ============= =====================
 arg                   description                           default       valid
-===================== ===================================== ============= =======
+===================== ===================================== ============= =====================
 ``--width``           width to resize to                    ``None``      ``str``
 ``--height``          height to resize to                   ``None``      ``str``
 ``--scale``           scale multiplier for width and height ``1``         ``str``
-``--resample-filter`` a filter to be used with resizing     ``'nearest'`` ``str``
-===================== ===================================== ============= =======
+``--resample-filter`` a filter to be used with resizing     ``'nearest'`` see ``choices`` above
+===================== ===================================== ============= =====================
 
-See: :py:class:`~pyrogis.kitchen.menu.resize_filling.ResizeFilling`
+See: :py:class:`~pierogis.kitchen.menu.resize_filling.ResizeFilling`, :py:class:`~resize.Resize`
diff --git a/docs/menu/rotate.rst b/docs/menu/rotate.rst
index 728ae37..ff54781 100644
--- a/docs/menu/rotate.rst
+++ b/docs/menu/rotate.rst
@@ -1,6 +1,6 @@
 .. _rotate:
 
-.. currentmodule:: pyrogis.ingredients
+.. py:currentmodule:: pierogis.ingredients
 
 rotate
 ~~~~~~
@@ -8,7 +8,7 @@ rotate
 
 .. code-block:: console
 
-   $ pyrogis rotate ./input.jpg -t 1 --ccw
+   $ pierogis rotate ./input.jpg -t 1 --ccw
 
 .. figure:: https://media.githubusercontent.com/media/pierogis/pierogis/develop/demo/out/gnome_rotate.png
    :alt: rotated gnome
@@ -24,6 +24,22 @@ Use ``--ccw`` to turn counterclockwise instead.
 
 Use ``--resample-filter`` to define a PIL resample filter (defaults to nearest neighbor).
 
+See `PIL documentation <https://pillow.readthedocs.io/en/stable/handbook/concepts.html#concept-filters>`_
+on filters.
+
+.. code-block:: python
+
+   choices = {
+       'default': Image.NEAREST,
+       'nearest': Image.NEAREST,
+       'box': Image.BOX,
+       'bicubic': Image.BICUBIC,
+       'bilinear': Image.BILINEAR,
+       'hamming': Image.HAMMING,
+       'lanczos': Image.LANCZOS,
+   }
+
+
 :ref:`sort` uses this under the hood.
 
 ===================== ====================================== ============= =======
@@ -36,4 +52,4 @@ arg                   description                            default       valid
 ``--resample-filter`` a filter to be used with resizing      ``'nearest'`` ``str``
 ===================== ====================================== ============= =======
 
-See: :py:class:`~pyrogis.kitchen.menu.rotate_filling.RotateFilling`
\ No newline at end of file
+See: :py:class:`~pierogis.kitchen.menu.rotate_filling.RotateFilling`, :py:class:`~rotate.Rotate`
\ No newline at end of file
diff --git a/docs/menu/sort.rst b/docs/menu/sort.rst
index 53926d3..1fefa0c 100644
--- a/docs/menu/sort.rst
+++ b/docs/menu/sort.rst
@@ -1,6 +1,6 @@
 .. _sort:
 
-.. currentmodule:: pyrogis.ingredients
+.. py:currentmodule:: pierogis.ingredients
 
 sort
 ~~~~
@@ -8,7 +8,7 @@ sort
 
 .. code-block:: console
 
-   $ pyrogis sort ./input.jpg -l 50 -u 180 -t 1
+   $ pierogis sort ./input.jpg -l 50 -u 180 --inner -t 1
 
 .. figure:: https://media.githubusercontent.com/media/pierogis/pierogis/develop/demo/out/gnome_sort.png
    :alt: sorted gnome
@@ -16,27 +16,35 @@ sort
 
    *very chill.*
 
-Use ``-l`` and ``-u`` as lower and upper thresholds
-where contiguous groups of pixels
+Use :py:class:`~sort.Sort` to sort a selection of contiguous pixels in order of their brightness.
+The pixels to sort are selected using :ref:`threshold`.
+
+``-l`` and ``-u`` serve as lower and upper thresholds
+where groups of pixels
 with brightness outside of the thresholds are sorted.
+Providing ``--inner`` sorts values between these thresholds.
 
 Use ``-t``, ``-a``, ``--clockwise``, ``resample-filter`` to describe the direction of the sort.
-See :ref:`sort`.
+See :ref:`rotate`.
 
 If only ``--lower-threshold`` is provided, ``--upper-threshold`` is set to 255.
 If only ``--upper-threshold`` is provided, ``--lower-threshold`` is set to 0.
 
-============================= =================================================== ========= =========
-arg                           description                                         default   valid
-============================= =================================================== ========= =========
-``-l``, ``--lower-threshold`` pixels with intensity *below* this value are sorted ``64``    ``0-255``
-``-u``, ``--upper-threshold`` pixels with intensity *above* this value are sorted ``180``   ``0-255``
-``-t``, ``--turns``           number of clockwise turns from sorting              ``0``     ``0-3``
+============================= =================================================== ============= =========
+arg                           description                                         default       valid
+============================= =================================================== ============= =========
+``-l``, ``--lower-threshold`` pixels with intensity *<=* this value are sorted    ``64``        ``0-255``
+``-u``, ``--upper-threshold`` pixels with intensity *>=* this value are sorted    ``180``       ``0-255``
+``--inner``                   if provided, pixels between (inclusive) the
+                              threshold values are included in the sort           ``False``     ``0-255``
+``-t``, ``--turns``           number of clockwise turns from sorting              ``0``         ``0-3``
                               bottom to top
-``--ccw``                     if provided, ``turns`` will be applied              ``False`` flag
+``-a``, ``--angle``           degrees to rotate through each turn                 ``90``        ``int``
+``--ccw``                     if provided, ``turns`` will be applied              ``False``     flag
                               counter-clockwise instead
-============================= =================================================== ========= =========
+``--resample-filter``         a filter to be used with rotating                   ``'nearest'`` ``str``
+============================= =================================================== ============= =========
 
-See: :py:class:`~pyrogis.kitchen.menu.sort_filling.SortFilling`,
-:py:class:`~pyrogis.kitchen.menu.rotate_filling.RotateFilling`,
-:py:class:`~pyrogis.kitchen.menu.threshold_filling.ThresholdFilling`
+See: :py:class:`~pierogis.kitchen.menu.sort_filling.SortFilling`,
+:py:class:`~pierogis.kitchen.menu.rotate_filling.RotateFilling`,
+:py:class:`~pierogis.kitchen.menu.threshold_filling.ThresholdFilling`
diff --git a/docs/menu/threshold.rst b/docs/menu/threshold.rst
index fb4dac4..5fddc98 100644
--- a/docs/menu/threshold.rst
+++ b/docs/menu/threshold.rst
@@ -1,6 +1,6 @@
 .. _threshold:
 
-.. currentmodule:: pyrogis.ingredients
+.. py:currentmodule:: pierogis.ingredients
 
 threshold
 ~~~~~~~~~
@@ -9,7 +9,7 @@ threshold
 
 .. code-block:: console
 
-   $ pyrogis threshold ./input.jpg -u 150 -l 20
+   $ pierogis threshold ./input.jpg -u 150 -l 20
 
 .. figure:: https://media.githubusercontent.com/media/pierogis/pierogis/develop/demo/out/gnome_threshold.png
    :alt: thresholded gnome
@@ -17,8 +17,10 @@ threshold
 
    *very chill.*
 
-Pixels with brightness outside of the thresholds provided become "included".
-Pixels within the thresholds become "excluded" (greater than lower, but less than upper).
+By default, pixels with brightness outside of the thresholds provided become "included",
+and pixels within the thresholds become "excluded" (greater than lower, but less than upper).
+This can be flipped by providing the ``--inner`` flag.
+
 By default, included means replaced with white, excluded with black.
 
 :ref:`sort` uses this under the hood.
@@ -28,8 +30,10 @@ arg                           description
 ============================= =================================================== ============ =========
 ``-l``, ``--lower-threshold`` pixels with intensity *below* this value are sorted ``64``       ``0-255``
 ``-u``, ``--upper-threshold`` pixels with intensity *above* this value are sorted ``180``      ``0-255``
+``--inner``                   if provided, pixels between the threshold values    ``180``      flag
+                              are included in the sort
 ``--include``                 hex color to substitute for white                   ``'ffffff'`` ``0-3``
-``--exclude``                 hex color to substitute for black                   ``'000000'`` flag
+``--exclude``                 hex color to substitute for black                   ``'000000'`` ``0-3``
 ============================= =================================================== ============ =========
 
-See: :py:class:`~pyrogis.kitchen.menu.threshold_filling.ThresholdFilling`
\ No newline at end of file
+See: :py:class:`~pierogis.kitchen.menu.threshold_filling.ThresholdFilling`, :py:class:`~threshold.Threshold`
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 20679cf..f1309b3 100644
--- a/setup.py
+++ b/setup.py
@@ -13,14 +13,15 @@
     from setuptools_rust import RustExtension, Binding
 
     rust_extensions = [
-        RustExtension("pierogis_rs", binding=Binding.PyO3)
+        RustExtension("pierogis.algorithms", binding=Binding.PyO3)
     ]
 else:
     # don't use RustExtensions
+    print('skipping rust build')
     rust_extensions = None
 
 setup(
-    name='pyrogis',
+    name='pierogis',
     author="pierogis-live",
     author_email="admin@pierogis.live",
     description="image and animation processing framework",
@@ -45,7 +46,7 @@
     rust_extensions=rust_extensions,
     entry_points={
         'console_scripts': [
-            "pyrogis=pyrogis.__main__:main"
+            "pierogis=pierogis.__main__:main"
         ]
     },
     zip_safe=False
diff --git a/src/pierogis/__init__.py b/src/pierogis/__init__.py
new file mode 100644
index 0000000..4b497de
--- /dev/null
+++ b/src/pierogis/__init__.py
@@ -0,0 +1,13 @@
+"""
+image and animation processing framework
+
+pierogis.ingredients -> Ingredient types
+
+pierogis.kitchen -> cooking "runtime"
+
+pierogis.restaurant -> feedback/terminal interface handler
+
+pierogis.course -> animation wrapper
+
+pierogis.algorithms -> Rust algorithm implementations via pyo3
+"""
diff --git a/src/pyrogis/__main__.py b/src/pierogis/__main__.py
similarity index 100%
rename from src/pyrogis/__main__.py
rename to src/pierogis/__main__.py
diff --git a/src/pyrogis/course.py b/src/pierogis/course.py
similarity index 50%
rename from src/pyrogis/course.py
rename to src/pierogis/course.py
index 5eeb8a2..6431c85 100644
--- a/src/pyrogis/course.py
+++ b/src/pierogis/course.py
@@ -3,6 +3,7 @@
 from typing import List
 
 import imageio
+import imageio_ffmpeg
 import numpy as np
 
 from .ingredients.dish import Dish
@@ -46,7 +47,10 @@ def save(
             fps: float = None
     ) -> None:
         """
-        :param duration: ms duration between frames
+        :param path: path to save output
+        :param optimize: whether or not to try to optimize a gif output with gifsicle
+        :param duration: ms duration between frames (gets converted into fps)
+        :param fps: frames per second for image
         """
         if len(self.dishes) > 1:
             if duration is not None:
@@ -58,20 +62,48 @@ def save(
                 else:
                     fps = self._fps
 
-            writer = imageio.get_writer(
-                path,
-                fps=fps
-            )
+            ext = os.path.splitext(path)[1]
+
+            if ext == ".gif":
+                # 30/60 fps is impossible for gif because maximum decimal precision is 2
+                # (duration .03 rounds to 25fps)
+                writer = imageio.get_writer(
+                    path,
+                    fps=fps
+                )
+
+                for dish in self.dishes:
+                    writer.append_data(np.asarray(dish.pierogi.image))
+
+            else:
+                if ext == ".webm":
+                    writer = imageio_ffmpeg.write_frames(
+                        path,
+                        size=self.dishes[0].pierogi.pixels.shape[:2],
+                        fps=fps,
+                        codec='libvpx-vp9',
+                        bitrate='0',
+                        output_params=['-crf', '30'],
+                        input_params=['-thread_queue_size', '128'],
+                    )
+                else:
+                    writer = imageio_ffmpeg.write_frames(
+                        path,
+                        size=self.dishes[0].pierogi.pixels.shape[:2],
+                        fps=fps
+                    )
+
+                writer.send(None)
 
-            for dish in self.dishes:
-                writer.append_data(np.asarray(dish.pierogi.image))
+                for dish in self.dishes:
+                    writer.send(np.asarray(dish.pierogi.image))
 
             writer.close()
 
-            if optimize and os.path.splitext(path)[1] == ".gif":
+            if optimize and ext == '.gif':
                 try:
                     return_code = subprocess.call(
-                        ["gifsicle", '--optimize', path, "--output", path],
+                        ['gifsicle', '--optimize', path, '--output', path],
                         stdout=subprocess.DEVNULL,
                         stderr=subprocess.DEVNULL
                     )
@@ -82,7 +114,7 @@ def save(
                     print("install gifsicle and ensure it's on PATH to optimize gif")
 
         elif len(self.dishes) == 1:
-            self.dishes[0].pierogi.save(path)
+            self.dishes[0].pierogi.save(path, optimize=optimize)
 
         else:
             raise Exception("Dish has no pierogis")
diff --git a/src/pyrogis/ingredients/__init__.py b/src/pierogis/ingredients/__init__.py
similarity index 93%
rename from src/pyrogis/ingredients/__init__.py
rename to src/pierogis/ingredients/__init__.py
index a2f4340..95b997c 100644
--- a/src/pyrogis/ingredients/__init__.py
+++ b/src/pierogis/ingredients/__init__.py
@@ -6,6 +6,7 @@
 from .dish import Dish
 from .flip import Flip
 from .ingredient import Ingredient
+from .mmpx import MMPX
 from .pierogi import Pierogi
 from .quantize import Quantize, SpatialQuantize
 from .recipe import Recipe
diff --git a/src/pyrogis/ingredients/crop.py b/src/pierogis/ingredients/crop.py
similarity index 100%
rename from src/pyrogis/ingredients/crop.py
rename to src/pierogis/ingredients/crop.py
diff --git a/src/pyrogis/ingredients/dish.py b/src/pierogis/ingredients/dish.py
similarity index 88%
rename from src/pyrogis/ingredients/dish.py
rename to src/pierogis/ingredients/dish.py
index b7f1320..a1a39de 100644
--- a/src/pyrogis/ingredients/dish.py
+++ b/src/pierogis/ingredients/dish.py
@@ -21,10 +21,8 @@ def prep(
         """
         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 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
         """
         self.pierogi = pierogi
diff --git a/src/pyrogis/ingredients/flip.py b/src/pierogis/ingredients/flip.py
similarity index 100%
rename from src/pyrogis/ingredients/flip.py
rename to src/pierogis/ingredients/flip.py
diff --git a/src/pyrogis/ingredients/ingredient.py b/src/pierogis/ingredients/ingredient.py
similarity index 100%
rename from src/pyrogis/ingredients/ingredient.py
rename to src/pierogis/ingredients/ingredient.py
diff --git a/src/pierogis/ingredients/mmpx.py b/src/pierogis/ingredients/mmpx.py
new file mode 100644
index 0000000..c7d0948
--- /dev/null
+++ b/src/pierogis/ingredients/mmpx.py
@@ -0,0 +1,27 @@
+import numpy as np
+
+from .ingredient import Ingredient
+
+
+class MMPX(Ingredient):
+    """
+    use the MMPX algorithm implemented in rust to scale 2x
+
+    produces interesting style preserving effects for "paletted" pierogis
+    """
+
+    def cook(self, pixels: np.ndarray):
+        """
+        use the binding to the rscolorq package in rust
+        to perform an optimization in quantizing and dithering
+        """
+
+        from ..algorithms import mmpx
+        a = np.full((*pixels.shape[:2], 1), 255)
+
+        # rotating and unrotating because different orientation is expected
+        cooked_pixels = mmpx(
+            np.ascontiguousarray(np.append(pixels, a, 2), dtype=np.dtype('uint8')),
+        )[:, :, :3]
+
+        return cooked_pixels
diff --git a/src/pyrogis/ingredients/pierogi.py b/src/pierogis/ingredients/pierogi.py
similarity index 98%
rename from src/pyrogis/ingredients/pierogi.py
rename to src/pierogis/ingredients/pierogi.py
index 7fc4fc9..913c9de 100644
--- a/src/pyrogis/ingredients/pierogi.py
+++ b/src/pierogis/ingredients/pierogi.py
@@ -84,7 +84,7 @@ def from_path(cls, path: str, frame_index: int = 0) -> 'Pierogi':
         """
         :param path: file path to load from
         :param frame_index: if path is a multiframe format (video),
-        use this specified frame
+            use this specified frame
         """
 
         def loader():
@@ -155,10 +155,9 @@ def resize(self, width: int, height: int, resample: int = RESIZE_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.
@@ -178,12 +177,13 @@ def resize(self, width: int, height: int, resample: int = RESIZE_RESAMPLE):
                 )
             )
 
-    def rotate(self, angle: float, resample: int = RESIZE_RESAMPLE):
+    def rotate(self, angle: float, resample: int = ROTATE_RESAMPLE):
         """
         rotate underlying pixel array by an angle
 
         :param angle: angle to rotate
         :param resample: resample method to use in Image.resize.
+
         PIL documentation:
 
         >    "An optional resampling filter.
diff --git a/src/pyrogis/ingredients/quantize.py b/src/pierogis/ingredients/quantize.py
similarity index 97%
rename from src/pyrogis/ingredients/quantize.py
rename to src/pierogis/ingredients/quantize.py
index 5d1bda3..abae354 100644
--- a/src/pyrogis/ingredients/quantize.py
+++ b/src/pierogis/ingredients/quantize.py
@@ -15,7 +15,7 @@ def prep(self,
         parameters for spatial color quantization
 
         :param colors: colors to use. can be a list of str
-        or pixel array likes
+            or pixel array likes
         """
 
         if colors is None:
@@ -133,10 +133,10 @@ def cook(self, pixels: np.ndarray):
         to perform an optimization in quantizing and dithering
         """
 
-        from pierogis_rs import algorithms
+        from ..algorithms import quantize
 
         # rotating and unrotating because different orientation is expected
-        cooked_pixels = np.rot90(algorithms.quantize(
+        cooked_pixels = np.rot90(quantize(
             np.ascontiguousarray(np.rot90(pixels), dtype=np.dtype('uint8')),
             self.palette,
             palette_size=self.palette_size,
diff --git a/src/pyrogis/ingredients/recipe.py b/src/pierogis/ingredients/recipe.py
similarity index 100%
rename from src/pyrogis/ingredients/recipe.py
rename to src/pierogis/ingredients/recipe.py
diff --git a/src/pyrogis/ingredients/resize.py b/src/pierogis/ingredients/resize.py
similarity index 100%
rename from src/pyrogis/ingredients/resize.py
rename to src/pierogis/ingredients/resize.py
diff --git a/src/pyrogis/ingredients/rotate.py b/src/pierogis/ingredients/rotate.py
similarity index 100%
rename from src/pyrogis/ingredients/rotate.py
rename to src/pierogis/ingredients/rotate.py
diff --git a/src/pyrogis/ingredients/seasonings/__init__.py b/src/pierogis/ingredients/seasonings/__init__.py
similarity index 100%
rename from src/pyrogis/ingredients/seasonings/__init__.py
rename to src/pierogis/ingredients/seasonings/__init__.py
diff --git a/src/pyrogis/ingredients/seasonings/cartography.py b/src/pierogis/ingredients/seasonings/cartography.py
similarity index 100%
rename from src/pyrogis/ingredients/seasonings/cartography.py
rename to src/pierogis/ingredients/seasonings/cartography.py
diff --git a/src/pyrogis/ingredients/seasonings/rectangle.py b/src/pierogis/ingredients/seasonings/rectangle.py
similarity index 100%
rename from src/pyrogis/ingredients/seasonings/rectangle.py
rename to src/pierogis/ingredients/seasonings/rectangle.py
diff --git a/src/pyrogis/ingredients/seasonings/seasoning.py b/src/pierogis/ingredients/seasonings/seasoning.py
similarity index 97%
rename from src/pyrogis/ingredients/seasonings/seasoning.py
rename to src/pierogis/ingredients/seasonings/seasoning.py
index 2addc79..c9be7d1 100644
--- a/src/pyrogis/ingredients/seasonings/seasoning.py
+++ b/src/pierogis/ingredients/seasonings/seasoning.py
@@ -5,7 +5,7 @@
 import numpy as np
 from PIL import ImageColor
 
-from pyrogis.ingredients.ingredient import Ingredient
+from pierogis.ingredients.ingredient import Ingredient
 
 
 class Seasoning(Ingredient):
diff --git a/src/pyrogis/ingredients/seasonings/threshold.py b/src/pierogis/ingredients/seasonings/threshold.py
similarity index 55%
rename from src/pyrogis/ingredients/seasonings/threshold.py
rename to src/pierogis/ingredients/seasonings/threshold.py
index 2172648..586c94b 100644
--- a/src/pyrogis/ingredients/seasonings/threshold.py
+++ b/src/pierogis/ingredients/seasonings/threshold.py
@@ -9,27 +9,25 @@
 class Threshold(Seasoning):
     """
     a seasoning that compares the brightness value of each pixel
-    in the :param target pixel array.
+    in the :param target pixel array and sets corresponding colors based on
+    if it is "included" or not
 
-    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
+    brightness = r * 0.299 + g * 0.587 + b * 0.114
     """
 
-    LOWER_THRESHOLD = 100
-    UPPER_THRESHOLD = 150
+    LOWER_THRESHOLD = 60
+    UPPER_THRESHOLD = 190
 
     lower_threshold: int
-    """pixels below are `True`"""
     upper_threshold: int
-    """pixels above are `True`"""
+    inner: bool
+    """if True, pixels between lower and upper are included"""
 
     def prep(
             self,
             lower_threshold: int = None,
             upper_threshold: int = None,
+            inner: bool = False,
             **kwargs
     ):
         """
@@ -40,6 +38,9 @@ def prep(
         pixels lower than lower_threshold
         or higher that upper_threshold
         are true (include_pixel)
+
+        pixels with brightness >= upper_threshold
+            or <= lower_threshold are replaced by include pixel (white)
         """
         # set include/exclude_pixel and target, if provided
         super().prep(**kwargs)
@@ -56,35 +57,23 @@ def prep(
 
         self.lower_threshold = lower_threshold
         self.upper_threshold = upper_threshold
+        self.inner = inner
 
     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()
+        input_pixels = pixels.copy()
 
-        from pierogis_rs import algorithms
-
-        # 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'))
-        )
+        try:
+            cooked_pixels = self.cook_rs(input_pixels)
+        except:
+            cooked_pixels = self.cook_np(input_pixels)
 
         return cooked_pixels
 
     def cook_np(self, pixels: np.ndarray):
-        """
-        perform the same operation as Threshold.cook, but only in numpy
-        """
-
+        # 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)
 
@@ -94,13 +83,37 @@ def cook_np(self, pixels: np.ndarray):
         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
-        )
+        if self.inner:
+            # if intensity >= lower and <= upper, include
+            boolean_array = np.logical_and(
+                intensities_array <= self.upper_threshold,
+                intensities_array >= self.lower_threshold
+            )
+        else:
+            # if intensity <= lower or >= upper, include
+            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
+
+    def cook_rs(self, pixels: np.ndarray):
+        from ...algorithms import threshold
+
+        include_pixel = self.include_pixel.astype(np.dtype('uint8'))
+        exclude_pixel = self.exclude_pixel.astype(np.dtype('uint8'))
+
+        # cook using the rust function
+        cooked_pixels = threshold(
+            pixels.astype(np.dtype('uint8')),
+            self.lower_threshold, self.upper_threshold,
+            include_pixel,
+            exclude_pixel,
+            self.inner
+        )
+
+        return cooked_pixels
diff --git a/src/pyrogis/ingredients/sort.py b/src/pierogis/ingredients/sort.py
similarity index 100%
rename from src/pyrogis/ingredients/sort.py
rename to src/pierogis/ingredients/sort.py
diff --git a/src/pyrogis/kitchen/__init__.py b/src/pierogis/kitchen/__init__.py
similarity index 100%
rename from src/pyrogis/kitchen/__init__.py
rename to src/pierogis/kitchen/__init__.py
diff --git a/src/pyrogis/kitchen/chef.py b/src/pierogis/kitchen/chef.py
similarity index 99%
rename from src/pyrogis/kitchen/chef.py
rename to src/pierogis/kitchen/chef.py
index 0289926..afecac9 100644
--- a/src/pyrogis/kitchen/chef.py
+++ b/src/pierogis/kitchen/chef.py
@@ -55,7 +55,7 @@ def create_ingredient_objects(
         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
+            with type, args, and kwargs keys
         :param pierogis: map of uuid keys created Pierogi
         """
         ingredients: Dict[str, Ingredient] = {}
diff --git a/src/pyrogis/kitchen/kitchen.py b/src/pierogis/kitchen/kitchen.py
similarity index 66%
rename from src/pyrogis/kitchen/kitchen.py
rename to src/pierogis/kitchen/kitchen.py
index d16b49d..ceae770 100644
--- a/src/pyrogis/kitchen/kitchen.py
+++ b/src/pierogis/kitchen/kitchen.py
@@ -37,7 +37,6 @@ def __init__(
             self,
             cooker: Cooker,
             cooked_dir: str = 'cooked',
-            output_dir: str = None,
             raw_dir: str = None
     ):
         """
@@ -56,7 +55,6 @@ def __init__(
         self.cooker = cooker
         self.pool = None
         self.cooked_dir = cooked_dir
-        self.output_dir = output_dir
 
         if raw_dir is None:
             raw_dir = tempfile.mkdtemp(suffix='raw')
@@ -97,108 +95,109 @@ def _auto_pilot(self, order: Order) -> List[Ticket]:
         """test some frames in the animation"""
         tickets = [ticket for ticket in order.tickets if not ticket.skip]
 
-        if len(tickets) == 0:
-            return []
-
-        # test with 5% of the frames, within 2 and 10
-        seq_pilot_frames = 2
-
-        frame_index = 0
-
-        next_frame_index = frame_index + seq_pilot_frames
-        next_tickets = tickets[frame_index:next_frame_index]
-        frame_index = next_frame_index
+        if len(order.tickets) > 8 and (order.presave is None or order.cook_async is None):
+            # test with 5% of the frames, within 2 and 10
+            seq_pilot_frames = 2
 
-        # sync cooking
-        start = time.perf_counter()
-        for ticket in next_tickets:
-            self.cook_ticket(self.cooker, ticket)
-        seq_rate = seq_pilot_frames / (time.perf_counter() - start)
+            frame_index = 0
 
-        if order.presave is None:
-            next_frame_index = frame_index + 2
+            next_frame_index = frame_index + seq_pilot_frames
             next_tickets = tickets[frame_index:next_frame_index]
-            order.reader.set_image_index(next_tickets[0].pierogis[next_tickets[0].base].frame_index)
             frame_index = next_frame_index
-            # sync cooking with presave frames
-            presave_start = time.perf_counter()
 
+            # sync cooking
+            start = time.perf_counter()
             for ticket in next_tickets:
-                frame = order.reader.get_next_data()
-                self._presave_ticket(frame, ticket)
                 self.cook_ticket(self.cooker, ticket)
+            seq_rate = seq_pilot_frames / (time.perf_counter() - start)
 
-            presave_rate = seq_pilot_frames / (time.perf_counter() - presave_start)
+            if order.presave is None:
+                next_frame_index = frame_index + 2
+                next_tickets = tickets[frame_index:next_frame_index]
+                order.reader.set_image_index(next_tickets[0].pierogis[next_tickets[0].base].frame_index)
+                frame_index = next_frame_index
+                # sync cooking with presave frames
+                presave_start = time.perf_counter()
 
-            if presave_rate > seq_rate:
-                order.presave = True
-                seq_rate = presave_rate
-            else:
-                order.presave = False
+                for ticket in next_tickets:
+                    frame = order.reader.get_next_data()
+                    self._presave_ticket(frame, ticket)
+                    self.cook_ticket(self.cooker, ticket)
 
-        if order.cook_async is None:
-            par_pilot_frames = max(2, min(round(len(tickets) * .05), 10))
+                presave_rate = seq_pilot_frames / (time.perf_counter() - presave_start)
 
-            if order.presave:
-                next_frame_index = frame_index + par_pilot_frames
-                next_tickets = tickets[frame_index:next_frame_index]
-                frame_index = next_frame_index
+                if presave_rate > seq_rate:
+                    order.presave = True
+                    seq_rate = presave_rate
+                else:
+                    order.presave = False
 
-                results = []
+            if order.cook_async is None:
+                par_pilot_frames = max(2, min(round(len(tickets) * .05), 10))
 
-                if self.pool is None:
-                    self._start_pool(order.processes)
+                if order.presave:
+                    next_frame_index = frame_index + par_pilot_frames
+                    next_tickets = tickets[frame_index:next_frame_index]
+                    frame_index = next_frame_index
 
-                # async cooking with presave frames
-                par_presave_start = time.perf_counter()
+                    results = []
 
-                for ticket in next_tickets:
-                    frame = order.reader.get_next_data()
-                    self._presave_ticket(frame, ticket)
+                    if self.pool is None:
+                        self._start_pool(order.processes)
 
-                    def error_callback(exception: Exception):
-                        order.failures.put((exception, ticket))
+                    # async cooking with presave frames
+                    par_presave_start = time.perf_counter()
 
-                    results.append(self.pool.apply_async(
-                        self.cook_ticket, (self.cooker, ticket), error_callback=error_callback
-                    ))
+                    for ticket in next_tickets:
+                        frame = order.reader.get_next_data()
+                        self._presave_ticket(frame, ticket)
 
-                for result in results:
-                    result.wait()
+                        def error_callback(exception: Exception):
+                            order.failures.put((exception, ticket))
 
-                par_rate = par_pilot_frames / (time.perf_counter() - par_presave_start)
+                        results.append(self.pool.apply_async(
+                            self.cook_ticket, (self.cooker, ticket), error_callback=error_callback
+                        ))
 
-            else:
-                # parallel cooking
+                    for result in results:
+                        result.wait()
 
-                next_frame_index = frame_index + par_pilot_frames
-                next_tickets = tickets[frame_index:next_frame_index]
-                frame_index = next_frame_index
+                    par_rate = par_pilot_frames / (time.perf_counter() - par_presave_start)
 
-                par_start = time.perf_counter()
+                else:
+                    # parallel cooking
 
-                results = []
+                    next_frame_index = frame_index + par_pilot_frames
+                    next_tickets = tickets[frame_index:next_frame_index]
+                    frame_index = next_frame_index
 
-                if self.pool is None:
-                    self._start_pool(order.processes)
+                    par_start = time.perf_counter()
 
-                for ticket in next_tickets:
-                    def error_callback(exception: Exception):
-                        order.failures.put((exception, ticket))
+                    results = []
+
+                    if self.pool is None:
+                        self._start_pool(order.processes)
+
+                    for ticket in next_tickets:
+                        def error_callback(exception: Exception):
+                            order.failures.put((exception, ticket))
+
+                        results.append(self.pool.apply_async(
+                            self.cook_ticket, (self.cooker, ticket), error_callback=error_callback
+                        ))
 
-                    results.append(self.pool.apply_async(
-                        self.cook_ticket, (self.cooker, ticket), error_callback=error_callback
-                    ))
+                    for result in results:
+                        result.wait()
 
-                for result in results:
-                    result.wait()
+                    par_rate = par_pilot_frames / (time.perf_counter() - par_start)
 
-                par_rate = par_pilot_frames / (time.perf_counter() - par_start)
+                if par_rate > seq_rate:
+                    order.cook_async = True
 
-            if par_rate > seq_rate:
-                order.cook_async = True
+            next_tickets = tickets[frame_index:]
 
-        next_tickets = tickets[frame_index:]
+        else:
+            next_tickets = tickets
 
         return next_tickets
 
@@ -272,10 +271,7 @@ def queue_order(
         if not os.path.isfile(order.input_path):
             order.presave = False
 
-        if len(order.tickets) > 8 and (order.presave is None or order.cook_async is None):
-            next_tickets = self._auto_pilot(order)
-        else:
-            next_tickets = order.tickets
+        next_tickets = self._auto_pilot(order)
 
         report_status(order, status='cooking')
 
@@ -331,28 +327,11 @@ def plate(
         optimize = order.optimize
         frame_duration = order.duration
 
-        if self.output_dir is not None:
-            output_path = os.path.join(self.output_dir, os.path.basename(order.output_path))
-
-            dup_index = 1
-
-            base, ext = os.path.splitext(output_path)
-
-            while os.path.isfile(output_path):
-                output_path = base + '-' + str(dup_index) + ext
-
-                dup_index += 1
-
-                output_path = os.path.join(output_path, output_path)
-
-        else:
-            output_path = order.output_path
-
         course.save(
-            output_path,
-            optimize,
+            order.output_path,
+            optimize=optimize,
             duration=frame_duration,
             fps=fps
         )
 
-        return output_path
+        return order.output_path
diff --git a/src/pyrogis/kitchen/menu/__init__.py b/src/pierogis/kitchen/menu/__init__.py
similarity index 77%
rename from src/pyrogis/kitchen/menu/__init__.py
rename to src/pierogis/kitchen/menu/__init__.py
index c3d1d6b..eb2e79d 100644
--- a/src/pyrogis/kitchen/menu/__init__.py
+++ b/src/pierogis/kitchen/menu/__init__.py
@@ -6,6 +6,7 @@
 from .sort_filling import SortFilling
 from .threshold_filling import ThresholdFilling
 from .crop_filling import CropFilling
+from .mmpx_filling import MMPXFilling
 
 menu = {
     SortFilling.type_name: SortFilling,
@@ -13,6 +14,7 @@
     'custom': CustomFilling,
     ThresholdFilling.type_name: ThresholdFilling,
     ResizeFilling.type_name: ResizeFilling,
-    'rotate': RotateFilling,
-    'crop': CropFilling
+    RotateFilling.type_name: RotateFilling,
+    CropFilling.type_name: CropFilling,
+    MMPXFilling.type_name: MMPXFilling,
 }
diff --git a/src/pyrogis/kitchen/menu/crop_filling.py b/src/pierogis/kitchen/menu/crop_filling.py
similarity index 100%
rename from src/pyrogis/kitchen/menu/crop_filling.py
rename to src/pierogis/kitchen/menu/crop_filling.py
diff --git a/src/pyrogis/kitchen/menu/custom_filling.py b/src/pierogis/kitchen/menu/custom_filling.py
similarity index 100%
rename from src/pyrogis/kitchen/menu/custom_filling.py
rename to src/pierogis/kitchen/menu/custom_filling.py
diff --git a/src/pyrogis/kitchen/menu/filling.py b/src/pierogis/kitchen/menu/filling.py
similarity index 99%
rename from src/pyrogis/kitchen/menu/filling.py
rename to src/pierogis/kitchen/menu/filling.py
index 258df45..cc75dde 100644
--- a/src/pyrogis/kitchen/menu/filling.py
+++ b/src/pierogis/kitchen/menu/filling.py
@@ -76,6 +76,5 @@ def get_parser(cls):
         return parser
 
     @classmethod
-    @abstractmethod
     def add_parser_arguments(cls, parser):
         pass
diff --git a/src/pierogis/kitchen/menu/mmpx_filling.py b/src/pierogis/kitchen/menu/mmpx_filling.py
new file mode 100644
index 0000000..be886e3
--- /dev/null
+++ b/src/pierogis/kitchen/menu/mmpx_filling.py
@@ -0,0 +1,7 @@
+from .filling import Filling
+from ...ingredients import MMPX
+
+
+class MMPXFilling(Filling):
+    type_name = 'mmpx'
+    type = MMPX
diff --git a/src/pyrogis/kitchen/menu/quantize_filling.py b/src/pierogis/kitchen/menu/quantize_filling.py
similarity index 100%
rename from src/pyrogis/kitchen/menu/quantize_filling.py
rename to src/pierogis/kitchen/menu/quantize_filling.py
diff --git a/src/pyrogis/kitchen/menu/resize_filling.py b/src/pierogis/kitchen/menu/resize_filling.py
similarity index 100%
rename from src/pyrogis/kitchen/menu/resize_filling.py
rename to src/pierogis/kitchen/menu/resize_filling.py
diff --git a/src/pyrogis/kitchen/menu/rotate_filling.py b/src/pierogis/kitchen/menu/rotate_filling.py
similarity index 100%
rename from src/pyrogis/kitchen/menu/rotate_filling.py
rename to src/pierogis/kitchen/menu/rotate_filling.py
diff --git a/src/pyrogis/kitchen/menu/sort_filling.py b/src/pierogis/kitchen/menu/sort_filling.py
similarity index 97%
rename from src/pyrogis/kitchen/menu/sort_filling.py
rename to src/pierogis/kitchen/menu/sort_filling.py
index 0c7c88f..2153a6f 100644
--- a/src/pyrogis/kitchen/menu/sort_filling.py
+++ b/src/pierogis/kitchen/menu/sort_filling.py
@@ -55,6 +55,7 @@ def generate_ticket(
         # check for implied threshold
         lower_threshold = kwargs.pop('lower_threshold')
         upper_threshold = kwargs.pop('upper_threshold')
+        inner = kwargs.pop('inner')
 
         # create threshold desc
         threshold_desc = IngredientDesc(
@@ -62,6 +63,7 @@ def generate_ticket(
             kwargs={
                 'lower_threshold': lower_threshold,
                 'upper_threshold': upper_threshold,
+                'inner': inner,
                 'pierogi': target_pierogi_uuid
             }
         )
diff --git a/src/pyrogis/kitchen/menu/threshold_filling.py b/src/pierogis/kitchen/menu/threshold_filling.py
similarity index 70%
rename from src/pyrogis/kitchen/menu/threshold_filling.py
rename to src/pierogis/kitchen/menu/threshold_filling.py
index 6285dcc..13a7dba 100644
--- a/src/pyrogis/kitchen/menu/threshold_filling.py
+++ b/src/pierogis/kitchen/menu/threshold_filling.py
@@ -13,15 +13,19 @@ def add_parser_arguments(cls, parser):
         """
         parser.add_argument(
             '-l', '--lower-threshold',
-            type=int,
-            help="Pixels with lightness below"
-                 "this threshold are included"
+            type=float,
+            help="lower end threshold value"
         )
         parser.add_argument(
             '-u', '--upper-threshold',
-            type=int,
-            help="Pixels with lightness above"
-                 "this threshold are included"
+            type=float,
+            help="upper end threshold value"
+        )
+        parser.add_argument(
+            '--inner',
+            dest='inner',
+            action='store_true',
+            help="provide to include pixels between the threshold values"
         )
         parser.add_argument(
             '--include',
diff --git a/src/pyrogis/kitchen/order.py b/src/pierogis/kitchen/order.py
similarity index 76%
rename from src/pyrogis/kitchen/order.py
rename to src/pierogis/kitchen/order.py
index 8bb4605..eac1c8d 100644
--- a/src/pyrogis/kitchen/order.py
+++ b/src/pierogis/kitchen/order.py
@@ -60,13 +60,22 @@ def output_path(self) -> str:
             elif self.frames == 0:
                 return None
             else:
-                output_path = order_name + '.gif'
+                output_path = order_name + '.mp4'
 
-            self._output_path = output_path
+            if self._output_dir is not None:
+                output_path = os.path.join(self._output_dir, os.path.basename(output_path))
+
+            dup_index = 1
+
+            base, ext = os.path.splitext(output_path)
+
+            while os.path.isfile(output_path):
+                output_path = base + '-' + str(dup_index) + ext
+
+                dup_index += 1
 
-        if self._output_path is not None:
             self._output_path = os.path.expanduser(
-                os.path.abspath(self._output_path)
+                os.path.abspath(output_path)
             )
 
         return self._output_path
@@ -76,6 +85,7 @@ def __init__(
             order_name: str,
             input_path: str,
             output_path: Union[str, Path] = None,
+            output_dir: Union[str, Path] = None,
             fps: float = None,
             duration: int = None,
             optimize: bool = None,
@@ -83,12 +93,14 @@ def __init__(
             cook_async: bool = None,
             processes: int = None,
             resume: bool = None,
+            frames_filter: str = None,
     ):
         self._order_name = order_name
         self.input_path = input_path
         self.tickets = []
         self.failures = Queue()
         self._output_path = output_path
+        self._output_dir = output_dir
         self.fps = fps
         self.duration = duration
         self.optimize = optimize
@@ -99,6 +111,13 @@ def __init__(
             processes = os.cpu_count()
         self.processes = processes
         self.resume = resume
+        self._frames_filter = frames_filter
 
     def add_ticket(self, ticket: Ticket):
         self.tickets.append(ticket)
+
+    def frames_filter(self, i: int, frames: int) -> bool:
+        if self._frames_filter is not None:
+            return eval(self._frames_filter)
+        else:
+            return True
diff --git a/src/pyrogis/kitchen/server.py b/src/pierogis/kitchen/server.py
similarity index 83%
rename from src/pyrogis/kitchen/server.py
rename to src/pierogis/kitchen/server.py
index 8f67983..1d0f901 100644
--- a/src/pyrogis/kitchen/server.py
+++ b/src/pierogis/kitchen/server.py
@@ -5,12 +5,15 @@
 import os
 import time
 from abc import abstractmethod
+from pathlib import Path
 from threading import Thread
-from typing import List, Callable, Protocol
+from typing import List, Callable, Protocol, Union, Dict
 
 import imageio
+from natsort import natsorted
 
 from .kitchen import Kitchen
+from .menu import Filling
 from .order import Order
 from .ticket import Ticket
 
@@ -31,10 +34,12 @@ class Server(OrderTaker):
     def __init__(
             self,
             report_callback: Callable = None,
+            output_dir: Union[Path, str] = None,
     ):
         self._report_callback = report_callback
+        self.output_dir = output_dir
 
-    def _create_parser(self, menu):
+    def _create_parser(self, menu: Dict[str, Filling]):
         # create top level parser
         parser = argparse.ArgumentParser(
             description='** image processing pipelines **'
@@ -56,6 +61,13 @@ def _create_parser(self, menu):
             name of order for filename prefixes; used to identify files for output
             """
         )
+        parser.add_argument(
+            '--frames-filter',
+            type=str,
+            default='True',
+            help="python expression to check a frame index against"
+                 "if true, the frame is cooked"
+        )
 
         subparsers.add_parser('togo', parents=[base_parser], add_help=False)
 
@@ -80,12 +92,12 @@ def _create_togo_parser(self):
         )
         togo_parser.add_argument(
             '--frame-duration',
-            type=int,
+            type=float,
             help="frame duration in ms"
         )
         togo_parser.add_argument(
             '--fps',
-            type=int,
+            type=float,
             help="frames per second"
         )
         togo_parser.add_argument(
@@ -119,7 +131,8 @@ def _write_tickets(
                 ticket = Ticket()
                 ticket = generate_ticket(ticket, input_path, frame_index, **parsed_vars.copy())
 
-                order.add_ticket(ticket)
+                if order.frames_filter(frame_index, frames):
+                    order.add_ticket(ticket)
 
         elif os.path.isdir(input_path):
             # debug here
@@ -129,7 +142,9 @@ def _write_tickets(
                 in os.listdir(input_path)
                 if filename != ".DS_Store"
             ]
-            for filename in sorted(filenames):
+            frame_index = 0
+            frames = len(filenames)
+            for filename in natsorted(filenames):
                 if order_name is None or filename.startswith(order_name):
                     ticket_input_path = os.path.join(
                         input_path,
@@ -139,7 +154,10 @@ def _write_tickets(
                     ticket = Ticket()
                     ticket = generate_ticket(ticket, ticket_input_path, 0, **parsed_vars.copy())
 
-                    order.add_ticket(ticket)
+                    if order.frames_filter(frame_index, frames):
+                        order.add_ticket(ticket)
+
+                    frame_index += 1
 
         else:
             raise FileNotFoundError(input_path)
@@ -153,15 +171,23 @@ def _handle_togo(self, order: Order):
                 in os.listdir(order.input_path)
                 if filename != ".DS_Store"
             ]
-            for filename in sorted(filenames):
+            frame_index = 0
+            frames = len(filenames)
+            for filename in natsorted(filenames):
                 if order.order_name is None or filename.startswith(order.order_name):
                     frame_path = os.path.join(order.input_path, filename)
                     ticket = Ticket(output_path=frame_path)
-                    order.add_ticket(ticket)
+
+                    if order.frames_filter(frame_index, frames):
+                        order.add_ticket(ticket)
+
+                    frame_index += 1
         else:
-            for i in range(imageio.get_reader(order.input_path).count_frames()):
+            frames = imageio.get_reader(order.input_path).count_frames()
+            for frame_index in range(frames):
                 ticket = Ticket()
-                order.add_ticket(ticket)
+                if order.frames_filter(frame_index, frames):
+                    order.add_ticket(ticket)
 
         frames = len(order.tickets)
         self._report_status(order, total=frames)
@@ -212,10 +238,11 @@ def take_order(
         parsed = parser.parse_args(unknown)
         parsed_vars = vars(parsed)
 
-        # need the path to use as input for some recipes
-        # like opening files for ingredients
+        # get all of the arguments not related to parameterizing
+        # the cooking operation and put them into a shared Order object
         input_path = os.path.expanduser(os.path.abspath(parsed_vars.pop('path')))
         order_name = parsed_vars.pop('order_name')
+        frames_filter = parsed_vars.pop('frames_filter')
 
         output_path = parsed_togo_vars.pop('output_path')
         fps = parsed_togo_vars.pop('fps')
@@ -225,9 +252,11 @@ def take_order(
         order = Order(
             order_name, input_path,
             output_path=output_path,
+            output_dir=self.output_dir,
             fps=fps,
             duration=frame_duration,
-            optimize=optimize
+            optimize=optimize,
+            frames_filter=frames_filter,
         )
 
         if order.fps is None:
@@ -241,13 +270,9 @@ def take_order(
             self._handle_filling(order, parsed_vars, kitchen)
 
         self._report_status(order, status='boxing')
-
-        kitchen.plate(
-            order=order
-        )
+        kitchen.plate(order=order)
 
         self._report_status(order, status='done')
-
         return order
 
     @staticmethod
diff --git a/src/pyrogis/kitchen/ticket.py b/src/pierogis/kitchen/ticket.py
similarity index 100%
rename from src/pyrogis/kitchen/ticket.py
rename to src/pierogis/kitchen/ticket.py
diff --git a/src/pyrogis/restaurant.py b/src/pierogis/restaurant.py
similarity index 100%
rename from src/pyrogis/restaurant.py
rename to src/pierogis/restaurant.py
diff --git a/src/pymodule.rs b/src/pymodule.rs
index f563a4d..e4aa997 100644
--- a/src/pymodule.rs
+++ b/src/pymodule.rs
@@ -1,3 +1,4 @@
+use image::{DynamicImage, RgbaImage, RgbImage};
 use ndarray::parallel::prelude::*;
 use numpy::{Ix1, Ix2, Ix3, PyArray, PyReadonlyArray, ToPyArray};
 use pyo3::{PyResult, Python};
@@ -7,15 +8,12 @@ use rayon::prelude::*;
 use crate::quantize;
 
 #[pymodule]
-fn pierogis_rs(py: Python<'_>, m: &PyModule) -> PyResult<()> {
-    let module = PyModule::new(py, "algorithms")?;
-    m.add_submodule(module)?;
-
+fn algorithms(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
     /// quantize(py_array, palette_size, /)
     /// --
     ///
-    /// This function adds two unsigned 64-bit integers.
-    #[pyfn(module, "quantize")]
+    /// quantize an image as a numpy array using rscolorq.
+    #[pyfn(m, "quantize")]
     #[allow(clippy::too_many_arguments)]
     fn py_quantize<'py>(
         py: Python<'py>,
@@ -56,7 +54,7 @@ fn pierogis_rs(py: Python<'_>, m: &PyModule) -> PyResult<()> {
         PyArray::from_vec(py, cooked_array).reshape((width, height, 3))
     }
 
-    #[pyfn(module, "threshold")]
+    #[pyfn(m, "threshold")]
     #[allow(clippy::too_many_arguments)]
     fn py_threshold<'py>(
         py: Python<'py>,
@@ -65,6 +63,7 @@ fn pierogis_rs(py: Python<'_>, m: &PyModule) -> PyResult<()> {
         upper_threshold: u8,
         include_pixel: PyReadonlyArray<u8, Ix1>,
         exclude_pixel: PyReadonlyArray<u8, Ix1>,
+        inner: bool,
     ) -> PyResult<&'py PyArray<u8, Ix3>> {
         let pixels = unsafe { pixels_py_array.as_slice_mut() }?;
         let include_pixel = include_pixel.as_slice()?;
@@ -76,17 +75,22 @@ fn pierogis_rs(py: Python<'_>, m: &PyModule) -> PyResult<()> {
             let pixel_value = (pixel_array[0] as f64) * 0.299
                 + (pixel_array[1] as f64) * 0.587
                 + (pixel_array[2] as f64) * 0.114;
+
             // get with the correct pixel based on if it is outside a threshold
-            let replacement = match (pixel_value >= upper_threshold as f64)
-                | (pixel_value <= lower_threshold as f64)
+            let included: bool = if inner {
+                (pixel_value <= upper_threshold as f64)
+                    & (pixel_value >= lower_threshold as f64)
+            } else {
+                (pixel_value >= upper_threshold as f64)
+                    | (pixel_value <= lower_threshold as f64)
+            };
+
+            let replacement = match included
             {
                 true => &include_pixel,
                 false => &exclude_pixel,
             };
 
-            // pixel.brightness() >= upper_threshold
-            // pixel.replace(replacement)
-
             // replace rgb on the current chunk
             pixel_array[0] = replacement[0] as u8;
             pixel_array[1] = replacement[1] as u8;
@@ -96,89 +100,25 @@ fn pierogis_rs(py: Python<'_>, m: &PyModule) -> PyResult<()> {
         pixels.to_pyarray(py).reshape(pixels_py_array.dims())
     }
 
-    // This alternate to the threshold uses ndarrays
-    // and was found to be about 3x slower than the slice threshold.
-    // I'm keeping it because that was still pretty fast
-    // and it's a good example of how to use ndarray and genrows with a pixels PyArray
+    #[pyfn(m, "mmpx")]
+    #[allow(clippy::too_many_arguments)]
+    fn py_mmpx<'py>(
+        py: Python<'py>,
+        pixels_py_array: &PyArray<u8, Ix3>,
+    ) -> PyResult<&'py PyArray<u8, Ix3>> {
+        let pixels = pixels_py_array.to_vec().unwrap();
+        let shape = pixels_py_array.shape();
+        let width = shape[0];
+        let height = shape[1];
+
+        let image = RgbaImage::from_raw(
+            height as u32, width as u32, pixels,
+        ).unwrap();
+
+        mmpx::magnify(&image)
+            .to_pyarray(py)
+            .reshape((width * 2, height * 2, 4))
+    }
 
-    // #[pyfn(ingredients_module, "athreshold")]
-    // fn py_arraythreshold<'py>(
-    //     py: Python<'py>,
-    //     array: &PyArray<f64, Ix3>,
-    //     upper_threshold: f64,
-    //     include_pixel: &PyArray<f64, Ix1>,
-    //     exclude_pixel: &PyArray<f64, Ix1>,
-    // ) -> PyResult<&'py PyArray<f64, Ix3>> {
-    //     let mut array = unsafe { array.as_array_mut() };
-    //     let include_pixel = unsafe { include_pixel.as_array_mut() };
-    //     let exclude_pixel = unsafe { exclude_pixel.as_array_mut() };
-    //
-    //     Zip::from(array
-    //         .genrows_mut())
-    //         .par_apply(|mut pixel| -> () {
-    //             let replacement =
-    //                 match (pixel[0] * 0.299 + pixel[1] * 0.587 + pixel[2] * 0.114) >= upper_threshold {
-    //                     true => &include_pixel,
-    //                     false => &exclude_pixel
-    //                 };
-    //
-    //             pixel[0] = replacement[0];
-    //             pixel[1] = replacement[1];
-    //             pixel[2] = replacement[2];
-    //         });
-    //
-    //     Ok(array.to_pyarray(py))
-    // }
-    // #[pyfn(ingredients_module, "sort")]
-    // fn sort_py_array<'py>(
-    //     py: Python<'py>,
-    //     py_array_pixels: &PyArray<f64, Ix3>,
-    //     py_array_mask: &PyArray<f64, Ix3>,
-    //     upper_threshold: f64,
-    // ) -> PyResult<&'py PyArray<f64, Ix3>> {
-    //     let mut pixels = unsafe { py_array_pixels.as_slice_mut() }?;
-    //
-    //     let (width, height, _) = py_array_pixels.shape();
-    //
-    //     // loop through rows in parallel to generate intervals
-    //     // loop through intervals in parallel to sort
-    //
-    //     for pixel in row {
-    //         if pixel
-    //     }
-    //     intervals.par_apply(|interval| {
-    //         interval.par_sort_unstable()
-    //     });
-    //
-    //     pixels.par_chunks_mut(3).collect().par_chunks_mut(height)
-    //         .for_each(|row| -> () {
-    //             row.for_each(|pixel| {
-    //                 if pixel == [255.0, 255.0, 255.0] {
-    //                     // add pixel to current interval
-    //                     pixel
-    //                 }
-    //             })
-    //             // let pixel = Pixel::from(pixel_array);
-    //
-    //
-    //
-    //             let replacement =
-    //                 match (pixel_array[0] * 0.299 + pixel_array[1] * 0.587 + pixel_array[2] * 0.114) >= upper_threshold {
-    //                     true => &include_pixel,
-    //                     false => &exclude_pixel,
-    //                 };
-    //
-    //             // pixel.brightness() >= upper_threshold
-    //             // pixel.replace(replacement)
-    //
-    //             pixel_array[0] = replacement[0];
-    //             pixel_array[1] = replacement[1];
-    //             pixel_array[2] = replacement[2];
-    //         });
-    //
-    //     println!("{}", py_array.shape()[0]);
-    //
-    //     pixels.to_pyarray(py).reshape(py_array.dims())
-    // }
     Ok(())
 }
\ No newline at end of file
diff --git a/src/pyrogis/__init__.py b/src/pyrogis/__init__.py
deleted file mode 100644
index 6e12de4..0000000
--- a/src/pyrogis/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-"""
-image and animation processing framework
-
-pyrogis.ingredients -> Ingredient types
-
-pyrogis.kitchen -> cooking "runtime"
-
-pyrogis.restaurant -> feedback/terminal interface handler
-
-pyrogis.course -> animation wrapper
-"""


Test patch:
diff --git a/tests/ingredients/test_crop.py b/tests/ingredients/test_crop.py
index 639d282..3946bd9 100644
--- a/tests/ingredients/test_crop.py
+++ b/tests/ingredients/test_crop.py
@@ -1,7 +1,7 @@
 import numpy as np
 import pytest
 
-from pyrogis.ingredients.crop import Crop
+from pierogis.ingredients.crop import Crop
 
 
 @pytest.fixture
diff --git a/tests/ingredients/test_dish.py b/tests/ingredients/test_dish.py
index 4053612..8811974 100644
--- a/tests/ingredients/test_dish.py
+++ b/tests/ingredients/test_dish.py
@@ -1,7 +1,7 @@
 import numpy as np
 import pytest
 
-from pyrogis.ingredients import Dish, Pierogi, Recipe
+from pierogis.ingredients import Dish, Pierogi, Recipe
 
 
 @pytest.fixture
diff --git a/tests/ingredients/test_mmpx.py b/tests/ingredients/test_mmpx.py
new file mode 100644
index 0000000..b131acf
--- /dev/null
+++ b/tests/ingredients/test_mmpx.py
@@ -0,0 +1,36 @@
+import numpy as np
+import pytest
+
+from pierogis.ingredients import MMPX
+
+
+@pytest.fixture
+def array():
+    return np.asarray([[[200, 200, 200], [30, 30, 30]],
+                       [[130, 130, 130], [60, 60, 60]],
+                       [[20, 40, 100], [10, 20, 200]]]).astype(np.dtype('uint8'))
+
+
+def test_cook(array):
+    """
+    provide only width
+    """
+    width, height = array.shape[:2]
+
+    mmpx = MMPX()
+    cooked_array = mmpx.cook(array)
+
+    assert cooked_array.shape[0] == width * 2
+    assert cooked_array.shape[1] == height * 2
+
+    # it's just nearest neighbor for this input
+    assert np.all(
+        cooked_array == [
+            [[200, 200, 200], [200, 200, 200], [30, 30, 30], [30, 30, 30]],
+            [[200, 200, 200], [200, 200, 200], [30, 30, 30], [30, 30, 30]],
+            [[130, 130, 130], [130, 130, 130], [60, 60, 60], [60, 60, 60]],
+            [[130, 130, 130], [130, 130, 130], [60, 60, 60], [60, 60, 60]],
+            [[20, 40, 100], [20, 40, 100], [10, 20, 200], [10, 20, 200]],
+            [[20, 40, 100], [20, 40, 100], [10, 20, 200], [10, 20, 200]]
+        ]
+    )
diff --git a/tests/ingredients/test_pierogi.py b/tests/ingredients/test_pierogi.py
index 925d0e5..df0f913 100644
--- a/tests/ingredients/test_pierogi.py
+++ b/tests/ingredients/test_pierogi.py
@@ -5,7 +5,7 @@
 import pytest
 from PIL import Image
 
-from pyrogis.ingredients import Pierogi
+from pierogis.ingredients import Pierogi
 
 
 @pytest.fixture
diff --git a/tests/ingredients/test_recipe.py b/tests/ingredients/test_recipe.py
index 0a1cac8..5fbdefb 100644
--- a/tests/ingredients/test_recipe.py
+++ b/tests/ingredients/test_recipe.py
@@ -1,7 +1,7 @@
 import numpy as np
 import pytest
 
-from pyrogis.ingredients import Recipe, Ingredient
+from pierogis.ingredients import Recipe, Ingredient
 
 
 @pytest.fixture
diff --git a/tests/ingredients/test_rectangle.py b/tests/ingredients/test_rectangle.py
index bf76406..bc1ea4a 100644
--- a/tests/ingredients/test_rectangle.py
+++ b/tests/ingredients/test_rectangle.py
@@ -1,8 +1,8 @@
 import numpy as np
 import pytest
 
-from pyrogis.ingredients.seasonings import Rectangle
-from pyrogis.ingredients.seasonings.rectangle import Direction
+from pierogis.ingredients.seasonings import Rectangle
+from pierogis.ingredients.seasonings.rectangle import Direction
 
 
 @pytest.fixture
diff --git a/tests/ingredients/test_resize.py b/tests/ingredients/test_resize.py
index c4767e7..659ff09 100644
--- a/tests/ingredients/test_resize.py
+++ b/tests/ingredients/test_resize.py
@@ -2,7 +2,7 @@
 import pytest
 from PIL import Image
 
-from pyrogis.ingredients import Resize
+from pierogis.ingredients import Resize
 
 
 @pytest.fixture
diff --git a/tests/ingredients/test_rotate.py b/tests/ingredients/test_rotate.py
index a84c09d..8ea8cab 100644
--- a/tests/ingredients/test_rotate.py
+++ b/tests/ingredients/test_rotate.py
@@ -1,7 +1,7 @@
 import numpy as np
 import pytest
 
-from pyrogis.ingredients import Rotate
+from pierogis.ingredients import Rotate
 
 
 @pytest.fixture
diff --git a/tests/ingredients/test_seasoning.py b/tests/ingredients/test_seasoning.py
index 111aa8c..902a6f9 100644
--- a/tests/ingredients/test_seasoning.py
+++ b/tests/ingredients/test_seasoning.py
@@ -1,7 +1,7 @@
 import numpy as np
 import pytest
 
-from pyrogis.ingredients.seasonings import Seasoning
+from pierogis.ingredients.seasonings import Seasoning
 
 
 @pytest.fixture
diff --git a/tests/ingredients/test_threshold.py b/tests/ingredients/test_threshold.py
index 5af292c..253386c 100644
--- a/tests/ingredients/test_threshold.py
+++ b/tests/ingredients/test_threshold.py
@@ -1,7 +1,7 @@
 import numpy as np
 import pytest
 
-from pyrogis.ingredients import Threshold
+from pierogis.ingredients import Threshold
 
 
 @pytest.fixture
@@ -22,6 +22,18 @@ def test_cook(array):
     assert np.all(cooked_array[1, 1] == 255)
 
 
+def test_cook_inner(array):
+    """default cook parameters"""
+    threshold = Threshold(inner=True)
+
+    cooked_array = threshold.cook(array)
+
+    assert np.all(cooked_array[0, 0] == 0)
+    assert np.all(cooked_array[0, 1] == 0)
+    assert np.all(cooked_array[1, 0] == 255)
+    assert np.all(cooked_array[1, 1] == 255)
+
+
 def test_cook_upper(array):
     """upper threshold provided; lower threshold should be 0"""
     threshold = Threshold(
@@ -87,11 +99,21 @@ def test_cook_include_exclude(array):
     assert np.all(cooked_array[1, 1] == 100)
 
 
-def test_cook_cook_np(array):
+def test_cook_np_rs(array):
     """test cook and cook_np method return the same array"""
     threshold = Threshold()
 
-    cooked_array = threshold.cook(array)
-    np_cooked_array = threshold.cook(array)
+    rs_cooked_array = threshold.cook_rs(array)
+    np_cooked_array = threshold.cook_np(array)
+
+    assert np.all(rs_cooked_array == np_cooked_array)
+
+
+def test_cook_np_rs_inner(array):
+    """test cook and cook_np method return the same array"""
+    threshold = Threshold(inner=True)
+
+    rs_cooked_array = threshold.cook_rs(array)
+    np_cooked_array = threshold.cook_np(array)
 
-    assert np.all(cooked_array == np_cooked_array)
+    assert np.all(rs_cooked_array == np_cooked_array)
diff --git a/tests/kitchen/conftest.py b/tests/kitchen/conftest.py
index 78a1f33..ed3fa6e 100644
--- a/tests/kitchen/conftest.py
+++ b/tests/kitchen/conftest.py
@@ -4,7 +4,7 @@
 import numpy as np
 import pytest
 
-from pyrogis.kitchen import Kitchen, Chef
+from pierogis.kitchen import Kitchen, Chef
 
 
 @pytest.fixture
@@ -15,7 +15,7 @@ def array():
 
 @pytest.fixture
 def kitchen(tmp_path) -> Kitchen:
-    return Kitchen(Chef, cooked_dir=str(tmp_path / 'cooked'), output_dir=str(tmp_path))
+    return Kitchen(Chef, cooked_dir=str(tmp_path / 'cooked'))
 
 
 @pytest.fixture
@@ -63,3 +63,7 @@ def gif_output_path(tmp_path):
 @pytest.fixture
 def mp4_output_path(tmp_path):
     return str(tmp_path / 'output.mp4')
+
+@pytest.fixture
+def webm_output_path(tmp_path):
+    return str(tmp_path / 'output.webm')
diff --git a/tests/kitchen/test_chef.py b/tests/kitchen/test_chef.py
index d240a3b..adf3656 100644
--- a/tests/kitchen/test_chef.py
+++ b/tests/kitchen/test_chef.py
@@ -4,9 +4,9 @@
 import pytest
 from PIL import Image
 
-from pyrogis.ingredients import Dish, Pierogi
-from pyrogis.kitchen import Chef, menu
-from pyrogis.kitchen.ticket import Ticket, PierogiDesc, IngredientDesc
+from pierogis.ingredients import Dish, Pierogi
+from pierogis.kitchen import Chef, menu
+from pierogis.kitchen.ticket import Ticket, PierogiDesc, IngredientDesc
 
 
 @pytest.fixture
diff --git a/tests/kitchen/test_kitchen.py b/tests/kitchen/test_kitchen.py
index c899da3..6ba9ce9 100644
--- a/tests/kitchen/test_kitchen.py
+++ b/tests/kitchen/test_kitchen.py
@@ -2,7 +2,7 @@
 
 import pytest
 
-from pyrogis.kitchen import Order, Kitchen, Ticket
+from pierogis.kitchen import Order, Kitchen, Ticket
 
 
 @pytest.fixture
@@ -24,7 +24,7 @@ def order(order_name: str, dir_path: str):
 
 
 def test_plate_gif(kitchen: Kitchen, order: Order, gif_output_path: str):
-    order.output_name = gif_output_path
+    order._output_path = gif_output_path
 
     output_path = kitchen.plate(
         order
@@ -41,26 +41,13 @@ def test_plate_mp4(kitchen: Kitchen, order: Order, mp4_output_path: str):
     )
 
     assert os.path.isfile(output_path)
-#
-#
-# def test_plate_dir(kitchen: Kitchen, resource_dir: str, tmp_path):
-#     input_path = os.path.join(resource_dir, 'frames')
-#     output_path = tmp_path / 'output.mp4'
-#     optimize = True
-#
-#     order = Order(
-#         'octo',
-#         input_path,
-#         output_path=output_path,
-#         fps=25,
-#         optimize=optimize
-#     )
-#
-#     order.tickets = [
-#         Ticket(output_path=os.path.join(input_path, 'octo-01.png')),
-#         Ticket(output_path=os.path.join(input_path, 'octo-02.png'))
-#     ]
-#
-#     kitchen.plate(order)
-#
-#     assert os.path.isfile(output_path)
+
+
+def test_plate_webm(kitchen: Kitchen, order: Order, webm_output_path: str):
+    order._output_path = webm_output_path
+
+    output_path = kitchen.plate(
+        order
+    )
+
+    assert os.path.isfile(output_path)
diff --git a/tests/kitchen/test_server.py b/tests/kitchen/test_server.py
index 8068f5e..1c04139 100644
--- a/tests/kitchen/test_server.py
+++ b/tests/kitchen/test_server.py
@@ -3,14 +3,14 @@
 
 import pytest
 
-from pyrogis.kitchen import Kitchen, Server, Ticket
-from pyrogis.kitchen.menu import ResizeFilling
-from pyrogis.kitchen.order import Order
+from pierogis.kitchen import Kitchen, Server, Ticket
+from pierogis.kitchen.menu import ResizeFilling
+from pierogis.kitchen.order import Order
 
 
 @pytest.fixture
-def server() -> Server:
-    return Server()
+def server(tmp_path) -> Server:
+    return Server(output_dir=str(tmp_path))
 
 
 @pytest.fixture
@@ -92,7 +92,12 @@ def test_take_order_sort(server, kitchen, image_path):
 
 def test_take_order_sort_options(server, kitchen, image_path):
     """test sort order with options"""
-    args = ["sort", image_path, "-u", "120", "-l", "20", "-t", "2", "--ccw"]
+    args = [
+        "sort", image_path,
+        "-u", "120", "-l", "20",
+        "--inner",
+        "-t", "2", "--ccw"
+    ]
 
     run_take_order(server, kitchen, args)
 
@@ -131,6 +136,7 @@ def test_take_order_threshold_options(server, kitchen, image_path):
         "threshold", image_path,
         "-u", "200",
         "-l", "20",
+        "--inner",
         "--include", "abaabb",
         "--exclude", "333433"
     ]
@@ -145,7 +151,6 @@ def test_take_order_resize(server, kitchen, image_path):
     order = run_take_order(server, kitchen, args)
 
     assert len(order.tickets) == 1
-    print(order.tickets[0].output_path)
 
 
 def test_take_order_resize_options(server, kitchen, image_path):
@@ -204,7 +209,7 @@ def test_take_order_custom_txt(server, kitchen, image_path, recipe_path):
     run_take_order(server, kitchen, args)
 
 
-def test_take_order_crop(server, kitchen, image_path, recipe_path):
+def test_take_order_crop(server, kitchen, image_path):
     """test custom with a txt file as a recipe"""
     args = [
         "crop", image_path,
@@ -215,7 +220,7 @@ def test_take_order_crop(server, kitchen, image_path, recipe_path):
     run_take_order(server, kitchen, args)
 
 
-def test_take_order_crop_options(server, kitchen, image_path, recipe_path):
+def test_take_order_crop_options(server, kitchen, image_path):
     """test custom with a txt file as a recipe"""
     args = [
         "crop", image_path,
@@ -227,7 +232,7 @@ def test_take_order_crop_options(server, kitchen, image_path, recipe_path):
     run_take_order(server, kitchen, args)
 
 
-def test_take_order_crop_options_float(server, kitchen, image_path, recipe_path):
+def test_take_order_crop_options_float(server, kitchen, image_path):
     """test custom with a txt file as a recipe"""
     args = [
         "crop", image_path,
@@ -241,6 +246,15 @@ def test_take_order_crop_options_float(server, kitchen, image_path, recipe_path)
     run_take_order(server, kitchen, args)
 
 
+def test_take_order_mmpx(server, kitchen, image_path):
+    """test custom with a txt file as a recipe"""
+    args = [
+        "mmpx", image_path
+    ]
+
+    run_take_order(server, kitchen, args)
+
+
 # non filling-related take_order tests
 def test_take_order_togo(server, kitchen, dir_path):
     """test togo"""