Skip to content

Commit

Permalink
Fix contigous (#1862)
Browse files Browse the repository at this point in the history
* Make both input and output contigeous

* Cleanup

* Updated data

* Fix with typing
  • Loading branch information
ternaus authored Jul 31, 2024
1 parent 512dc63 commit bcaf7d6
Show file tree
Hide file tree
Showing 11 changed files with 196 additions and 140 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ repos:
hooks:
- id: markdownlint
- repo: https://github.com/tox-dev/pyproject-fmt
rev: "2.1.4"
rev: "2.2.0"
hooks:
- id: pyproject-fmt
- repo: https://github.com/pre-commit/mirrors-mypy
Expand Down
3 changes: 0 additions & 3 deletions albumentations/augmentations/functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
MAX_VALUES_BY_DTYPE,
clip,
clipped,
contiguous,
is_grayscale_image,
is_rgb_image,
maybe_process_in_chunks,
Expand Down Expand Up @@ -651,7 +650,6 @@ def add_sun_flare(
return to_float(output, max_value=255) if needs_float else output


@contiguous
@preserve_channel_dim
def add_shadow(img: np.ndarray, vertices_list: list[np.ndarray]) -> np.ndarray:
"""Add shadows to the image by reducing the intensity of the RGB values in specified regions.
Expand Down Expand Up @@ -692,7 +690,6 @@ def add_shadow(img: np.ndarray, vertices_list: list[np.ndarray]) -> np.ndarray:
return img_shadowed


@contiguous
@preserve_channel_dim
def add_gravel(img: np.ndarray, gravels: list[Any]) -> np.ndarray:
"""Add gravel to the image.
Expand Down
9 changes: 4 additions & 5 deletions albumentations/augmentations/geometric/functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import cv2
import numpy as np
import skimage.transform
from albucore.utils import clipped, maybe_process_in_chunks, preserve_channel_dim, contiguous, get_num_channels
from albucore.utils import clipped, maybe_process_in_chunks, preserve_channel_dim, get_num_channels

from albumentations import random_utils
from albumentations.augmentations.functional import center
Expand Down Expand Up @@ -841,11 +841,11 @@ def bbox_piecewise_affine(


def vflip(img: np.ndarray) -> np.ndarray:
return np.ascontiguousarray(img[::-1, ...])
return img[::-1, ...]


def hflip(img: np.ndarray) -> np.ndarray:
return np.ascontiguousarray(img[:, ::-1, ...])
return img[:, ::-1, ...]


def hflip_cv2(img: np.ndarray) -> np.ndarray:
Expand Down Expand Up @@ -896,7 +896,7 @@ def d4(img: np.ndarray, group_member: D4Type) -> np.ndarray:

# Execute the appropriate transformation
if group_member in transformations:
return np.ascontiguousarray(transformations[group_member](img))
return transformations[group_member](img)

raise ValueError(f"Invalid group member: {group_member}")

Expand Down Expand Up @@ -924,7 +924,6 @@ def transpose(img: np.ndarray) -> np.ndarray:
return img.transpose(new_axes)


@contiguous
def rot90(img: np.ndarray, factor: int) -> np.ndarray:
return np.rot90(img, factor)

Expand Down
2 changes: 1 addition & 1 deletion albumentations/augmentations/geometric/rotate.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class RandomRotate90(DualTransform):

_targets = (Targets.IMAGE, Targets.MASK, Targets.BBOXES, Targets.KEYPOINTS)

def apply(self, img: np.ndarray, factor: float, **params: Any) -> np.ndarray:
def apply(self, img: np.ndarray, factor: int, **params: Any) -> np.ndarray:
return fgeometric.rot90(img, factor)

def get_params(self) -> dict[str, int]:
Expand Down
6 changes: 3 additions & 3 deletions albumentations/augmentations/transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -1996,7 +1996,7 @@ def __init__(self, p: float = 1.0, always_apply: bool | None = None):
def apply(self, img: np.ndarray, **params: Any) -> np.ndarray:
if is_rgb_image(img):
warnings.warn("The image is already an RGB.", stacklevel=2)
return img
return np.ascontiguousarray(img)
if not is_grayscale_image(img):
msg = "ToRGB transformation expects 2-dim images or 3-dim with the last dimension equal to 1."
raise TypeError(msg)
Expand Down Expand Up @@ -2733,8 +2733,8 @@ def __init__(
self.max_size = max_size
self.interpolation = interpolation

def get_transform_init_args_names(self) -> tuple[str, str, str, str]:
return ("p_replace", "n_segments", "max_size", "interpolation")
def get_transform_init_args_names(self) -> tuple[str, ...]:
return "p_replace", "n_segments", "max_size", "interpolation"

def get_params(self) -> dict[str, Any]:
n_segments = random.randint(self.n_segments[0], self.n_segments[1])
Expand Down
12 changes: 0 additions & 12 deletions albumentations/core/composition.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,6 @@ def preprocess(self, data: Any) -> None:

def postprocess(self, data: dict[str, Any]) -> dict[str, Any]:
if self.main_compose:
data = Compose._make_targets_contiguous(data) # ensure output targets are contiguous
for p in self.processors.values():
p.postprocess(data)
return data
Expand Down Expand Up @@ -402,17 +401,6 @@ def _check_args(self, **kwargs: Any) -> None:
)
raise ValueError(msg)

@staticmethod
def _make_targets_contiguous(data: Any) -> dict[str, Any]:
result = {}
for key, value in data.items():
if isinstance(value, np.ndarray):
result[key] = np.ascontiguousarray(value)
else:
result[key] = value

return result


class OneOf(BaseCompose):
"""Select one of transforms to apply. Selected transform will be called with `force_apply=True`.
Expand Down
10 changes: 8 additions & 2 deletions albumentations/core/transforms_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import cv2
from pydantic import BaseModel, ConfigDict, Field


from albumentations.core.validation import ValidatedTransformMeta

from .serialization import Serializable, SerializableMeta, get_shortest_class_fullname
Expand Down Expand Up @@ -132,7 +131,14 @@ def apply_with_params(self, params: dict[str, Any], *args: Any, **kwargs: Any) -
for key, arg in kwargs.items():
if key in self._key2func and arg is not None:
target_function = self._key2func[key]
res[key] = target_function(arg, **params)
if isinstance(arg, np.ndarray):
result = target_function(np.require(arg, requirements=["C_CONTIGUOUS"]), **params)
if isinstance(result, np.ndarray):
res[key] = np.require(result, requirements=["C_CONTIGUOUS"])
else:
res[key] = result
else:
res[key] = target_function(arg, **params)
else:
res[key] = arg
return res
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"numpy>=1.24.4", "scipy>=1.10.0", "scikit-image>=0.21.0",
"PyYAML", "typing-extensions>=4.9.0",
"pydantic>=2.7.0",
"albucore>=0.0.11",
"albucore>=0.0.13",
"eval-type-backport"
]

Expand Down
89 changes: 0 additions & 89 deletions tests/test_augmentations.py
Original file line number Diff line number Diff line change
Expand Up @@ -1092,92 +1092,3 @@ def test_pixel_domain_adaptation(kind: str) -> None:
atol=2 if img.dtype == np.uint8 else 0.01,
err_msg=f"{adapted.mean()} {img.mean()} {ref_img.mean()}",
)


@pytest.mark.parametrize(
["augmentation_cls", "params"],
get_transforms(
custom_arguments={
# only image
A.HistogramMatching: {
"reference_images": [SQUARE_UINT8_IMAGE],
"read_fn": lambda x: x,
},
A.FDA: {
"reference_images": [SQUARE_FLOAT_IMAGE],
"read_fn": lambda x: x,
},
A.PixelDistributionAdaptation: {
"reference_images": [SQUARE_FLOAT_IMAGE],
"read_fn": lambda x: x,
"transform_type": "standard",
},
A.MedianBlur: {"blur_limit": (3, 5)},
A.TemplateTransform: {
"templates": SQUARE_UINT8_IMAGE,
},
A.RingingOvershoot: {"blur_limit": (3, 5)},
# dual
A.Crop: {"y_min": 0, "y_max": 10, "x_min": 0, "x_max": 10},
A.CenterCrop: {"height": 10, "width": 10},
A.CropNonEmptyMaskIfExists: {"height": 10, "width": 10},
A.RandomCrop: {"height": 10, "width": 10},
A.RandomResizedCrop: {"height": 10, "width": 10},
A.RandomSizedCrop: {"min_max_height": (4, 8), "height": 10, "width": 10},
A.CropAndPad: {"px": 10},
A.Resize: {"height": 10, "width": 10},
A.RandomSizedBBoxSafeCrop: {"height": 10, "width": 10},
A.BBoxSafeRandomCrop: {"erosion_rate": 0.5},
A.XYMasking: {
"num_masks_x": (1, 3),
"num_masks_y": (1, 3),
"mask_x_length": 10,
"mask_y_length": 10,
"mask_fill_value": 1,
"fill_value": 0,
},
A.MixUp: {
"reference_data": [{"image": SQUARE_UINT8_IMAGE,
"mask": np.random.randint(0, 1, [100, 100, 3], dtype=np.uint8),
}],
"read_fn": lambda x: x,
},
A.TextImage: dict(font_path="./tests/files/LiberationSerif-Bold.ttf")
},
),
)
def test_non_contiguous_input(augmentation_cls, params, bboxes):
image = np.empty([3, 100, 100], dtype=np.uint8).transpose(1, 2, 0)
mask = np.empty([3, 100, 100], dtype=np.uint8).transpose(1, 2, 0)

# check preconditions
assert not image.flags["C_CONTIGUOUS"]
assert not mask.flags["C_CONTIGUOUS"]

if augmentation_cls == A.RandomCropNearBBox:
# requires "cropping_bbox" arg
aug = augmentation_cls(p=1, **params)
aug(image=image, mask=mask, cropping_bbox=bboxes[0])
elif augmentation_cls in [A.RandomSizedBBoxSafeCrop, A.BBoxSafeRandomCrop]:
# requires "bboxes" arg
aug = A.Compose([augmentation_cls(p=1, **params)], bbox_params=A.BboxParams(format="pascal_voc"))
aug(image=image, mask=mask, bboxes=bboxes)
elif augmentation_cls == A.TextImage:
aug = A.Compose([augmentation_cls(p=1, **params)], bbox_params=A.BboxParams(format="pascal_voc"))
aug(image=image, mask=mask, bboxes=bboxes, textimage_metadata=[])
elif augmentation_cls == A.OverlayElements:
# requires "metadata" arg
aug = A.Compose([augmentation_cls(p=1, **params)])
aug(image=image, overlay_metadata=[], mask=mask)
else:
# standard args: image and mask
if augmentation_cls == A.FromFloat:
# requires float image
image = (image / 255).astype(np.float32)
assert not image.flags["C_CONTIGUOUS"]
elif augmentation_cls == A.MaskDropout:
# requires single channel mask
mask = mask[:, :, 0]

aug = augmentation_cls(p=1, **params)
aug(image=image, mask=mask)
Loading

0 comments on commit bcaf7d6

Please sign in to comment.