Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: rotate #89

Merged
merged 4 commits into from
Sep 13, 2023
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
1 change: 1 addition & 0 deletions giford/action/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .abstract_frame_action import AbstractFrameAction

from .rotate import Rotate, RotateMany
from .reshape import Reshape, ReshapeMethod
from .scroll import Scroll
from .shake import Shake
Expand Down
87 changes: 87 additions & 0 deletions giford/action/rotate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from typing import Optional

import numpy as np
from PIL import Image as PillowImage

from giford.frame.frame_batch import FrameBatch
from giford.frame.raw_data import RawDataFrame

from .abstract_frame_action import AbstractFrameAction


class Rotate(AbstractFrameAction):
def __init__(self) -> None:
super().__init__()

FULL_ROTATION_DEGREES: int = 360

def process(
self,
input_batch: FrameBatch,
rotate_degrees: int = 0,
is_clockwise: bool = True,
) -> FrameBatch:
"""
rotate images in batch

:param input_batch: input framebatch
:param rotate_degrees: degrees to rotate, defaults to 0
:param is_clockwise: rotate clockwise, defaults to True
:return: batch of rotated frames
"""

if rotate_degrees == 0:
return input_batch.clone()

if not is_clockwise:
rotate_degrees = Rotate.FULL_ROTATION_DEGREES - rotate_degrees

output_batch = FrameBatch()
for frame in input_batch.frames:
# will clone data
rdf = RawDataFrame(frame.get_data_arr())

# cheating using pimg, but tbh idc they did a good job
# use reference because already cloning data
pimg = PillowImage.fromarray(rdf.get_data_arr(is_return_reference=True))
pimg = pimg.rotate(rotate_degrees)
output_batch.add_frame(RawDataFrame(np.asarray(pimg)))

return output_batch


class RotateMany(AbstractFrameAction):
def __init__(self) -> None:
super().__init__()

def process(
self,
input_batch: FrameBatch,
rotate_count: int = 30,
is_clockwise: bool = True,
) -> FrameBatch:
"""
Completes a full rotation for each frame in the input batch

:param input_batch: input framebatch
:param rotate_count: number of frames to Rotate, defaults to None
:param is_clockwise: if true, rotate clockwise
:return: frame batch
"""

rotate_degrees = 360
step_size: float = rotate_degrees / rotate_count

r = Rotate()
output_batch = FrameBatch()
for frame in input_batch.frames:
for step_idx in range(rotate_count):
rotate_step: int = int(step_idx * step_size)

temp_in_batch = FrameBatch.create_from_frame(frame)
temp_out_batch = r.process(
temp_in_batch, rotate_degrees=rotate_step, is_clockwise=is_clockwise
)
output_batch.add_batch(temp_out_batch)

return output_batch
6 changes: 6 additions & 0 deletions giford/frame/frame_batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,9 @@ def create_from_image(cls, img: AbstractImage) -> "FrameBatch":
for rdf in img.raw_data_frames:
batch.add_frame(rdf)
return batch

@classmethod
def create_from_frame(cls, Frame: RawDataFrame) -> "FrameBatch":
batch = cls()
batch.add_frame(Frame)
return batch
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/baseline_data/test_rotate_False_220.png
Binary file added tests/baseline_data/test_rotate_False_30.png
Binary file added tests/baseline_data/test_rotate_True_220.png
Binary file added tests/baseline_data/test_rotate_True_30.png
61 changes: 61 additions & 0 deletions tests/test_rotate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import os
import pytest

from giford.action import Rotate, RotateMany
from giford.frame import FrameBatch

from tests.util import BASELINE_DIRECTORY, save_batch_and_compare


@pytest.mark.parametrize(
"is_clockwise, rotate_degrees",
[
(False, 30),
(False, 220),
(True, 30),
(True, 220),
],
)
def test_rotate(
temp_output_png,
orange_image_batch: FrameBatch,
is_clockwise: bool,
rotate_degrees: int,
):
baseline = os.path.join(
BASELINE_DIRECTORY, f"test_rotate_{is_clockwise}_{rotate_degrees}.png"
)

r = Rotate()
output_batch = r.process(orange_image_batch, rotate_degrees, is_clockwise)

assert save_batch_and_compare(baseline, output_batch, temp_output_png)


@pytest.mark.parametrize(
"is_clockwise, rotate_count",
[
(False, 4),
(False, 13),
(True, 4),
(True, 13),
],
)
def test_rotate_many(
temp_out_png_generator,
orange_image_batch: FrameBatch,
is_clockwise: bool,
rotate_count: int,
):


r = RotateMany()
output_batch = r.process(orange_image_batch, is_clockwise=is_clockwise, rotate_count=rotate_count)

for idx, frame in enumerate(output_batch.frames):
baseline = os.path.join(
BASELINE_DIRECTORY, f"rotate_many/test_rotate_many_{is_clockwise}_{rotate_count}_{idx}.png"
)
temp_output_png = next(temp_out_png_generator)
test_batch = FrameBatch.create_from_frame(frame)
assert save_batch_and_compare(baseline, test_batch, temp_output_png)
8 changes: 8 additions & 0 deletions tests/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ def save_batch_and_compare(
is_force_multi_image: bool = False,
target_format: SingleImageFormat = DEFAULT_TEST_SINGLE_IMAGE_FORMAT,
is_overwrite_existing: bool = False,
is_create_baseline: bool = False,
# overwriting a baseline should be rare, and this will prevent accidents
yes_overwrite_confirmation: bool = False,
) -> bool:
assert not batch.is_empty()

Expand All @@ -85,4 +88,9 @@ def save_batch_and_compare(

wrapper.save(test_filepath, overwrite_existing=is_overwrite_existing)

if is_create_baseline:
if not yes_overwrite_confirmation and os.path.exists(baseline_filepath):
raise FileExistsError("baseline exists dummyhead")
wrapper.save(baseline_filepath, overwrite_existing=yes_overwrite_confirmation)

return compare_image_files(baseline_filepath, test_filepath)