cgse-coordinates 0.17.3__py3-none-any.whl → 0.17.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
egse/coordinates/point.py CHANGED
@@ -1,23 +1,21 @@
1
1
  """
2
- The point module provides two classes, a `Point` class which simply represents a point
3
- in the 3D space, and a `Points` class which is a collection of Point objects.
2
+ The point module provides two classes, a `Point` class which simply represents a point in the 3D space, and a `Points`
3
+ class which is a collection of Point objects.
4
4
 
5
- A Point is defined with respect to a given reference frame and is given a name.
5
+ Point objects defined in the same reference frame can be combined with the natural `+`, `-` , `+=` and `-=` operations.
6
6
 
7
- Point objects defined in the same reference frame can be combined with the
8
- natural `+`, `-` , `+=` and `-=` operations.
9
-
10
- In order to work with 4x4 transformation matrices, the 3D [x,y,z] coordinates are
11
- automatically converted to a [x,y,z,1] coordinates array attribute.
12
-
13
- @author: Pierre Royer
7
+ In order to work with 4x4 transformation matrices, the 3D [x, y, z] coordinates are automatically converted to a
8
+ [x, y, z, 1] coordinates array attribute.
14
9
  """
15
10
 
16
11
  import logging
17
12
  import random
18
13
  import string
14
+ from typing import Any
19
15
 
16
+ import numpy
20
17
  import numpy as np
18
+ from numpy import floating
21
19
 
22
20
  import egse.coordinates.transform3d_addon as t3add
23
21
 
@@ -25,451 +23,549 @@ LOGGER = logging.getLogger(__name__)
25
23
 
26
24
 
27
25
  class Point:
28
- """
29
- A Point object represents a point in 3D space and is defined with respect to
30
- a given reference frame.
26
+ from egse.coordinates.reference_frame import ReferenceFrame
31
27
 
32
- .. note::
33
- There is no check that the randomly generated name is unique, so two Point
34
- objects can be different but have the same name.
28
+ """Representation of a point in 3D space, defined w.r.t. a given reference frame."""
35
29
 
36
- """
30
+ def __init__(self, coordinates: np.ndarray | list, reference_frame: ReferenceFrame, name: str = None):
31
+ """Initialisation of a point in 3D space, defined w.r.t. a given reference frame.
37
32
 
38
- debug = 0
33
+ In order to work with 4x4 transformation matrices, the 3D [x, y, z] coordinates are automatically converted to a
34
+ [x, y, z, 1] coordinates array attribute.
39
35
 
40
- def __init__(self, coordinates, ref, name=None):
36
+ Args:
37
+ coordinates (np.ndarray, list): 1x3 or 1x4 matrix defining this system in the given reference frame
38
+ (1x3 being x, y, z + an additional 1 for the affine operations).
39
+ reference_frame (ReferenceFrame): Reference system in which this point object will be defined.
40
+ name (str): Name of the point. If not given, a random name will be generated. Note that there is no check
41
+ that the randomly generated name is unique, so two `Point` objects can be different but have
42
+ the same name.
43
+
44
+ Raises:
45
+ ValueError: If the reference frame is None.
41
46
  """
42
- This initializes a Point object in a given reference frame.
43
47
 
44
- Args:
45
- coordinates (numpy.ndarray, list): 1x3 or 1x4 matrix defining this system in "ref" system
46
- (1x3 being x,y,z + an additional 1 for the affine operations)
48
+ # Coordinates
47
49
 
48
- ref (ReferenceFrame): the reference system in which this Point object will be defined,
49
- if not given or None the master reference frame will be used
50
+ self.x = None
51
+ self.y = None
52
+ self.z = None
53
+ self.coordinates = np.ndarray([])
50
54
 
51
- """
55
+ self.set_coordinates(coordinates) # Format (x, y, z, 1)
52
56
 
53
- # Makes sure of format [x,y,z,1] and sets coordinates
54
- self.setCoordinates(coordinates)
57
+ # Reference frame
55
58
 
56
- # set the reference frame of reference
57
- if ref is None:
58
- raise ValueError("A Point shall be defined with a reference frame, ref can not be None.")
59
+ if reference_frame is None:
60
+ raise ValueError("The reference frame must not be None.")
59
61
  else:
60
- self.ref = ref
62
+ self.reference_frame = reference_frame
63
+
64
+ # Name
61
65
 
62
- self.setName(name)
66
+ self.name = None
67
+ self.set_name(name)
63
68
 
64
- self.definition = [self.coordinates[:-1], self.ref, self.name]
69
+ # Definition
65
70
 
66
- def __repr__(self):
67
- return f"{self.coordinates[:-1]} (ref {self.ref.name})"
71
+ self.definition = [self.coordinates[:-1], self.reference_frame, self.name]
68
72
 
69
- def __str__(self):
70
- return f"{self.coordinates[:-1]} (ref {self.ref.name}), name {self.name}"
73
+ def __repr__(self) -> str:
74
+ """Returns a representation the point.
71
75
 
72
- def __eq__(self, other):
76
+ Returns:
77
+ Representation of the point.
73
78
  """
74
- Re-implements the == operator which by default checks if id(self) == id(other).
75
79
 
76
- Two Point objects are equal when:
80
+ return f"{self.coordinates[:-1]} (ref {self.reference_frame.name})"
81
+
82
+ def __str__(self) -> str:
83
+ """Returns a printable string representation of the point.
77
84
 
78
- * their coordinates are equal
79
- * the reference system in which they are defined is equal
80
- * the name must not be equal
85
+ Returns:
86
+ Printable string representation of the point.
81
87
  """
88
+
89
+ return f"{self.coordinates[:-1]} (ref {self.reference_frame.name}), name {self.name}"
90
+
91
+ def __eq__(self, other) -> bool:
92
+ """Implements the == operator.
93
+
94
+ Two points are equal when:
95
+ - Their coordinates are equal,
96
+ - The reference system in which they are defined is equal.
97
+
98
+ The name does not have to be equal.
99
+
100
+ Args:
101
+ other (Point): Other point to compare with.
102
+
103
+ Returns:
104
+ True if the points are equal, False otherwise.
105
+ """
106
+
82
107
  if self is other:
83
108
  return True
84
109
 
85
110
  if isinstance(other, Point):
86
111
  if not np.array_equal(self.coordinates, other.coordinates):
87
112
  return False
88
- if self.ref != other.ref:
113
+ if self.reference_frame != other.reference_frame:
89
114
  return False
90
115
  return True
91
- return NotImplemented
92
116
 
93
- def __hash__(self):
94
- return id(self.definition) // 16
117
+ return False
118
+
119
+ def __hash__(self) -> int:
120
+ """Returns a unique integer hash value for the point.
95
121
 
96
- def isSame(self, other):
122
+ Returns:
123
+ Unique integer hash value for the point.
97
124
  """
98
- This checks if a Point is the same as another Point in a different reference frame.
99
125
 
100
- Two Point objects are the same when their position, i.e. coordinates, are equal
101
- after they have been expressed in the same reference frame.
126
+ return id(self.definition) // 16
102
127
 
103
- :param other: a Point object that you want to check
104
- :type other: Point
128
+ def is_same(self, other) -> bool:
129
+ """Checks whether two points are the same, even if they are defined in different reference frames.
105
130
 
106
- :returns: True when the two Point objects are the same, False otherwise
131
+ Args:
132
+ other (Point): Other point to compare with.
107
133
 
108
- :raises TypeError: when other is not a compatible type, NotImplemented will returned
109
- which will result in a ```TypeError: unsupported operand type(s) for +:```.
134
+ Returns:
135
+ True if the points are the same, False otherwise.
110
136
  """
111
137
 
112
138
  if isinstance(other, Point):
113
139
  if self == other:
114
140
  return True
115
- else:
116
- if np.array_equal(self.coordinates, other.expressIn(self.ref)):
117
- return True
118
- return False
119
- return NotImplemented
141
+ if np.array_equal(self.coordinates, other.express_in(self.reference_frame)):
142
+ return True
143
+
144
+ return False
120
145
 
121
146
  @staticmethod
122
- def __coords__(coordinates):
123
- """
124
- Formats 1x3 or 1x4 input lists or np.arrays into 1x4 np.array coordinates
125
- Static --> can be called 'from outside', without passing a Point object
147
+ def __coords__(coordinates) -> np.ndarray:
148
+ """Formats the input list into 1x4 np.array coordinates.
149
+
150
+ Args:
151
+ coordinates (Point | np.ndarray | list):
152
+
153
+ Returns:
154
+ Coordinates formatted as a 1x4 np.ndarray.
155
+
156
+ Raises:
157
+ ValueError: If the input is not a list, numpy.ndarray, or Point.
126
158
  """
159
+
127
160
  if isinstance(coordinates, Point):
128
161
  return coordinates.coordinates
129
162
  elif isinstance(coordinates, (np.ndarray, list)):
130
163
  coordinates = list(coordinates)
131
164
  if len(coordinates) == 3:
132
165
  coordinates.append(1)
133
- return coordinates
166
+ return np.ndarray(coordinates)
134
167
  else:
135
- raise ValueError("input must be a list, numpy.ndarray or Point")
136
-
137
- def setName(self, name=None):
138
- """
139
- Set or change the name of a Point object.
168
+ raise ValueError("Input must be a list, numpy.ndarray or Point")
140
169
 
141
- :param str name: the new name for the Point, if None, a random name will be generated.
170
+ def set_name(self, name: (str | None) = None) -> None:
171
+ """Sets the name of the point.
142
172
 
143
- .. todo:: Should we care about the possibility the the generation of random names does not
144
- necessarily create a unique name for the Point?
173
+ Args:
174
+ name (str | None): Name to use for the point. If None, a random name will be generated.
145
175
  """
176
+
146
177
  if name is None:
178
+ # TODO Should we care about the possibility the the generation of random names does not necessarily create
179
+ # a unique name for the point?
147
180
  self.name = "p" + "".join(random.choices(string.ascii_lowercase, k=3))
148
181
  else:
149
182
  self.name = name
150
183
 
151
- def setCoordinates(self, coordinates):
152
- """
153
- Set the coordinates of this Point object.
184
+ def set_coordinates(self, coordinates: np.ndarray | list) -> None:
185
+ """Sets the coordinates of the point.
186
+
187
+ Args:
188
+ coordinates (np.ndarray | list): Coordinates to set.
154
189
  """
190
+
155
191
  coordinates = Point.__coords__(coordinates)
156
- self.coordinates = np.array(coordinates)
192
+ self.coordinates = np.ndarray(coordinates)
157
193
 
158
194
  self.x = self.coordinates[0]
159
195
  self.y = self.coordinates[1]
160
196
  self.z = self.coordinates[2]
161
197
 
162
- def getCoordinates(self, ref=None):
163
- """
164
- Returns the coordinates of this Points object.
198
+ def get_coordinates(self, reference_frame: ReferenceFrame | None = None) -> np.ndarray:
199
+ """Returns the coordinates of the point.
165
200
 
166
- If no reference frame is given, the coordinates of the Point will just be returned,
167
- other wise this method behaves the same as the ``expressIn(ref)`` method.
201
+ Args:
202
+ reference_frame (ReferenceFrame | None): Reference frame in which the point coordinates are returned.
168
203
 
169
- :param ref: the Reference Frame in which the Point shall be defined
170
- :type ref: ReferenceFrame
204
+ Returns:
205
+ Coordinates of the point in the given reference frame.
171
206
  """
172
- if ref is None:
207
+
208
+ if reference_frame is None:
173
209
  return self.coordinates
174
210
  else:
175
- return self.expressIn(ref)
211
+ return self.express_in(reference_frame)
176
212
 
177
- def distanceTo(self, target):
178
- """
179
- Returns the distance of this Point object to the target. Target can be another Point,
180
- a ReferenceFrame object or a Numpy dnarray or list with coordinates.
213
+ def distance_to(self, target) -> floating[Any]:
214
+ """Returns the distance from this point to the target.
215
+
216
+ Args:
217
+ target (Point | ReferenceFrame | np.ndarray | list): Target to compute the distance to.
218
+
219
+ Returns:
220
+ Distance from this point to the target.
221
+
222
+ Raises:
223
+ ValueError: If the target is not a Point, ReferenceFrame, numpy.ndarray, or list.
181
224
  """
182
- from egse.coordinates.referenceFrame import ReferenceFrame
225
+
226
+ from egse.coordinates.reference_frame import ReferenceFrame
183
227
 
184
228
  if isinstance(target, Point):
185
- targetCoords = target.expressIn(self.ref)[:3]
229
+ target_coordinates = target.express_in(self.reference_frame)[:3]
186
230
  elif isinstance(target, ReferenceFrame):
187
- return np.linalg.norm(self.expressIn(target)[:3])
231
+ return np.linalg.norm(self.express_in(target)[:3])
188
232
  elif isinstance(target, (np.ndarray, list)):
189
233
  if len(target) > 3:
190
234
  target = target[:3]
191
- targetCoords = target
235
+ target_coordinates = target
192
236
  else:
193
- raise ValueError("input must be a list, numpy.ndarray, Point or ReferenceFrame")
237
+ raise ValueError("Target must be a list, numpy.ndarray, Point, or ReferenceFrame")
194
238
 
195
- LOGGER.info(f"self={self.coordinates[:-1]}, target={targetCoords}")
239
+ LOGGER.info(f"self={self.coordinates[:-1]}, target={target_coordinates}")
196
240
 
197
- return np.linalg.norm(self.coordinates[:3] - targetCoords)
241
+ return np.linalg.norm(self.coordinates[:3] - target_coordinates)
198
242
 
199
- def inPlaneDistanceTo(self, target, plane="xy"):
200
- """
201
- Returns the distance of this Point object to the target, considering 2 coordinates only!
243
+ def in_plane_distance_to(self, target, plane: str = "xy") -> np.floating[Any]:
244
+ """Returns the distance of this point object to the target, considering 2 coordinates only.
202
245
 
203
- target: can be another Point, a ReferenceFrame object or a Numpy dnarray or list with coordinates.
246
+ Note that this is not a commutative operation, because the plane used to project the points coordinates
247
+ before computing the distances is taken from the coordinate system of `self`.
204
248
 
205
- plane : must be in ['xy', 'xz', 'yz']
249
+ Args:
250
+ target (Point | ReferenceFrame | np.ndarray | list): Target to compute the distance to.
251
+ plane (str): Plane to consider. Must be in ['xy', 'xz', 'yz'].
206
252
 
207
- NB: The xy, yz or xz plane used to project the points coordinates before
208
- computing the distances is taken from the coordinate system of "self"
209
- ==> pointA.inPlaneDistanceTo(pointB) != pointB.inPlaneDistanceTo(pointA)
210
- The first projects on the xy plane of pointA.ref, the second on the xy plane of pointB.ref
253
+ Returns:
254
+ Distance from this point to the target.
211
255
  """
212
- from egse.coordinates.referenceFrame import ReferenceFrame
256
+
257
+ from egse.coordinates.reference_frame import ReferenceFrame
213
258
 
214
259
  if isinstance(target, Point):
215
- targetCoords = target.expressIn(self.ref)
260
+ target_coordinates = target.express_in(self.reference_frame)
216
261
  elif isinstance(target, ReferenceFrame):
217
- targetCoords = target.getOrigin().expressIn(self)
262
+ target_coordinates = target.get_origin().express_in(self)
218
263
  elif isinstance(target, (np.ndarray, list)):
219
- targetCoords = target
264
+ target_coordinates = target
220
265
  else:
221
266
  raise ValueError("input must be a list, numpy.ndarray, Point or ReferenceFrame")
222
267
 
223
- LOGGER.info(f"self={self.coordinates[:-1]}, target={targetCoords}")
268
+ LOGGER.info(f"self={self.coordinates[:-1]}, target={target_coordinates}")
224
269
 
225
- planeSelect = {"xy": [0, 1], "xz": [0, 2], "yz": [1, 2]}
270
+ plane_selection = {"xy": [0, 1], "xz": [0, 2], "yz": [1, 2]}
226
271
 
227
- LOGGER.info(f"self.coordinates[planeSelect[plane]] {self.coordinates[planeSelect[plane]]}")
228
- LOGGER.info(f"targetCoords[planeSelect[plane]] {targetCoords[planeSelect[plane]]}")
272
+ LOGGER.info(f"self.coordinates[planeSelect[plane]] {self.coordinates[plane_selection[plane]]}")
273
+ LOGGER.info(f"targetCoords[planeSelect[plane]] {target_coordinates[plane_selection[plane]]}")
229
274
  LOGGER.info(
230
- f"Difference {self.coordinates[planeSelect[plane]] - targetCoords[planeSelect[plane]]}"
275
+ f"Difference {self.coordinates[plane_selection[plane]] - target_coordinates[plane_selection[plane]]}"
231
276
  )
232
277
 
233
- return np.linalg.norm(self.coordinates[planeSelect[plane]] - targetCoords[planeSelect[plane]])
278
+ return np.linalg.norm(self.coordinates[plane_selection[plane]] - target_coordinates[plane_selection[plane]])
279
+
280
+ def distance_to_plane(self, plane: str = "xy", reference_frame: ReferenceFrame | None = None) -> float:
281
+ """Calculates the distance from the point to a plane in a given reference frame.
234
282
 
235
- def distanceToPlane(self, plane="xy", ref=None):
283
+ Args:
284
+ plane (str): Target plane, must be in ["xy", "xz", "yz"]
285
+ reference_frame (ReferenceFrame | None, optional): Reference frame in which the distance is calculated.
286
+
287
+ Returns:
288
+ Distance from the point to the plane.
236
289
  """
237
- distanceToPlane(self,plane="xy",ref=None)
238
290
 
239
- The target plane is defined by one of it coordinate planes: ["xy", "yz", "xz"]
291
+ if (reference_frame is None) or (self.reference_frame == reference_frame):
292
+ coordinates = self.coordinates[:-1]
293
+ else:
294
+ coordinates = self.express_in(reference_frame)
295
+
296
+ out_of_plane_index = {"xy": 2, "xz": 1, "yz": 0}
297
+
298
+ return coordinates[out_of_plane_index[plane]]
240
299
 
241
- :param ref: reference frame
242
- :type ref: ReferenceFrame
300
+ def __sub__(self, point):
301
+ """Implements the subtraction operator (-).
243
302
 
244
- :param plane: in ["xy", "xz", "yz"]
245
- :type plane: str
303
+ Args:
304
+ point (Point | np.ndarray | list): Point to subtract from `self`.
246
305
 
247
- :returns: the distance from self to plane
306
+ Returns:
307
+ Point: New point resulting from the subtraction.
248
308
  """
249
- if (ref is None) or (self.ref == ref):
250
- coordinates = self.coordinates[:-1]
251
- elif self.ref != ref:
252
- coordinates = self.expressIn(ref)
253
309
 
254
- outOfPlaneIndex = {"xy": 2, "xz": 1, "yz": 0}
310
+ if isinstance(point, Point):
311
+ if point.reference_frame != self.reference_frame:
312
+ raise NotImplementedError("The points have different reference frames")
255
313
 
256
- return coordinates[outOfPlaneIndex[plane]]
314
+ new_coordinates = self.coordinates - point.coordinates
257
315
 
258
- def __sub__(self, apoint):
259
- """
260
- Takes care for
261
- newPoint = self + point
262
- """
263
- if isinstance(apoint, Point):
264
- try:
265
- if apoint.ref != self.ref:
266
- raise ValueError
267
- except ValueError:
268
- print("WARNING: The points have different reference frames, returning NotImplemented")
269
- return NotImplemented
270
- newCoordinates = self.coordinates - apoint.coordinates
316
+ elif isinstance(point, (np.ndarray, list)):
317
+ new_coordinates = self.coordinates - Point.__coords__(point)
271
318
 
272
- elif isinstance(apoint, (np.ndarray, list)):
273
- newCoordinates = self.coordinates - Point.__coords__(apoint)
319
+ else:
320
+ raise ValueError("The point must be a Point, numpy.ndarray, or list")
274
321
 
275
322
  # For the affine transforms, the 4th digit must be set to 1 (it has been modified above)
276
- newCoordinates[-1] = 1
277
323
 
278
- return Point(coordinates=newCoordinates, ref=self.ref)
324
+ new_coordinates[-1] = 1
279
325
 
280
- def __isub__(self, apoint):
281
- """
282
- Takes care for
283
- self += coordinates (modifies self in place)
326
+ return Point(coordinates=new_coordinates, reference_frame=self.reference_frame)
327
+
328
+ def __isub__(self, point):
329
+ """Implements the subtraction assignment operator (-=).
330
+
331
+ Args:
332
+ point (Point | np.ndarray | list): Point to add to `self`.
333
+
334
+ Returns:
335
+ Modified point.
336
+
337
+ Raises:
338
+ ValueError: If the point is not a Point, numpy.ndarray, or list.
284
339
  """
285
- if isinstance(apoint, Point):
286
- try:
287
- if apoint.ref != self.ref:
288
- raise ValueError
289
- except ValueError:
290
- print("WARNING: The points have different reference frames, returning NotImplemented")
291
- return NotImplemented
292
- newCoordinates = self.coordinates - apoint.coordinates
293
340
 
294
- elif isinstance(apoint, (np.ndarray, list)):
295
- newCoordinates = self.coordinates - Point.__coords__(apoint)
341
+ if isinstance(point, Point):
342
+ if point.reference_frame != self.reference_frame:
343
+ raise NotImplementedError("The points have different reference frames")
344
+
345
+ new_coordinates = self.coordinates - point.coordinates
346
+
347
+ elif isinstance(point, (np.ndarray, list)):
348
+ new_coordinates = self.coordinates - Point.__coords__(point)
349
+
350
+ else:
351
+ raise ValueError("The point must be a Point, numpy.ndarray, or list")
296
352
 
297
353
  # For the affine transforms, the 4th digit must be set to 1 (it has been modified above)
298
- newCoordinates[-1] = 1
354
+ new_coordinates[-1] = 1
299
355
 
300
- self.coordinates = newCoordinates
356
+ self.coordinates = new_coordinates
301
357
 
302
358
  return self
303
359
 
304
- def __add__(self, apoint):
305
- """
306
- Takes care for
307
- newPoint = self + point
360
+ def __add__(self, point):
361
+ """Implements the addition operator (+).
362
+
363
+ Args:
364
+ point (Point | np.ndarray | list): Point to add to `self`.
365
+
366
+ Returns:
367
+ Point: New point resulting from the addition.
368
+
369
+ Raises:
370
+ ValueError: If the point is not a Point, numpy.ndarray, or list.
308
371
  """
309
- if isinstance(apoint, Point):
310
- try:
311
- if apoint.ref != self.ref:
312
- print(f"DEBUG: {apoint} = {apoint.expressIn(self.ref)}")
313
- raise ValueError
314
- except ValueError:
315
- print("WARNING: The points have different reference frames, returning NotImplemented")
316
- return NotImplemented
317
- newCoordinates = self.coordinates + apoint.coordinates
318
372
 
319
- elif isinstance(apoint, (np.ndarray, list)):
320
- newCoordinates = self.coordinates + Point.__coords__(apoint)
373
+ if isinstance(point, Point):
374
+ if point.reference_frame != self.reference_frame:
375
+ raise NotImplementedError("The points have different reference frames")
376
+
377
+ new_coordinates = self.coordinates + point.coordinates
378
+
379
+ elif isinstance(point, (np.ndarray, list)):
380
+ new_coordinates = self.coordinates + Point.__coords__(point)
321
381
 
322
382
  else:
323
- return NotImplemented
383
+ raise ValueError("The point must be a Point, numpy.ndarray, or list")
324
384
 
325
385
  # For the affine transforms, the 4th digit must be set to 1 (it has been modified above)
326
- newCoordinates[-1] = 1
327
386
 
328
- return Point(coordinates=newCoordinates, ref=self.ref)
387
+ new_coordinates[-1] = 1
329
388
 
330
- def __iadd__(self, apoint):
331
- """
332
- Takes care for
333
- self += coordinates (modifies self in place)
389
+ return Point(coordinates=new_coordinates, reference_frame=self.reference_frame)
390
+
391
+ def __iadd__(self, point):
392
+ """Implements the addition assignment operator (+=).
393
+
394
+ Args:
395
+ point (Point | np.ndarray | list): Point to add to `self`.
396
+
397
+ Returns:
398
+ Modified point.
399
+
400
+ Raises:
401
+ ValueError: If the point is not a Point, numpy.ndarray, or list.
334
402
  """
335
- if isinstance(apoint, Point):
336
- try:
337
- if apoint.ref != self.ref:
338
- raise ValueError
339
- except ValueError:
340
- print("WARNING: The points have different reference frames, returning NotImplemented")
341
- return NotImplemented
342
- newCoordinates = self.coordinates + apoint.coordinates
343
403
 
344
- elif isinstance(apoint, (np.ndarray, list)):
345
- newCoordinates = self.coordinates + Point.__coords__(apoint)
404
+ if isinstance(point, Point):
405
+ if point.reference_frame != self.reference_frame:
406
+ raise NotImplementedError("The points have different reference frames")
407
+
408
+ new_coordinates = self.coordinates + point.coordinates
409
+
410
+ elif isinstance(point, (np.ndarray, list)):
411
+ new_coordinates = self.coordinates + Point.__coords__(point)
412
+
413
+ else:
414
+ raise ValueError("The point must be a Point, numpy.ndarray, or list")
346
415
 
347
416
  # For the affine transforms, the 4th digit must be set to 1 (it has been modified above)
348
- newCoordinates[-1] = 1
349
417
 
350
- self.coordinates = newCoordinates
418
+ new_coordinates[-1] = 1
419
+ self.coordinates = new_coordinates
351
420
 
352
421
  return self
353
422
 
354
- def expressIn(self, targetFrame):
355
- """
356
- expressIn(self,targetFrame)
423
+ def express_in(self, target_frame: ReferenceFrame) -> np.ndarray:
424
+ """Expresses the coordinates in another reference frame.
425
+
426
+ Args:
427
+ target_frame (ReferenceFrame): Target reference frame.
428
+
429
+ Returns:
430
+ Coordinates in the target reference frame.
357
431
  """
358
- if targetFrame == self.ref:
359
- """
360
- targetFrame == self.ref
361
- We're after the definition of self
362
- """
432
+
433
+ if target_frame == self.reference_frame:
363
434
  result = self.coordinates
435
+
364
436
  else:
365
- """
366
- We're after the coordinates of self, i.e. the definition of self in targetFrame
367
-
368
- We know the coordinates in self.ref
369
- #We need to apply the transformation from targetFrame to self.ref
370
- self.ref --> self (self.transformation)
371
- """
372
- # transform = targetFrame.getTransformationFrom(self.ref)
373
- transform = self.ref.getPassiveTransformationTo(targetFrame)
374
- if self.debug:
375
- print("transform \n{0}".format(transform))
437
+ # Apply coordinate transformation of self.coordinates from self.reference_frame to target_frame
438
+ transform = self.reference_frame.get_passive_transformation_to(target_frame)
376
439
  result = np.dot(transform, self.coordinates)
377
- return result
378
440
 
379
- def changeRef(self, targetFrame):
380
- """
381
- We redefine self as attached to another reference frame
382
- . calculate self's coordinates in the new reference frame
383
- . update the definition
384
- """
385
- newCoordinates = self.expressIn(targetFrame)
386
- self.setCoordinates(newCoordinates)
387
- self.ref = targetFrame
388
- return
441
+ LOGGER.debug(f"transform: \n{transform}")
389
442
 
443
+ return result
390
444
 
391
- class Points:
392
- """
393
- A Points object is a collection of Point objects.
445
+ def change_reference_frame(self, target_frame: ReferenceFrame):
446
+ """Re-defines `self` as attached to another reference frame.
394
447
 
395
- Points can be constructed from either a numpy.ndarray of shape 3 x n or 4 x n, or
396
- a list of Point objects. The coordinates of the Point objects are transferred to
397
- the desired ReferenceFrame and concatenated in the list order.
448
+ This is done by:
449
+ - Calculating the coordinates of `self` in the target reference frame,
450
+ - Updating the definition of `self` in the new reference frame (with the newly calculated coordinates and the
451
+ target reference frame).
398
452
 
399
- When automatically generated a Points object name consists in a capital 'P' followed by
400
- three lower case letters. A Point can be extracted from a Points object by its
401
- position in the coordinates array (see below).
453
+ Args:
454
+ target_frame (ReferenceFrame): Target reference frame.
455
+ """
402
456
 
403
- """
457
+ new_coordinates = self.express_in(target_frame)
458
+ self.set_coordinates(new_coordinates)
404
459
 
405
- debug = 0
460
+ self.reference_frame = target_frame
406
461
 
407
- def __init__(self, coordinates, ref, name=None):
408
- """
409
- Points.__init__(self, coordinates, ref, name=None)
410
462
 
411
- Constructor
463
+ class Points:
464
+ """Representation of a collection of points in 3D space."""
465
+
466
+ from egse.coordinates.reference_frame import ReferenceFrame
412
467
 
413
- coordinates : must be of one of the following type:
414
- * numpy.ndarray:
415
- 4xn matrix defining this system in "ref" system
416
- (3 being x,y,z + an additional 1 for the affine operations)
417
- * list of Point objects:
418
- the coordinates of the Point objects are extracted in the order of the list
419
- and concatenated in a numpy.ndarray
468
+ debug = 0
420
469
 
421
- ref : reference system in which self is defined
470
+ def __init__(self, coordinates: numpy.ndarray | list, reference_frame: ReferenceFrame, name: str = None):
471
+ """Initialisation of a new set of points.
422
472
 
423
- Both parameters are mandatory.
473
+ Args:
474
+ coordinates (numpy.ndarray | list): Either a 4xn matrix (3 being x, y, z + an additional 1 for the affine
475
+ operations), defining n coordinates in the given reference frame, or a
476
+ list of Point(s) objects.
477
+ reference_frame (ReferenceFrame): Reference frame in which the coordinates are defined.
478
+ name (str | None): Name of the Points object. When None, a name will be generated automatically, consisting
479
+ of a capital "P" followed by three lower case letters.
424
480
  """
425
481
 
426
- # TODO: accept a list made of Point and Points rather than strictly Point
482
+ # Coordinates
483
+
484
+ self.x = None
485
+ self.y = None
486
+ self.z = None
487
+ self.coordinates = np.ndarray([])
427
488
 
428
489
  if isinstance(coordinates, list):
429
- coordinateList = []
430
- for apoint in coordinates:
431
- if not isinstance(apoint, Point):
490
+ coordinate_list = []
491
+
492
+ for item in coordinates:
493
+ if isinstance(item, Point):
494
+ coordinate_list.append(item.express_in(reference_frame))
495
+ elif isinstance(item, Points):
496
+ coordinate_list.extend(item.express_in(reference_frame))
497
+ else:
432
498
  raise ValueError("If the input is a list, all items in it must be Point(s) objects")
433
- coordinateList.append(apoint.expressIn(ref))
434
- self.setCoordinates(np.array(coordinateList).T)
499
+ self.set_coordinates(np.array(coordinate_list).T)
435
500
  elif isinstance(coordinates, np.ndarray):
436
- self.setCoordinates(coordinates)
501
+ self.set_coordinates(coordinates)
437
502
  else:
438
- raise ValueError("The input must be either a numpy.ndarray or a list of Point objects")
503
+ raise ValueError("The input must be either a numpy.ndarray or a list of Point/Points objects")
439
504
 
440
- self.ref = ref
505
+ # Reference frame
441
506
 
442
- self.setName(name)
507
+ if reference_frame is None:
508
+ raise ValueError("The reference frame must not be None.")
509
+ else:
510
+ self.reference_frame = reference_frame
511
+
512
+ # Name
513
+
514
+ self.name = None
515
+ self.set_name(name)
516
+
517
+ def __repr__(self) -> str:
518
+ """Returns a representation the points.
519
+
520
+ Returns:
521
+ Representation of the points.
522
+ """
443
523
 
444
- return
524
+ return "{0} (ref {1})".format(self.coordinates[:-1], self.reference_frame.name)
445
525
 
446
- def __repr__(self):
447
- return "{0} (ref {1})".format(self.coordinates[:-1], self.ref.name)
526
+ def __str__(self) -> str:
527
+ """Returns a printable string representation of the point.
448
528
 
449
- def __str__(self):
450
- return "{1} (ref {2}), name {0}".format(self.name, self.coordinates[:-1], self.ref.name)
529
+ Returns:
530
+ Printable string representation of the point.
531
+ """
532
+
533
+ return "{1} (ref {2}), name {0}".format(self.name, self.coordinates[:-1], self.reference_frame.name)
451
534
 
452
535
  @staticmethod
453
536
  def __coords__(coordinates):
537
+ """Formats the input list into 4xn np.array coordinates.
538
+
539
+ Args:
540
+ coordinates (Point | np.ndarray | list):
541
+
542
+ Returns:
543
+ Coordinates formatted as a 1x4 np.ndarray.
544
+
545
+ Raises:
546
+ ValueError: If the input is not a list, numpy.ndarray, or Point.
454
547
  """
455
- Formats 3xn or 4xn input lists or np.arrays into 4xn np.array coordinates
456
- Static --> can be called 'from outside', without passing a Points object
457
- """
548
+
458
549
  if isinstance(coordinates, Point):
459
550
  return coordinates.coordinates
460
551
  elif isinstance(coordinates, np.ndarray):
461
552
  if coordinates.shape[0] not in [3, 4]:
462
553
  raise ValueError("Input coordinates array must be 3 x n or 4 x n")
463
-
464
554
  if coordinates.shape[0] == 3:
465
- newCoords = np.ones([4, coordinates.shape[1]])
466
- newCoords[:3, :] = coordinates
467
- coordinates = newCoords
555
+ new_coordinates = np.ones([4, coordinates.shape[1]])
556
+ new_coordinates[:3, :] = coordinates
557
+ coordinates = new_coordinates
468
558
  return coordinates
469
559
  else:
470
560
  raise ValueError("input must be a list, numpy.ndarray or Point")
471
561
 
472
- def setName(self, name=None):
562
+ def set_name(self, name: str | None = None):
563
+ """Sets the name of the point.
564
+
565
+ Args:
566
+ name (str | None): Name to use for the point. If None, a random name will be generated.
567
+ """
568
+
473
569
  if name is None:
474
570
  self.name = (
475
571
  "P"
@@ -479,9 +575,13 @@ class Points:
479
575
  else:
480
576
  self.name = name
481
577
 
482
- def setCoordinates(self, coordinates):
483
- # coordinates = list(coordinates)
484
- # if len(coordinates)==3: coordinates.append(1)
578
+ def set_coordinates(self, coordinates: numpy.ndarray | list) -> None:
579
+ """Sets the coordinates of the point.
580
+
581
+ Args:
582
+ coordinates (np.ndarray | list): Coordinates to set.
583
+ """
584
+
485
585
  coordinates = Points.__coords__(coordinates)
486
586
  self.coordinates = coordinates
487
587
 
@@ -489,219 +589,245 @@ class Points:
489
589
  self.y = self.coordinates[1, :]
490
590
  self.z = self.coordinates[2, :]
491
591
 
492
- return
592
+ def get_coordinates(self, reference_frame: ReferenceFrame | None = None) -> numpy.ndarray:
593
+ """Returns the coordinates of the point.
493
594
 
494
- def getCoordinates(self, ref=None):
495
- if ref is None:
595
+ Args:
596
+ reference_frame (ReferenceFrame | None): Reference frame in which the point coordinates are returned.
597
+
598
+ Returns:
599
+ Coordinates of the point in the given reference frame.
600
+ """
601
+
602
+ if reference_frame is None:
496
603
  return self.coordinates
497
604
  else:
498
- return self.expressIn(ref)
605
+ return self.express_in(reference_frame)
499
606
 
500
- def expressIn(self, targetFrame):
501
- """
502
- expressIn(self,targetFrame)
607
+ def express_in(self, target_frame: ReferenceFrame) -> np.ndarray:
608
+ """Expresses the coordinates in another reference frame.
609
+
610
+ Args:
611
+ target_frame (ReferenceFrame): Target reference frame.
612
+
613
+ Returns:
614
+ Coordinates in the target reference frame.
503
615
  """
504
- if targetFrame == self.ref:
505
- """
506
- targetFrame == self.ref
507
- We're after the definition of self
508
- """
616
+
617
+ if target_frame == self.reference_frame:
509
618
  result = self.coordinates
510
619
  else:
511
- """
512
- We're after the coordinates of self, i.e. the definition of self in targetFrame
513
-
514
- We know the coordinates in self.ref
515
- #We need to apply the transformation from targetFrame to self.ref
516
- self.ref --> self (self.transformation)
517
- """
518
- # transform = targetFrame.getTransformationFrom(self.ref)
519
- transform = self.ref.getPassiveTransformationTo(targetFrame)
620
+ # Apply coordinate transformation of self.coordinates from self.reference_frame to target_frame
621
+ transform = self.reference_frame.get_passive_transformation_to(target_frame)
520
622
  if self.debug:
521
623
  print("transform \n{0}".format(transform))
522
624
  result = np.dot(transform, self.coordinates)
523
625
  return result
524
626
 
525
- def changeRef(self, targetFrame):
526
- """
527
- We redefine self as attached to another reference frame
528
- . calculate self's coordinates in the new reference frame
529
- . update the definition
530
- """
531
- newCoordinates = self.expressIn(targetFrame)
532
- self.setCoordinates(newCoordinates)
533
- self.ref = targetFrame
534
- return
627
+ def change_reference_frame(self, target_frame: ReferenceFrame):
628
+ """Re-defines `self` as attached to another reference frame.
629
+
630
+ This is done by:
631
+ - Calculating the coordinates of `self` in the target reference frame,
632
+ - Updating the definition of `self` in the new reference frame (with the newly calculated coordinates and the
633
+ target reference frame).
535
634
 
536
- def getPoint(self, index, name=None):
635
+ Args:
636
+ target_frame (ReferenceFrame): Target reference frame.
537
637
  """
538
- Returns the point with coordinates self.coordinates[:,index], in reference frame pts.ref
638
+
639
+ new_coordinates = self.express_in(target_frame)
640
+ self.set_coordinates(new_coordinates)
641
+
642
+ self.reference_frame = target_frame
643
+
644
+ def get_num_points(self) -> int:
645
+ """Returns the number of points in the set of points.
646
+
647
+ Returns:
648
+ Number of points in the set of points.
539
649
  """
540
- return Point(self.coordinates[:, index], ref=self.ref, name=name)
541
650
 
542
- get = getPoint
651
+ return self.coordinates.shape[1]
652
+
653
+ def get_point(self, index: int, name: str | None = None) -> Point:
654
+ """Returns the point with the given index and assigns the given name to it.
543
655
 
544
- def bestFittingPlane(self, fitPlane="xy", usesvd=False, verbose=True):
656
+ Args:
657
+ index (int): Index of the point to return.
658
+ name (str): Name of the point.
659
+
660
+ Returns:
661
+ Point with the given index.
545
662
  """
546
- bestFittingPlane(self,fitPlane="xy", usesvd=False,verbose=True)
547
663
 
548
- fitPlane in ['xy,'yz','zx']
549
- usesvd see transform3d_addon.affine_matrix_from_points.__doc__
664
+ return Point(self.coordinates[:, index], reference_frame=self.reference_frame, name=name)
665
+
666
+ get = get_point
667
+
668
+ def best_fitting_plane(self, plane: str = "xy", use_svd: bool = False, verbose: bool = True):
669
+ """Returns the reference frame with the given plane as best fitting to all points in this collection of points.
670
+
671
+ Args:
672
+ plane (str): Plane to fit. Must be in ["xy", "yz", "zx"].
673
+ use_svd (bool): Indicates whether to use SVD-base solution (Singular Value Decomposition) for
674
+ rigid/similarity transformations. If False and ndims = 3, the quaternion-based solution is
675
+ used.
676
+ verbose (bool): Indicates whether to print verbose output.
550
677
 
551
- OUTPUT
552
- returns the reference Frame having as X-Y plane the plane best fitting all points in self
678
+ Returns:
679
+ Reference frame with the given plane as best fitting to all points in this collection of points.
553
680
  """
681
+
554
682
  # Import necessary due to a circular dependency between Point and ReferenceFrame
555
- from egse.coordinates.referenceFrame import ReferenceFrame
683
+ from egse.coordinates.reference_frame import ReferenceFrame
556
684
 
557
685
  debug = True
558
686
 
559
- a, b, c = self.fitPlane(fitPlane=fitPlane, verbose=verbose)
560
- # print (f"a {a}, b {b}, c {c}")
561
- # print()
687
+ a, b, c = self.fit_plane(plane=plane, verbose=verbose)
562
688
 
563
- unitaxes = Points.fromPlaneParameters(a, b, c, ref=self.ref, plane=fitPlane)
564
- # print(f"Unitaxes coordinates \n{np.round(unitaxes.coordinates,3)}")
565
- # print()
689
+ unit_axes = Points.from_plane_parameters(a, b, c, reference_frame=self.reference_frame, plane=plane)
690
+ # print(f"Unit axes coordinates \n{np.round(unit_axes.coordinates,3)}")
566
691
 
567
- # unitaxes contain 3 unit axes and an origin
692
+ # unit_axes contain 3 unit axes and an origin
568
693
  # => the unit vectors do NOT belong to the target plane
569
694
  # => they must be translated before
570
- unitcoords = unitaxes.coordinates
695
+ unit_coordinates = unit_axes.coordinates
571
696
  for ax in range(3):
572
- unitcoords[:3, ax] += unitcoords[:3, 3]
697
+ unit_coordinates[:3, ax] += unit_coordinates[:3, 3]
573
698
 
574
- newaxes = Points(unitcoords, ref=self.ref)
699
+ new_axes = Points(unit_coordinates, reference_frame=self.reference_frame)
575
700
 
576
- # print(f"newaxes {np.round(newaxes.coordinates,3)}")
701
+ # print(f"new_axes {np.round(new_axes.coordinates,3)}")
577
702
 
578
- selfaxes = Points(np.identity(4), ref=self.ref)
703
+ self_axes = Points(np.identity(4), reference_frame=self.reference_frame)
579
704
 
580
705
  transform = t3add.affine_matrix_from_points(
581
- selfaxes.coordinates[:3, :], newaxes.coordinates[:3, :], shear=False, scale=False, usesvd=usesvd
706
+ self_axes.coordinates[:3, :], new_axes.coordinates[:3, :], shear=False, scale=False, use_svd=use_svd
582
707
  )
583
708
 
584
709
  if debug:
585
- transform2 = t3add.rigid_transform_3D(
586
- selfaxes.coordinates[:3, :], newaxes.coordinates[:3, :], verbose=verbose
587
- )
710
+ transform2 = t3add.rigid_transform_3d(self_axes.coordinates[:3, :], new_axes.coordinates[:3, :])
588
711
 
589
712
  if verbose:
590
- print()
591
713
  print(f"Transform \n{np.round(transform, 3)}")
592
714
  if debug:
593
- print()
594
715
  print(f"Transform2 \n{np.round(transform2, 3)}")
595
- print()
596
716
  print(f"Both methods consistent ? {np.allclose(transform, transform2)}")
597
717
 
598
- return ReferenceFrame(transformation=transform, ref=self.ref)
718
+ return ReferenceFrame(transformation=transform, reference_frame=self.reference_frame)
599
719
 
600
- def fitPlane(self, fitPlane="xy", verbose=True):
601
- """
602
- fitPlane(self,fitPlane="xy",verbose=True)
720
+ def fit_plane(self, plane: str = "xy", verbose: bool = True) -> tuple[float, float, float]:
721
+ """Fits the plane best fitting the points.
603
722
 
604
- :returns: the best fitting parameters a, b, c corresponding to the fitted plane
723
+ Depending on the `plane` parameter, the plane is fitted in the xy, yz, or zx plane:
724
+ - "xy": z = ax + by + c
725
+ - "yz": x = ay + bz + c
726
+ - "zx": y = az + bx + c
605
727
 
606
- :param fitPlane: defines the expression of the fitted plane in ['xy','yz','zx']:
607
- 'xy' : z = ax + by + c
608
- 'yz' : x = ay + bz + c
609
- 'zx' : y = az + bx + c
728
+ Args:
729
+ plane (str): Plane to fit. Must be in ["xy", "yz", "zx"].
730
+ verbose (bool): If True, print the results on screen.
610
731
 
611
- :param verbose: default = True
732
+ Returns:
733
+ Best fitting parameters (a, b, c), corresponding to the fitted plane.
612
734
  """
735
+
613
736
  xyz = [self.x, self.y, self.z]
614
737
 
615
- ndata = len(xyz[0])
738
+ num_points = len(xyz[0])
616
739
 
617
- # Account for cases
618
- startingIndex = {"xy": 0, "yz": 1, "zx": 2}[fitPlane]
740
+ starting_index = {"xy": 0, "yz": 1, "zx": 2}[plane]
619
741
 
620
- # System matrix
621
- A = np.vstack([xyz[startingIndex], xyz[(startingIndex + 1) % 3], np.ones(ndata)]).T
742
+ # Coefficients matrix
743
+ coefficients_matrix = np.vstack([xyz[starting_index], xyz[(starting_index + 1) % 3], np.ones(num_points)]).T
622
744
 
623
745
  # Solve linear equations
624
- a, b, c = np.linalg.lstsq(A, xyz[(startingIndex + 2) % 3], rcond=None)[0]
746
+ a, b, c = np.linalg.lstsq(coefficients_matrix, xyz[(starting_index + 2) % 3], rcond=None)[0]
625
747
 
626
748
  # Print results on screen
627
749
  if verbose:
628
750
  hprint = {"xy": "z = ax + by + c", "yz": "x = ay + bz + c", "zx": "y = az + bx + c"}
629
- print(f"{hprint[fitPlane]} : \n a = {a:7.3e} \n b = {b:7.3e} \n c = {c:7.3e}")
751
+ print(f"{hprint[plane]} : \n a = {a:7.3e} \n b = {b:7.3e} \n c = {c:7.3e}")
630
752
 
631
753
  return a, b, c
632
754
 
633
755
  @classmethod
634
- def fromPlaneParameters(cls, a, b, c, ref, plane="xy", verbose=False):
635
- """fromPlaneParameters(cls,a,b,c,ref,plane='xy',verbose=False)
756
+ def from_plane_parameters(
757
+ cls, a: float, b: float, c: float, reference_frame: ReferenceFrame, plane: str = "xy", verbose: bool = False
758
+ ):
759
+ """Returns the unit axes and the origin of the given reference frame.
636
760
 
637
- Returns a Points object describing the unit axes and the origin of the reference frame defined by
638
- the input parameters.
639
-
640
- :param plane: The plane definition depends on the "plane" parameter plane must be in ["xy","yz","zx"]
641
- 'xy' : z = ax + by + c (origin defined at x = y = 0)
642
- 'yz' : x = ay + bz + c --> NOT IMPLEMENTED
643
- 'zx' : y = az + bx + c --> NOT IMPLEMENTED
761
+ Args:
762
+ a (float): Coefficient `a` in the equation of the target plane (z = ax + by + c)
763
+ b (float): Coefficient `b` in the equation of the target plane (z = ax = by + c)
764
+ c (float): Coefficient `c` in the equation of the target plane (z = ax + by + c)
765
+ reference_frame (ReferenceFrame): Reference frame.
766
+ plane (str): Plane to fit. Must be in ["xy", "yz", "zx"].
767
+ verbose (bool): If True, print the results on screen.
644
768
 
645
- :returns: A Points with 4 Point objects corresponding to
646
- - the 3 unit axes defining a coordinate system with this plane as "plane" plane
647
- - the origin
648
- in this order (origin last)
769
+ Returns:
770
+ Points object describing the unit axes and the origin of the reference frame defined by the input parameters.
649
771
  """
772
+
650
773
  if plane != "xy":
651
774
  print(f"WARNING: plane = {plane} NOT IMPLEMENTED")
652
- # p0 : on the Z-axis
653
- # x = y = 0
654
- p0 = np.array([0, 0, c])
655
775
 
656
- # pxy : on the intersection between target plane and plane // to xy passing through z=c
776
+ origin_coords = np.array([0, 0, c]) # Origin coordinates (x = y = 0, z = c)
777
+
778
+ # Intersection between
779
+ # - Target plane (z = ax + by + c)
780
+ # - Plane // to xy passing through z=c
781
+
657
782
  if np.abs(b) > 1.0e-5:
658
- # z = c, x = 1
659
- pxy = np.array([1, -a / float(b), c])
783
+ pxy = np.array([1, -a / float(b), c]) # z = c, x = 1
660
784
  else:
661
- # z = c, y = 1
662
- pxy = np.array([-b / float(a), 1, c])
785
+ pxy = np.array([-b / float(a), 1, c]) # z = c, y = 1
663
786
 
664
- # pyz : intersection of target plane and Y-Z plane
665
- # x = 0, y = 1
666
- pyz = np.array([0, 1, b + c])
787
+ # Intersection between:
788
+ # - Target plane (z = ax + by + c)
789
+ # - yz-plane (x = 0)
667
790
 
668
- """
669
- # pzx : intersection of target plane and Z-X plane
670
- if np.abs(a)>1.e-3:
671
- # y = 0, z = 1
672
- pzx = np.array([(1-c)/float(a),0,1])
673
- else:
674
- # y = 0, x = 1
675
- pzx = np.array([1,0,a+c])
676
- """
677
- #
678
- # xunit = unit vector from [0,0,0] along the intersection between target plane and plane // to xy passing through z=c
679
- xunit = (pxy - p0) / np.linalg.norm(pxy - p0)
680
- # ytemp = unit vector from [0,0,0] along the intersection between target plane and Y-Z plane
681
- # ytemp is in the target plane, but not perpendicular to xunit
682
- # its norm doesn't matter
683
- ytemp = pyz - p0 # /np.linalg.norm(pyz-p0)
684
-
685
- # xunit and ytemp are both in the plane
686
- # => zunit is perpendicular to both
687
- zunit = np.cross(xunit, ytemp)
688
- zunit /= np.linalg.norm(zunit)
689
-
690
- # yunit completes the right handed reference frame
691
- # The renormalisation shouldn't be necessary...
692
- yunit = np.cross(zunit, xunit)
693
- yunit /= np.linalg.norm(yunit)
694
-
695
- xu = Point(xunit, ref=ref)
696
- yu = Point(yunit, ref=ref)
697
- zu = Point(zunit, ref=ref)
698
- origin = Point(p0, ref=ref)
791
+ pyz = np.array([0, 1, b + c]) # x = 0, y = 1
792
+
793
+ # # pzx : intersection of target plane and Z-X plane
794
+ # if np.abs(a)>1.e-3:
795
+ # # y = 0, z = 1
796
+ # pzx = np.array([(1-c)/float(a),0,1])
797
+ # else:
798
+ # # y = 0, x = 1
799
+ # pzx = np.array([1,0,a+c])
800
+
801
+ # Unit vector from [0, 0, 0] along the intersection between the target plane and the plane // to xy passing
802
+ # through z=c
803
+
804
+ x_unit_coords = (pxy - origin_coords) / np.linalg.norm(pxy - origin_coords) # Normalise
805
+
806
+ # Unit vector from [0, 0, 0] along the intersection between the target plane and the yz-plane
807
+ # In the target plane but not perpendicular to x_unit_coords (its norm doesn't matter)
808
+
809
+ y_temp = pyz - origin_coords # /np.linalg.norm(pyz-p0)
810
+
811
+ # x_unit_coords and y_temp are both in the plane
812
+ # -> z_unit_coords is perpendicular to both
813
+
814
+ z_unit_coords = np.cross(x_unit_coords, y_temp)
815
+ z_unit_coords /= np.linalg.norm(z_unit_coords) # Normalise
816
+
817
+ # y_unit_coords completes the right-handed reference frame
818
+
819
+ y_unit_coords = np.cross(z_unit_coords, x_unit_coords)
820
+ y_unit_coords /= np.linalg.norm(y_unit_coords) # Normalise
821
+
822
+ x_unit_point = Point(x_unit_coords, reference_frame=reference_frame)
823
+ y_unit_point = Point(y_unit_coords, reference_frame=reference_frame)
824
+ z_unit_point = Point(z_unit_coords, reference_frame=reference_frame)
825
+ origin = Point(origin_coords, reference_frame=reference_frame)
699
826
 
700
827
  if verbose:
701
- print(f"xunit {xunit}")
702
- print(f"yunit {yunit}")
703
- print(f"zunit {zunit}")
704
- print(f"origin {p0}")
705
- print()
828
+ print(f"x_unit_coords {x_unit_coords}")
829
+ print(f"y_unit_coords {y_unit_coords}")
830
+ print(f"z_unit_coords {z_unit_coords}")
831
+ print(f"origin_coords {origin_coords}")
706
832
 
707
- return cls([xu, yu, zu, origin], ref=ref)
833
+ return cls([x_unit_point, y_unit_point, z_unit_point, origin], reference_frame=reference_frame)