flightanalysis 0.2.4__tar.gz → 0.2.6__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.
- {flightanalysis-0.2.4/flightanalysis.egg-info → flightanalysis-0.2.6}/PKG-INFO +9 -1
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/README.md +1 -0
- flightanalysis-0.2.6/flightanalysis/__init__.py +10 -0
- flightanalysis-0.2.6/flightanalysis/analysis/__init__.py +3 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/analysis/el_analysis.py +6 -3
- flightanalysis-0.2.6/flightanalysis/analysis/manoeuvre_analysis/__init__.py +8 -0
- flightanalysis-0.2.6/flightanalysis/analysis/manoeuvre_analysis/alignment.py +71 -0
- flightanalysis-0.2.6/flightanalysis/analysis/manoeuvre_analysis/analysis.py +16 -0
- flightanalysis-0.2.6/flightanalysis/analysis/manoeuvre_analysis/basic.py +83 -0
- flightanalysis-0.2.6/flightanalysis/analysis/manoeuvre_analysis/complete.py +156 -0
- flightanalysis-0.2.6/flightanalysis/analysis/manoeuvre_analysis/scored.py +27 -0
- flightanalysis-0.2.6/flightanalysis/analysis/sch_analysis.py +120 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/data/IMAC_Unlimited2024_schedule.json +80 -108
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/data/__init__.py +0 -1
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/data/f3a_a25_schedule.json +66 -66
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/data/f3a_f25_schedule.json +108 -108
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/data/f3a_p23_schedule.json +96 -96
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/data/f3a_p25_schedule.json +631 -388
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/data/f3auk_clubman_schedule.json +52 -52
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/data/f3auk_inter_schedule.json +45 -45
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/__init__.py +4 -2
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/builders/elbuilders.py +10 -17
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/builders/manbuilder.py +19 -14
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/collectors.py +1 -2
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/eldef.py +5 -5
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/mandef.py +38 -32
- flightanalysis-0.2.6/flightanalysis/definition/manoption.py +38 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/manparm.py +15 -16
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/scheddef.py +13 -71
- flightanalysis-0.2.6/flightanalysis/definition/scheduleinfo.py +45 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/elements/autorotation.py +2 -5
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/elements/element.py +12 -14
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/elements/line.py +5 -2
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/elements/loop.py +8 -7
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/elements/nose_drop.py +4 -3
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/elements/pitch_break.py +3 -2
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/manoeuvre.py +25 -15
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/criteria/__init__.py +1 -1
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/criteria/criteria.py +0 -1
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/criteria/exponential.py +8 -5
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/criteria/f3a_criteria.py +14 -11
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/criteria/inter/combination.py +4 -2
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/criteria/intra/bounded.py +18 -5
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/criteria/intra/continuous.py +49 -29
- flightanalysis-0.2.6/flightanalysis/scoring/criteria/intra/single.py +35 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/measurement.py +77 -16
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/results.py +10 -9
- {flightanalysis-0.2.4 → flightanalysis-0.2.6/flightanalysis.egg-info}/PKG-INFO +9 -1
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis.egg-info/SOURCES.txt +12 -27
- flightanalysis-0.2.6/flightanalysis.egg-info/requires.txt +7 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis.egg-info/top_level.txt +1 -1
- flightanalysis-0.2.6/scripts/batch_analyse.py +52 -0
- flightanalysis-0.2.6/scripts/collect_scores.py +46 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/setup.cfg +13 -2
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/tests/test_criiteria.py +75 -5
- flightanalysis-0.2.4/examples/schedules_construction/AMA_Intermediate2024.py +0 -201
- flightanalysis-0.2.4/examples/schedules_construction/create_all.py +0 -21
- flightanalysis-0.2.4/examples/schedules_construction/f3a_a25.py +0 -247
- flightanalysis-0.2.4/examples/schedules_construction/f3a_f25.py +0 -265
- flightanalysis-0.2.4/examples/schedules_construction/f3a_p23.py +0 -231
- flightanalysis-0.2.4/examples/schedules_construction/f3a_p25.py +0 -222
- flightanalysis-0.2.4/examples/schedules_construction/f3auk_Intermediate.py +0 -201
- flightanalysis-0.2.4/examples/schedules_construction/f3auk_clubman.py +0 -207
- flightanalysis-0.2.4/examples/schedules_construction/imac_sport2024.py +0 -132
- flightanalysis-0.2.4/examples/schedules_construction/imac_unlim2024.py +0 -139
- flightanalysis-0.2.4/examples/schedules_construction/make_manoeuvre.py +0 -56
- flightanalysis-0.2.4/examples/schedules_construction/nsrca_inter2024.py +0 -210
- flightanalysis-0.2.4/examples/scoring/__init__.py +0 -28
- flightanalysis-0.2.4/examples/scoring/analysis_reprocess.py +0 -24
- flightanalysis-0.2.4/examples/scoring/f3a_criteria_maker.py +0 -41
- flightanalysis-0.2.4/examples/scoring/judging.py +0 -50
- flightanalysis-0.2.4/examples/scoring/manoeuvres/inter_analysis.py +0 -19
- flightanalysis-0.2.4/examples/scoring/manoeuvres/intra_analysis.py +0 -26
- flightanalysis-0.2.4/examples/scoring/manoeuvres/mans/__init__.py +0 -0
- flightanalysis-0.2.4/examples/scoring/manoeuvres/mans/extract_mans.py +0 -28
- flightanalysis-0.2.4/examples/scoring/manoeuvres/positioning_analysis.py +0 -11
- flightanalysis-0.2.4/examples/scoring/split_optimisation.py +0 -50
- flightanalysis-0.2.4/flightanalysis/__init__.py +0 -6
- flightanalysis-0.2.4/flightanalysis/analysis/__init__.py +0 -3
- flightanalysis-0.2.4/flightanalysis/analysis/man_analysis.py +0 -267
- flightanalysis-0.2.4/flightanalysis/analysis/sch_analysis.py +0 -86
- flightanalysis-0.2.4/flightanalysis/definition/builders/__init__.py +0 -0
- flightanalysis-0.2.4/flightanalysis/scoring/criteria/inter/__init__.py +0 -0
- flightanalysis-0.2.4/flightanalysis/scoring/criteria/intra/__init__.py +0 -0
- flightanalysis-0.2.4/flightanalysis/scoring/criteria/intra/single.py +0 -38
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/LICENSE +0 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/MANIFEST.in +0 -0
- {flightanalysis-0.2.4/examples → flightanalysis-0.2.6/flightanalysis/definition/builders}/__init__.py +0 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/builders/lines.py +0 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/maninfo.py +0 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/operations/__init__.py +0 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/operations/funopp.py +0 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/operations/itemopp.py +0 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/operations/mathopp.py +0 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/operations/operation.py +0 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/elements/__init__.py +0 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/elements/recovery.py +0 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/elements/stall_turn.py +0 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/schedule.py +0 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/__init__.py +0 -0
- {flightanalysis-0.2.4/examples/data → flightanalysis-0.2.6/flightanalysis/scoring/criteria/inter}/__init__.py +0 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/criteria/inter/comparison.py +0 -0
- {flightanalysis-0.2.4/examples/schedules_construction → flightanalysis-0.2.6/flightanalysis/scoring/criteria/intra}/__init__.py +0 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/downgrade.py +0 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis.egg-info/dependency_links.txt +0 -0
- {flightanalysis-0.2.4/examples/scoring/manoeuvres → flightanalysis-0.2.6/scripts}/__init__.py +0 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/setup.py +0 -0
- {flightanalysis-0.2.4 → flightanalysis-0.2.6}/tests/test_data.py +0 -0
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: flightanalysis
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: A package for analysing flight data
|
|
5
5
|
Home-page: https://github.com/PyFlightCoach/FlightAnalysis
|
|
6
6
|
Author: Thomas David
|
|
7
7
|
Author-email: thomasdavid0@gmail.com
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE
|
|
10
|
+
Requires-Dist: numpy
|
|
11
|
+
Requires-Dist: pandas
|
|
12
|
+
Requires-Dist: scipy
|
|
13
|
+
Requires-Dist: fastdtw
|
|
14
|
+
Requires-Dist: simplejson
|
|
15
|
+
Requires-Dist: pfc-geometry>=0.2.4
|
|
16
|
+
Requires-Dist: flightdata>=0.2.6
|
|
10
17
|
|
|
11
18
|
# FlightAnalysis
|
|
12
19
|
|
|
@@ -14,3 +21,4 @@ This package contains tools for analysing recorded flight log data.The main obje
|
|
|
14
21
|
to facilitate automated judging for precision aerobatic competitions.
|
|
15
22
|
|
|
16
23
|
|
|
24
|
+
Documentation will follow here: https://pfcdocumentation.readthedocs.io/pyflightcoach/flightanalysis.html
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from loguru import logger
|
|
2
|
+
from .elements import *
|
|
3
|
+
from .manoeuvre import Manoeuvre
|
|
4
|
+
from .schedule import Schedule
|
|
5
|
+
from .definition import *
|
|
6
|
+
from .scoring import *
|
|
7
|
+
from .analysis import ScheduleAnalysis, ElementAnalysis
|
|
8
|
+
from .analysis import manoeuvre_analysis as ma
|
|
9
|
+
|
|
10
|
+
logger.disable('flightanalysis')
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
from flightdata import State
|
|
3
3
|
from typing import Self
|
|
4
|
-
from flightanalysis
|
|
5
|
-
|
|
4
|
+
from flightanalysis import ElDef, Element, ManParms
|
|
5
|
+
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
import geometry as g
|
|
8
8
|
|
|
@@ -10,6 +10,7 @@ import geometry as g
|
|
|
10
10
|
@dataclass
|
|
11
11
|
class ElementAnalysis:
|
|
12
12
|
edef:ElDef
|
|
13
|
+
mps: ManParms
|
|
13
14
|
el: Element
|
|
14
15
|
fl: State
|
|
15
16
|
tp: State
|
|
@@ -24,8 +25,10 @@ class ElementAnalysis:
|
|
|
24
25
|
|
|
25
26
|
@staticmethod
|
|
26
27
|
def from_dict(data) -> Self:
|
|
28
|
+
mps = ManParms.from_dict(data['mps'])
|
|
27
29
|
return ElementAnalysis(
|
|
28
|
-
ElDef.from_dict(data['edef']),
|
|
30
|
+
ElDef.from_dict(data['edef'], mps),
|
|
31
|
+
mps,
|
|
29
32
|
Element.from_dict(data['el']),
|
|
30
33
|
State.from_dict(data['fl']),
|
|
31
34
|
State.from_dict(data['tp']),
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from flightdata import State
|
|
4
|
+
from flightanalysis.manoeuvre import Manoeuvre
|
|
5
|
+
from loguru import logger
|
|
6
|
+
from .basic import AlinmentStage, Basic
|
|
7
|
+
from flightanalysis.definition import ManDef
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class Alignment(Basic):
|
|
12
|
+
manoeuvre: Manoeuvre
|
|
13
|
+
template: State
|
|
14
|
+
|
|
15
|
+
def run_all(self):
|
|
16
|
+
while self.__class__.__name__ != 'Scored':
|
|
17
|
+
new = self.run()
|
|
18
|
+
if new.__class__.__name__ == self.__class__.__name__:
|
|
19
|
+
break
|
|
20
|
+
self = new
|
|
21
|
+
return new
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def from_dict(data: dict, fallback=True):
|
|
25
|
+
ia = Basic.from_dict(data)
|
|
26
|
+
try:
|
|
27
|
+
ia = Alignment(
|
|
28
|
+
manoeuvre=Manoeuvre.from_dict(data['manoeuvre']),
|
|
29
|
+
template=State.from_dict(data['template']),
|
|
30
|
+
**ia.__dict__
|
|
31
|
+
)
|
|
32
|
+
except Exception as e:
|
|
33
|
+
if fallback:
|
|
34
|
+
logger.debug(f'Failed to parse Alignment {repr(e)}')
|
|
35
|
+
else:
|
|
36
|
+
raise e
|
|
37
|
+
return ia
|
|
38
|
+
|
|
39
|
+
def alignment(self, radius=10):
|
|
40
|
+
assert self.stage < AlinmentStage.SECONDARY
|
|
41
|
+
logger.debug(f'Running alignment stage {self.stage}')
|
|
42
|
+
aligned = State.align(self.flown, self.template, radius, self.stage==AlinmentStage.SETUP)[1]
|
|
43
|
+
return Alignment(
|
|
44
|
+
self.mdef, aligned, self.direction, self.stage + 1,
|
|
45
|
+
*self.manoeuvre.match_intention(self.template[0], aligned)
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def run_alignment(self, radius=10):
|
|
49
|
+
while self.stage < AlinmentStage.SECONDARY:
|
|
50
|
+
try:
|
|
51
|
+
self = self.alignment(radius)
|
|
52
|
+
except Exception as ex:
|
|
53
|
+
logger.exception(f'Error running alignment stage {self.stage}, {ex}')
|
|
54
|
+
break
|
|
55
|
+
return self
|
|
56
|
+
|
|
57
|
+
def run(self) -> Complete:
|
|
58
|
+
self = self.run_alignment()
|
|
59
|
+
if self.stage < AlinmentStage.SECONDARY:
|
|
60
|
+
return self
|
|
61
|
+
else:
|
|
62
|
+
mdef = ManDef(self.mdef.info, self.mdef.mps.update_defaults(self.manoeuvre), self.mdef.eds)
|
|
63
|
+
correction = mdef.create(self.template[0].transform).add_lines()
|
|
64
|
+
|
|
65
|
+
return Complete(
|
|
66
|
+
mdef, self.flown, self.direction, AlinmentStage.SECONDARY,
|
|
67
|
+
self.manoeuvre, self.template, correction,
|
|
68
|
+
correction.create_template(self.template[0], self.flown)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
from .complete import Complete # noqa: E402
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
class AlinmentStage:
|
|
5
|
+
SETUP=0
|
|
6
|
+
PRELIM=1
|
|
7
|
+
SECONDARY=2
|
|
8
|
+
OPTIMISED=3
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class Analysis:
|
|
13
|
+
def to_dict(self):
|
|
14
|
+
return {k: (v.to_dict() if hasattr(v, 'to_dict') else v) for k, v in self.__dict__.items()}
|
|
15
|
+
|
|
16
|
+
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from flightdata import State, Flight, Origin
|
|
4
|
+
from flightanalysis.definition import ManDef, SchedDef, ManOption
|
|
5
|
+
import geometry as g
|
|
6
|
+
from json import load
|
|
7
|
+
from .analysis import AlinmentStage, Analysis
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class Basic(Analysis):
|
|
12
|
+
mdef: ManDef | ManOption
|
|
13
|
+
flown: State
|
|
14
|
+
direction: int
|
|
15
|
+
stage: AlinmentStage
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def name(self):
|
|
19
|
+
return self.mdef.uid
|
|
20
|
+
|
|
21
|
+
def run_all(self):
|
|
22
|
+
res = [s for s in [s.run_all() for s in self.run()] if isinstance(s, Scored)]
|
|
23
|
+
|
|
24
|
+
if len(res) == 0:
|
|
25
|
+
return self
|
|
26
|
+
|
|
27
|
+
min_dg = None
|
|
28
|
+
for n in res:
|
|
29
|
+
if hasattr(n, 'scores'):
|
|
30
|
+
if min_dg is None or n.scores.intra.total < min_dg:
|
|
31
|
+
min_dg = n.scores.intra.total
|
|
32
|
+
self = n
|
|
33
|
+
|
|
34
|
+
return self
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def from_dict(Cls, data:dict) -> Basic:
|
|
39
|
+
return Basic(
|
|
40
|
+
ManDef.from_dict(data["mdef"]),
|
|
41
|
+
State.from_dict(data["flown"]),
|
|
42
|
+
data['direction'],
|
|
43
|
+
data['stage']
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def create_itrans(self) -> g.Transformation:
|
|
47
|
+
return g.Transformation(
|
|
48
|
+
self.flown[0].pos,
|
|
49
|
+
self.mdef.info.start.initial_rotation(self.direction)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def from_fcj(file: str, mid: int):
|
|
54
|
+
with open(file, 'r') as f:
|
|
55
|
+
data = load(f)
|
|
56
|
+
flight = Flight.from_fc_json(data)
|
|
57
|
+
box = Origin.from_fcjson_parmameters(data["parameters"])
|
|
58
|
+
|
|
59
|
+
sdef = SchedDef.load(data["parameters"]["schedule"][1])
|
|
60
|
+
|
|
61
|
+
state = State.from_flight(flight, box).splitter_labels(
|
|
62
|
+
data["mans"],
|
|
63
|
+
[m.info.short_name for m in sdef]
|
|
64
|
+
)
|
|
65
|
+
mdef= sdef[mid]
|
|
66
|
+
return Basic(mdef, state.get_manoeuvre(mdef.uid), AlinmentStage.SETUP)
|
|
67
|
+
|
|
68
|
+
def run(self) -> list[Alignment]:
|
|
69
|
+
itrans = self.create_itrans()
|
|
70
|
+
mopt = ManOption([self.mdef]) if isinstance(self.mdef, ManDef) else self.mdef
|
|
71
|
+
|
|
72
|
+
als = []
|
|
73
|
+
for mdef in mopt:
|
|
74
|
+
man = mdef.create(itrans).add_lines()
|
|
75
|
+
als.append(Alignment(
|
|
76
|
+
mdef, self.flown, self.direction, AlinmentStage.SETUP,
|
|
77
|
+
man, man.create_template(itrans)
|
|
78
|
+
))
|
|
79
|
+
return als
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
from .alignment import Alignment # noqa: E402
|
|
83
|
+
from .scored import Scored # noqa: E402
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from ..el_analysis import ElementAnalysis
|
|
4
|
+
from flightdata import State
|
|
5
|
+
from flightanalysis.definition import ManDef
|
|
6
|
+
from flightanalysis.manoeuvre import Manoeuvre
|
|
7
|
+
from flightanalysis.scoring import Results, Result, ManoeuvreResults, Measurement
|
|
8
|
+
from flightanalysis.scoring.criteria.f3a_criteria import F3A
|
|
9
|
+
from flightanalysis.definition.maninfo import Position
|
|
10
|
+
import numpy as np
|
|
11
|
+
from .alignment import Alignment, AlinmentStage
|
|
12
|
+
from loguru import logger
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class Complete(Alignment):
|
|
17
|
+
corrected: Manoeuvre
|
|
18
|
+
corrected_template: State
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def from_dict(data:dict, fallback=True):
|
|
22
|
+
pa = Alignment.from_dict(data, fallback)
|
|
23
|
+
try:
|
|
24
|
+
pa = Complete(
|
|
25
|
+
**pa.__dict__,
|
|
26
|
+
corrected=Manoeuvre.from_dict(data["corrected"]),
|
|
27
|
+
corrected_template=State.from_dict(data["corrected_template"]),
|
|
28
|
+
)
|
|
29
|
+
except Exception as e:
|
|
30
|
+
if fallback:
|
|
31
|
+
logger.debug(f"Failed to parse Complete: {repr(e)}")
|
|
32
|
+
else:
|
|
33
|
+
raise e
|
|
34
|
+
return pa
|
|
35
|
+
|
|
36
|
+
def run(self) -> Scored:
|
|
37
|
+
if self.stage < AlinmentStage.OPTIMISED:
|
|
38
|
+
self = self.optimise_alignment()
|
|
39
|
+
return Scored(**self.__dict__,
|
|
40
|
+
scores=ManoeuvreResults(self.inter(), self.intra(), self.positioning())
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def elnames(self):
|
|
45
|
+
return list(self.mdef.eds.data.keys())
|
|
46
|
+
|
|
47
|
+
def __iter__(self):
|
|
48
|
+
for elname in self.manoeuvre.all_elements().data.keys():
|
|
49
|
+
yield self.get_ea(self.mdef.eds[elname])
|
|
50
|
+
|
|
51
|
+
def __getitem__(self, i):
|
|
52
|
+
return self.get_ea(['entry_line'] + self.mdef.eds[i] + ['exit_line'])
|
|
53
|
+
|
|
54
|
+
def __getattr__(self, name):
|
|
55
|
+
if name in self.mdef.eds.data.keys():
|
|
56
|
+
return self.get_ea(self.mdef.eds[name])
|
|
57
|
+
raise AttributeError(f'Attribute {name} not found in {self.__class__.__name__}')
|
|
58
|
+
|
|
59
|
+
def get_ea(self, edef):
|
|
60
|
+
el = getattr(self.manoeuvre.all_elements(), edef.name)
|
|
61
|
+
st = el.get_data(self.flown)
|
|
62
|
+
tp = el.get_data(self.template).relocate(st.pos[0])
|
|
63
|
+
return ElementAnalysis(edef, self.mdef.mps, el, st, tp, el.ref_frame(tp))
|
|
64
|
+
|
|
65
|
+
def optimise_alignment(self):
|
|
66
|
+
aligned = self.manoeuvre.optimise_alignment(self.template, self.flown)
|
|
67
|
+
manoeuvre, template = self.manoeuvre.match_intention(self.template[0], aligned)
|
|
68
|
+
mdef = ManDef(self.mdef.info, self.mdef.mps.update_defaults(self.manoeuvre), self.mdef.eds)
|
|
69
|
+
correction = mdef.create(self.template[0].transform).add_lines()
|
|
70
|
+
|
|
71
|
+
return Complete(
|
|
72
|
+
mdef, aligned, self.direction, AlinmentStage.OPTIMISED,
|
|
73
|
+
manoeuvre, template, correction, correction.create_template(template[0])
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def side_box(self):
|
|
77
|
+
return F3A.intra.box(
|
|
78
|
+
'side box',
|
|
79
|
+
Measurement.side_box(self.flown)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def top_box(self):
|
|
83
|
+
return F3A.intra.box(
|
|
84
|
+
'top box',
|
|
85
|
+
Measurement.top_box(self.flown)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def centre(self):
|
|
89
|
+
results = Results('centres')
|
|
90
|
+
for cpid in self.mdef.info.centre_points:
|
|
91
|
+
results.add(F3A.single.angle(
|
|
92
|
+
f'centre point {cpid}',
|
|
93
|
+
Measurement.centre_box(self.flown.get_element(cpid+1)[0])
|
|
94
|
+
))
|
|
95
|
+
|
|
96
|
+
for ceid, fac in self.mdef.info.centred_els:
|
|
97
|
+
ce = self.flown.get_element(ceid+1)
|
|
98
|
+
path_length = (abs(ce.vel) * ce.dt).cumsum()
|
|
99
|
+
id = np.abs(path_length - path_length[-1] * fac).argmin()
|
|
100
|
+
results.add(F3A.single.angle(
|
|
101
|
+
f'centred element {ceid}',
|
|
102
|
+
Measurement.centre_box(State(ce.data.iloc[[id], :]))
|
|
103
|
+
))
|
|
104
|
+
|
|
105
|
+
if len(results) == 0 and self.mdef.info.position == Position.CENTRE:
|
|
106
|
+
al = self.flown.get_element(slice(1,-1,None))
|
|
107
|
+
midy = (self.flown.get_element(1).y[0] + self.flown.get_element(-1).y[-1]) / 2
|
|
108
|
+
midid = np.abs(al.pos.y - midy).argmin()
|
|
109
|
+
results.add(F3A.single.angle(
|
|
110
|
+
'centred manoeuvre',
|
|
111
|
+
Measurement.centre_box(al[midid])
|
|
112
|
+
))
|
|
113
|
+
|
|
114
|
+
return results
|
|
115
|
+
|
|
116
|
+
def distance(self):
|
|
117
|
+
#TODO doesnt quite cover it, stalled manoeuvres could drift to > 170 for no downgrade
|
|
118
|
+
return F3A.intra.depth(
|
|
119
|
+
'distance',
|
|
120
|
+
Measurement.depth(self.flown)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# val = self.flown.pos.y.mean()
|
|
124
|
+
# id = np.abs(self.flown.pos.y - val).argmin()
|
|
125
|
+
# m = Measurement.depth(self.flown[id])
|
|
126
|
+
# error = np.maximum(m.value, 170) - 170
|
|
127
|
+
# dist_dg = F3A.single.distance.lookup(error) * m.visibility
|
|
128
|
+
# return Result("distance", m, m.value, error, dist_dg, [id])
|
|
129
|
+
|
|
130
|
+
def intra(self):
|
|
131
|
+
return self.manoeuvre.analyse(self.flown, self.template)
|
|
132
|
+
|
|
133
|
+
def inter(self):
|
|
134
|
+
return self.mdef.mps.collect(self.manoeuvre, self.template)
|
|
135
|
+
|
|
136
|
+
def positioning(self):
|
|
137
|
+
pres = Results('positioning')
|
|
138
|
+
if self.mdef.info.position == Position.CENTRE:
|
|
139
|
+
pres.add(self.centre())
|
|
140
|
+
tp_width = max(self.corrected_template.y) - min(self.corrected_template.y)
|
|
141
|
+
if tp_width < 10:
|
|
142
|
+
pres.add(self.distance())
|
|
143
|
+
tb = self.top_box()
|
|
144
|
+
if tb.total > 0:
|
|
145
|
+
pres.add(self.top_box())
|
|
146
|
+
sb = self.side_box()
|
|
147
|
+
if sb.total > 0:
|
|
148
|
+
pres.add(self.side_box())
|
|
149
|
+
return pres
|
|
150
|
+
|
|
151
|
+
def plot_3d(self, **kwargs):
|
|
152
|
+
from flightplotting import plotsec, plotdtw
|
|
153
|
+
fig = plotdtw(self.flown, self.flown.data.element.unique())
|
|
154
|
+
return plotsec(self.flown, color="blue", nmodels=20, fig=fig, **kwargs)
|
|
155
|
+
|
|
156
|
+
from .scored import Scored # noqa: E402
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from .complete import Complete
|
|
3
|
+
from flightanalysis.scoring import ManoeuvreResults
|
|
4
|
+
from loguru import logger
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class Scored(Complete):
|
|
9
|
+
scores: ManoeuvreResults
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def from_dict(data:dict, fallback=True):
|
|
13
|
+
ca = Complete.from_dict(data, fallback)
|
|
14
|
+
try:
|
|
15
|
+
ca = Scored(
|
|
16
|
+
**ca.__dict__,
|
|
17
|
+
scores=ManoeuvreResults.from_dict(data["scores"])
|
|
18
|
+
)
|
|
19
|
+
except Exception as e:
|
|
20
|
+
if fallback:
|
|
21
|
+
logger.debug(f"Failed to read scores, {repr(e)}")
|
|
22
|
+
else:
|
|
23
|
+
raise e
|
|
24
|
+
return ca
|
|
25
|
+
|
|
26
|
+
def run(self):
|
|
27
|
+
return self
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Self, Union
|
|
3
|
+
from json import load
|
|
4
|
+
from flightdata import Flight, State, Origin, Collection
|
|
5
|
+
from flightanalysis.definition import SchedDef, ScheduleInfo
|
|
6
|
+
from . import manoeuvre_analysis as analysis
|
|
7
|
+
from loguru import logger
|
|
8
|
+
from joblib import Parallel, delayed
|
|
9
|
+
import os
|
|
10
|
+
import pandas as pd
|
|
11
|
+
from importlib.metadata import version
|
|
12
|
+
import geometry as g
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ScheduleAnalysis(Collection):
|
|
16
|
+
VType=analysis.Analysis
|
|
17
|
+
uid='name'
|
|
18
|
+
|
|
19
|
+
def __init__(self, data: list[analysis.Analysis], sinfo: ScheduleInfo):
|
|
20
|
+
super().__init__(data)
|
|
21
|
+
self.sinfo = sinfo
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def from_fcj(file: Union[str, dict], info: ScheduleInfo=None) -> ScheduleAnalysis:
|
|
25
|
+
if isinstance(file, str):
|
|
26
|
+
data = load(open(file, 'r'))
|
|
27
|
+
else:
|
|
28
|
+
data = file
|
|
29
|
+
flight = Flight.from_fc_json(data)
|
|
30
|
+
box = Origin.from_fcjson_parmameters(data["parameters"])
|
|
31
|
+
if info is None:
|
|
32
|
+
info = ScheduleInfo.from_str(data["parameters"]["schedule"][1])
|
|
33
|
+
sdef = SchedDef.load(info)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
state = State.from_flight(flight, box).splitter_labels(
|
|
38
|
+
data["mans"],
|
|
39
|
+
sdef.uids
|
|
40
|
+
)#.move(g.Transformation(g.Point(-data['parameters']['moveEast'], -data['parameters']['moveNorth'], 0)))
|
|
41
|
+
|
|
42
|
+
direction = -state.get_manoeuvre(0)[0].direction()[0]
|
|
43
|
+
|
|
44
|
+
return ScheduleAnalysis(
|
|
45
|
+
[analysis.Basic(
|
|
46
|
+
mdef,
|
|
47
|
+
state.get_manoeuvre(mdef.uid),
|
|
48
|
+
direction,
|
|
49
|
+
analysis.AlinmentStage.SETUP
|
|
50
|
+
) for mdef in sdef],
|
|
51
|
+
info
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def run_all(self) -> Self:
|
|
55
|
+
|
|
56
|
+
def parse_analyse_serialise(pad):
|
|
57
|
+
res = analysis.Basic.from_dict(pad).run_all()
|
|
58
|
+
logger.info(f'Completed {res.name}')
|
|
59
|
+
return res.to_dict()
|
|
60
|
+
|
|
61
|
+
logger.info(f'Starting {os.cpu_count()} analysis processes')
|
|
62
|
+
madicts = Parallel(n_jobs=os.cpu_count())(
|
|
63
|
+
delayed(parse_analyse_serialise)(ma.to_dict()) for ma in self
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return ScheduleAnalysis([analysis.Scored.from_dict(mad) for mad in madicts], self.sinfo)
|
|
67
|
+
|
|
68
|
+
def optimize_alignment(self) -> Self:
|
|
69
|
+
|
|
70
|
+
def parse_analyse_serialise(mad):
|
|
71
|
+
an = analysis.Complete.from_dict(mad)
|
|
72
|
+
an.stage = analysis.AlinmentStage.SECONDARY
|
|
73
|
+
return an.run_all().to_dict()
|
|
74
|
+
|
|
75
|
+
logger.info(f'Starting {os.cpu_count()} alinment optimisation processes')
|
|
76
|
+
inmadicts = [mdef.to_dict() for mdef in self]
|
|
77
|
+
madicts = Parallel(n_jobs=os.cpu_count())(delayed(parse_analyse_serialise)(mad) for mad in inmadicts)
|
|
78
|
+
return ScheduleAnalysis([analysis.Scored.from_dict(mad) for mad in madicts], self.sinfo)
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def from_fcscore(file: Union[str, dict], fallback=True) -> ScheduleAnalysis:
|
|
82
|
+
if isinstance(file, str) or isinstance(file, os.PathLike):
|
|
83
|
+
data = load(open(file, 'r'))
|
|
84
|
+
else:
|
|
85
|
+
data = file
|
|
86
|
+
sinfo = ScheduleInfo(**data['sinfo'])
|
|
87
|
+
sdef = SchedDef.load(sinfo)
|
|
88
|
+
|
|
89
|
+
mas = []
|
|
90
|
+
for mdef in sdef:
|
|
91
|
+
mas.append(analysis.Scored.from_dict(
|
|
92
|
+
data['data'][mdef.info.short_name],
|
|
93
|
+
fallback
|
|
94
|
+
))
|
|
95
|
+
|
|
96
|
+
return ScheduleAnalysis(mas, sinfo)
|
|
97
|
+
|
|
98
|
+
def scores(self):
|
|
99
|
+
scores = {}
|
|
100
|
+
total = 0
|
|
101
|
+
scores = {ma.name: (ma.scores.score() if hasattr(ma, 'scores') else 0) for ma in self}
|
|
102
|
+
total = sum([ma.mdef.info.k * v for ma, v in zip(self, scores.values())])
|
|
103
|
+
return total, scores
|
|
104
|
+
|
|
105
|
+
def summarydf(self):
|
|
106
|
+
return pd.DataFrame([ma.scores.summary() if hasattr(ma, 'scores') else {} for ma in self])
|
|
107
|
+
|
|
108
|
+
def to_fcscore(self, name: str) -> dict:
|
|
109
|
+
total, scores = self.scores()
|
|
110
|
+
|
|
111
|
+
odata = dict(
|
|
112
|
+
name = name,
|
|
113
|
+
client_version = 'Py',
|
|
114
|
+
server_version = version('flightanalysis'),
|
|
115
|
+
sinfo = self.sinfo.__dict__,
|
|
116
|
+
score = total,
|
|
117
|
+
manscores = scores,
|
|
118
|
+
data = self.to_dict()
|
|
119
|
+
)
|
|
120
|
+
return odata
|