BAMWelDX / weldx

The welding data exchange format
https://www.bam.de/weldx
BSD 3-Clause "New" or "Revised" License
20 stars 10 forks source link

support TimeSeries as coordinate/rotation objects #51

Open CagtayFabry opened 4 years ago

CagtayFabry commented 4 years ago

Similar to the astropy implementation of DynamicMatrixTransform I think we should consider supporting callable functions for defining translations and rotations as a future addition

These objects would return values at specific timestamps on request (in the appropriate xarray format), ideally with a similar syntax as our "data-based" coordinate systems. Schema implementation could reuse the mathematical_expression with matching input/output shape definitions.

Some example use cases regarding welding applications:

CagtayFabry commented 4 years ago

we should be closer to this with #76

vhirtham commented 3 years ago

So just to get this sorted. We are aiming for some interfaces like this?

t = DynamicTranslation(x="2*t + 3", y="3", z="2*sin(t)")
m = t.interp_time(Q_([1, 2, 3], "s"))

where m is an instance of LocalCoordinateSystem? Or for a rotation:

r = DynamicRotation(sequence = "xy", angles=["10*t", "5*t"], degrees=True) 
CagtayFabry commented 3 years ago

ideally something like:

# omitting `TimeSeries` args but of course there would have to be a check on dimensions etc.
lcs_0 = LCS(coordinates=TimeSeries(...), orientation=TimeSeries(...))

only interpolate when requested by CSM or otherwise. Of course mix/match between discrete translation/rotation for LCS should also be viable

Creating something like DynamicRotation would also make sense when subclassing TimeSeries with additional restrictions (or just "easily/safely" create proper a TimeSeries )

vhirtham commented 3 years ago

Not sure about the TimeSeries here. If we have dedicated objects with an overloaded call operator I think it is okay, because it is their purpose. However, the TimeSeries has multiple other functionalities, and making it callable would be somehow strange. Feels unintuitive. Maybe we can simply use a function of the same name that is implemented for all objects that should support this feature?

CagtayFabry commented 3 years ago

my thought was that TimeSeries is the default object representing time dependent data so it would fit here We only really need interp_time from the TimeSeries object for this

That should allow something like

ts_sine = weldx.util.sine(f=Q_(0.5 * 2 * np.pi, "Hz"), amp=Q_([[0, 0.75, 0]], "mm"))
tcp_sine = lcs(coordinates=ts_sine)

instead of

ts_sine = util.sine(f=Q_(0.5 * 2 * np.pi, "Hz"), amp=Q_([[0, 0.75, 0]], "mm"))
t = pd.timedelta_range(start=t_start, end=t_end, freq="10ms")
ts_sine_data = util.lcs_coords_from_ts(ts_sine, t)
tcp_sine = lcs(coordinates=ts_sine_data)

(from https://weldx.readthedocs.io/en/latest/tutorials/welding_example_02_weaving.html#add-a-sine-wave-to-the-TCP-movement)

/maybe my *args syntax was unfortunate in the initial example

vhirtham commented 3 years ago

I think I got confused about the return value of the call-operator. Got it now.

vhirtham commented 3 years ago

Reminder: Implement interp_like for TimeSeries and use it in the CSM

vhirtham commented 3 years ago

I started implementing this feature in PR #366. Turns out that this is rather complicated for several reasons. I decided to split the work across several PRs. The mentioned one will feature only coordinates as TimeSeries. In a follow-up, we should discuss how to threat orientations because I am not really sure about the best approach here. Sympy supports matrices as far as I can see, but I don't know if the interfaces aren't too complex to pass them to our future user base without wrapping the important features first.

CagtayFabry commented 2 years ago

for rotations I suggest the following: we add the ability to pass a TimeSeries to the LCS constructor like we did with the coordinates.

For now it should be enough to support only one very specific definition of this TimeSeries (otherwise we would have to expose an additional API to let the user classify the format). For now the TimeSeries should be be of shape [t,3] and represent a xyz Euler rotation

here is an example that defines a TimeSeries representing a rotation of 60 deg/s around the x-axis, -10 deg/s around the y-axis and 1 deg/s around the z-axis:

import pandas as pd

from weldx import MathematicalExpression, TimeSeries, WXRotation, Q_
from weldx.welding.util import sine

expr_string = "o*t"
parameters = {"o": Q_([[60, -10, 1]], "deg/s")}
expr = MathematicalExpression(expression=expr_string, parameters=parameters)
expr
#> <MathematicalExpression>
#> Expression:
#>   o*t
#> Parameters:
#>  o = [[ 60 -10   1]] deg / s
#> 

ts_vector = TimeSeries(data=expr)
ts = ts_vector.interp_time(pd.timedelta_range(start="0s", end="5s", freq="1000ms"))
ts.data.shape
#> (6, 3)
ts.data
#> <Quantity([[ 0.         -0.          0.        ]
#>  [ 1.04719755 -0.17453293  0.01745329]
#>  [ 2.0943951  -0.34906585  0.03490659]
#>  [ 3.14159265 -0.52359878  0.05235988]
#>  [ 4.1887902  -0.6981317   0.06981317]
#>  [ 5.23598776 -0.87266463  0.08726646]], 'dimensionless')>

rot = WXRotation.from_euler(seq="xyz", angles=ts.data, degrees=False)
rot.as_euler(seq="xyz", degrees=True)
#> array([[   0.,    0.,    0.],
#>        [  60.,  -10.,    1.],
#>        [ 120.,  -20.,    2.],
#>        [ 180.,  -30.,    3.],
#>        [-120.,  -40.,    4.],
#>        [ -60.,  -50.,    5.]])

The ts_vector would be the input for LCS(orientation=ts_vector)

One thing I noticed: For some reason, the Mathexpression converts back to dimensionless (=rad) instead of deg, you can see that in the output of ts.data. I am not sure if that always happens or where. You can see that it is actually the correct rotation represented in rad when transforming back using the WXRotation object. Internally in the LCS we could try to always convert to and work with rad for interpolation just to be sure.