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.
Files changed (108) hide show
  1. {flightanalysis-0.2.4/flightanalysis.egg-info → flightanalysis-0.2.6}/PKG-INFO +9 -1
  2. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/README.md +1 -0
  3. flightanalysis-0.2.6/flightanalysis/__init__.py +10 -0
  4. flightanalysis-0.2.6/flightanalysis/analysis/__init__.py +3 -0
  5. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/analysis/el_analysis.py +6 -3
  6. flightanalysis-0.2.6/flightanalysis/analysis/manoeuvre_analysis/__init__.py +8 -0
  7. flightanalysis-0.2.6/flightanalysis/analysis/manoeuvre_analysis/alignment.py +71 -0
  8. flightanalysis-0.2.6/flightanalysis/analysis/manoeuvre_analysis/analysis.py +16 -0
  9. flightanalysis-0.2.6/flightanalysis/analysis/manoeuvre_analysis/basic.py +83 -0
  10. flightanalysis-0.2.6/flightanalysis/analysis/manoeuvre_analysis/complete.py +156 -0
  11. flightanalysis-0.2.6/flightanalysis/analysis/manoeuvre_analysis/scored.py +27 -0
  12. flightanalysis-0.2.6/flightanalysis/analysis/sch_analysis.py +120 -0
  13. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/data/IMAC_Unlimited2024_schedule.json +80 -108
  14. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/data/__init__.py +0 -1
  15. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/data/f3a_a25_schedule.json +66 -66
  16. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/data/f3a_f25_schedule.json +108 -108
  17. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/data/f3a_p23_schedule.json +96 -96
  18. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/data/f3a_p25_schedule.json +631 -388
  19. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/data/f3auk_clubman_schedule.json +52 -52
  20. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/data/f3auk_inter_schedule.json +45 -45
  21. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/__init__.py +4 -2
  22. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/builders/elbuilders.py +10 -17
  23. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/builders/manbuilder.py +19 -14
  24. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/collectors.py +1 -2
  25. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/eldef.py +5 -5
  26. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/mandef.py +38 -32
  27. flightanalysis-0.2.6/flightanalysis/definition/manoption.py +38 -0
  28. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/manparm.py +15 -16
  29. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/scheddef.py +13 -71
  30. flightanalysis-0.2.6/flightanalysis/definition/scheduleinfo.py +45 -0
  31. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/elements/autorotation.py +2 -5
  32. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/elements/element.py +12 -14
  33. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/elements/line.py +5 -2
  34. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/elements/loop.py +8 -7
  35. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/elements/nose_drop.py +4 -3
  36. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/elements/pitch_break.py +3 -2
  37. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/manoeuvre.py +25 -15
  38. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/criteria/__init__.py +1 -1
  39. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/criteria/criteria.py +0 -1
  40. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/criteria/exponential.py +8 -5
  41. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/criteria/f3a_criteria.py +14 -11
  42. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/criteria/inter/combination.py +4 -2
  43. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/criteria/intra/bounded.py +18 -5
  44. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/criteria/intra/continuous.py +49 -29
  45. flightanalysis-0.2.6/flightanalysis/scoring/criteria/intra/single.py +35 -0
  46. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/measurement.py +77 -16
  47. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/results.py +10 -9
  48. {flightanalysis-0.2.4 → flightanalysis-0.2.6/flightanalysis.egg-info}/PKG-INFO +9 -1
  49. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis.egg-info/SOURCES.txt +12 -27
  50. flightanalysis-0.2.6/flightanalysis.egg-info/requires.txt +7 -0
  51. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis.egg-info/top_level.txt +1 -1
  52. flightanalysis-0.2.6/scripts/batch_analyse.py +52 -0
  53. flightanalysis-0.2.6/scripts/collect_scores.py +46 -0
  54. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/setup.cfg +13 -2
  55. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/tests/test_criiteria.py +75 -5
  56. flightanalysis-0.2.4/examples/schedules_construction/AMA_Intermediate2024.py +0 -201
  57. flightanalysis-0.2.4/examples/schedules_construction/create_all.py +0 -21
  58. flightanalysis-0.2.4/examples/schedules_construction/f3a_a25.py +0 -247
  59. flightanalysis-0.2.4/examples/schedules_construction/f3a_f25.py +0 -265
  60. flightanalysis-0.2.4/examples/schedules_construction/f3a_p23.py +0 -231
  61. flightanalysis-0.2.4/examples/schedules_construction/f3a_p25.py +0 -222
  62. flightanalysis-0.2.4/examples/schedules_construction/f3auk_Intermediate.py +0 -201
  63. flightanalysis-0.2.4/examples/schedules_construction/f3auk_clubman.py +0 -207
  64. flightanalysis-0.2.4/examples/schedules_construction/imac_sport2024.py +0 -132
  65. flightanalysis-0.2.4/examples/schedules_construction/imac_unlim2024.py +0 -139
  66. flightanalysis-0.2.4/examples/schedules_construction/make_manoeuvre.py +0 -56
  67. flightanalysis-0.2.4/examples/schedules_construction/nsrca_inter2024.py +0 -210
  68. flightanalysis-0.2.4/examples/scoring/__init__.py +0 -28
  69. flightanalysis-0.2.4/examples/scoring/analysis_reprocess.py +0 -24
  70. flightanalysis-0.2.4/examples/scoring/f3a_criteria_maker.py +0 -41
  71. flightanalysis-0.2.4/examples/scoring/judging.py +0 -50
  72. flightanalysis-0.2.4/examples/scoring/manoeuvres/inter_analysis.py +0 -19
  73. flightanalysis-0.2.4/examples/scoring/manoeuvres/intra_analysis.py +0 -26
  74. flightanalysis-0.2.4/examples/scoring/manoeuvres/mans/__init__.py +0 -0
  75. flightanalysis-0.2.4/examples/scoring/manoeuvres/mans/extract_mans.py +0 -28
  76. flightanalysis-0.2.4/examples/scoring/manoeuvres/positioning_analysis.py +0 -11
  77. flightanalysis-0.2.4/examples/scoring/split_optimisation.py +0 -50
  78. flightanalysis-0.2.4/flightanalysis/__init__.py +0 -6
  79. flightanalysis-0.2.4/flightanalysis/analysis/__init__.py +0 -3
  80. flightanalysis-0.2.4/flightanalysis/analysis/man_analysis.py +0 -267
  81. flightanalysis-0.2.4/flightanalysis/analysis/sch_analysis.py +0 -86
  82. flightanalysis-0.2.4/flightanalysis/definition/builders/__init__.py +0 -0
  83. flightanalysis-0.2.4/flightanalysis/scoring/criteria/inter/__init__.py +0 -0
  84. flightanalysis-0.2.4/flightanalysis/scoring/criteria/intra/__init__.py +0 -0
  85. flightanalysis-0.2.4/flightanalysis/scoring/criteria/intra/single.py +0 -38
  86. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/LICENSE +0 -0
  87. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/MANIFEST.in +0 -0
  88. {flightanalysis-0.2.4/examples → flightanalysis-0.2.6/flightanalysis/definition/builders}/__init__.py +0 -0
  89. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/builders/lines.py +0 -0
  90. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/maninfo.py +0 -0
  91. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/operations/__init__.py +0 -0
  92. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/operations/funopp.py +0 -0
  93. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/operations/itemopp.py +0 -0
  94. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/operations/mathopp.py +0 -0
  95. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/definition/operations/operation.py +0 -0
  96. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/elements/__init__.py +0 -0
  97. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/elements/recovery.py +0 -0
  98. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/elements/stall_turn.py +0 -0
  99. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/schedule.py +0 -0
  100. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/__init__.py +0 -0
  101. {flightanalysis-0.2.4/examples/data → flightanalysis-0.2.6/flightanalysis/scoring/criteria/inter}/__init__.py +0 -0
  102. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/criteria/inter/comparison.py +0 -0
  103. {flightanalysis-0.2.4/examples/schedules_construction → flightanalysis-0.2.6/flightanalysis/scoring/criteria/intra}/__init__.py +0 -0
  104. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis/scoring/downgrade.py +0 -0
  105. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/flightanalysis.egg-info/dependency_links.txt +0 -0
  106. {flightanalysis-0.2.4/examples/scoring/manoeuvres → flightanalysis-0.2.6/scripts}/__init__.py +0 -0
  107. {flightanalysis-0.2.4 → flightanalysis-0.2.6}/setup.py +0 -0
  108. {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.4
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
@@ -4,3 +4,4 @@ This package contains tools for analysing recorded flight log data.The main obje
4
4
  to facilitate automated judging for precision aerobatic competitions.
5
5
 
6
6
 
7
+ 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')
@@ -0,0 +1,3 @@
1
+ from .el_analysis import ElementAnalysis
2
+ from .manoeuvre_analysis import Analysis, Basic, Alignment, Complete, Scored
3
+ from .sch_analysis import ScheduleAnalysis
@@ -1,8 +1,8 @@
1
1
 
2
2
  from flightdata import State
3
3
  from typing import Self
4
- from flightanalysis.definition import ElDef
5
- from flightanalysis.elements import Element
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,8 @@
1
+ from .analysis import Analysis, AlinmentStage
2
+ from .basic import Basic
3
+ from .alignment import Alignment
4
+ from .complete import Complete, Scored
5
+
6
+
7
+ def parse_dict(data: dict) -> Analysis:
8
+ return Scored.from_dict(data)
@@ -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