Summary

Patch release: maintainability refactor of very large modules (no intended public API moves) plus targeted OpenCV-backed hot paths in functional helpers. No new transforms.

  • Module split (facades unchanged): oversized functional.py / transform modules were split into smaller internal files; public import paths like albumentations.augmentations.pixel.functional and albumentations.augmentations.geometric.functional remain re-export facades (#247).
  • UnsharpMask: final thresholded blend uses cv2.compare + cv2.copyTo for common channel layouts (C ∈ {1, 3, 4}); other layouts keep the prior np.where path (#248).
  • PixelDropout: uint8 / float32 image paths use cv2.copyTo with explicitly contiguous mask/value buffers where applicable (#248).
  • ToGray (method="desaturation", uint8): per-pixel min/max uses cv2.split / cv2.max / cv2.min on single (H, W, C) images; batch/volume uint8 inputs keep the prior axis-reduction implementation so semantics stay correct (#248).
  • Elastic-style displacement noise: generate_displacement_fields Gaussian normalization uses cv2.norm(..., NORM_INF) instead of allocating a full abs buffer (#248).
  • CopyAndPaste visibility helper: compute_instance_visibility precomputes binary instance masks once and uses cv2.countNonZero / cv2.bitwise_and (#248).
  • CropNonEmptyMaskIfExists: random non-empty pixel sampling uses cv2.findNonZero instead of np.argwhere on the aggregated mask (#248).
  • HEStain / stain math: rgb_to_optical_density uses in-place cv2.multiply / cv2.max / cv2.log on a contiguous (N*H*W, 3) float32 buffer (avoids silent cv2 dst layout failures on non-contiguous reshapes) (#248).j

Breaking changes

None.


New features

None.


Bug fixes

  • ToGray (method="desaturation", uint8): OpenCV cv2.split does not split the channel axis for ndim > 3 inputs; to_gray_desaturation now avoids that path for batch/volume tensors and matches the old np.max / np.min reduction. Regression coverage added in tests/functional/test_functional.py (#248).

Performance

Microbenchmarks during development (local timeit, contiguous outputs where relevant) showed meaningful wins on common shapes for:

  • UnsharpMask threshold blend (OpenCV path): ~1.3–1.6× on 256–1024 RGB uint8 / float32 for C ∈ {1, 3} (no intended speedup for C ∉ {1, 3, 4} where NumPy fallback remains).
  • ToGray desaturation (uint8, (H, W, C)): large speedups vs np.max/np.min reductions on RGB/RGBA-ish stacks (exact ratio depends on H×W×C); multispectral C=5 / C=9 still benefits on the 3D path, with no OpenCV split path for ndim > 3.
  • PixelDropout: ~2–20× on 512–1024 RGB uint8 vs np.where in microbench (mask density dependent).
  • generate_displacement_fields max-abs normalization: ~2.7–5.7× vs np.abs(...).max() on 256–1024 planes.
  • compute_instance_visibility: ~2.2–2.8× vs boolean reductions for 8–64 instance masks at 512×512.
  • to_distance_maps (internal helper): OpenCV subtract/magnitude loop wins for modest keypoint counts; falls back to vectorized NumPy for large N to avoid Python-loop overhead at high keypoint counts (#248).

Misc

  • Packaging: pyproject.toml / uv.lock version → **2.2.4**.
  • Pre-commit: cv2.resize / cv2.remap remain forbidden in-repo; geometric resize/remap continues to go through albucore where required by hooks.

Commits

CommitPRDescription
9b65e9e#247Split oversized transform/functional modules into smaller files; keep public facades + export surfaces
b2e287c#248OpenCV hot paths: UnsharpMask, PixelDropout, ToGray desaturation, HEStain optical density, mixing visibility, crops mask sampling, displacement norm, keypoint distance maps
159c456chore: set release version to 2.2.4 (pyproject.toml, uv.lock)