flightdata 0.2.20__tar.gz → 0.2.22__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.20/flightdata.egg-info → flightdata-0.2.22}/PKG-INFO +3 -3
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/__init__.py +5 -2
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/base/constructs.py +4 -6
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/bindata.py +26 -12
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/flight/fields.py +1 -1
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/flight/flight.py +55 -65
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/origin.py +44 -16
- flightdata-0.2.22/flightdata/schemas/fcj.py +174 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/state.py +10 -10
- {flightdata-0.2.20 → flightdata-0.2.22/flightdata.egg-info}/PKG-INFO +3 -3
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata.egg-info/SOURCES.txt +2 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata.egg-info/requires.txt +2 -2
- {flightdata-0.2.20 → flightdata-0.2.22}/requirements-dev.txt +1 -1
- {flightdata-0.2.20 → flightdata-0.2.22}/requirements.txt +1 -1
- {flightdata-0.2.20 → flightdata-0.2.22}/test/conftest.py +4 -2
- {flightdata-0.2.20 → flightdata-0.2.22}/test/test_flight.py +5 -8
- flightdata-0.2.22/test/test_state/__init__.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/test_state/test_state_builders.py +6 -8
- {flightdata-0.2.20 → flightdata-0.2.22}/.github/workflows/publish_pypi.yml +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/.gitignore +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/.vscode/launch.json +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/.vscode/settings.json +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/LICENSE +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/README.md +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/examples/__init__.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/examples/data/__init__.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/examples/data/manual_F3A_F23_22_04_28_00000231.json +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/examples/data/manual_F3A_P23_22_05_31_00000350.json +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/examples/data/manual_F3A_P23_23_08_11_00000094.json +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/examples/flight_dynamics/00000150.json +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/examples/flight_dynamics/__init__.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/examples/flight_dynamics/box.f3a +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/examples/flight_dynamics/param_id.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/examples/state_analysis/__init__.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/examples/state_analysis/axes.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/examples/state_analysis/state_fill_plot.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/base/__init__.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/base/collection.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/base/labeling.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/base/numpy_encoder.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/base/table.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/coefficients.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/environment/__init__.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/environment/environment.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/environment/wind.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/flight/__init__.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/flight/ardupilot.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/flight/parameters.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/flow.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/model/__init__.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/model/aerodynamic.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/model/thrust.py +0 -0
- {flightdata-0.2.20/test → flightdata-0.2.22/flightdata/schemas}/__init__.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/scripts/collect_logs.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata/scripts/flightline.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata.egg-info/dependency_links.txt +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata.egg-info/entry_points.txt +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/flightdata.egg-info/top_level.txt +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/pyproject.toml +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/setup.cfg +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/EmailedBox.f3a +0 -0
- {flightdata-0.2.20/test/base → flightdata-0.2.22/test}/__init__.py +0 -0
- {flightdata-0.2.20/test/test_environment → flightdata-0.2.22/test/base}/__init__.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/base/test_base_constructs.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/base/test_table.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/data/bin_parser_GPS.csv +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/data/bin_parser_POS.csv +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/data/make_inputs.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/data/manual_F3A_P23.json +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/data/p23.BIN +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/data/p23.json +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/data/p23_box.f3a +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/data/p23_fc.json +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/data/p23_flight.json +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/data/vtol_hover.bin +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/data/vtol_hover.json +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/data/web_bin_parse.json +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/test_bindata.py +0 -0
- {flightdata-0.2.20/test/test_model → flightdata-0.2.22/test/test_environment}/__init__.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/test_environment/test_environment.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/test_environment/test_environment_wind.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/test_fields.py +0 -0
- {flightdata-0.2.20/test/test_state → flightdata-0.2.22/test/test_model}/__init__.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/test_model/test_model_coefficients.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/test_model/test_model_flow.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/test_origin.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/test_state/test_state.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/test_state/test_state_conversions.py +0 -0
- {flightdata-0.2.20 → flightdata-0.2.22}/test/test_state/test_state_measurements.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: flightdata
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.22
|
|
4
4
|
Summary: Module for handling UAV flight log data
|
|
5
5
|
Author-email: Thomas David <thomasdavid0@gmail.com>
|
|
6
6
|
License: GNU GPL v3
|
|
@@ -11,13 +11,13 @@ License-File: LICENSE
|
|
|
11
11
|
Requires-Dist: numpy
|
|
12
12
|
Requires-Dist: pandas
|
|
13
13
|
Requires-Dist: simplejson
|
|
14
|
-
Requires-Dist: pfc-geometry>=0.2.
|
|
14
|
+
Requires-Dist: pfc-geometry>=0.2.12
|
|
15
15
|
Requires-Dist: json_stream
|
|
16
16
|
Provides-Extra: dev
|
|
17
17
|
Requires-Dist: numpy; extra == "dev"
|
|
18
18
|
Requires-Dist: pandas; extra == "dev"
|
|
19
19
|
Requires-Dist: simplejson; extra == "dev"
|
|
20
|
-
Requires-Dist: pfc-geometry>=0.2.
|
|
20
|
+
Requires-Dist: pfc-geometry>=0.2.12; extra == "dev"
|
|
21
21
|
Requires-Dist: ardupilot-log-reader>=0.3.3; extra == "dev"
|
|
22
22
|
Requires-Dist: pytest; extra == "dev"
|
|
23
23
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
from .schemas import fcj
|
|
1
2
|
from .base import *
|
|
2
3
|
from .bindata import BinData
|
|
3
|
-
from .origin import Origin
|
|
4
|
+
from .origin import Origin, FCJOrigin
|
|
4
5
|
from .flight import *
|
|
5
6
|
from .environment import *
|
|
6
7
|
from .coefficients import Coefficients
|
|
7
8
|
from .flow import Flow, Attack
|
|
8
9
|
from .state import State
|
|
9
|
-
from .model import *
|
|
10
|
+
from .model import *
|
|
11
|
+
|
|
12
|
+
|
|
@@ -8,9 +8,7 @@
|
|
|
8
8
|
rvel = rotational velocity (body axis rates)
|
|
9
9
|
|
|
10
10
|
"""
|
|
11
|
-
|
|
12
|
-
import pandas as pd
|
|
13
|
-
from typing import List, Dict, Union
|
|
11
|
+
from typing import Union
|
|
14
12
|
from .collection import Collection
|
|
15
13
|
from itertools import chain
|
|
16
14
|
|
|
@@ -31,18 +29,18 @@ class Constructs(Collection):
|
|
|
31
29
|
super().__init__(data)
|
|
32
30
|
self._cols = list(chain(*[c.keys for c in self.data.values()]))
|
|
33
31
|
|
|
34
|
-
def subset(self, names:
|
|
32
|
+
def subset(self, names: list[str]):
|
|
35
33
|
"""get a subset of the constructs"""
|
|
36
34
|
return Constructs({key: value for key, value in self.data.items() if key in names})
|
|
37
35
|
|
|
38
|
-
def existing(self, vars:
|
|
36
|
+
def existing(self, vars: list[str]):
|
|
39
37
|
"""return a subset that is fully populated by the list of keys input"""
|
|
40
38
|
return self.subset([
|
|
41
39
|
key for key, value in self.data.items()
|
|
42
40
|
if all(val in vars for val in value.keys)
|
|
43
41
|
])
|
|
44
42
|
|
|
45
|
-
def missing(self, vars:
|
|
43
|
+
def missing(self, vars: list[str]):
|
|
46
44
|
"""return a subset that has not been populated by the list of vars"""
|
|
47
45
|
return self.subset([
|
|
48
46
|
key for key, value in self.data.items()
|
|
@@ -4,7 +4,8 @@ import numpy as np
|
|
|
4
4
|
import pandas as pd
|
|
5
5
|
import logging
|
|
6
6
|
|
|
7
|
-
logger = logging.getLogger(__name__)
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
8
9
|
|
|
9
10
|
@dataclass
|
|
10
11
|
class BinData:
|
|
@@ -15,6 +16,14 @@ class BinData:
|
|
|
15
16
|
return self.dfs[name]
|
|
16
17
|
raise AttributeError(f"No such attribute: {name}")
|
|
17
18
|
|
|
19
|
+
def get_dft(self, df_name: str, t: float, coren: str|None='C', coreid: int=0) -> pd.DataFrame:
|
|
20
|
+
bdx = self.dfs[df_name].copy()
|
|
21
|
+
bdx['TimeUS'] = bdx.TimeUS / 1e6
|
|
22
|
+
if coren and coren in bdx.columns:
|
|
23
|
+
bdx = bdx.loc[bdx[coren]==coreid]
|
|
24
|
+
bdx = bdx.set_index('TimeUS')
|
|
25
|
+
return bdx.iloc[bdx.index.get_indexer([t], method='nearest')]
|
|
26
|
+
|
|
18
27
|
def parameters(self) -> dict[str, pd.DataFrame]:
|
|
19
28
|
gb = self.PARM.groupby("Name")
|
|
20
29
|
|
|
@@ -35,11 +44,10 @@ class BinData:
|
|
|
35
44
|
@staticmethod
|
|
36
45
|
def parse_json(bindata: dict[str, dict[str, list]]) -> BinData:
|
|
37
46
|
# dfs = {k: pd.DataFrame(v) for k,v in bindata.items()}
|
|
38
|
-
|
|
47
|
+
|
|
39
48
|
dfs: dict[str, pd.DataFrame] = {}
|
|
40
49
|
groups = {}
|
|
41
50
|
for k, v in bindata.items():
|
|
42
|
-
|
|
43
51
|
if k == "PARM":
|
|
44
52
|
new_df = pd.DataFrame(
|
|
45
53
|
[
|
|
@@ -49,17 +57,23 @@ class BinData:
|
|
|
49
57
|
],
|
|
50
58
|
index=["time_boot_s", "Name", "Value"],
|
|
51
59
|
).T
|
|
52
|
-
if
|
|
60
|
+
if "Default" in v:
|
|
53
61
|
new_df["Default"] = v["Default"]
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
elif k == "MSG":
|
|
63
|
+
new_df = pd.DataFrame(
|
|
64
|
+
[
|
|
65
|
+
pd.Series(v["time_boot_s"]).reset_index(drop=True),
|
|
66
|
+
pd.Series(v["Message"]),
|
|
67
|
+
],
|
|
68
|
+
index=["time_boot_s", "Message"],
|
|
69
|
+
).T
|
|
56
70
|
else:
|
|
57
71
|
try:
|
|
58
|
-
new_df = pd.DataFrame(v)
|
|
72
|
+
new_df = pd.DataFrame(v)
|
|
59
73
|
except Exception as ex:
|
|
60
74
|
logger.info(f"Error parsing {k}: {ex}")
|
|
61
75
|
new_df = None
|
|
62
|
-
|
|
76
|
+
|
|
63
77
|
if new_df is not None:
|
|
64
78
|
if "[" not in k:
|
|
65
79
|
dfs[k] = new_df
|
|
@@ -68,7 +82,7 @@ class BinData:
|
|
|
68
82
|
if nk not in groups:
|
|
69
83
|
groups[nk] = []
|
|
70
84
|
groups[nk].append(new_df)
|
|
71
|
-
|
|
85
|
+
|
|
72
86
|
for k, v in groups.items():
|
|
73
87
|
dfs[k] = pd.concat(v).sort_values("time_boot_s")
|
|
74
88
|
|
|
@@ -76,12 +90,12 @@ class BinData:
|
|
|
76
90
|
start_time = (
|
|
77
91
|
BinData._gpsTimeToTime(dfs["GPS"].GWk.iloc[0], dfs["GPS"].GMS.iloc[0])
|
|
78
92
|
- dfs["GPS"].time_boot_s.iloc[0]
|
|
79
|
-
)
|
|
93
|
+
)
|
|
80
94
|
else:
|
|
81
|
-
start_time = 0
|
|
95
|
+
start_time = 0
|
|
82
96
|
|
|
83
97
|
def process_df(df: pd.DataFrame) -> pd.DataFrame:
|
|
84
|
-
df.insert(0, "TimeUS", np.floor(df.time_boot_s * 1e6))
|
|
98
|
+
df.insert(0, "TimeUS", np.floor(df.time_boot_s * 1e6)) # ms
|
|
85
99
|
df.insert(0, "timestamp", start_time + df.time_boot_s)
|
|
86
100
|
|
|
87
101
|
return df.drop(columns="time_boot_s")
|
|
@@ -21,9 +21,11 @@ from time import time
|
|
|
21
21
|
from json import load, dump
|
|
22
22
|
from flightdata.base.numpy_encoder import NumpyEncoder
|
|
23
23
|
from .ardupilot import flightmodes
|
|
24
|
-
from flightdata import Origin
|
|
24
|
+
from flightdata import Origin, fcj
|
|
25
25
|
from numbers import Number
|
|
26
26
|
from scipy.signal import filtfilt, butter
|
|
27
|
+
from datetime import datetime
|
|
28
|
+
|
|
27
29
|
|
|
28
30
|
def filter(data, cutoff=25, order=5, fs=25):
|
|
29
31
|
return filtfilt(
|
|
@@ -32,6 +34,7 @@ def filter(data, cutoff=25, order=5, fs=25):
|
|
|
32
34
|
padlen=len(data) - 1,
|
|
33
35
|
)
|
|
34
36
|
|
|
37
|
+
|
|
35
38
|
class Flight:
|
|
36
39
|
ardupilot_types = [
|
|
37
40
|
"XKF1",
|
|
@@ -92,10 +95,11 @@ class Flight:
|
|
|
92
95
|
]
|
|
93
96
|
except KeyError:
|
|
94
97
|
if isinstance(cols, Field):
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
return pd.Series(np.full(len(self), np.nan), name=cols.col)
|
|
99
|
+
else:
|
|
100
|
+
return pd.DataFrame(
|
|
101
|
+
data=np.full((len(self), len(cols)), np.nan), columns=[f.col for f in cols]
|
|
102
|
+
)
|
|
99
103
|
raise AttributeError(f"'Flight' object has no attribute '{name}'")
|
|
100
104
|
|
|
101
105
|
def make_param_labels(
|
|
@@ -132,23 +136,17 @@ class Flight:
|
|
|
132
136
|
if sli < 0:
|
|
133
137
|
return self.data.iloc[sli]
|
|
134
138
|
else:
|
|
135
|
-
gl = self.data.index.
|
|
139
|
+
gl = self.data.index.get_indexer([sli], method="nearest")
|
|
136
140
|
return Flight(
|
|
137
141
|
self.data.iloc[gl],
|
|
138
|
-
self.parameters
|
|
142
|
+
self.parameters[:sli] if self.parameters else None,
|
|
143
|
+
self.origin,
|
|
144
|
+
self.primary_pos_source,
|
|
139
145
|
)
|
|
140
146
|
elif isinstance(sli, slice):
|
|
141
147
|
return Flight(
|
|
142
|
-
self.data.loc[
|
|
143
|
-
|
|
144
|
-
None if sli.start is None else sli.start + self.data.index[0],
|
|
145
|
-
None if sli.stop is None else sli.stop + self.data.index[0],
|
|
146
|
-
sli.step,
|
|
147
|
-
)
|
|
148
|
-
],
|
|
149
|
-
self.parameters.loc[
|
|
150
|
-
: None if sli.stop is None else sli.stop + self.data.index[0]
|
|
151
|
-
],
|
|
148
|
+
self.data.loc[slice(sli.start, sli.stop, sli.step)],
|
|
149
|
+
self.parameters[:sli.stop] if self.parameters else None,
|
|
152
150
|
self.origin,
|
|
153
151
|
self.primary_pos_source,
|
|
154
152
|
)
|
|
@@ -312,6 +310,10 @@ class Flight:
|
|
|
312
310
|
except Exception:
|
|
313
311
|
return False
|
|
314
312
|
|
|
313
|
+
def boot_time(self):
|
|
314
|
+
timestamp = self.time_actual.iloc[0]
|
|
315
|
+
return datetime.fromtimestamp(timestamp) if not np.isnan(timestamp) else None
|
|
316
|
+
|
|
315
317
|
@staticmethod
|
|
316
318
|
def from_log(
|
|
317
319
|
log: str,
|
|
@@ -324,8 +326,9 @@ class Flight:
|
|
|
324
326
|
ppsource = xkf or pos
|
|
325
327
|
"""
|
|
326
328
|
parser = log
|
|
327
|
-
if not hasattr(log,
|
|
329
|
+
if not hasattr(log, "dfs"):
|
|
328
330
|
from ardupilot_log_reader.reader import Ardupilot
|
|
331
|
+
|
|
329
332
|
parser = Ardupilot.parse(
|
|
330
333
|
str(log),
|
|
331
334
|
types=list(
|
|
@@ -400,15 +403,13 @@ class Flight:
|
|
|
400
403
|
)
|
|
401
404
|
|
|
402
405
|
for i, df in enumerate(newdfs):
|
|
403
|
-
|
|
404
406
|
ekffs = 1 / np.mean(np.diff(df.time_actual))
|
|
405
|
-
ps =
|
|
406
|
-
df[f
|
|
407
|
-
df[f
|
|
408
|
-
df[f
|
|
407
|
+
ps = "" if i == 0 else f"_{i}"
|
|
408
|
+
df[f"velocity_N{ps}"] = filter(df[f"velocity_N{ps}"], 5, 5, ekffs)
|
|
409
|
+
df[f"velocity_E{ps}"] = filter(df[f"velocity_E{ps}"], 5, 5, ekffs)
|
|
410
|
+
df[f"velocity_D{ps}"] = filter(df[f"velocity_D{ps}"], 5, 5, ekffs)
|
|
409
411
|
dfs.append(df)
|
|
410
412
|
|
|
411
|
-
|
|
412
413
|
if ekf2 is not None:
|
|
413
414
|
dfs = dfs + Flight.parse_instances(
|
|
414
415
|
ekf2,
|
|
@@ -439,7 +440,10 @@ class Flight:
|
|
|
439
440
|
if ekf1 is not None:
|
|
440
441
|
if "C" in ekf1.columns:
|
|
441
442
|
imu = pd.merge_asof(
|
|
442
|
-
imu,
|
|
443
|
+
imu,
|
|
444
|
+
ekf1.loc[ekf1.C == imu_instance],
|
|
445
|
+
on="timestamp",
|
|
446
|
+
direction="nearest",
|
|
443
447
|
)
|
|
444
448
|
else:
|
|
445
449
|
imu = pd.merge_asof(imu, ekf1, on="timestamp", direction="nearest")
|
|
@@ -463,18 +467,18 @@ class Flight:
|
|
|
463
467
|
imu["AccX"] = imu.AccX + imu.AX / 100
|
|
464
468
|
imu["AccY"] = imu.AccY + imu.AY / 100
|
|
465
469
|
imu["AccZ"] = imu.AccZ + imu.AZ / 100
|
|
466
|
-
|
|
470
|
+
|
|
467
471
|
_imufs = 1 / np.mean(np.diff(imu.timestamp))
|
|
468
|
-
|
|
472
|
+
|
|
469
473
|
dfs.append(
|
|
470
474
|
Flight.build_cols(
|
|
471
475
|
time_actual=imu.timestamp,
|
|
472
|
-
acceleration_x=filter(imu.AccX,10, 5, _imufs),
|
|
473
|
-
acceleration_y=filter(imu.AccY,10, 5, _imufs),
|
|
474
|
-
acceleration_z=filter(imu.AccZ,10, 5, _imufs),
|
|
475
|
-
axisrate_roll=filter(imu.GyrX,10, 5, _imufs),
|
|
476
|
-
axisrate_pitch=filter(imu.GyrY,10, 5, _imufs),
|
|
477
|
-
axisrate_yaw=filter(imu.GyrZ,10, 5, _imufs),
|
|
476
|
+
acceleration_x=filter(imu.AccX, 10, 5, _imufs),
|
|
477
|
+
acceleration_y=filter(imu.AccY, 10, 5, _imufs),
|
|
478
|
+
acceleration_z=filter(imu.AccZ, 10, 5, _imufs),
|
|
479
|
+
axisrate_roll=filter(imu.GyrX, 10, 5, _imufs),
|
|
480
|
+
axisrate_pitch=filter(imu.GyrY, 10, 5, _imufs),
|
|
481
|
+
axisrate_yaw=filter(imu.GyrZ, 10, 5, _imufs),
|
|
478
482
|
)
|
|
479
483
|
)
|
|
480
484
|
|
|
@@ -608,7 +612,9 @@ class Flight:
|
|
|
608
612
|
indf: pd.DataFrame, colmap: dict[str, str], instancecol="Instance"
|
|
609
613
|
):
|
|
610
614
|
"""Where an instance column exists in an input df split the values into two columns"""
|
|
611
|
-
instances =
|
|
615
|
+
instances = (
|
|
616
|
+
sorted(indf[instancecol].unique()) if instancecol in indf.columns else [0]
|
|
617
|
+
)
|
|
612
618
|
dfs = []
|
|
613
619
|
for i in instances:
|
|
614
620
|
_subdf = (
|
|
@@ -629,11 +635,23 @@ class Flight:
|
|
|
629
635
|
return dfs
|
|
630
636
|
|
|
631
637
|
@staticmethod
|
|
632
|
-
def
|
|
633
|
-
|
|
638
|
+
def from_fc_json(fc_json: fcj.FCJ) -> Flight:
|
|
639
|
+
if fc_json.parameters:
|
|
640
|
+
origin = Origin.from_fcjson_parameters(fc_json.parameters)
|
|
641
|
+
shift = origin.rotation.transform_point(
|
|
642
|
+
Point(
|
|
643
|
+
fc_json.parameters.moveEast,
|
|
644
|
+
-fc_json.parameters.moveNorth,
|
|
645
|
+
0,
|
|
646
|
+
)
|
|
647
|
+
)
|
|
648
|
+
else:
|
|
649
|
+
origin = Origin("dummy_origin", GPS(0, 0, 0), -np.pi / 2)
|
|
650
|
+
shift = P0()
|
|
651
|
+
|
|
652
|
+
df = pd.DataFrame([d.__dict__ for d in fc_json.data], dtype=float)
|
|
634
653
|
|
|
635
654
|
df = Flight.build_cols(
|
|
636
|
-
time_actual=df["time"] / 1e6 + int(time()),
|
|
637
655
|
time_flight=df["time"] / 1e6,
|
|
638
656
|
attitude_roll=np.radians(df["r"]),
|
|
639
657
|
attitude_pitch=np.radians(df["p"]),
|
|
@@ -653,31 +671,6 @@ class Flight:
|
|
|
653
671
|
df["position_D"] = df["position_D"] + shift.z
|
|
654
672
|
return Flight(df.set_index("time_flight", drop=False), None, origin, "xkf_c0")
|
|
655
673
|
|
|
656
|
-
@staticmethod
|
|
657
|
-
def from_fc_json(fc_json: Union[str, dict, IO]) -> Self:
|
|
658
|
-
if isinstance(fc_json, str):
|
|
659
|
-
with open(fc_json, "r") as f:
|
|
660
|
-
fc_json = load(f)
|
|
661
|
-
elif isinstance(fc_json, IO):
|
|
662
|
-
fc_json = load(f)
|
|
663
|
-
|
|
664
|
-
if "parameters" in fc_json:
|
|
665
|
-
origin = Origin.from_fcjson_parameters(fc_json["parameters"])
|
|
666
|
-
shift = origin.rotation.transform_point(
|
|
667
|
-
Point(
|
|
668
|
-
fc_json["parameters"]["moveEast"],
|
|
669
|
-
-fc_json["parameters"]["moveNorth"],
|
|
670
|
-
0,
|
|
671
|
-
)
|
|
672
|
-
)
|
|
673
|
-
else:
|
|
674
|
-
origin = Origin("dummy_origin", GPS(0, 0, 0), -np.pi / 2)
|
|
675
|
-
shift = P0()
|
|
676
|
-
|
|
677
|
-
return Flight.parse_fcj_data(
|
|
678
|
-
pd.DataFrame(fc_json["data"], dtype=float), origin, shift
|
|
679
|
-
)
|
|
680
|
-
|
|
681
674
|
def remove_time_flutter(self):
|
|
682
675
|
# I think the best option is just to take the average of the timestep.
|
|
683
676
|
# FC loop rate seems consistent but there is noise in the time that is recorded
|
|
@@ -745,6 +738,3 @@ class Flight:
|
|
|
745
738
|
T = (ts[-1] - ts[0]) / N
|
|
746
739
|
fs = 1 / T
|
|
747
740
|
return self.filter(*butter(order, cutoff, fs=fs, btype="low", analog=False))
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
@@ -5,7 +5,8 @@ from typing import Self
|
|
|
5
5
|
import json_stream
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from dataclasses import dataclass
|
|
8
|
-
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
from flightdata import fcj
|
|
9
10
|
|
|
10
11
|
@dataclass
|
|
11
12
|
class Origin(object):
|
|
@@ -15,6 +16,18 @@ class Origin(object):
|
|
|
15
16
|
pos: g.GPS # position of pilot
|
|
16
17
|
heading: float# direction pilot faces in radians from North (clockwise)
|
|
17
18
|
|
|
19
|
+
@property
|
|
20
|
+
def lat(self):
|
|
21
|
+
return self.pos.lat[0]
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def long(self):
|
|
25
|
+
return self.pos.long[0]
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def alt(self):
|
|
29
|
+
return self.pos.alt[0]
|
|
30
|
+
|
|
18
31
|
@property
|
|
19
32
|
def rotation(self):
|
|
20
33
|
# converts NED to x right, y heading direction, z up
|
|
@@ -72,7 +85,7 @@ class Origin(object):
|
|
|
72
85
|
This is a convenient, but not very accurate way to setup the box.
|
|
73
86
|
'''
|
|
74
87
|
|
|
75
|
-
position = g.GPS(flight.gps_latitude[0], flight.gps_longitude[0], flight.gps_altitude[0])
|
|
88
|
+
position = g.GPS(flight.gps_latitude.iloc[0], flight.gps_longitude.iloc[0], flight.gps_altitude.iloc[0])
|
|
76
89
|
heading = g.Euler(flight.attitude)[0].transform_point(g.PX())
|
|
77
90
|
|
|
78
91
|
return Origin('origin', position, np.arctan2(heading.y, heading.x)[0])
|
|
@@ -118,26 +131,41 @@ class Origin(object):
|
|
|
118
131
|
)
|
|
119
132
|
|
|
120
133
|
@staticmethod
|
|
121
|
-
def from_fcjson_parameters(
|
|
122
|
-
if not isinstance(data, dict):
|
|
123
|
-
data = json_stream.to_standard_types(
|
|
124
|
-
json_stream.load(open(data, 'r'))['parameters']
|
|
125
|
-
)
|
|
134
|
+
def from_fcjson_parameters(parms: fcj.Parameters):
|
|
126
135
|
return Origin(
|
|
127
136
|
"FCJ_box",
|
|
128
137
|
g.GPS(
|
|
129
|
-
float(
|
|
130
|
-
float(
|
|
131
|
-
float(
|
|
138
|
+
float(parms.pilotLat),
|
|
139
|
+
float(parms.pilotLng),
|
|
140
|
+
float(parms.pilotAlt)
|
|
132
141
|
),
|
|
133
|
-
float(
|
|
142
|
+
float(parms.rotation)
|
|
134
143
|
)
|
|
135
144
|
|
|
136
|
-
|
|
137
|
-
|
|
138
145
|
def gps_to_point(self, gps: g.GPS) -> g.Point:
|
|
139
|
-
|
|
140
|
-
return self.rotation.transform_point(g.Point(pned.y, pned.x, -pned.z ))
|
|
146
|
+
return self.rotation.transform_point(gps - self.pos)
|
|
141
147
|
|
|
142
148
|
|
|
143
|
-
|
|
149
|
+
|
|
150
|
+
class FCJOrigin(BaseModel):
|
|
151
|
+
lat: float
|
|
152
|
+
lng: float
|
|
153
|
+
alt: float
|
|
154
|
+
heading: float
|
|
155
|
+
move_east: float=0
|
|
156
|
+
move_north: float=0
|
|
157
|
+
|
|
158
|
+
def origin(self):
|
|
159
|
+
"""Create a flightdata.Origin object from the FCJOrigin object."""
|
|
160
|
+
return Origin(
|
|
161
|
+
"fcj",
|
|
162
|
+
g.GPS(self.lat, self.lng, self.alt).offset(
|
|
163
|
+
g.Point(self.move_north, self.move_east, 0)
|
|
164
|
+
),
|
|
165
|
+
np.radians(self.heading),
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def shift(self):
|
|
169
|
+
return self.origin().rotation.transform_point(
|
|
170
|
+
g.Point(self.move_east, -self.move_north, 0)
|
|
171
|
+
)
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import datetime
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FCJ(BaseModel):
|
|
9
|
+
version: str
|
|
10
|
+
comments: str
|
|
11
|
+
name: str
|
|
12
|
+
view: View
|
|
13
|
+
parameters: Parameters
|
|
14
|
+
scored: bool
|
|
15
|
+
scores: list[float]
|
|
16
|
+
human_scores: list[HumanResult] = []
|
|
17
|
+
fcs_scores: list[Result] = []
|
|
18
|
+
mans: list[Man]
|
|
19
|
+
data: list[Data]
|
|
20
|
+
jhash: int | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def score_df(self):
|
|
24
|
+
return pd.concat(
|
|
25
|
+
{fcjr.fa_version: fcjr.to_df() for fcjr in self.fcs_scores},
|
|
26
|
+
axis=0,
|
|
27
|
+
names=["version", "manoeuvre", "difficulty", "truncate"],
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def man_df(self):
|
|
31
|
+
return pd.DataFrame(
|
|
32
|
+
[man.__dict__ for man in self.mans[1:-1]],
|
|
33
|
+
index=pd.Index(range(len(self.mans[1:-1])), name="manoeuvre"),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def pfc_version_df(self):
|
|
37
|
+
sdf = self.score_df().loc[pd.IndexSlice[:, :, 3, False]]
|
|
38
|
+
return pd.concat(
|
|
39
|
+
[sdf, sdf.mul(self.man_df().k, axis=0)], axis=1, keys=["raw", "kfac"]
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def version_summary_df(self):
|
|
43
|
+
return self.pfc_version_df().groupby("version").kfac.sum()
|
|
44
|
+
|
|
45
|
+
def latest_version(self):
|
|
46
|
+
return max([fcjr.fa_version for fcjr in self.fcs_scores])
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def id(self):
|
|
50
|
+
return re.search(r"\d{8}", self.name)[0]
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def created(self):
|
|
54
|
+
try:
|
|
55
|
+
return datetime.datetime.strptime(
|
|
56
|
+
re.search(r"_\d{2}_\d{2}_\d{2}_", self.name)[0], "_%y_%m_%d_"
|
|
57
|
+
)
|
|
58
|
+
except Exception:
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class View(BaseModel):
|
|
63
|
+
position: dict
|
|
64
|
+
target: dict
|
|
65
|
+
|
|
66
|
+
class Parameters(BaseModel):
|
|
67
|
+
rotation: float
|
|
68
|
+
start: int
|
|
69
|
+
stop: int
|
|
70
|
+
moveEast: float
|
|
71
|
+
moveNorth: float
|
|
72
|
+
wingspan: float
|
|
73
|
+
modelwingspan: float
|
|
74
|
+
elevate: float
|
|
75
|
+
originLat: float
|
|
76
|
+
originLng: float
|
|
77
|
+
originAlt: float
|
|
78
|
+
pilotLat: str | float
|
|
79
|
+
pilotLng: str | float
|
|
80
|
+
pilotAlt: str | float
|
|
81
|
+
centerLat: str | float
|
|
82
|
+
centerLng: str | float
|
|
83
|
+
centerAlt: str | float
|
|
84
|
+
schedule: list[str]
|
|
85
|
+
|
|
86
|
+
class HumanResult(BaseModel):
|
|
87
|
+
name: str
|
|
88
|
+
date: datetime.date
|
|
89
|
+
scores: list[float]
|
|
90
|
+
|
|
91
|
+
class Result(BaseModel):
|
|
92
|
+
fa_version: str
|
|
93
|
+
manresults: list[ManResult | None]
|
|
94
|
+
|
|
95
|
+
def to_df(self) -> pd.DataFrame:
|
|
96
|
+
return pd.concat(
|
|
97
|
+
{i: fcjmr.to_df() for i, fcjmr in enumerate(self.manresults[1:]) if fcjmr},
|
|
98
|
+
axis=0,
|
|
99
|
+
names=["manoeuvre", "difficulty", "truncate"],
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
class ManResult(BaseModel):
|
|
103
|
+
els: list[El]
|
|
104
|
+
results: list[Score]
|
|
105
|
+
|
|
106
|
+
def to_df(self) -> pd.DataFrame:
|
|
107
|
+
return pd.DataFrame(
|
|
108
|
+
data=[res.score.__dict__ for res in self.results],
|
|
109
|
+
index=pd.MultiIndex.from_frame(
|
|
110
|
+
pd.DataFrame([res.properties.__dict__ for res in self.results])
|
|
111
|
+
),
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def get_score(self, props: ScoreProperties):
|
|
115
|
+
for r in self.results:
|
|
116
|
+
if r.properties == props:
|
|
117
|
+
return r.score
|
|
118
|
+
|
|
119
|
+
class El(BaseModel):
|
|
120
|
+
name: str
|
|
121
|
+
start: int
|
|
122
|
+
stop: int
|
|
123
|
+
|
|
124
|
+
class Score(BaseModel):
|
|
125
|
+
score: ScoreValues
|
|
126
|
+
properties: ScoreProperties
|
|
127
|
+
|
|
128
|
+
class ScoreValues(BaseModel):
|
|
129
|
+
intra: float
|
|
130
|
+
inter: float
|
|
131
|
+
positioning: float
|
|
132
|
+
total: float
|
|
133
|
+
|
|
134
|
+
class ScoreProperties(BaseModel):
|
|
135
|
+
difficulty: int=3
|
|
136
|
+
truncate: bool=False
|
|
137
|
+
|
|
138
|
+
def __eq__(self, other):
|
|
139
|
+
if not isinstance(other, ScoreProperties):
|
|
140
|
+
return False
|
|
141
|
+
return self.difficulty == other.difficulty and self.truncate == other.truncate
|
|
142
|
+
|
|
143
|
+
class Man(BaseModel):
|
|
144
|
+
name: str
|
|
145
|
+
k: float
|
|
146
|
+
id: str
|
|
147
|
+
sp: int
|
|
148
|
+
wd: float
|
|
149
|
+
start: int
|
|
150
|
+
stop: int
|
|
151
|
+
sel: bool
|
|
152
|
+
background: str
|
|
153
|
+
|
|
154
|
+
class Data(BaseModel):
|
|
155
|
+
VN: float = None
|
|
156
|
+
VE: float = None
|
|
157
|
+
VD: float = None
|
|
158
|
+
dPD: float = None #
|
|
159
|
+
r: float
|
|
160
|
+
p: float
|
|
161
|
+
yw: float
|
|
162
|
+
N: float
|
|
163
|
+
E: float
|
|
164
|
+
D: float
|
|
165
|
+
time: int
|
|
166
|
+
roll: float
|
|
167
|
+
pitch: float
|
|
168
|
+
yaw: float
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def get_scores(file: str) -> pd.DataFrame:
|
|
173
|
+
fcj = FCJ.model_validate_json(open(file, "r").read())
|
|
174
|
+
return fcj.pfc_version_df()
|
|
@@ -8,7 +8,7 @@ import numpy.typing as npt
|
|
|
8
8
|
import pandas as pd
|
|
9
9
|
|
|
10
10
|
import geometry as g
|
|
11
|
-
from flightdata import Constructs, Environment, Flight, Flow, Origin, SVar, Table
|
|
11
|
+
from flightdata import Constructs, Environment, Flight, Flow, Origin, SVar, Table, fcj
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class State(Table):
|
|
@@ -199,7 +199,7 @@ class State(Table):
|
|
|
199
199
|
|
|
200
200
|
def splitter_labels(
|
|
201
201
|
self: State,
|
|
202
|
-
mans: list[
|
|
202
|
+
mans: list[fcj.Man],
|
|
203
203
|
better_names: list[str] = None,
|
|
204
204
|
target_col="manoeuvre",
|
|
205
205
|
t0=0,
|
|
@@ -217,31 +217,31 @@ class State(Table):
|
|
|
217
217
|
"""
|
|
218
218
|
i0 = self.data.index.get_indexer([t0], "nearest")[0]
|
|
219
219
|
|
|
220
|
-
takeoff = self.data.iloc[0 : int(mans[0]
|
|
220
|
+
takeoff = self.data.iloc[0 : int(mans[0].stop) + i0 + 1]
|
|
221
221
|
|
|
222
|
-
labels = [mans[0]
|
|
222
|
+
labels = [mans[0].name]
|
|
223
223
|
labelled = [State(takeoff).label(**{target_col: labels[0]})]
|
|
224
224
|
if better_names:
|
|
225
225
|
better_names.append("land")
|
|
226
226
|
|
|
227
227
|
for i, split_man in enumerate(mans[1:]):
|
|
228
|
-
while split_man
|
|
229
|
-
split_man
|
|
228
|
+
while split_man.name in labels:
|
|
229
|
+
split_man.name = split_man.name + "2"
|
|
230
230
|
|
|
231
|
-
name = better_names[i] if better_names else split_man
|
|
231
|
+
name = better_names[i] if better_names else split_man.name
|
|
232
232
|
|
|
233
233
|
labelled.append(
|
|
234
234
|
State(
|
|
235
235
|
self.data.iloc[
|
|
236
|
-
int(split_man
|
|
236
|
+
int(split_man.start) + i0 : int(split_man.stop) + i0 + 1
|
|
237
237
|
]
|
|
238
238
|
).label(**{target_col: name})
|
|
239
239
|
)
|
|
240
|
-
labels.append(split_man
|
|
240
|
+
labels.append(split_man.name)
|
|
241
241
|
|
|
242
242
|
return State.stack(labelled)
|
|
243
243
|
|
|
244
|
-
def label_els(self, els:
|
|
244
|
+
def label_els(self, els: list[fcj.El]):
|
|
245
245
|
return self.splitter_labels(
|
|
246
246
|
pd.DataFrame(els).to_dict('records'), target_col='element'
|
|
247
247
|
).str_replace_label(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: flightdata
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.22
|
|
4
4
|
Summary: Module for handling UAV flight log data
|
|
5
5
|
Author-email: Thomas David <thomasdavid0@gmail.com>
|
|
6
6
|
License: GNU GPL v3
|
|
@@ -11,13 +11,13 @@ License-File: LICENSE
|
|
|
11
11
|
Requires-Dist: numpy
|
|
12
12
|
Requires-Dist: pandas
|
|
13
13
|
Requires-Dist: simplejson
|
|
14
|
-
Requires-Dist: pfc-geometry>=0.2.
|
|
14
|
+
Requires-Dist: pfc-geometry>=0.2.12
|
|
15
15
|
Requires-Dist: json_stream
|
|
16
16
|
Provides-Extra: dev
|
|
17
17
|
Requires-Dist: numpy; extra == "dev"
|
|
18
18
|
Requires-Dist: pandas; extra == "dev"
|
|
19
19
|
Requires-Dist: simplejson; extra == "dev"
|
|
20
|
-
Requires-Dist: pfc-geometry>=0.2.
|
|
20
|
+
Requires-Dist: pfc-geometry>=0.2.12; extra == "dev"
|
|
21
21
|
Requires-Dist: ardupilot-log-reader>=0.3.3; extra == "dev"
|
|
22
22
|
Requires-Dist: pytest; extra == "dev"
|
|
23
23
|
|
|
@@ -48,6 +48,8 @@ flightdata/flight/parameters.py
|
|
|
48
48
|
flightdata/model/__init__.py
|
|
49
49
|
flightdata/model/aerodynamic.py
|
|
50
50
|
flightdata/model/thrust.py
|
|
51
|
+
flightdata/schemas/__init__.py
|
|
52
|
+
flightdata/schemas/fcj.py
|
|
51
53
|
flightdata/scripts/collect_logs.py
|
|
52
54
|
flightdata/scripts/flightline.py
|
|
53
55
|
test/EmailedBox.f3a
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from pytest import fixture
|
|
2
|
-
from flightdata import Flight, Origin
|
|
3
|
-
from flightdata import State
|
|
2
|
+
from flightdata import Flight, Origin, State, fcj
|
|
4
3
|
|
|
4
|
+
@fixture(scope='session')
|
|
5
|
+
def fcjson():
|
|
6
|
+
return Flight.from_fc_json(fcj.FCJ.model_validate_json(open('test/data/p23_fc.json', 'r').read()))
|
|
5
7
|
|
|
6
8
|
@fixture(scope="session")
|
|
7
9
|
def flight():
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from flightdata import Flight, Origin, BinData
|
|
2
|
-
from io import open
|
|
3
2
|
from json import load
|
|
4
3
|
from pytest import fixture, approx, mark
|
|
5
4
|
import numpy as np
|
|
6
5
|
import pandas as pd
|
|
7
6
|
from ardupilot_log_reader import Ardupilot
|
|
7
|
+
from .conftest import fcjson
|
|
8
8
|
|
|
9
9
|
@fixture(scope='session')
|
|
10
10
|
def parser():
|
|
@@ -14,9 +14,6 @@ def parser():
|
|
|
14
14
|
def fl(parser):
|
|
15
15
|
return Flight.from_log(parser)
|
|
16
16
|
|
|
17
|
-
@fixture(scope='session')
|
|
18
|
-
def fcj():
|
|
19
|
-
return Flight.from_fc_json('test/data/p23_fc.json')
|
|
20
17
|
|
|
21
18
|
def test_duration(fl):
|
|
22
19
|
assert fl.duration == approx(687, rel=1e-3)
|
|
@@ -30,10 +27,10 @@ def test_to_from_dict(fl):
|
|
|
30
27
|
fl2 = Flight.from_dict(data)
|
|
31
28
|
assert fl == fl2
|
|
32
29
|
|
|
33
|
-
def test_from_fc_json(
|
|
34
|
-
assert isinstance(
|
|
35
|
-
assert
|
|
36
|
-
assert
|
|
30
|
+
def test_from_fc_json(fcjson):
|
|
31
|
+
assert isinstance(fcjson, Flight)
|
|
32
|
+
assert fcjson.duration > 200
|
|
33
|
+
assert fcjson.position_D.max() < -10
|
|
37
34
|
|
|
38
35
|
@mark.skip
|
|
39
36
|
def test_unique_identifier():
|
|
File without changes
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
from flightdata import Flight, State, Origin, BinData
|
|
1
|
+
from flightdata import Flight, State, Origin, BinData, fcj
|
|
2
2
|
from pytest import approx, fixture
|
|
3
3
|
from geometry import Transformation, PX, PY, P0, Time
|
|
4
4
|
from geometry.testing import assert_almost_equal
|
|
5
5
|
import numpy as np
|
|
6
6
|
from time import sleep, time
|
|
7
7
|
from json import load
|
|
8
|
-
from ..conftest import state, flight, origin
|
|
8
|
+
from ..conftest import state, flight, origin, fcjson
|
|
9
9
|
|
|
10
10
|
def test_extrapolate():
|
|
11
11
|
initial = State.from_transform(
|
|
@@ -16,7 +16,7 @@ def test_extrapolate():
|
|
|
16
16
|
extrapolated = initial.extrapolate(10)
|
|
17
17
|
assert extrapolated.x[-1] == approx(300)
|
|
18
18
|
|
|
19
|
-
assert len(extrapolated) ==
|
|
19
|
+
assert len(extrapolated) == 250
|
|
20
20
|
assert_almost_equal(extrapolated.pos[0], initial.pos)
|
|
21
21
|
|
|
22
22
|
|
|
@@ -49,11 +49,9 @@ def test_from_flight_pos(flight: Flight, state: State, origin: Origin):
|
|
|
49
49
|
#pd.testing.assert_frame_equal(state.data, st2.data)
|
|
50
50
|
assert st2.z.mean() > 0
|
|
51
51
|
|
|
52
|
-
def test_fc_json():
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
fl = Flight.from_fc_json(fcj)
|
|
56
|
-
origin = Origin.from_fcjson_parmameters(fcj['parameters'])
|
|
52
|
+
def test_fc_json(fcjson: fcj.FCJ):
|
|
53
|
+
fl = Flight.from_fc_json(fcjson)
|
|
54
|
+
origin = Origin.from_fcjson_parameters(fcjson.parameters)
|
|
57
55
|
st = State.from_flight(fl, origin)
|
|
58
56
|
assert st.z.mean() > 0
|
|
59
57
|
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|