Source code for arrlp.modules.transform_parameters_LP.transform_parameters
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Date : 2026-04-25
# Author : Lancelot PINCET
# GitHub : https://github.com/LancelotPincet
# Library : arrLP
# Module : transform_parameters
"""
Defines the affine transformations in 2D for a 3x3 transformation matrix.
"""
# %% Libraries
import numpy as np
[docs]
def transform_parameters(matrix, shape=None, *, shears=None, angle=None, scales=None, stacks=False):
"""
Recover transform_matrix parameters from a 3x3 affine matrix.
One of shears, angle, or scales may be provided to make the inverse unique.
If none is provided, shears=(0, 0) is assumed.
Parameters
----------
matrix : np.ndarray
3x3 affine transformation matrix.
shape : int, tuple, np.ndarray, optional
Same convention as transform_matrix.
shears : tuple, optional
Known shears as (sheary, shearx).
angle : float, optional
Known rotation angle in degrees.
scales : tuple, optional
Known scales as (scaley, scalex).
Returns
-------
shiftx, shifty, shearx, sheary, angle, scalex, scaley
"""
matrix = np.asarray(matrix, dtype=float)
if matrix.shape != (3, 3):
raise ValueError("matrix must be a 3x3 array.")
# Manage shape argument
if shape is None:
shape = (0, 0)
elif isinstance(shape, (int, float)):
shape = (shape,) * 2
elif isinstance(shape, tuple):
pass
else:
shape = shape.shape[:2] if not stacks else shape.shape[1:3]
h, w = shape
center = np.asarray([h / 2, w / 2], dtype=float)
A = matrix[:2, :2]
t = matrix[:2, 2]
defined = sum(x is not None for x in (shears, angle, scales))
if defined == 0:
shears = (0.0, 0.0)
elif defined > 1:
raise ValueError("Only one of shears, angle, or scales may be defined.")
if shears is not None:
sheary, shearx = shears
H = np.asarray([
[1.0, sheary],
[shearx, 1.0],
])
B = np.linalg.solve(H, A)
scaley = np.linalg.norm(B[:, 0])
scalex = np.linalg.norm(B[:, 1])
if scaley == 0 or scalex == 0:
raise ValueError("Cannot recover angle with zero scale.")
R = B @ np.diag([1 / scaley, 1 / scalex])
theta = np.arctan2(R[1, 0], R[0, 0])
angle = -np.degrees(theta)
elif angle is not None:
theta = -np.radians(angle)
c, s = np.cos(theta), np.sin(theta)
r0 = A[1, 0] / A[0, 0]
r1 = A[0, 1] / A[1, 1]
M = np.asarray([
[c, -r0 * s],
[r1 * s, c],
])
b = np.asarray([
r0 * c - s,
r1 * c + s,
])
shearx, sheary = np.linalg.solve(M, b)
scaley = A[0, 0] / (c + sheary * s)
scalex = A[1, 1] / (-shearx * s + c)
elif scales is not None:
scaley, scalex = scales
if scaley == 0 or scalex == 0:
raise ValueError("Cannot recover angle/shear with zero scale.")
B = A @ np.diag([1 / scaley, 1 / scalex])
M = np.asarray([
[B[0, 0], -B[0, 1]],
[B[1, 1], B[1, 0]],
])
c, s = np.linalg.solve(M, np.ones(2))
norm = np.hypot(c, s)
c, s = c / norm, s / norm
theta = np.arctan2(s, c)
angle = -np.degrees(theta)
Rinv = np.asarray([
[c, s],
[-s, c],
])
H = B @ Rinv
sheary = H[0, 1]
shearx = H[1, 0]
L = A
shift = np.linalg.solve(
L,
t - center + L @ center,
)
shifty = shift[0]
shiftx = shift[1]
return shiftx, shifty, shearx, sheary, angle, scalex, scaley
# %% Test function run
if __name__ == "__main__":
from corelp import test
test(__file__)