Coverage for install/scipp/spatial/__init__.py: 50%
60 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-04-28 01:28 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-04-28 01:28 +0000
1# SPDX-License-Identifier: BSD-3-Clause
2# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
3"""Transformations of vectors.
5Functions in this module can be used to construct, deconstruct, and modify
6variables with transformations on vectors.
8Despite the name, transformations in this module can be applied to 3-vectors
9in any vector space and coordinate system, not just the physical space.
10The user has to ensure that transformations are applied to the correct vectors.
12See Also
13--------
14scipp.vector:
15 Construct a scalar variable holding a 3-vector.
16scipp.vectors:
17 Construct an array variable holding 3-vectors.
18"""
20from typing import Sequence, Union
22import numpy as _np
23import numpy as np
25from .. import units
26from .._scipp import core as _core_cpp
27from ..core import DType, Unit, UnitError, Variable, vectors
28from ..core._cpp_wrapper_util import call_func as _call_cpp_func
31def _to_eigen_layout(a):
32 # Numpy and scipp use row-major, but Eigen matrices use column-major,
33 # transpose matrix axes for copying values.
34 return _np.moveaxis(a, -1, -2)
37def as_vectors(x: Variable, y: Variable, z: Variable) -> Variable:
38 """Return inputs combined into vectors.
40 Inputs may be broadcast to a common shape,.
42 Parameters
43 ----------
44 x:
45 Variable containing x components.
46 y:
47 Variable containing y components.
48 z:
49 Variable containing z components.
51 Returns
52 -------
53 :
54 Zip of input x, y and z with dtype ``vector3``.
55 The output unit is the same as input unit.
57 Raises
58 ------
59 scipp.DTypeError
60 If the dtypes of inputs are not ``float64``.
62 See also
63 --------
64 scipp.vector:
65 Construct a vector from plain numbers.
66 scipp.vectors:
67 Construct vectors from plain numpy arrays or lists.
69 .. versionadded:: 23.03.1
70 """
71 return _call_cpp_func(
72 _core_cpp.geometry.as_vectors,
73 *(c.to(dtype='float64', copy=False) for c in (x, y, z)),
74 )
77def translation(
78 *,
79 unit: Union[Unit, str] = units.dimensionless,
80 value: Union[_np.ndarray, list],
81):
82 """
83 Creates a translation transformation from a single provided 3-vector.
85 Parameters
86 ----------
87 unit:
88 The unit of the translation
89 value:
90 A list or NumPy array of 3 items
92 Returns
93 -------
94 :
95 A scalar variable of dtype ``translation3``.
96 """
97 return translations(dims=(), unit=unit, values=value)
100def translations(
101 *,
102 dims: Sequence[str],
103 unit: Union[Unit, str] = units.dimensionless,
104 values: Union[_np.ndarray, list],
105):
106 """
107 Creates translation transformations from multiple 3-vectors.
109 Parameters
110 ----------
111 dims:
112 The dimensions of the created variable
113 unit:
114 The unit of the translation
115 values:
116 A list or NumPy array of 3-vectors
118 Returns
119 -------
120 :
121 An array variable of dtype ``translation3``.
122 """
123 return _core_cpp.Variable(
124 dims=dims, unit=unit, values=values, dtype=DType.translation3
125 )
128def scaling_from_vector(*, value: Union[_np.ndarray, list]):
129 """
130 Creates a scaling transformation from a provided 3-vector.
132 Parameters
133 ----------
134 value:
135 A list or NumPy array of 3 values, corresponding to scaling
136 coefficients in the x, y and z directions respectively.
138 Returns
139 -------
140 :
141 A scalar variable of dtype ``linear_transform3``.
142 """
143 return linear_transforms(dims=[], values=_np.diag(value))
146def scalings_from_vectors(*, dims: Sequence[str], values: Union[_np.ndarray, list]):
147 """
148 Creates scaling transformations from corresponding to the provided 3-vectors.
150 Parameters
151 ----------
152 dims:
153 The dimensions of the variable.
154 values:
155 A list or NumPy array of 3-vectors, each corresponding to scaling
156 coefficients in the x, y and z directions respectively.
158 Returns
159 -------
160 :
161 An array variable of dtype ``linear_transform3``.
162 """
163 identity = linear_transform(value=np.identity(3))
164 matrices = identity.broadcast(dims=dims, shape=(len(values),)).copy()
165 for field_name, index in (("xx", 0), ("yy", 1), ("zz", 2)):
166 matrices.fields[field_name] = Variable(
167 dims=dims, values=np.asarray(values)[:, index], dtype="float64"
168 )
169 return matrices
172def rotation(*, value: Union[_np.ndarray, list]):
173 """
174 Creates a rotation-type variable from the provided quaternion coefficients.
176 The quaternion coefficients are provided in scalar-last order (x, y, z, w), where
177 x, y, z and w form the quaternion
179 .. math::
181 q = w + xi + yj + zk.
183 Attention
184 ---------
185 The quaternion must be normalized in order to represent a rotation.
186 You can use, e.g.
188 >>> q = np.array([1, 2, 3, 4])
189 >>> rot = sc.spatial.rotation(value=q / np.linalg.norm(q))
191 Parameters
192 ----------
193 value:
194 A NumPy array or list with length 4, corresponding to the quaternion
195 coefficients (x*i, y*j, z*k, w)
197 Returns
198 -------
199 :
200 A scalar variable of dtype ``rotation3``.
201 """
202 return rotations(dims=(), values=value)
205def rotations(*, dims: Sequence[str], values: Union[_np.ndarray, list]):
206 """
207 Creates a rotation-type variable from the provided quaternion coefficients.
209 The quaternion coefficients are provided in scalar-last order (x, y, z, w), where
210 x, y, z and w form the quaternion
212 .. math::
214 q = w + xi + yj + zk.
216 Attention
217 ---------
218 The quaternions must be normalized in order to represent a rotation.
219 You can use, e.g.
221 >>> q = np.array([[1, 2, 3, 4], [-1, -2, -3, -4]])
222 >>> rot = sc.spatial.rotations(
223 ... dims=['x'],
224 ... values=q / np.linalg.norm(q, axis=1)[:, np.newaxis])
226 Parameters
227 ----------
228 dims:
229 The dimensions of the variable.
230 values:
231 A NumPy array of NumPy arrays corresponding to the quaternion
232 coefficients (w, x*i, y*j, z*k)
234 Returns
235 -------
236 :
237 An array variable of dtype ``rotation3``.
238 """
239 values = np.asarray(values)
240 if values.shape[-1] != 4:
241 raise ValueError(
242 "Inputs must be Quaternions to create a rotation, i.e., have "
243 "4 components. If you want to pass a rotation matrix, use "
244 "sc.linear_transforms instead."
245 )
246 return _core_cpp.Variable(dims=dims, values=values, dtype=DType.rotation3)
249def rotations_from_rotvecs(rotation_vectors: Variable) -> Variable:
250 """
251 Creates rotation transformations from rotation vectors.
253 This requires ``scipy`` to be installed, as is wraps
254 :meth:`scipy.spatial.transform.Rotation.from_rotvec`.
256 A rotation vector is a 3 dimensional vector which is co-directional to the axis of
257 rotation and whose norm gives the angle of rotation.
259 Parameters
260 ----------
261 rotation_vectors:
262 A Variable with vector dtype
264 Returns
265 -------
266 :
267 An array variable of dtype ``rotation3``.
268 """
269 from scipy.spatial.transform import Rotation as R
271 supported = [units.deg, units.rad]
272 unit = rotation_vectors.unit
273 if unit not in supported:
274 raise UnitError(f"Rotation vector unit must be one of {supported}.")
275 r = R.from_rotvec(rotation_vectors.values, degrees=unit == units.deg)
276 return rotations(dims=rotation_vectors.dims, values=r.as_quat())
279def rotation_as_rotvec(rotation: Variable, *, unit='rad') -> Variable:
280 """
281 Represent a rotation matrix (or matrices) as rotation vector(s).
283 This requires ``scipy`` to be installed, as is wraps
284 :meth:`scipy.spatial.transform.Rotation.as_rotvec`.
286 A rotation vector is a 3 dimensional vector which is co-directional to the axis of
287 rotation and whose norm gives the angle of rotation.
289 Parameters
290 ----------
291 rotation:
292 A variable with rotation matrices.
293 unit:
294 Angle unit for the rotation vectors.
296 Returns
297 -------
298 :
299 An array variable with rotation vectors of dtype ``vector3``.
300 """
301 unit = Unit(unit)
302 supported = [units.deg, units.rad]
303 if unit not in supported:
304 raise UnitError(f"Rotation vector unit must be one of {supported}.")
305 from scipy.spatial.transform import Rotation as R
307 r = R.from_matrix(rotation.values)
308 if rotation.unit not in [units.one, units.dimensionless]:
309 raise UnitError(f"Rotation matrix must be dimensionless, got {rotation.unit}.")
310 return vectors(
311 dims=rotation.dims, values=r.as_rotvec(degrees=unit == units.deg), unit=unit
312 )
315def affine_transform(
316 *,
317 unit: Union[Unit, str] = units.dimensionless,
318 value: Union[_np.ndarray, list],
319):
320 """
321 Initializes a single affine transformation from the provided affine matrix
322 coefficients.
324 Parameters
325 ----------
326 unit:
327 The unit of the affine transformation's translation component.
328 value:
329 A 4x4 matrix of affine coefficients.
331 Returns
332 -------
333 :
334 A scalar variable of dtype ``affine_transform3``.
335 """
336 return affine_transforms(dims=[], unit=unit, values=value)
339def affine_transforms(
340 *,
341 dims: Sequence[str],
342 unit: Union[Unit, str] = units.dimensionless,
343 values: Union[_np.ndarray, list],
344):
345 """
346 Initializes affine transformations from the provided affine matrix
347 coefficients.
349 Parameters
350 ----------
351 dims:
352 The dimensions of the variable.
353 unit:
354 The unit of the affine transformation's translation component.
355 values:
356 An array of 4x4 matrices of affine coefficients.
358 Returns
359 -------
360 :
361 An array variable of dtype ``affine_transform3``.
362 """
363 return _core_cpp.Variable(
364 dims=dims,
365 unit=unit,
366 values=_to_eigen_layout(values),
367 dtype=DType.affine_transform3,
368 )
371def linear_transform(
372 *,
373 unit: Union[Unit, str] = units.dimensionless,
374 value: Union[_np.ndarray, list],
375):
376 """Constructs a zero dimensional :class:`Variable` holding a single 3x3
377 matrix.
379 Parameters
380 ----------
381 value:
382 Initial value, a list of lists or 2-D NumPy array.
383 unit:
384 Optional, unit. Default=dimensionless
386 Returns
387 -------
388 :
389 A scalar variable of dtype ``linear_transform3``.
390 """
391 return linear_transforms(
392 dims=(),
393 unit=unit,
394 values=value,
395 )
398def linear_transforms(
399 *,
400 dims: Sequence[str],
401 unit: Union[Unit, str] = units.dimensionless,
402 values: Union[_np.ndarray, list],
403):
404 """Constructs a :class:`Variable` with given dimensions holding an array
405 of 3x3 matrices.
407 Parameters
408 ----------
409 dims:
410 Dimension labels.
411 values:
412 Initial values.
413 unit:
414 Optional, data unit. Default=dimensionless
416 Returns
417 -------
418 :
419 An array variable of dtype ``linear_transform3``.
420 """
421 return _core_cpp.Variable(
422 dims=dims,
423 unit=unit,
424 values=_to_eigen_layout(values),
425 dtype=DType.linear_transform3,
426 )
429def inv(var: Variable) -> Variable:
430 """Return the inverse of a spatial transformation.
432 Parameters
433 ----------
434 var:
435 Input variable.
436 Its ``dtype`` must be one of
438 - :attr:`scipp.DType.linear_transform3`
439 - :attr:`scipp.DType.affine_transform3`
440 - :attr:`scipp.DType.rotation3`
441 - :attr:`scipp.DType.translation3`
443 Returns
444 -------
445 :
446 A variable holding the inverse transformation to ``var``.
447 """
448 return _call_cpp_func(_core_cpp.inv, var)
451__all__ = [
452 'rotation',
453 'rotations',
454 'rotations_from_rotvecs',
455 'rotation_as_rotvec',
456 'scaling_from_vector',
457 'scalings_from_vectors',
458 'translation',
459 'translations',
460 'affine_transform',
461 'affine_transforms',
462 'linear_transform',
463 'linear_transforms',
464 'inv',
465]