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

1# SPDX-License-Identifier: BSD-3-Clause 

2# Copyright (c) 2023 Scipp contributors (https://github.com/scipp) 

3"""Transformations of vectors. 

4 

5Functions in this module can be used to construct, deconstruct, and modify 

6variables with transformations on vectors. 

7 

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. 

11 

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""" 

19 

20from typing import Sequence, Union 

21 

22import numpy as _np 

23import numpy as np 

24 

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 

29 

30 

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) 

35 

36 

37def as_vectors(x: Variable, y: Variable, z: Variable) -> Variable: 

38 """Return inputs combined into vectors. 

39 

40 Inputs may be broadcast to a common shape,. 

41 

42 Parameters 

43 ---------- 

44 x: 

45 Variable containing x components. 

46 y: 

47 Variable containing y components. 

48 z: 

49 Variable containing z components. 

50 

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. 

56 

57 Raises 

58 ------ 

59 scipp.DTypeError 

60 If the dtypes of inputs are not ``float64``. 

61 

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. 

68 

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 ) 

75 

76 

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. 

84 

85 Parameters 

86 ---------- 

87 unit: 

88 The unit of the translation 

89 value: 

90 A list or NumPy array of 3 items 

91 

92 Returns 

93 ------- 

94 : 

95 A scalar variable of dtype ``translation3``. 

96 """ 

97 return translations(dims=(), unit=unit, values=value) 

98 

99 

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. 

108 

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 

117 

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 ) 

126 

127 

128def scaling_from_vector(*, value: Union[_np.ndarray, list]): 

129 """ 

130 Creates a scaling transformation from a provided 3-vector. 

131 

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. 

137 

138 Returns 

139 ------- 

140 : 

141 A scalar variable of dtype ``linear_transform3``. 

142 """ 

143 return linear_transforms(dims=[], values=_np.diag(value)) 

144 

145 

146def scalings_from_vectors(*, dims: Sequence[str], values: Union[_np.ndarray, list]): 

147 """ 

148 Creates scaling transformations from corresponding to the provided 3-vectors. 

149 

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. 

157 

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 

170 

171 

172def rotation(*, value: Union[_np.ndarray, list]): 

173 """ 

174 Creates a rotation-type variable from the provided quaternion coefficients. 

175 

176 The quaternion coefficients are provided in scalar-last order (x, y, z, w), where 

177 x, y, z and w form the quaternion 

178 

179 .. math:: 

180 

181 q = w + xi + yj + zk. 

182 

183 Attention 

184 --------- 

185 The quaternion must be normalized in order to represent a rotation. 

186 You can use, e.g. 

187 

188 >>> q = np.array([1, 2, 3, 4]) 

189 >>> rot = sc.spatial.rotation(value=q / np.linalg.norm(q)) 

190 

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) 

196 

197 Returns 

198 ------- 

199 : 

200 A scalar variable of dtype ``rotation3``. 

201 """ 

202 return rotations(dims=(), values=value) 

203 

204 

205def rotations(*, dims: Sequence[str], values: Union[_np.ndarray, list]): 

206 """ 

207 Creates a rotation-type variable from the provided quaternion coefficients. 

208 

209 The quaternion coefficients are provided in scalar-last order (x, y, z, w), where 

210 x, y, z and w form the quaternion 

211 

212 .. math:: 

213 

214 q = w + xi + yj + zk. 

215 

216 Attention 

217 --------- 

218 The quaternions must be normalized in order to represent a rotation. 

219 You can use, e.g. 

220 

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]) 

225 

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) 

233 

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) 

247 

248 

249def rotations_from_rotvecs(rotation_vectors: Variable) -> Variable: 

250 """ 

251 Creates rotation transformations from rotation vectors. 

252 

253 This requires ``scipy`` to be installed, as is wraps 

254 :meth:`scipy.spatial.transform.Rotation.from_rotvec`. 

255 

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. 

258 

259 Parameters 

260 ---------- 

261 rotation_vectors: 

262 A Variable with vector dtype 

263 

264 Returns 

265 ------- 

266 : 

267 An array variable of dtype ``rotation3``. 

268 """ 

269 from scipy.spatial.transform import Rotation as R 

270 

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()) 

277 

278 

279def rotation_as_rotvec(rotation: Variable, *, unit='rad') -> Variable: 

280 """ 

281 Represent a rotation matrix (or matrices) as rotation vector(s). 

282 

283 This requires ``scipy`` to be installed, as is wraps 

284 :meth:`scipy.spatial.transform.Rotation.as_rotvec`. 

285 

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. 

288 

289 Parameters 

290 ---------- 

291 rotation: 

292 A variable with rotation matrices. 

293 unit: 

294 Angle unit for the rotation vectors. 

295 

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 

306 

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 ) 

313 

314 

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. 

323 

324 Parameters 

325 ---------- 

326 unit: 

327 The unit of the affine transformation's translation component. 

328 value: 

329 A 4x4 matrix of affine coefficients. 

330 

331 Returns 

332 ------- 

333 : 

334 A scalar variable of dtype ``affine_transform3``. 

335 """ 

336 return affine_transforms(dims=[], unit=unit, values=value) 

337 

338 

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. 

348 

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. 

357 

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 ) 

369 

370 

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. 

378 

379 Parameters 

380 ---------- 

381 value: 

382 Initial value, a list of lists or 2-D NumPy array. 

383 unit: 

384 Optional, unit. Default=dimensionless 

385 

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 ) 

396 

397 

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. 

406 

407 Parameters 

408 ---------- 

409 dims: 

410 Dimension labels. 

411 values: 

412 Initial values. 

413 unit: 

414 Optional, data unit. Default=dimensionless 

415 

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 ) 

427 

428 

429def inv(var: Variable) -> Variable: 

430 """Return the inverse of a spatial transformation. 

431 

432 Parameters 

433 ---------- 

434 var: 

435 Input variable. 

436 Its ``dtype`` must be one of 

437 

438 - :attr:`scipp.DType.linear_transform3` 

439 - :attr:`scipp.DType.affine_transform3` 

440 - :attr:`scipp.DType.rotation3` 

441 - :attr:`scipp.DType.translation3` 

442 

443 Returns 

444 ------- 

445 : 

446 A variable holding the inverse transformation to ``var``. 

447 """ 

448 return _call_cpp_func(_core_cpp.inv, var) 

449 

450 

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]