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
20 changes: 14 additions & 6 deletions aperturedb/Images.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,15 @@ def rotate(points, angle, c_x=0, c_y=0):
points: The rotated points as a NumPy array of shape (n,2) and type int
"""
ANGLE = np.deg2rad(angle)
return np.array(
return np.round(np.array(
[
[
c_x + np.cos(ANGLE) * (px - c_x) - np.sin(ANGLE) * (py - c_y),
c_y + np.sin(ANGLE) * (px - c_x) + np.cos(ANGLE) * (py - c_y)
]
for px, py in points
]
).astype(int)
)).astype(int)


def resolve(points: np.array, image_meta, operations) -> np.array:
Expand All @@ -100,12 +100,20 @@ def resolve(points: np.array, image_meta, operations) -> np.array:
image_meta_height = image_meta["adb_image_height"]
for operation in operations:
if operation["type"] == "resize":
x_ratio = operation["width"] / image_meta_width
y_ratio = operation["height"] / image_meta_height
if "scale" in operation:
x_ratio = operation["scale"]
y_ratio = operation["scale"]
else:
x_ratio = operation["width"] / image_meta_width
y_ratio = operation["height"] / image_meta_height
np.multiply(resolved, [x_ratio, y_ratio],
out=resolved, casting="unsafe")
image_meta_width = operation["width"]
image_meta_height = operation["height"]
if "scale" in operation:
image_meta_width *= x_ratio
image_meta_height *= y_ratio
else:
image_meta_width = operation["width"]
image_meta_height = operation["height"]
if operation["type"] == "rotate":
angle = operation["angle"]
resolved = rotate(
Expand Down
48 changes: 44 additions & 4 deletions aperturedb/Operations.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from __future__ import annotations
from typing import Optional


class Operations(object):
Expand All @@ -15,13 +16,25 @@ def __init__(self):
def get_operations_arr(self):
return self.operations_arr

def resize(self, width: int, height: int) -> Operations:
def resize(self, width: Optional[int] = None, height: Optional[int] = None, scale: Optional[float] = None) -> Operations:

if scale is not None:
if width is not None or height is not None:
raise ValueError(
"Provide either 'scale' or both 'width' and 'height', but not a mix.")
else:
if width is None or height is None:
raise ValueError(
"Provide either 'scale' or both 'width' and 'height'.")
Comment thread
ad-claw000 marked this conversation as resolved.

op = {
"type": "resize",
"width": width,
"height": height,
"type": "resize"
}
if width is not None:
op["width"] = width
op["height"] = height
if scale is not None:
op["scale"] = scale
Comment thread
ad-claw000 marked this conversation as resolved.

self.operations_arr.append(op)
return self
Expand Down Expand Up @@ -71,3 +84,30 @@ def interval(self, start: int, stop: int, step: int) -> Operations:

self.operations_arr.append(op)
return self

def threshold(self, value: int) -> Operations:

op = {
"type": "threshold",
"value": value,
}

self.operations_arr.append(op)
return self

def preview(self, max_frame_count: Optional[int] = None, max_time_fraction: Optional[float] = None, max_time_offset: Optional[str] = None, max_size_mb: Optional[float] = None) -> Operations:

op = {
"type": "preview"
}
if max_frame_count is not None:
op["max_frame_count"] = max_frame_count
if max_time_fraction is not None:
op["max_time_fraction"] = max_time_fraction
if max_time_offset is not None:
op["max_time_offset"] = max_time_offset
if max_size_mb is not None:
op["max_size_mb"] = max_size_mb

self.operations_arr.append(op)
return self
25 changes: 25 additions & 0 deletions test/test_Images.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ def test_resolve_resize():
assert resolved[1][1] == 10


def test_resolve_resize_scale():
points = np.array([[10, 10], [20, 20]], dtype=float)
meta = {"adb_image_width": 100, "adb_image_height": 100}
operations = [{"type": "resize", "scale": 0.5}]
resolved = resolve(points, meta, operations)
assert resolved[0][0] == 5
assert resolved[0][1] == 5
assert resolved[1][0] == 10
assert resolved[1][1] == 10


def test_resolve_rotate():
points = np.array([[10, 10]], dtype=float)
meta = {"adb_image_width": 100, "adb_image_height": 100}
Expand All @@ -32,6 +43,20 @@ def test_resolve_rotate():
assert resolved[0][0] == 90 and abs(resolved[0][1] - 10) <= 1


def test_resolve_ignored_operations():
points = np.array([[10, 10], [20, 20]], dtype=float)
meta = {"adb_image_width": 100, "adb_image_height": 100}
operations = [
{"type": "flip", "code": "horizontal"},
{"type": "crop", "x": 10, "y": 10, "width": 50, "height": 50},
{"type": "interval", "start": 0, "stop": 10, "step": 1},
{"type": "threshold", "value": 128},
{"type": "preview"}
]
resolved = resolve(points, meta, operations)
assert np.array_equal(resolved, points)


class MockClient:
def __init__(self):
self.responses = []
Expand Down
74 changes: 74 additions & 0 deletions test/test_Operations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import pytest
from aperturedb.Operations import Operations


class TestOperations:
def test_empty_operations(self):
op = Operations()
assert op.get_operations_arr() == []

def test_resize_width_height(self):
op = Operations().resize(width=100, height=200)
assert op.get_operations_arr() == [
{"type": "resize", "width": 100, "height": 200}]

def test_resize_scale(self):
op = Operations().resize(scale=0.5)
assert op.get_operations_arr() == [{"type": "resize", "scale": 0.5}]

def test_rotate(self):
op = Operations().rotate(angle=90)
assert op.get_operations_arr() == [
{"type": "rotate", "angle": 90, "resize": False}]

def test_rotate_resize(self):
op = Operations().rotate(angle=90, resize=True)
assert op.get_operations_arr() == [
{"type": "rotate", "angle": 90, "resize": True}]

def test_flip(self):
op = Operations().flip(code="horizontal")
assert op.get_operations_arr() == [
{"type": "flip", "code": "horizontal"}]

def test_crop(self):
op = Operations().crop(x=10, y=20, width=100, height=200)
assert op.get_operations_arr() == [
{"type": "crop", "x": 10, "y": 20, "width": 100, "height": 200}]

def test_interval(self):
op = Operations().interval(start=0, stop=100, step=2)
assert op.get_operations_arr() == [
{"type": "interval", "start": 0, "stop": 100, "step": 2}]

def test_threshold(self):
op = Operations().threshold(value=128)
assert op.get_operations_arr() == [{"type": "threshold", "value": 128}]

def test_preview(self):
op = Operations().preview(max_frame_count=10, max_time_fraction=0.5)
assert op.get_operations_arr() == [
{"type": "preview", "max_frame_count": 10, "max_time_fraction": 0.5}]

def test_preview_all_args(self):
op = Operations().preview(max_frame_count=10, max_time_fraction=0.5,
max_time_offset="00:00:10", max_size_mb=10.5)
assert op.get_operations_arr() == [
{"type": "preview", "max_frame_count": 10, "max_time_fraction": 0.5, "max_time_offset": "00:00:10", "max_size_mb": 10.5}]

def test_chained_operations(self):
op = Operations().resize(width=100, height=100).rotate(
angle=90).crop(x=0, y=0, width=50, height=50)
assert op.get_operations_arr() == [
{"type": "resize", "width": 100, "height": 100},
{"type": "rotate", "angle": 90, "resize": False},
{"type": "crop", "x": 0, "y": 0, "width": 50, "height": 50}
]

def test_resize_invalid_args(self):
with pytest.raises(ValueError, match="Provide either 'scale' or both 'width' and 'height'"):
Operations().resize()
with pytest.raises(ValueError, match="Provide either 'scale' or both 'width' and 'height'"):
Operations().resize(width=100)
with pytest.raises(ValueError, match="Provide either 'scale' or both 'width' and 'height', but not a mix"):
Operations().resize(width=100, height=100, scale=0.5)
Loading