Update app.py
Browse files
app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# app.py
|
| 2 |
import pandas as pd
|
| 3 |
import numpy as np
|
| 4 |
import matplotlib.pyplot as plt
|
|
@@ -30,6 +30,7 @@ class EnhancedAIvsRealGazeAnalyzer:
|
|
| 30 |
self.model = None
|
| 31 |
self.scaler = None
|
| 32 |
self.feature_names = []
|
|
|
|
| 33 |
|
| 34 |
def _find_and_standardize_participant_col(self, df, filename):
|
| 35 |
participant_col = next((c for c in df.columns if 'participant' in str(c).lower()), None)
|
|
@@ -84,7 +85,10 @@ class EnhancedAIvsRealGazeAnalyzer:
|
|
| 84 |
self.combined_data['Answer_Correctness'] = self.combined_data['Correct'].map({True: 'Correct', False: 'Incorrect'})
|
| 85 |
|
| 86 |
self.numeric_cols = self.combined_data.select_dtypes(include=np.number).columns.tolist()
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
| 88 |
self.valid_playback_participants = sorted(list(self.valid_playback_trials.keys()))
|
| 89 |
print(f"--- Data Loading Successful. Found {len(self.valid_playback_participants)} participants with fixation data. ---")
|
| 90 |
return self
|
|
@@ -178,28 +182,22 @@ class EnhancedAIvsRealGazeAnalyzer:
|
|
| 178 |
valid_questions = self.valid_playback_trials.get(participant, [])
|
| 179 |
return gr.Dropdown(choices=sorted(valid_questions), interactive=True, value=None, label="2. Select a Question")
|
| 180 |
|
| 181 |
-
# <<< FIX START: Step 1 -> Add a new handler method for when a question is selected. >>>
|
| 182 |
-
# This function provides a clean, single entry point to reset the visualization.
|
| 183 |
def handle_new_trial_selection(self, participant, question):
|
| 184 |
"""Called when a new trial is selected. Resets the view to the first fixation."""
|
| 185 |
if not participant or not question:
|
| 186 |
-
# Return a default/empty state for all outputs if nothing is selected
|
| 187 |
return "Select a trial to begin.", None, gr.Slider(value=0, interactive=False)
|
| 188 |
-
|
| 189 |
-
# Start the playback at the very first fixation for a better user experience.
|
| 190 |
-
# You can change '1' to '0' if you prefer it to start completely empty.
|
| 191 |
initial_fixation_num = 1
|
| 192 |
-
|
| 193 |
-
# Call the main generation function with this fixed initial value
|
| 194 |
return self.generate_gaze_playback(participant, question, initial_fixation_num)
|
| 195 |
-
# <<< FIX END >>>
|
| 196 |
|
| 197 |
# --- DATA SETUP & GRADIO APP ---
|
| 198 |
def setup_and_load_data():
|
| 199 |
repo_url = "https://github.com/RextonRZ/GenAIEyeTrackingCleanedDataset"
|
| 200 |
repo_dir = "GenAIEyeTrackingCleanedDataset"
|
| 201 |
-
if not os.path.exists(repo_dir):
|
| 202 |
-
|
|
|
|
|
|
|
|
|
|
| 203 |
base_path = repo_dir
|
| 204 |
response_file_path = os.path.join(repo_dir, "GenAI Response.xlsx")
|
| 205 |
analyzer = EnhancedAIvsRealGazeAnalyzer().load_and_process_data(base_path, response_file_path)
|
|
@@ -248,35 +246,52 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
| 248 |
|
| 249 |
rq1_metric_dropdown.change(fn=analyzer.analyze_rq1_metric, inputs=rq1_metric_dropdown, outputs=[rq1_plot_output, rq1_summary_output])
|
| 250 |
|
| 251 |
-
rq2_test_size_slider.release(fn=analyzer.run_prediction_model, inputs=[rq2_test_size_slider, rq2_estimators_slider], outputs=outputs_rq2)
|
| 252 |
rq2_estimators_slider.release(fn=analyzer.run_prediction_model, inputs=[rq2_test_size_slider, rq2_estimators_slider], outputs=outputs_rq2)
|
| 253 |
|
| 254 |
-
# <<< FIX START: Step 2 -> Update the event wiring for Tab 3. >>>
|
| 255 |
-
# Chained dropdown logic for Tab 3
|
| 256 |
playback_participant.change(
|
| 257 |
fn=analyzer.update_question_dropdown,
|
| 258 |
inputs=playback_participant,
|
| 259 |
outputs=playback_question
|
| 260 |
)
|
| 261 |
|
| 262 |
-
# When a question is selected, call the new handler to reset the entire playback view.
|
| 263 |
-
# This is much cleaner and more reliable than the previous .then() chain.
|
| 264 |
playback_question.change(
|
| 265 |
fn=analyzer.handle_new_trial_selection,
|
| 266 |
inputs=[playback_participant, playback_question],
|
| 267 |
outputs=outputs_playback
|
| 268 |
)
|
| 269 |
|
| 270 |
-
# The slider event handler remains the same and works correctly for scrubbing through fixations.
|
| 271 |
playback_slider.release(
|
| 272 |
fn=analyzer.generate_gaze_playback,
|
| 273 |
inputs=[playback_participant, playback_question, playback_slider],
|
| 274 |
outputs=outputs_playback
|
| 275 |
)
|
| 276 |
-
# <<< FIX END >>>
|
| 277 |
|
| 278 |
-
|
| 279 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
|
| 281 |
if __name__ == "__main__":
|
| 282 |
demo.launch()
|
|
|
|
| 1 |
+
# app.py (Corrected and Ready to Run)
|
| 2 |
import pandas as pd
|
| 3 |
import numpy as np
|
| 4 |
import matplotlib.pyplot as plt
|
|
|
|
| 30 |
self.model = None
|
| 31 |
self.scaler = None
|
| 32 |
self.feature_names = []
|
| 33 |
+
self.time_metrics = [] # Initialize here
|
| 34 |
|
| 35 |
def _find_and_standardize_participant_col(self, df, filename):
|
| 36 |
participant_col = next((c for c in df.columns if 'participant' in str(c).lower()), None)
|
|
|
|
| 85 |
self.combined_data['Answer_Correctness'] = self.combined_data['Correct'].map({True: 'Correct', False: 'Incorrect'})
|
| 86 |
|
| 87 |
self.numeric_cols = self.combined_data.select_dtypes(include=np.number).columns.tolist()
|
| 88 |
+
|
| 89 |
+
# <<< FIX: Removed the space in the variable name here >>>
|
| 90 |
+
self.time_metrics = [c for c in self.numeric_cols if any(k in c.lower() for k in ['time', 'duration', 'fixation'])]
|
| 91 |
+
|
| 92 |
self.valid_playback_participants = sorted(list(self.valid_playback_trials.keys()))
|
| 93 |
print(f"--- Data Loading Successful. Found {len(self.valid_playback_participants)} participants with fixation data. ---")
|
| 94 |
return self
|
|
|
|
| 182 |
valid_questions = self.valid_playback_trials.get(participant, [])
|
| 183 |
return gr.Dropdown(choices=sorted(valid_questions), interactive=True, value=None, label="2. Select a Question")
|
| 184 |
|
|
|
|
|
|
|
| 185 |
def handle_new_trial_selection(self, participant, question):
|
| 186 |
"""Called when a new trial is selected. Resets the view to the first fixation."""
|
| 187 |
if not participant or not question:
|
|
|
|
| 188 |
return "Select a trial to begin.", None, gr.Slider(value=0, interactive=False)
|
|
|
|
|
|
|
|
|
|
| 189 |
initial_fixation_num = 1
|
|
|
|
|
|
|
| 190 |
return self.generate_gaze_playback(participant, question, initial_fixation_num)
|
|
|
|
| 191 |
|
| 192 |
# --- DATA SETUP & GRADIO APP ---
|
| 193 |
def setup_and_load_data():
|
| 194 |
repo_url = "https://github.com/RextonRZ/GenAIEyeTrackingCleanedDataset"
|
| 195 |
repo_dir = "GenAIEyeTrackingCleanedDataset"
|
| 196 |
+
if not os.path.exists(repo_dir):
|
| 197 |
+
print(f"Cloning repository {repo_url}...")
|
| 198 |
+
git.Repo.clone_from(repo_url, repo_dir)
|
| 199 |
+
else:
|
| 200 |
+
print("Data repository already exists.")
|
| 201 |
base_path = repo_dir
|
| 202 |
response_file_path = os.path.join(repo_dir, "GenAI Response.xlsx")
|
| 203 |
analyzer = EnhancedAIvsRealGazeAnalyzer().load_and_process_data(base_path, response_file_path)
|
|
|
|
| 246 |
|
| 247 |
rq1_metric_dropdown.change(fn=analyzer.analyze_rq1_metric, inputs=rq1_metric_dropdown, outputs=[rq1_plot_output, rq1_summary_output])
|
| 248 |
|
| 249 |
+
train_event = rq2_test_size_slider.release(fn=analyzer.run_prediction_model, inputs=[rq2_test_size_slider, rq2_estimators_slider], outputs=outputs_rq2)
|
| 250 |
rq2_estimators_slider.release(fn=analyzer.run_prediction_model, inputs=[rq2_test_size_slider, rq2_estimators_slider], outputs=outputs_rq2)
|
| 251 |
|
|
|
|
|
|
|
| 252 |
playback_participant.change(
|
| 253 |
fn=analyzer.update_question_dropdown,
|
| 254 |
inputs=playback_participant,
|
| 255 |
outputs=playback_question
|
| 256 |
)
|
| 257 |
|
|
|
|
|
|
|
| 258 |
playback_question.change(
|
| 259 |
fn=analyzer.handle_new_trial_selection,
|
| 260 |
inputs=[playback_participant, playback_question],
|
| 261 |
outputs=outputs_playback
|
| 262 |
)
|
| 263 |
|
|
|
|
| 264 |
playback_slider.release(
|
| 265 |
fn=analyzer.generate_gaze_playback,
|
| 266 |
inputs=[playback_participant, playback_question, playback_slider],
|
| 267 |
outputs=outputs_playback
|
| 268 |
)
|
|
|
|
| 269 |
|
| 270 |
+
# Pre-load the initial state of the dashboard
|
| 271 |
+
def initial_load():
|
| 272 |
+
# Load the first tab's content
|
| 273 |
+
rq1_fig, rq1_summary = analyzer.analyze_rq1_metric(analyzer.time_metrics[0] if analyzer.time_metrics else None)
|
| 274 |
+
|
| 275 |
+
# Train the initial model for the second tab
|
| 276 |
+
model_summary, report_df, feature_fig, status_md = analyzer.run_prediction_model(0.3, 100)
|
| 277 |
+
|
| 278 |
+
# Return all the values needed to populate the outputs on load
|
| 279 |
+
return {
|
| 280 |
+
rq1_plot_output: rq1_fig,
|
| 281 |
+
rq1_summary_output: rq1_summary,
|
| 282 |
+
rq2_summary_output: model_summary,
|
| 283 |
+
rq2_table_output: report_df,
|
| 284 |
+
rq2_plot_output: feature_fig,
|
| 285 |
+
rq2_status: status_md
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
demo.load(
|
| 289 |
+
fn=initial_load,
|
| 290 |
+
outputs=[
|
| 291 |
+
rq1_plot_output, rq1_summary_output,
|
| 292 |
+
rq2_summary_output, rq2_table_output, rq2_plot_output, rq2_status
|
| 293 |
+
]
|
| 294 |
+
)
|
| 295 |
|
| 296 |
if __name__ == "__main__":
|
| 297 |
demo.launch()
|