Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ Examples

$ data-morph --start-shape music --target-shape bullseye --output-dir path/to/dir

7. Morph the sheep shape into vertical lines, slowly ramping in and out for the animation:
7. Morph the sheep shape into vertical lines, slowly easing in and out for the animation:

.. code-block:: console

$ data-morph --start-shape sheep --target-shape v_lines --ramp-in --ramp-out
$ data-morph --start-shape sheep --target-shape v_lines --ease
11 changes: 6 additions & 5 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,13 @@ within your current working directory:

Morphing the panda :class:`.Dataset` into the star :class:`.Shape`.

You can smooth the transition with the ``--ramp-in`` and ``--ramp-out`` flags. The ``--freeze``
flag allows you to start the animation with the specified number of frames of the initial shape:
You can smooth the transition with the ``--ease`` or ``--ease-in`` and ``--ease-out`` flags.
The ``--freeze`` flag allows you to start the animation with the specified number of frames
of the initial shape:

.. code:: console

$ data-morph --start-shape panda --target-shape star --freeze 50 --ramp-in --ramp-out
$ data-morph --start-shape panda --target-shape star --freeze 50 --ease

Here is the resulting animation:

Expand Down Expand Up @@ -115,8 +116,8 @@ With the :class:`.Dataset` and :class:`.Shape` created, here is a minimal exampl
start_shape=dataset,
target_shape=target_shape,
freeze_for=50,
ramp_in=True,
ramp_out=True,
ease_in=True,
ease_out=True,
)

.. note::
Expand Down
55 changes: 35 additions & 20 deletions src/data_morph/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ def generate_parser() -> argparse.ArgumentParser:
description='Specify the start and target shapes.',
)
shape_config_group.add_argument(
'--start',
'--start-shape',
dest='start_shape',
required=True,
nargs='+',
help=(
Expand All @@ -67,7 +69,9 @@ def generate_parser() -> argparse.ArgumentParser:
),
)
shape_config_group.add_argument(
'--target',
'--target-shape',
dest='target_shape',
required=True,
nargs='+',
help=(
Expand Down Expand Up @@ -149,6 +153,7 @@ def generate_parser() -> argparse.ArgumentParser:
),
)
file_group.add_argument(
'-o',
'--output-dir',
default=ARG_DEFAULTS['output_dir'],
metavar='DIRECTORY',
Expand All @@ -168,29 +173,17 @@ def generate_parser() -> argparse.ArgumentParser:
'Animation Configuration', description='Customize aspects of the animation.'
)
frame_group.add_argument(
'--forward-only',
'--ease',
default=False,
action='store_true',
help=(
'By default, this module will create an animation that plays '
'first forward (applying the transformation) and then unwinds, '
'playing backward to undo the transformation. Pass this argument '
'to only play the animation in the forward direction before looping.'
),
)
frame_group.add_argument(
'--freeze',
default=ARG_DEFAULTS['freeze'],
type=int,
metavar='NUM_FRAMES',
help=(
'Number of frames to freeze at the first and final frame of the transition '
'in the animation. This only affects the frames selected, not the algorithm. '
f'Defaults to {ARG_DEFAULTS["freeze"]}.'
'Whether to slow down the transition near the start and end of the '
'transformation. This is a shortcut for --ease-in --ease-out. This only '
'affects the frames selected, not the algorithm.'
),
)
frame_group.add_argument(
'--ramp-in',
'--ease-in',
default=False,
action='store_true',
help=(
Expand All @@ -199,14 +192,36 @@ def generate_parser() -> argparse.ArgumentParser:
),
)
frame_group.add_argument(
'--ramp-out',
'--ease-out',
default=False,
action='store_true',
help=(
'Whether to slow down the transition from input to target towards the end '
'of the animation. This only affects the frames selected, not the algorithm.'
),
)
frame_group.add_argument(
'--forward-only',
default=False,
action='store_true',
help=(
'By default, this module will create an animation that plays '
'first forward (applying the transformation) and then rewinds, '
'playing backward to undo the transformation. Pass this argument '
'to only play the animation in the forward direction before looping.'
),
)
frame_group.add_argument(
'--freeze',
default=ARG_DEFAULTS['freeze'],
type=int,
metavar='NUM_FRAMES',
help=(
'Number of frames to freeze at the first and final frame of the transition '
'in the animation. This only affects the frames selected, not the algorithm. '
f'Defaults to {ARG_DEFAULTS["freeze"]}.'
),
)

return parser

Expand Down Expand Up @@ -260,7 +275,7 @@ def main(argv: Sequence[str] | None = None) -> None:
target_shape=shape_factory.generate_shape(target_shape),
iterations=args.iterations,
min_shake=args.shake,
ramp_in=args.ramp_in,
ramp_out=args.ramp_out,
ease_in=args.ease_in or args.ease,
ease_out=args.ease_out or args.ease,
freeze_for=args.freeze,
)
24 changes: 12 additions & 12 deletions src/data_morph/morpher.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def __init__(
self._looper = tqdm.tnrange if in_notebook else tqdm.trange

def _select_frames(
self, iterations: int, ramp_in: bool, ramp_out: bool, freeze_for: int
self, iterations: int, ease_in: bool, ease_out: bool, freeze_for: int
) -> list:
"""
Identify the frames to capture for the animation.
Expand All @@ -133,9 +133,9 @@ def _select_frames(
----------
iterations : int
The number of iterations.
ramp_in : bool
ease_in : bool
Whether to more slowly transition in the beginning.
ramp_out : bool
ease_out : bool
Whether to slow down the transition at the end.
freeze_for : int
The number of frames to freeze at the beginning and end. Must be in the
Expand Down Expand Up @@ -166,11 +166,11 @@ def _select_frames(
# freeze initial frame
frames = [0] * freeze_for

if ramp_in and not ramp_out:
if ease_in and not ease_out:
easing_function = ease_in_sine
elif ramp_out and not ramp_in:
elif ease_out and not ease_in:
easing_function = ease_out_sine
elif ramp_out and ramp_in:
elif ease_out and ease_in:
easing_function = ease_in_out_sine
else:
easing_function = linear
Expand Down Expand Up @@ -346,8 +346,8 @@ def morph(
min_shake: Number = 0.3,
max_shake: Number = 1,
allowed_dist: Number = 2,
ramp_in: bool = False,
ramp_out: bool = False,
ease_in: bool = False,
ease_out: bool = False,
freeze_for: int = 0,
) -> pd.DataFrame:
"""
Expand Down Expand Up @@ -376,10 +376,10 @@ def morph(
at ``max_shake`` and move toward ``min_shake``.
allowed_dist : numbers.Number
The farthest apart the perturbed points can be from the target shape.
ramp_in : bool, default ``False``
ease_in : bool, default ``False``
Whether to more slowly transition in the beginning.
This only affects the frames, not the algorithm.
ramp_out : bool, default ``False``
ease_out : bool, default ``False``
Whether to slow down the transition at the end.
This only affects the frames, not the algorithm.
freeze_for : int, default ``0``
Expand Down Expand Up @@ -440,8 +440,8 @@ def morph(
# iteration numbers that we will end up writing to file as frames
frame_numbers = self._select_frames(
iterations=iterations,
ramp_in=ramp_in,
ramp_out=ramp_out,
ease_in=ease_in,
ease_out=ease_out,
freeze_for=freeze_for,
)

Expand Down
14 changes: 9 additions & 5 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def test_cli_bad_input_integers(field, value, capsys):
@pytest.mark.input_validation
@pytest.mark.parametrize('value', [1, 0, 's', -1, 0.5, True, False])
@pytest.mark.parametrize(
'field', ['ramp-in', 'ramp-out', 'forward-only', 'keep-frames']
'field', ['ease-in', 'ease-out', 'forward-only', 'keep-frames']
)
def test_cli_bad_input_boolean(field, value, capsys):
"""Test that invalid input for Boolean switches are handled correctly."""
Expand Down Expand Up @@ -133,8 +133,9 @@ def test_cli_one_shape(start_shape, flag, mocker, tmp_path):
'min_shake': 0.5 if flag else None,
'iterations': 1000,
'freeze': 3 if flag else None,
'ramp_in': flag,
'ramp_out': flag,
'ease_in': flag,
'ease_out': flag,
'ease': not flag,
}

morpher_init = mocker.patch.object(cli.DataMorpher, '__init__', autospec=True)
Expand All @@ -153,8 +154,9 @@ def test_cli_one_shape(start_shape, flag, mocker, tmp_path):
'--forward-only' if init_args['forward_only_animation'] else '',
f'--shake={morph_args["min_shake"]}' if morph_args['min_shake'] else '',
f'--freeze={morph_args["freeze"]}' if morph_args['freeze'] else '',
'--ramp-in' if morph_args['ramp_in'] else '',
'--ramp-out' if morph_args['ramp_out'] else '',
'--ease-in' if morph_args['ease_in'] else '',
'--ease-out' if morph_args['ease_out'] else '',
'--ease' if morph_args['ease'] else '',
]
cli.main([arg for arg in argv if arg])

Expand All @@ -171,6 +173,8 @@ def test_cli_one_shape(start_shape, flag, mocker, tmp_path):
elif arg == 'start_shape':
assert isinstance(value, Dataset)
assert value.name == Path(morph_args['start_shape_name']).stem
elif morph_args['ease'] and arg.startswith('ease_'):
assert value
elif arg in ['freeze_for', 'min_shake']:
arg = 'freeze' if arg == 'freeze_for' else arg
assert value == (morph_args[arg] or cli.ARG_DEFAULTS[arg])
Expand Down
20 changes: 10 additions & 10 deletions tests/test_morpher.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def test_input_validation_freeze_for(self, freeze_for):
ValueError, match='freeze_for must be a non-negative integer'
):
_ = morpher._select_frames(
iterations=100, ramp_in=True, ramp_out=True, freeze_for=freeze_for
iterations=100, ease_in=True, ease_out=True, freeze_for=freeze_for
)

@pytest.mark.input_validation
Expand All @@ -81,19 +81,19 @@ def test_input_validation_iterations(self, iterations):

with pytest.raises(ValueError, match='iterations must be a positive integer'):
_ = morpher._select_frames(
iterations=iterations, ramp_in=True, ramp_out=True, freeze_for=0
iterations=iterations, ease_in=True, ease_out=True, freeze_for=0
)

@pytest.mark.parametrize(
('ramp_in', 'ramp_out', 'expected_frames'),
('ease_in', 'ease_out', 'expected_frames'),
[
(True, True, [0, 1, 2, 5, 8, 12, 15, 18, 19]),
(True, False, [0, 0, 1, 3, 5, 7, 10, 13, 17]),
(False, True, [0, 3, 7, 10, 13, 15, 17, 19, 20]),
(False, False, [0, 2, 4, 7, 9, 11, 13, 16, 18]),
],
)
def test_frames(self, ramp_in, ramp_out, expected_frames):
def test_frames(self, ease_in, ease_out, expected_frames):
"""Confirm that frames produced by the _select_frames() method are correct."""
freeze_for = 2
iterations = 20
Expand All @@ -103,8 +103,8 @@ def test_frames(self, ramp_in, ramp_out, expected_frames):
)
frames = morpher._select_frames(
iterations=iterations,
ramp_in=ramp_in,
ramp_out=ramp_out,
ease_in=ease_in,
ease_out=ease_out,
freeze_for=freeze_for,
)

Expand Down Expand Up @@ -166,8 +166,8 @@ def test_no_writing(self, capsys):
start_shape=dataset,
target_shape=shape_factory.generate_shape(target_shape),
iterations=iterations,
ramp_in=False,
ramp_out=False,
ease_in=False,
ease_out=False,
freeze_for=0,
)

Expand Down Expand Up @@ -205,8 +205,8 @@ def test_saving_data(self, tmp_path):
start_shape=dataset,
target_shape=shape_factory.generate_shape(target_shape),
iterations=iterations,
ramp_in=False,
ramp_out=False,
ease_in=False,
ease_out=False,
freeze_for=0,
)

Expand Down