fix mode into table

add change folder for analisys
This commit is contained in:
VALLONGOL 2025-07-14 16:14:08 +02:00
parent 5175132deb
commit b9b37a9aa8
6 changed files with 353 additions and 657 deletions

View File

@ -4,6 +4,7 @@
"last_out_output_dir": "C:/src/____GitProjects/radar_data_reader/flight_workspace/250515_122252_Flight",
"last_rec_output_dir": "C:\\src\\____GitProjects\\radar_data_reader\\_rec",
"last_flight_folder": "C:/__Voli/Volo_12_25maggio2025/rec",
"last_flight_workspace_parent_dir": "C:\\src\\____GitProjects\\radar_data_reader\\flight_workspace",
"active_out_export_profile_name": "gsp_data",
"export_profiles": [
{
@ -451,8 +452,8 @@
"create_separate_folders": true
},
"flight_analysis_options": {
"aggregate_by_scale": false,
"aggregate_by_waveform": false
"aggregate_by_scale": true,
"aggregate_by_waveform": true
},
"segment_export_g_reconvert_config": {
"cpp_executable_path": "C:/src/GRIFO-E/REP/Projects/Tools/wsLuna/g_reconvert/Debug/g_reconvert.exe",

View File

@ -6,10 +6,10 @@
import re
# --- Version Data (Generated) ---
__version__ = "v.0.0.0.38-0-g7251cf8-dirty"
GIT_COMMIT_HASH = "7251cf8e301716f904aab60b759fe8d9abd6664b"
__version__ = "v.0.0.0.39-0-g4066d4b"
GIT_COMMIT_HASH = "4066d4b92bffb9779b5bb530ff48d938a3f3fc95"
GIT_BRANCH = "master"
BUILD_TIMESTAMP = "2025-07-14T13:26:54.921185+00:00"
BUILD_TIMESTAMP = "2025-07-14T13:43:12.150911+00:00"
IS_GIT_REPO = True
# --- Default Values (for comparison or fallback) ---

File diff suppressed because it is too large Load Diff

View File

@ -27,7 +27,6 @@ log = logger.get_logger(__name__)
TICK_DURATION_S = 64e-6
class FlightAnalyzer:
"""Manages the multi-step process of analyzing a flight folder."""
@ -40,30 +39,30 @@ class FlightAnalyzer:
self.analysis_options: dict = {}
def start_analysis(
self, rec_folder_str: str, flight_name: str, analysis_options: dict
self, rec_folder_str: str, flight_name: str, workspace_path: Path, analysis_options: dict
) -> threading.Thread:
self.current_flight_name = flight_name
self.analysis_options = analysis_options
analysis_thread = threading.Thread(
target=self._flight_analysis_orchestrator,
args=(rec_folder_str, flight_name),
args=(rec_folder_str, flight_name, workspace_path),
daemon=True,
)
analysis_thread.start()
return analysis_thread
def _flight_analysis_orchestrator(self, rec_folder_str: str, flight_name: str):
def _flight_analysis_orchestrator(self, rec_folder_str: str, flight_name: str, flight_dir: Path):
self.current_flight_folder_path = None
try:
workspace_dir = Path.cwd() / "flight_workspace"
flight_dir = workspace_dir / flight_name
flight_dir.mkdir(parents=True, exist_ok=True)
self.current_flight_folder_path = flight_dir
cpp_config = self.config_manager.get_cpp_converter_config()
exe_path = cpp_config.get("cpp_executable_path")
if not exe_path or not Path(exe_path).is_file():
raise ValueError(f"C++ executable not found at path: {exe_path}")
raise ValueError(
f"C++ executable not found at path: {exe_path}"
)
rec_files = sorted(Path(rec_folder_str).glob("*.rec"))
if not rec_files:
@ -80,9 +79,7 @@ class FlightAnalyzer:
"/a",
]
log.info(
f"Running g_reconverter for full analysis: {' '.join(command_list)}"
)
log.info(f"Running g_reconverter for full analysis: {' '.join(command_list)}")
self.worker_process = mp.Process(
target=run_cpp_converter,
@ -102,12 +99,7 @@ class FlightAnalyzer:
def handle_final_analysis_steps(self):
if not self.current_flight_folder_path:
log.error("Cannot run final analysis steps: flight folder path is not set.")
self.result_queue.put(
{
"type": "error",
"message": "Internal state error: flight folder path missing.",
}
)
self.result_queue.put({"type": "error", "message": "Internal state error: flight folder path missing."})
return
try:
@ -129,21 +121,17 @@ class FlightAnalyzer:
summary_df = self._create_and_save_summary(
storyboard_df, self.current_flight_folder_path, self.analysis_options
)
self._create_flight_report_txt(summary_df, self.current_flight_folder_path)
self.result_queue.put(
{
"type": "analysis_summary_data",
"data": summary_df,
"flight_folder_path": self.current_flight_folder_path,
}
)
self.result_queue.put({
"type": "analysis_summary_data",
"data": summary_df,
"flight_folder_path": self.current_flight_folder_path
})
log.info("Flight analysis complete. All artifacts saved.")
self.result_queue.put(
{"type": "complete", "message": "Analysis successful."}
)
self.result_queue.put({"type": "complete", "message": "Analysis successful."})
except Exception as e:
log.error(f"Final analysis steps failed: {e}", exc_info=True)
@ -171,27 +159,25 @@ class FlightAnalyzer:
header_line = f.readline().strip()
raw_columns = [h.strip() for h in header_line.split(";") if h.strip()]
unique_column_names = self._make_columns_unique(raw_columns)
storyboard_df = pd.read_csv(
txt_path,
sep=";",
header=0,
names=unique_column_names,
on_bad_lines="skip",
encoding="utf-8",
encoding_errors="ignore",
txt_path,
sep=';',
header=0,
names=unique_column_names,
on_bad_lines='skip',
encoding='utf-8',
encoding_errors='ignore'
)
for col in storyboard_df.select_dtypes(include=["object"]).columns:
for col in storyboard_df.select_dtypes(include=['object']).columns:
storyboard_df[col] = storyboard_df[col].str.strip()
numeric_cols = ["Batch", "TTAG"]
for col in numeric_cols:
if col in storyboard_df.columns:
storyboard_df[col] = pd.to_numeric(
storyboard_df[col], errors="coerce"
)
storyboard_df[col] = pd.to_numeric(storyboard_df[col], errors="coerce")
storyboard_df.dropna(subset=["Batch", "TTAG"], inplace=True)
storyboard_df["Batch"] = storyboard_df["Batch"].astype(int)
storyboard_df["TTAG"] = storyboard_df["TTAG"].astype(int)
@ -199,112 +185,97 @@ class FlightAnalyzer:
except Exception as e:
log.error(f"Failed to read or process summary file {txt_path.name}: {e}")
return None
if storyboard_df.empty:
log.warning(f"DataFrame is empty after cleaning {txt_path.name}")
return None
csv_path = output_dir / "flight_storyboard.csv"
json_path = output_dir / "flight_storyboard.json"
log.info(f"Saving full storyboard to {csv_path}")
storyboard_df.to_csv(csv_path, index=False)
log.info(f"Saving full storyboard to {json_path}")
storyboard_df.to_json(json_path, orient="records", indent=4)
return storyboard_df
def _create_and_save_summary(
self, storyboard_df: "pd.DataFrame", output_dir: Path, options: dict
) -> "pd.DataFrame":
df = storyboard_df.copy()
agg_by_scale = options.get("aggregate_by_scale", True)
agg_by_waveform = options.get("aggregate_by_waveform", True)
# Costruzione dinamica della chiave di stato
status_components = []
mode_part = (
df.get("Mode", pd.Series(index=df.index, dtype=str))
.astype(str)
.str.strip()
.replace("", "N/A")
)
status_components.append(mode_part)
mode_part = df.get('Mode', pd.Series(index=df.index, dtype=str)).astype(str).str.strip().replace('', 'N/A')
submode_part = df.get('Mode.3', pd.Series(index=df.index, dtype=str)).astype(str).str.strip().replace('', 'N/A')
# Unisci sempre Mode e Submode
full_mode_part = mode_part + "-" + submode_part
status_components.append(full_mode_part)
if agg_by_scale:
scale_part = (
df.get("Scal.2", pd.Series(index=df.index, dtype=str))
.astype(str)
.str.strip()
.replace("", "N/A")
)
scale_part = df.get('Scal.2', pd.Series(index=df.index, dtype=str)).astype(str).str.strip().replace('', 'N/A')
status_components.append(scale_part)
if agg_by_waveform:
wf_part1 = (
df.get("WF", pd.Series(index=df.index, dtype=str))
.astype(str)
.str.strip()
.replace("", "N/A")
)
wf_part2 = (
df.get("WF.2", pd.Series(index=df.index, dtype=str))
.astype(str)
.str.strip()
.replace("", "N/A")
)
wf_part1 = df.get('WF', pd.Series(index=df.index, dtype=str)).astype(str).str.strip().replace('', 'N/A')
wf_part2 = df.get('WF.2', pd.Series(index=df.index, dtype=str)).astype(str).str.strip().replace('', 'N/A')
status_components.append("wf-" + wf_part1 + "-" + wf_part2)
df["status"] = status_components[0]
df['status'] = status_components[0]
for component in status_components[1:]:
df["status"] = df["status"] + "_" + component
df['status'] = df['status'] + "_" + component
df["status_changed"] = df["status"].ne(df["status"].shift())
df['status_changed'] = df['status'].ne(df['status'].shift())
min_ttag = df['TTAG'].min()
df['flight_time_s'] = (df['TTAG'] - min_ttag) * TICK_DURATION_S
min_ttag = df["TTAG"].min()
df["flight_time_s"] = (df["TTAG"] - min_ttag) * TICK_DURATION_S
change_indices = df[df["status_changed"]].index.tolist()
change_indices = df[df['status_changed']].index.tolist()
if not change_indices or change_indices[0] != 0:
change_indices.insert(0, 0)
if df.index[-1] + 1 not in change_indices:
change_indices.append(df.index[-1] + 1)
summary_records = []
for i in range(len(change_indices) - 1):
start_loc, end_loc = change_indices[i], change_indices[i + 1] - 1
start_loc, end_loc = change_indices[i], change_indices[i+1] - 1
segment = df.loc[start_loc:end_loc]
if segment.empty:
continue
start_time_s = segment["flight_time_s"].iloc[0]
end_time_s = segment["flight_time_s"].iloc[-1]
summary_records.append(
{
"Segment (Mode | Scale | WF)": segment["status"].iloc[0],
"Start Batch": segment["Batch"].iloc[0],
"End Batch": segment["Batch"].iloc[-1],
"Batch Count": segment["Batch"].iloc[-1]
- segment["Batch"].iloc[0]
+ 1,
"Duration (s)": end_time_s - start_time_s,
"start_time_str": str(timedelta(seconds=int(start_time_s))),
"end_time_str": str(timedelta(seconds=int(end_time_s))),
"Start File": segment["file"].iloc[0],
"End File": segment["file"].iloc[-1],
"# Files": segment["file"].nunique(),
}
)
if segment.empty: continue
start_time_s = segment['flight_time_s'].iloc[0]
end_time_s = segment['flight_time_s'].iloc[-1]
summary_records.append({
'Segment (Mode | Scale | WF)': segment['status'].iloc[0],
'Start Batch': segment['Batch'].iloc[0],
'End Batch': segment['Batch'].iloc[-1],
'Batch Count': segment['Batch'].iloc[-1] - segment['Batch'].iloc[0] + 1,
'Duration (s)': end_time_s - start_time_s,
'start_time_str': str(timedelta(seconds=int(start_time_s))),
'end_time_str': str(timedelta(seconds=int(end_time_s))),
'Start File': segment['file'].iloc[0],
'End File': segment['file'].iloc[-1],
'# Files': segment['file'].nunique()
})
summary_df = pd.DataFrame(summary_records)
csv_path = output_dir / "flight_summary.csv"
json_path = output_dir / "flight_summary.json"
log.info(f"Saving aggregated summary to {csv_path}")
summary_df.to_csv(csv_path, index=False)
log.info(f"Saving aggregated summary to {json_path}")
summary_df.to_json(json_path, orient="records", indent=4)
return summary_df
def _create_flight_report_txt(self, summary_df: "pd.DataFrame", output_dir: Path):
@ -312,8 +283,8 @@ class FlightAnalyzer:
log.info(f"Generating human-readable flight report to {report_path}")
try:
total_duration = summary_df["Duration (s)"].sum()
total_batches = summary_df["Batch Count"].sum()
total_duration = summary_df['Duration (s)'].sum()
total_batches = summary_df['Batch Count'].sum()
num_segments = len(summary_df)
with open(report_path, "w", encoding="utf-8") as f:
@ -327,39 +298,26 @@ class FlightAnalyzer:
f.write(f"Total Segments: {num_segments}\n\n")
f.write("--- SEGMENT SUMMARY ---\n")
report_df = summary_df.copy()
report_df["Duration (s)"] = report_df["Duration (s)"].map(
"{:.2f}".format
)
report_df = report_df[
[
"Segment (Mode | Scale | WF)",
"Start Batch",
"End Batch",
"Batch Count",
"start_time_str",
"end_time_str",
"Duration (s)",
"Start File",
"End File",
"# Files",
]
]
report_df.rename(
columns={
"start_time_str": "Start Time",
"end_time_str": "End Time",
},
inplace=True,
)
report_df['Duration (s)'] = report_df['Duration (s)'].map('{:.2f}'.format)
report_df = report_df[[
'Segment (Mode | Scale | WF)',
'Start Batch', 'End Batch', 'Batch Count',
'start_time_str', 'end_time_str', 'Duration (s)',
'Start File', 'End File', '# Files'
]]
report_df.rename(columns={
'start_time_str': 'Start Time',
'end_time_str': 'End Time'
}, inplace=True)
df_string = report_df.to_string(index=True)
f.write(df_string)
f.write("\n\n" + "=" * 80 + "\n")
f.write(" End of Report\n")
except Exception as e:
log.error(f"Failed to generate flight text report: {e}")
log.error(f"Failed to generate flight text report: {e}")

View File

@ -81,6 +81,7 @@ class MainWindow(tk.Frame):
self.analyzer_rec_folder_var = tk.StringVar()
self.analyzer_flight_name_var = tk.StringVar()
self.analyzer_workspace_dir_var = tk.StringVar()
self.analyzer_info_var = tk.StringVar(
value="Please select a folder and a flight name."
)
@ -137,98 +138,87 @@ class MainWindow(tk.Frame):
def _create_flight_analyzer_tab(self, parent):
parent.columnconfigure(0, weight=1)
parent.rowconfigure(4, weight=1)
parent.rowconfigure(5, weight=1) # Aumenta per fare spazio ai nuovi frame
setup_frame = ttk.LabelFrame(parent, text="Flight Setup")
# --- Frame 1: Flight Source ---
setup_frame = ttk.LabelFrame(parent, text="Flight Source")
setup_frame.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
setup_frame.columnconfigure(1, weight=1)
ttk.Label(setup_frame, text="Recordings Folder:").grid(
row=0, column=0, padx=5, pady=5, sticky="w"
)
rec_folder_entry = ttk.Entry(
setup_frame, textvariable=self.analyzer_rec_folder_var, state="readonly"
)
ttk.Label(setup_frame, text="Recordings Folder:").grid(row=0, column=0, padx=5, pady=5, sticky="w")
rec_folder_entry = ttk.Entry(setup_frame, textvariable=self.analyzer_rec_folder_var, state="readonly")
rec_folder_entry.grid(row=0, column=1, sticky="ew", padx=5)
ttk.Button(setup_frame, text="Browse...", command=self.controller.select_and_analyze_flight_folder).grid(row=0, column=2, padx=5)
# --- Frame 2: Workspace ---
workspace_frame = ttk.LabelFrame(parent, text="Flight Workspace (Analysis Output)")
workspace_frame.grid(row=1, column=0, sticky="ew", padx=5, pady=5)
workspace_frame.columnconfigure(1, weight=1)
ttk.Label(workspace_frame, text="Workspace Path:").grid(row=0, column=0, padx=5, pady=5, sticky="w")
workspace_entry = ttk.Entry(workspace_frame, textvariable=self.analyzer_workspace_dir_var)
workspace_entry.grid(row=0, column=1, sticky="ew", padx=5)
dir_buttons_frame = ttk.Frame(workspace_frame)
dir_buttons_frame.grid(row=0, column=2, padx=5)
ttk.Button(
setup_frame,
text="Browse...",
command=self.controller.select_and_analyze_flight_folder,
).grid(row=0, column=2, padx=5)
ttk.Label(setup_frame, text="Flight Name:").grid(
row=1, column=0, padx=5, pady=5, sticky="w"
)
flight_name_entry = ttk.Entry(
setup_frame, textvariable=self.analyzer_flight_name_var
)
dir_buttons_frame, text="Browse...",
command=lambda: self.controller.select_output_dir(self.analyzer_workspace_dir_var)
).pack(side=tk.LEFT)
ttk.Button(
dir_buttons_frame, text="Open...",
command=lambda: self.controller.open_folder_from_path(self.analyzer_workspace_dir_var.get())
).pack(side=tk.LEFT, padx=(5, 0))
ttk.Label(workspace_frame, text="Flight Name:").grid(row=1, column=0, padx=5, pady=5, sticky="w")
flight_name_entry = ttk.Entry(workspace_frame, textvariable=self.analyzer_flight_name_var)
flight_name_entry.grid(row=1, column=1, columnspan=2, sticky="ew", padx=5)
aggregation_frame = ttk.LabelFrame(parent, text="Segment Definition")
aggregation_frame.grid(row=1, column=0, sticky="ew", padx=5, pady=5)
# --- Frame 3: Segment Definition ---
aggregation_frame = ttk.LabelFrame(parent, text="Segment Definition Options")
aggregation_frame.grid(row=2, column=0, sticky="ew", padx=5, pady=5)
ttk.Checkbutton(
aggregation_frame,
aggregation_frame,
text="Create new segment on Scale change",
variable=self.aggregate_by_scale_var,
variable=self.aggregate_by_scale_var
).pack(side=tk.LEFT, padx=10, pady=5)
ttk.Checkbutton(
aggregation_frame,
aggregation_frame,
text="Create new segment on Waveform (WF) change",
variable=self.aggregate_by_waveform_var,
variable=self.aggregate_by_waveform_var
).pack(side=tk.LEFT, padx=10, pady=5)
# --- Frame 4: Actions & Info ---
action_frame = ttk.Frame(parent)
action_frame.grid(row=2, column=0, sticky="ew", padx=5, pady=10)
self.start_analysis_button = ttk.Button(
action_frame,
text="Start Flight Analysis",
command=self.controller.start_flight_analysis,
state=tk.DISABLED,
)
action_frame.grid(row=3, column=0, sticky="ew", padx=5, pady=10)
self.start_analysis_button = ttk.Button(action_frame, text="Start Flight Analysis", command=self.controller.start_flight_analysis, state=tk.DISABLED)
self.start_analysis_button.pack(side=tk.LEFT, padx=(0, 5))
self.open_flight_folder_button = ttk.Button(
action_frame,
text="Open Flight Folder",
command=self.controller.open_current_flight_folder,
state=tk.DISABLED,
)
self.open_flight_folder_button = ttk.Button(action_frame, text="Open Flight Folder", command=self.controller.open_current_flight_folder, state=tk.DISABLED)
self.open_flight_folder_button.pack(side=tk.LEFT, padx=5)
info_label = ttk.Label(action_frame, textvariable=self.analyzer_info_var)
info_label.pack(side=tk.LEFT, padx=20)
# --- Frame 5: Progress ---
progress_frame = ttk.Frame(parent)
progress_frame.grid(row=3, column=0, sticky="ew", padx=5, pady=5)
progress_frame.grid(row=4, column=0, sticky="ew", padx=5, pady=5)
progress_frame.columnconfigure(1, weight=1)
ttk.Label(progress_frame, text="Analysis Progress:").grid(
row=0, column=0, sticky="w"
)
self.analyzer_progressbar = ttk.Progressbar(
progress_frame, variable=self.analyzer_progress_var
)
ttk.Label(progress_frame, text="Analysis Progress:").grid(row=0, column=0, sticky="w")
self.analyzer_progressbar = ttk.Progressbar(progress_frame, variable=self.analyzer_progress_var)
self.analyzer_progressbar.grid(row=0, column=1, sticky="ew", padx=5)
ttk.Label(progress_frame, textvariable=self.analyzer_progress_text_var).grid(
row=0, column=2, sticky="w"
)
ttk.Label(progress_frame, textvariable=self.analyzer_progress_text_var).grid(row=0, column=2, sticky="w")
# --- Frame 6: Results ---
results_frame = ttk.LabelFrame(parent, text="Flight Summary & Segments")
results_frame.grid(row=4, column=0, sticky="nsew", padx=5, pady=5)
results_frame.grid(row=5, column=0, sticky="nsew", padx=5, pady=5)
results_frame.columnconfigure(0, weight=1)
results_frame.rowconfigure(0, weight=1)
self.flight_timeline_tree = ttk.Treeview(
results_frame,
columns=(
"status",
"start_batch",
"end_batch",
"batch_count",
"duration",
"start_file",
"end_file",
"file_count",
),
show="headings",
selectmode="extended",
columns=("status", "start_batch", "end_batch", "batch_count", "duration", "start_file", "end_file", "file_count"),
show="headings", selectmode="extended"
)
self.flight_timeline_tree.heading("status", text="Segment (Mode | Scale | WF)")
self.flight_timeline_tree.heading("start_batch", text="Start Batch")
@ -247,32 +237,17 @@ class MainWindow(tk.Frame):
self.flight_timeline_tree.column("end_file", width=200, stretch=True)
self.flight_timeline_tree.column("file_count", width=60, anchor="center")
self.flight_timeline_tree.grid(row=0, column=0, sticky="nsew")
tree_scrollbar = ttk.Scrollbar(
results_frame, orient="vertical", command=self.flight_timeline_tree.yview
)
tree_scrollbar = ttk.Scrollbar(results_frame, orient="vertical", command=self.flight_timeline_tree.yview)
self.flight_timeline_tree.configure(yscrollcommand=tree_scrollbar.set)
tree_scrollbar.grid(row=0, column=1, sticky="ns")
buttons_frame = ttk.Frame(results_frame)
buttons_frame.grid(row=1, column=0, columnspan=2, sticky="ew", pady=5)
ttk.Button(
buttons_frame, text="Select All", command=self._select_all_segments
).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(
buttons_frame, text="Select None", command=self._deselect_all_segments
).pack(side=tk.LEFT)
self.export_segment_button = ttk.Button(
buttons_frame,
text="Export Selected Segment(s)",
state=tk.DISABLED,
command=self.controller.start_segment_export,
)
ttk.Button(buttons_frame, text="Select All", command=self._select_all_segments).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(buttons_frame, text="Select None", command=self._deselect_all_segments).pack(side=tk.LEFT)
self.export_segment_button = ttk.Button(buttons_frame, text="Export Selected Segment(s)", state=tk.DISABLED, command=self.controller.start_segment_export)
self.export_segment_button.pack(side=tk.RIGHT)
configure_export_button = ttk.Button(
buttons_frame,
text="Configure Segment Export...",
command=self.controller.open_export_config_editor,
)
configure_export_button = ttk.Button(buttons_frame, text="Configure Segment Export...", command=self.controller.open_export_config_editor)
configure_export_button.pack(side=tk.RIGHT, padx=5)
def _select_all_segments(self):

View File

@ -99,6 +99,7 @@ class ConfigManager:
"last_out_output_dir": "",
"last_rec_output_dir": "",
"last_flight_folder": "",
"last_flight_workspace_parent_dir": "",
"active_out_export_profile_name": "Default",
"export_profiles": [default_export_profile.to_dict()],
"cpp_converter_config": default_cpp_config,