flightdata 0.2.3__tar.gz → 0.2.5__tar.gz
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.
- {flightdata-0.2.3 → flightdata-0.2.5}/PKG-INFO +1 -1
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata/base/table.py +28 -11
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata/flight/ardupilot.py +3 -0
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata/flight/fields.py +13 -3
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata/flight/flight.py +71 -36
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata/flow.py +1 -1
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata/state.py +11 -7
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata.egg-info/PKG-INFO +1 -1
- {flightdata-0.2.3 → flightdata-0.2.5}/setup.cfg +1 -1
- {flightdata-0.2.3 → flightdata-0.2.5}/test/test_flight.py +2 -34
- {flightdata-0.2.3 → flightdata-0.2.5}/LICENSE +0 -0
- {flightdata-0.2.3 → flightdata-0.2.5}/README.md +0 -0
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata/__init__.py +0 -0
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata/base/__init__.py +0 -0
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata/base/collection.py +0 -0
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata/base/constructs.py +0 -0
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata/base/numpy_encoder.py +0 -0
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata/coefficients.py +0 -0
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata/environment/__init__.py +0 -0
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata/environment/environment.py +0 -0
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata/environment/wind.py +0 -0
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata/flight/__init__.py +0 -0
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata/model/__init__.py +0 -0
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata/model/aerodynamic.py +0 -0
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata/model/thrust.py +0 -0
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata/origin.py +0 -0
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata.egg-info/SOURCES.txt +0 -0
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata.egg-info/dependency_links.txt +0 -0
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata.egg-info/requires.txt +0 -0
- {flightdata-0.2.3 → flightdata-0.2.5}/flightdata.egg-info/top_level.txt +0 -0
- {flightdata-0.2.3 → flightdata-0.2.5}/setup.py +0 -0
- {flightdata-0.2.3 → flightdata-0.2.5}/test/test_fields.py +0 -0
- {flightdata-0.2.3 → flightdata-0.2.5}/test/test_origin.py +0 -0
|
@@ -63,7 +63,7 @@ class Table:
|
|
|
63
63
|
|
|
64
64
|
@classmethod
|
|
65
65
|
def from_dict(Cls, data):
|
|
66
|
-
if
|
|
66
|
+
if 'data' in data:
|
|
67
67
|
data = data['data']
|
|
68
68
|
return Cls(pd.DataFrame.from_dict(data).set_index("t", drop=False))
|
|
69
69
|
|
|
@@ -119,7 +119,7 @@ class Table:
|
|
|
119
119
|
yield self[ind]
|
|
120
120
|
|
|
121
121
|
@classmethod
|
|
122
|
-
def from_constructs(cls, *args,**kwargs):
|
|
122
|
+
def from_constructs(cls, *args,**kwargs) -> Self:
|
|
123
123
|
kwargs = dict(
|
|
124
124
|
**{list(cls.constructs.data.keys())[i]: arg for i, arg in enumerate(args)},
|
|
125
125
|
**kwargs
|
|
@@ -130,7 +130,7 @@ class Table:
|
|
|
130
130
|
x.to_pandas(
|
|
131
131
|
columns=cls.constructs[key].keys,
|
|
132
132
|
index=kwargs["time"].t
|
|
133
|
-
) for key, x in kwargs.items() if
|
|
133
|
+
) for key, x in kwargs.items() if x is not None
|
|
134
134
|
],
|
|
135
135
|
axis=1
|
|
136
136
|
)
|
|
@@ -163,10 +163,7 @@ class Table:
|
|
|
163
163
|
axis=0,
|
|
164
164
|
ignore_index=True
|
|
165
165
|
).set_index("t", drop=False))
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
166
|
+
|
|
170
167
|
def label(self, **kwargs) -> Self:
|
|
171
168
|
return self.__class__(self.data.assign(**kwargs))
|
|
172
169
|
|
|
@@ -203,6 +200,7 @@ class Table:
|
|
|
203
200
|
return 0
|
|
204
201
|
|
|
205
202
|
def unique_labels(self, cols = None) -> pd.DataFrame:
|
|
203
|
+
'''TODO Fix This'''
|
|
206
204
|
if cols is None:
|
|
207
205
|
cols = self.label_cols
|
|
208
206
|
return self.data.loc[:, cols].reset_index(drop=True).drop_duplicates().reset_index(drop=True)
|
|
@@ -319,14 +317,33 @@ class Table:
|
|
|
319
317
|
labs = np.array(self.single_labels())
|
|
320
318
|
return self.__class__(self.data[labs == lab])
|
|
321
319
|
|
|
322
|
-
def split_labels(self) -> dict[str, Self]:
|
|
323
|
-
'''
|
|
320
|
+
def split_labels(self, cols=None) -> dict[str, Self]:
|
|
321
|
+
'''Split into multiple tables based on the labels'''
|
|
324
322
|
res = {}
|
|
325
|
-
for
|
|
326
|
-
ld =
|
|
323
|
+
for label in self.unique_labels(cols).iterrows():
|
|
324
|
+
ld = label[1].to_dict()
|
|
327
325
|
res['_'.join(ld.values())] = self.get_label_subset(**ld)
|
|
328
326
|
return res
|
|
329
327
|
|
|
328
|
+
def cumulative_labels(self, *cols) -> Self:
|
|
329
|
+
'''Return a string concatenation of the requested labels. append an indexer to the end
|
|
330
|
+
of the string for repeat descrete groups of the same label.'''
|
|
331
|
+
cols = self.label_cols if len(cols)==0 else cols
|
|
332
|
+
labs = self.data.loc[:, cols].stack().groupby(level=0).apply('_'.join)
|
|
333
|
+
|
|
334
|
+
changes = labs.shift() != labs
|
|
335
|
+
new_labels = labs.loc[changes]
|
|
336
|
+
uls = []
|
|
337
|
+
for i, nl in enumerate(new_labels):
|
|
338
|
+
uls.append(sum(new_labels.iloc[:i] == nl))
|
|
339
|
+
|
|
340
|
+
df = pd.DataFrame(labs).assign(indexer = np.array(uls)[changes.cumsum() - 1])
|
|
341
|
+
strdf = df.copy()
|
|
342
|
+
strdf['indexer'] = strdf['indexer'].astype(int).astype(str)
|
|
343
|
+
strdf = strdf.stack().groupby(level=0).apply('_'.join)
|
|
344
|
+
strdf.loc[df.indexer==0] = df[0]
|
|
345
|
+
return strdf.values
|
|
346
|
+
|
|
330
347
|
@staticmethod
|
|
331
348
|
def copy_labels(template: Self, flown: Self, path=None, min_len=0) -> Self:
|
|
332
349
|
"""Copy the labels from along the index warping path"""
|
|
@@ -20,6 +20,9 @@ class Field:
|
|
|
20
20
|
def col(self) -> str:
|
|
21
21
|
return f'{self.column}_{self.i}' if self.i > 0 else self.column
|
|
22
22
|
|
|
23
|
+
def __repr__(self):
|
|
24
|
+
return f'{self.column}_{self.i}'
|
|
25
|
+
|
|
23
26
|
class Fields:
|
|
24
27
|
def __init__(self, data: Union[list[Field], dict[str: Field]]):
|
|
25
28
|
if isinstance(data, list):
|
|
@@ -51,9 +54,8 @@ class Fields:
|
|
|
51
54
|
else:
|
|
52
55
|
return self.data[f'{group}_{col}'].instance(instance)
|
|
53
56
|
except KeyError:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
return None
|
|
58
|
+
#raise AttributeError(f'Field {name} not found')
|
|
57
59
|
|
|
58
60
|
def get_fields(self, names: list[str]) -> list[Field]:
|
|
59
61
|
_l = lambda v: [v] if isinstance(v, Field) else v
|
|
@@ -62,6 +64,8 @@ class Fields:
|
|
|
62
64
|
def get_cols(self, names: list[str]) -> list[str]:
|
|
63
65
|
return [f.col for f in self.get_fields(names)]
|
|
64
66
|
|
|
67
|
+
def __repr__(self) -> str:
|
|
68
|
+
return f'Fields({','.join(list(self.data.keys()))})'
|
|
65
69
|
|
|
66
70
|
fields = Fields([
|
|
67
71
|
Field('time_flight', 'time since the start of the flight, seconds'),
|
|
@@ -80,9 +84,15 @@ fields = Fields([
|
|
|
80
84
|
Field('attitude_roll', 'roll angle, radians'),
|
|
81
85
|
Field('attitude_pitch', 'pitch angle, radians'),
|
|
82
86
|
Field('attitude_yaw', 'yaw angle, radians'),
|
|
87
|
+
Field('attdes_roll', 'desired roll angle, radians'),
|
|
88
|
+
Field('attdes_pitch', 'desired pitch angle, radians'),
|
|
89
|
+
Field('attdes_yaw', 'desired yaw angle, radians'),
|
|
83
90
|
Field('axisrate_roll', 'roll rate, radians / second'),
|
|
84
91
|
Field('axisrate_pitch', 'pitch rate, radians / second'),
|
|
85
92
|
Field('axisrate_yaw', 'yaw rate, radians / second'),
|
|
93
|
+
Field('desrate_roll', 'roll rate, radians / second'),
|
|
94
|
+
Field('desrate_pitch', 'pitch rate, radians / second'),
|
|
95
|
+
Field('desrate_yaw', 'yaw rate, radians / second'),
|
|
86
96
|
Field('battery_voltage', 'volts'),
|
|
87
97
|
Field('battery_current', 'amps'),
|
|
88
98
|
Field('battery_totalcurrent', 'Ah'),
|
|
@@ -9,6 +9,7 @@ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
9
9
|
You should have received a copy of the GNU General Public License along with
|
|
10
10
|
this program. If not, see <http://www.gnu.org/licenses/>.
|
|
11
11
|
"""
|
|
12
|
+
from __future__ import annotations
|
|
12
13
|
from typing import Self, Union, IO
|
|
13
14
|
import numpy as np
|
|
14
15
|
import pandas as pd
|
|
@@ -29,7 +30,7 @@ from scipy.signal import butter, filtfilt
|
|
|
29
30
|
class Flight:
|
|
30
31
|
ardupilot_types = [
|
|
31
32
|
'XKF1', 'XKF2', 'NKF1', 'NKF2',
|
|
32
|
-
'POS', 'ATT', 'ACC', 'GYRO', 'IMU',
|
|
33
|
+
'POS', 'ATT', 'RATE', 'ACC', 'GYRO', 'IMU',
|
|
33
34
|
'ARSP', 'GPS', 'RCIN', 'RCOU', 'BARO', 'MODE',
|
|
34
35
|
'RPM', 'MAG', 'BAT', 'BAT2', 'VEL', 'ORGN', 'ESC', 'CURRENT']
|
|
35
36
|
|
|
@@ -43,6 +44,10 @@ class Flight:
|
|
|
43
44
|
|
|
44
45
|
def __getattr__(self, name):
|
|
45
46
|
cols = getattr(fields, name)
|
|
47
|
+
if cols is None:
|
|
48
|
+
cols = [f for f in self.data.columns if f.startswith(name)]
|
|
49
|
+
if len(cols) > 0:
|
|
50
|
+
return self.data[cols]
|
|
46
51
|
try:
|
|
47
52
|
if isinstance(cols, Field):
|
|
48
53
|
return self.data[cols.col]
|
|
@@ -76,7 +81,15 @@ class Flight:
|
|
|
76
81
|
.loc[sli].set_index("time_flight", drop=False),
|
|
77
82
|
self.parameters, self.origin, self.primary_pos_source
|
|
78
83
|
)
|
|
79
|
-
|
|
84
|
+
|
|
85
|
+
def slice_time_flight(self, sli):
|
|
86
|
+
return Flight(
|
|
87
|
+
self.data.reset_index(drop=True)
|
|
88
|
+
.set_index('time_flight', drop=False)
|
|
89
|
+
.loc[sli],
|
|
90
|
+
self.parameters, self.origin, self.primary_pos_source
|
|
91
|
+
)
|
|
92
|
+
|
|
80
93
|
def copy(self, **kwargs):
|
|
81
94
|
return Flight(
|
|
82
95
|
kwargs['data'] if 'data' in kwargs else self.data.copy() ,
|
|
@@ -149,8 +162,6 @@ class Flight:
|
|
|
149
162
|
Returns:
|
|
150
163
|
list[Flight]: list of flights
|
|
151
164
|
"""
|
|
152
|
-
|
|
153
|
-
|
|
154
165
|
modechanges = (self.flightmode_a.diff().fillna(value=0) != 0).astype(int).cumsum()
|
|
155
166
|
|
|
156
167
|
flights = {flightmodes[m]: [] for m in self.flightmode_a.unique()}
|
|
@@ -185,17 +196,17 @@ class Flight:
|
|
|
185
196
|
assert_almost_equal(self.origin.pos, other.origin.pos)
|
|
186
197
|
assert self.origin.heading == other.origin.heading
|
|
187
198
|
return True
|
|
188
|
-
except:
|
|
199
|
+
except Exception as ex:
|
|
189
200
|
return False
|
|
190
201
|
|
|
191
202
|
@staticmethod
|
|
192
|
-
def from_log(log:Union[Ardupilot, str]):
|
|
203
|
+
def from_log(log:Union[Ardupilot, str], extra_types: list[str] = None, **kwargs) -> Flight:
|
|
193
204
|
"""Constructor from an ardupilot bin file."""
|
|
194
|
-
|
|
195
|
-
if
|
|
196
|
-
|
|
197
|
-
if isinstance(log, str):
|
|
198
|
-
parser = Ardupilot(log, types=Flight.ardupilot_types)
|
|
205
|
+
|
|
206
|
+
extra_types = [] if extra_types is None else extra_types
|
|
207
|
+
|
|
208
|
+
if isinstance(log, str) or isinstance(log, Path):
|
|
209
|
+
parser = Ardupilot(str(log), types=list(set(Flight.ardupilot_types + extra_types)))
|
|
199
210
|
else:
|
|
200
211
|
parser = log
|
|
201
212
|
|
|
@@ -219,6 +230,9 @@ class Flight:
|
|
|
219
230
|
attitude_roll = np.radians(att.Roll),
|
|
220
231
|
attitude_pitch = np.radians(att.Pitch),
|
|
221
232
|
attitude_yaw = np.radians(att.Yaw),
|
|
233
|
+
attdes_roll = np.radians(att.DesRoll),
|
|
234
|
+
attdes_pitch = np.radians(att.DesPitch),
|
|
235
|
+
attdes_yaw = np.radians(att.DesYaw),
|
|
222
236
|
))
|
|
223
237
|
|
|
224
238
|
if 'POS' in parser.dfs:
|
|
@@ -232,7 +246,7 @@ class Flight:
|
|
|
232
246
|
else:
|
|
233
247
|
ppsorce = 'position'
|
|
234
248
|
|
|
235
|
-
if
|
|
249
|
+
if ekf1 is not None:
|
|
236
250
|
dfs = dfs + Flight.parse_instances(ekf1, dict(
|
|
237
251
|
position_N='PN',
|
|
238
252
|
position_E='PE',
|
|
@@ -242,28 +256,46 @@ class Flight:
|
|
|
242
256
|
velocity_D='VD',
|
|
243
257
|
), 'C')
|
|
244
258
|
|
|
245
|
-
if
|
|
259
|
+
if ekf2 is not None:
|
|
246
260
|
dfs = dfs + Flight.parse_instances(ekf2, {
|
|
247
261
|
'wind_N': 'VWN',
|
|
248
262
|
'wind_E': 'VWE',
|
|
249
263
|
}, 'C')
|
|
264
|
+
if 'RATE' in parser.dfs:
|
|
265
|
+
dfs.append(Flight.build_cols(
|
|
266
|
+
time_actual = parser.rate.timestamp,
|
|
267
|
+
axisrate_roll = parser.rate.R,
|
|
268
|
+
axisrate_pitch = parser.rate.P,
|
|
269
|
+
axisrate_yaw = parser.rate.Y,
|
|
270
|
+
desrate_roll = parser.rate.RDes,
|
|
271
|
+
desrate_pitch = parser.rate.PDes,
|
|
272
|
+
desrate_yaw = parser.rate.YDes,
|
|
273
|
+
))
|
|
250
274
|
|
|
251
275
|
if 'IMU' in parser.dfs:
|
|
252
276
|
imu = parser.IMU
|
|
253
277
|
if 'I' in imu:
|
|
254
278
|
imu = imu.loc[imu.I==0, :]
|
|
255
279
|
|
|
256
|
-
if
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
280
|
+
if ekf1 is not None:
|
|
281
|
+
if 'C' in ekf1.columns:
|
|
282
|
+
imu = pd.merge_asof(imu, ekf1.loc[ekf1.C==0], on='timestamp', direction='nearest')
|
|
283
|
+
else:
|
|
284
|
+
imu = pd.merge_asof(imu, ekf1, on='timestamp', direction='nearest')
|
|
285
|
+
if all([v in imu.columns for v in ['GX', 'GY', 'GZ']]):
|
|
286
|
+
imu['GyrX'] = imu.GyrX + np.radians(imu.GX) / 100
|
|
287
|
+
imu['GyrY'] = imu.GyrY + np.radians(imu.GY) / 100
|
|
288
|
+
imu['GyrZ'] = imu.GyrZ + np.radians(imu.GZ) / 100
|
|
289
|
+
|
|
290
|
+
if ekf2 is not None:
|
|
291
|
+
if 'C' in ekf2.columns:
|
|
292
|
+
imu = pd.merge_asof(imu, ekf2.loc[ekf2.C==0], on='timestamp', direction='nearest')
|
|
293
|
+
else:
|
|
294
|
+
imu = pd.merge_asof(imu, ekf2, on='timestamp', direction='nearest')
|
|
295
|
+
if all([v in imu.columns for v in ['AX', 'AY', 'AZ']]):
|
|
296
|
+
imu['AccX'] = imu.AccX + imu.AX / 100
|
|
297
|
+
imu['AccY'] = imu.AccY + imu.AY / 100
|
|
298
|
+
imu['AccZ'] = imu.AccZ + imu.AZ / 100
|
|
267
299
|
|
|
268
300
|
dfs.append(Flight.build_cols(
|
|
269
301
|
time_actual = imu.timestamp,
|
|
@@ -307,6 +339,7 @@ class Flight:
|
|
|
307
339
|
))
|
|
308
340
|
|
|
309
341
|
if 'MODE' in parser.dfs:
|
|
342
|
+
|
|
310
343
|
df = Flight.build_cols(
|
|
311
344
|
time_actual = parser.MODE.timestamp,
|
|
312
345
|
flightmode_a = parser.MODE.Mode,
|
|
@@ -337,20 +370,25 @@ class Flight:
|
|
|
337
370
|
**{f'motor_rpm_{i}': parser.RPM[f'rpm{i}'] for i in range(2) if f'rpm{i}' in parser.RPM.columns},
|
|
338
371
|
))
|
|
339
372
|
|
|
340
|
-
|
|
373
|
+
for k, v in kwargs.items():
|
|
374
|
+
if k in parser.dfs:
|
|
375
|
+
dfs = dfs + Flight.parse_instances(parser.dfs[k], v)
|
|
341
376
|
|
|
342
|
-
origin = Origin('ekf_origin', GPS(parser.ORGN.iloc[:,-3:]), 0)
|
|
343
|
-
|
|
344
377
|
dfout = dfs[0]
|
|
345
|
-
|
|
378
|
+
dt = dfout.time_actual.diff().max()
|
|
346
379
|
for df in dfs[1:]:
|
|
347
|
-
dfout = pd.merge_asof(
|
|
380
|
+
dfout = pd.merge_asof(
|
|
381
|
+
dfout, df, on='time_actual', direction='nearest',
|
|
382
|
+
tolerance=min(max(dt, df.time_actual.diff().max()), 0.1)
|
|
383
|
+
)
|
|
348
384
|
|
|
349
|
-
|
|
385
|
+
origin = Origin('ekf_origin', GPS(parser.ORGN.iloc[:,-3:]), 0)
|
|
386
|
+
|
|
387
|
+
return Flight(dfout.set_index('time_flight', drop=False), parser.parms, origin, ppsorce)
|
|
350
388
|
|
|
351
389
|
@staticmethod
|
|
352
390
|
def parse_instances(indf: pd.DataFrame, colmap:dict[str, str], instancecol='Instance'):
|
|
353
|
-
|
|
391
|
+
'''Where an instance column exists in an input df split the values into two columns'''
|
|
354
392
|
instances = indf[instancecol].unique() if instancecol in indf.columns else [0]
|
|
355
393
|
dfs = []
|
|
356
394
|
for i in instances:
|
|
@@ -413,7 +451,6 @@ class Flight:
|
|
|
413
451
|
], axis=1).reset_index(drop=True).set_index('time_flight', drop=False)
|
|
414
452
|
)
|
|
415
453
|
|
|
416
|
-
|
|
417
454
|
def filter(self, b, a):
|
|
418
455
|
dont_filter = [c for c in fields.get_cols(['time', 'flightmode', 'rcin', 'rcout']) if c in self.data.columns]
|
|
419
456
|
unwrap_cols = [c for c in fields.get_cols(['attitude']) if c in self.data.columns]
|
|
@@ -432,11 +469,9 @@ class Flight:
|
|
|
432
469
|
)
|
|
433
470
|
|
|
434
471
|
def butter_filter(self, cutoff, order=5):
|
|
435
|
-
|
|
436
472
|
ts = self.time_flight.to_numpy()
|
|
437
473
|
N = len(self)
|
|
438
474
|
T = (ts[-1] - ts[0]) / N
|
|
439
|
-
|
|
440
475
|
fs = 1/T
|
|
441
|
-
|
|
442
|
-
|
|
476
|
+
return self.filter(*butter(order, cutoff, fs=fs, btype='low', analog=False))
|
|
477
|
+
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
from typing import Union, List, Tuple, Self
|
|
3
|
-
import warnings
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
import numpy as np
|
|
6
5
|
import pandas as pd
|
|
@@ -29,10 +28,10 @@ class State(Table):
|
|
|
29
28
|
return g.Transformation(-self.pos, self.att.inverse())
|
|
30
29
|
|
|
31
30
|
@staticmethod
|
|
32
|
-
def from_transform(transform: g.Transformation=None, **kwargs):
|
|
31
|
+
def from_transform(transform: g.Transformation=None, **kwargs) -> State:
|
|
33
32
|
if transform is None:
|
|
34
33
|
transform = g.Transformation()
|
|
35
|
-
if
|
|
34
|
+
if "time" not in kwargs:
|
|
36
35
|
kwargs["time"] = g.Time.from_t(np.linspace(0, State._construct_freq*len(transform), len(transform)))
|
|
37
36
|
return State.from_constructs(pos=transform.p, att=transform.q, **kwargs)
|
|
38
37
|
|
|
@@ -64,8 +63,8 @@ class State(Table):
|
|
|
64
63
|
att = st.att.body_rotate(rvel * time.t)
|
|
65
64
|
pos = g.Point.concatenate([
|
|
66
65
|
g.P0(),
|
|
67
|
-
(att.transform_point(vel)).cumsum()[:-1]
|
|
68
|
-
])
|
|
66
|
+
(att.transform_point(vel) * time.dt).cumsum()[:-1]
|
|
67
|
+
]) + st.pos
|
|
69
68
|
return State.from_constructs(time,pos, att, vel, rvel)
|
|
70
69
|
|
|
71
70
|
|
|
@@ -424,9 +423,14 @@ class State(Table):
|
|
|
424
423
|
def direction(self):
|
|
425
424
|
"""returns 1 for going right, -1 for going left"""
|
|
426
425
|
return np.sign(self.att.transform_point(g.Point(1, 0, 0)).x)
|
|
427
|
-
|
|
426
|
+
|
|
427
|
+
def cross_direction(self):
|
|
428
|
+
"""returns 1 for going out, -1 for coming in"""
|
|
429
|
+
return np.sign(self.att.transform_point(g.Point(1, 0, 0)).y)
|
|
430
|
+
|
|
431
|
+
|
|
428
432
|
def inverted(self):
|
|
429
|
-
return
|
|
433
|
+
return self.att.transform_point(g.Point(0, 0, 1)).z > 0
|
|
430
434
|
|
|
431
435
|
def upright(self):
|
|
432
436
|
return not self.inverted()
|
|
@@ -5,7 +5,7 @@ author_email = thomasdavid0@gmail.com
|
|
|
5
5
|
description = Module for handling UAV flight log data
|
|
6
6
|
long_description = file: README.md
|
|
7
7
|
long_description_content_type = text/markdown
|
|
8
|
-
version = 0.2.
|
|
8
|
+
version = 0.2.5
|
|
9
9
|
url = https://github.com/PyFlightCoach/FlightData
|
|
10
10
|
|
|
11
11
|
[options]
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
from flightdata import Flight, Origin
|
|
2
|
-
import os
|
|
3
2
|
from io import open
|
|
4
|
-
from json import load
|
|
3
|
+
from json import load
|
|
5
4
|
from pytest import fixture, approx, mark
|
|
6
5
|
import numpy as np
|
|
7
6
|
import pandas as pd
|
|
8
7
|
from ardupilot_log_reader import Ardupilot
|
|
9
|
-
from geometry import GPS
|
|
10
|
-
from geometry.testing import assert_almost_equal
|
|
11
8
|
|
|
12
9
|
|
|
13
10
|
@fixture(scope='session')
|
|
@@ -23,7 +20,6 @@ def fl():
|
|
|
23
20
|
def fcj():
|
|
24
21
|
return Flight.from_fc_json('test/test_inputs/00000137.json')
|
|
25
22
|
|
|
26
|
-
|
|
27
23
|
def test_duration(fl):
|
|
28
24
|
assert fl.duration == approx(685, rel=1e-3)
|
|
29
25
|
|
|
@@ -31,7 +27,6 @@ def test_slice(fl):
|
|
|
31
27
|
short_flight = fl[100:200]
|
|
32
28
|
assert short_flight.duration == approx(100, 0.01)
|
|
33
29
|
|
|
34
|
-
|
|
35
30
|
def test_to_from_dict(fl):
|
|
36
31
|
data = fl.to_dict()
|
|
37
32
|
fl2 = Flight.from_dict(data)
|
|
@@ -43,7 +38,6 @@ def test_from_fc_json(fcj):
|
|
|
43
38
|
assert fcj.duration > 200
|
|
44
39
|
assert fcj.position_D.max() < -10
|
|
45
40
|
|
|
46
|
-
|
|
47
41
|
@mark.skip
|
|
48
42
|
def test_unique_identifier():
|
|
49
43
|
with open("test/test_inputs/manual_F3A_P21_21_09_24_00000052.json", "r") as f:
|
|
@@ -54,27 +48,18 @@ def test_unique_identifier():
|
|
|
54
48
|
|
|
55
49
|
assert flight1.unique_identifier() == flight2.unique_identifier()
|
|
56
50
|
|
|
57
|
-
|
|
58
51
|
@mark.skip
|
|
59
52
|
def test_baro(fl):
|
|
60
53
|
press = fl.air_pressure
|
|
61
|
-
temp = fl.air_temperature
|
|
62
54
|
assert press.iloc[0,0] < 120000
|
|
63
55
|
assert press.iloc[0,0] > 90000
|
|
64
56
|
|
|
65
|
-
|
|
66
|
-
@mark.skip
|
|
67
|
-
def test_ekfv2(fl):
|
|
68
|
-
pass
|
|
69
|
-
|
|
70
|
-
|
|
71
57
|
def test_flying_only(fl: Flight):
|
|
72
58
|
flt = fl.flying_only()
|
|
73
59
|
assert isinstance(flt, Flight)
|
|
74
60
|
assert flt.duration < fl.duration
|
|
75
61
|
assert flt[0].gps_altitude > 5
|
|
76
62
|
|
|
77
|
-
|
|
78
63
|
def test_slice_raw_t(fl: Flight):
|
|
79
64
|
sli = fl.slice_raw_t(slice(100, None, None))
|
|
80
65
|
assert isinstance(sli, Flight)
|
|
@@ -86,7 +71,7 @@ def test_origin(fl: Flight):
|
|
|
86
71
|
|
|
87
72
|
@fixture(scope='session')
|
|
88
73
|
def vtol_hover():
|
|
89
|
-
return Flight.
|
|
74
|
+
return Flight.from_json('test/data/vtol_hover.json')
|
|
90
75
|
|
|
91
76
|
def test_flightmode_split(vtol_hover: Flight):
|
|
92
77
|
smodes = vtol_hover.split_modes()
|
|
@@ -94,8 +79,6 @@ def test_flightmode_split(vtol_hover: Flight):
|
|
|
94
79
|
assert isinstance(smodes['QHOVER'], list)
|
|
95
80
|
assert isinstance(smodes['QHOVER'][0], Flight)
|
|
96
81
|
|
|
97
|
-
|
|
98
|
-
|
|
99
82
|
def _fft(col: pd.Series):
|
|
100
83
|
from scipy.fft import fft, fftfreq
|
|
101
84
|
ts = col.index
|
|
@@ -107,7 +90,6 @@ def _fft(col: pd.Series):
|
|
|
107
90
|
|
|
108
91
|
return xf, 2.0/N * np.abs(yf[0:N//2])
|
|
109
92
|
|
|
110
|
-
|
|
111
93
|
def test_butter_filter(fl: Flight):
|
|
112
94
|
filtered = fl.butter_filter(1,5)
|
|
113
95
|
|
|
@@ -119,17 +101,3 @@ def test_butter_filter(fl: Flight):
|
|
|
119
101
|
def test_remove_time_flutter(fl: Flight):
|
|
120
102
|
flf = fl.remove_time_flutter()
|
|
121
103
|
assert np.gradient(np.gradient(flf.data.index)) == approx(0)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
#import plotly.graph_objects as go
|
|
125
|
-
|
|
126
|
-
#fig = go.Figure()
|
|
127
|
-
#fig.add_trace(go.Scatter(x=fl.time_flight, y=fl.acceleration_x, name='original'))
|
|
128
|
-
#fig.add_trace(go.Scatter(x=filtered.time_flight, y=filtered.acceleration_x, name='filtered'))
|
|
129
|
-
#fig.show()
|
|
130
|
-
#
|
|
131
|
-
#
|
|
132
|
-
#fig = go.Figure()
|
|
133
|
-
#fig.add_trace(go.Scatter(x=x, y=y, name='original'))
|
|
134
|
-
#fig.add_trace(go.Scatter(x=xf, y=yf, name='filtered'))
|
|
135
|
-
#fig.show()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|