diff --git a/masque/library.py b/masque/library.py index efc3aad..8dc63a4 100644 --- a/masque/library.py +++ b/masque/library.py @@ -466,9 +466,11 @@ class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta): memo = {} if transform is None or transform is True: - transform = numpy.zeros(4) + transform = numpy.array([0, 0, 0, 0, 1], dtype=float) elif transform is not False: transform = numpy.asarray(transform, dtype=float) + if transform.size == 4: + transform = numpy.append(transform, 1.0) original_pattern = pattern diff --git a/masque/ref.py b/masque/ref.py index 8a72167..6423394 100644 --- a/masque/ref.py +++ b/masque/ref.py @@ -191,10 +191,11 @@ class Ref( xys = self.offset[None, :] if self.repetition is not None: xys = xys + self.repetition.displacements - transforms = numpy.empty((xys.shape[0], 4)) + transforms = numpy.empty((xys.shape[0], 5)) transforms[:, :2] = xys transforms[:, 2] = self.rotation transforms[:, 3] = self.mirrored + transforms[:, 4] = self.scale return transforms def get_bounds_single( diff --git a/masque/utils/transform.py b/masque/utils/transform.py index 62e0f1e..ed0453b 100644 --- a/masque/utils/transform.py +++ b/masque/utils/transform.py @@ -87,33 +87,41 @@ def apply_transforms( Apply a set of transforms (`outer`) to a second set (`inner`). This is used to find the "absolute" transform for nested `Ref`s. - The two transforms should be of shape Ox4 and Ix4. - Rows should be of the form `(x_offset, y_offset, rotation_ccw_rad, mirror_across_x)`. - The output will be of the form (O*I)x4 (if `tensor=False`) or OxIx4 (`tensor=True`). + The two transforms should be of shape Ox5 and Ix5. + Rows should be of the form `(x_offset, y_offset, rotation_ccw_rad, mirror_across_x, scale)`. + The output will be of the form (O*I)x5 (if `tensor=False`) or OxIx5 (`tensor=True`). Args: - outer: Transforms for the container refs. Shape Ox4. - inner: Transforms for the contained refs. Shape Ix4. - tensor: If `True`, an OxIx4 array is returned, with `result[oo, ii, :]` corresponding + outer: Transforms for the container refs. Shape Ox5. + inner: Transforms for the contained refs. Shape Ix5. + tensor: If `True`, an OxIx5 array is returned, with `result[oo, ii, :]` corresponding to the `oo`th `outer` transform applied to the `ii`th inner transform. - If `False` (default), this is concatenated into `(O*I)x4` to allow simple + If `False` (default), this is concatenated into `(O*I)x5` to allow simple chaining into additional `apply_transforms()` calls. Returns: - OxIx4 or (O*I)x4 array. Final dimension is - `(total_x, total_y, total_rotation_ccw_rad, net_mirrored_x)`. + OxIx5 or (O*I)x5 array. Final dimension is + `(total_x, total_y, total_rotation_ccw_rad, net_mirrored_x, total_scale)`. """ outer = numpy.atleast_2d(outer).astype(float, copy=False) inner = numpy.atleast_2d(inner).astype(float, copy=False) + if outer.shape[1] == 4: + outer = numpy.pad(outer, ((0, 0), (0, 1)), constant_values=1.0) + if inner.shape[1] == 4: + inner = numpy.pad(inner, ((0, 0), (0, 1)), constant_values=1.0) + # If mirrored, flip y's xy_mir = numpy.tile(inner[:, :2], (outer.shape[0], 1, 1)) # dims are outer, inner, xyrm xy_mir[outer[:, 3].astype(bool), :, 1] *= -1 + # Apply outer scale to inner offset + xy_mir *= outer[:, None, 4, None] + rot_mats = [rotation_matrix_2d(angle) for angle in outer[:, 2]] xy = numpy.einsum('ort,oit->oir', rot_mats, xy_mir) - tot = numpy.empty((outer.shape[0], inner.shape[0], 4)) + tot = numpy.empty((outer.shape[0], inner.shape[0], 5)) tot[:, :, :2] = outer[:, None, :2] + xy # If mirrored, flip inner rotation @@ -122,6 +130,7 @@ def apply_transforms( tot[:, :, 2] = rotations % (2 * pi) tot[:, :, 3] = (outer[:, None, 3] + inner[None, :, 3]) % 2 # net mirrored + tot[:, :, 4] = outer[:, None, 4] * inner[None, :, 4] # net scale if tensor: return tot