dark mode release

This commit is contained in:
Scott Pruett 2022-05-22 22:23:28 -04:00
parent 4a920b4b42
commit 34a2b61757
15 changed files with 427 additions and 342 deletions

14
car-classes.txt Normal file
View File

@ -0,0 +1,14 @@
Piccino
Superlight
Eurotruck
Muscle Car
Stock Car
Super Truck
Rally
50s GT
Touring Car
GT
Prototype
60s GP
80s GP
GP

View File

@ -4,7 +4,7 @@
"name": "lap",
"x": 2300,
"y": 46,
"width": 145,
"width": 140,
"height": 90
},
{
@ -40,7 +40,8 @@
"x": 2325,
"y": 222,
"width": 183,
"height": 43
"height": 43,
"use_ocr_cache": false
}
],
"track_region": {

3
race_stats.csv Normal file
View File

@ -0,0 +1,3 @@
2022-05-22-20:39,whistle valley,gp,1,22.349,22.349,100,,
2022-05-22-20:39,whistle valley,gp,2,21.888,21.888,99,83,82
2022-05-22-20:39,whistle valley,gp,3,22.031,21.888,99,75,73
1 2022-05-22-20:39 whistle valley gp 1 22.349 22.349 100
2 2022-05-22-20:39 whistle valley gp 2 21.888 21.888 99 83 82
3 2022-05-22-20:39 whistle valley gp 3 22.031 21.888 99 75 73

View File

@ -4,7 +4,7 @@
"name": "lap",
"x": 2300,
"y": 46,
"width": 145,
"width": 142,
"height": 90
},
{
@ -40,7 +40,8 @@
"x": 2325,
"y": 222,
"width": 183,
"height": 43
"height": 43,
"use_ocr_cache": false
}
],
"track_region": {

View File

@ -57,6 +57,7 @@ fn merge_frames(prev: &ParsedFrame, next: &ParsedFrame) -> ParsedFrame {
tyres: merge_with_max(&prev.tyres, &next.tyres),
lap_time: merge_with_max(&prev.lap_time, &next.lap_time),
best_time: merge_with_max(&prev.best_time, &next.best_time),
striked: false,
}
}
@ -158,7 +159,7 @@ fn run_loop_once(capturer: &mut Capturer, state: &SharedAppState) -> Result<()>
}
{
let mut state = state.lock().unwrap();
let parsed = ParsedFrame::parse(&ocr_results);
let mut parsed = ParsedFrame::parse(&ocr_results);
handle_new_frame(&mut state, parsed, &frame);
state.raw_data = ocr_results;
state.saved_frames = saved_frames;
@ -172,6 +173,7 @@ pub fn run_control_loop(state: SharedAppState) {
if let Err(e) = run_loop_once(&mut capturer, &state) {
eprintln!("Error in control loop: {:?}", e)
}
thread::sleep(Duration::from_millis(state.lock().unwrap().config.ocr_interval_ms.unwrap_or(500)));
let interval = state.lock().unwrap().config.ocr_interval_ms.unwrap_or(500);
thread::sleep(Duration::from_millis(interval));
}
}

View File

@ -11,6 +11,7 @@ pub struct Region {
width: usize,
height: usize,
pub threshold: Option<f64>,
pub use_ocr_cache: Option<bool>,
}
pub fn extract_and_filter(image: &RgbImage, region: &Region) -> RgbImage {

View File

@ -1,24 +1,26 @@
// #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
mod capture;
mod config;
mod control_loop;
mod image_processing;
mod ocr;
mod state;
mod config;
mod stats_writer;
use std::{
sync::{Arc, Mutex},
time::Duration, thread,
thread,
time::{Duration, Instant},
};
use config::{Config, LearnedConfig};
use eframe::{
egui::{self, Ui},
epaint::Color32, emath::Vec2,
egui::{self, Ui, Visuals},
emath::Vec2,
epaint::Color32,
};
use state::{AppState, RaceState, SharedAppState};
use state::{AppState, RaceState, SharedAppState, ParsedFrame};
use stats_writer::export_race_stats;
fn main() -> anyhow::Result<()> {
@ -99,11 +101,16 @@ struct MyApp {
impl MyApp {
pub fn new(state: SharedAppState) -> Self {
Self { state, config_load_err: None, hash_to_learn: "".to_owned(), value_to_learn: "".to_owned() }
Self {
state,
config_load_err: None,
hash_to_learn: "".to_owned(),
value_to_learn: "".to_owned(),
}
}
}
fn show_race_state(ui: &mut Ui, race_name: &str, race: &RaceState) {
fn show_race_state(ui: &mut Ui, race_name: &str, race: &mut RaceState) {
egui::Grid::new(format!("race:{}", race_name)).show(ui, |ui| {
ui.label("Lap");
ui.label("Time");
@ -113,9 +120,9 @@ fn show_race_state(ui: &mut Ui, race_name: &str, race: &RaceState) {
ui.label("Gas");
ui.label("Tyres");
ui.end_row();
for (i, lap) in race.laps.iter().enumerate() {
let mut prev_lap: Option<&ParsedFrame> = None;
for (i, lap) in race.laps.iter_mut().enumerate() {
if let Some(lap_time) = lap.lap_time {
let prev_lap = race.laps.get(i - 1);
ui.label(format!("#{}", lap.lap.unwrap_or(i + 1)));
ui.label(format_time(lap_time));
@ -141,14 +148,24 @@ fn show_race_state(ui: &mut Ui, race_name: &str, race: &RaceState) {
&lap.tyres,
);
if lap.striked {
ui.colored_label(Color32::RED, "Striked from the record");
} else {
if ui.button("Strike").clicked() {
lap.striked = true;
}
}
ui.end_row();
}
prev_lap = Some(lap);
}
});
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
ctx.set_visuals(Visuals::dark());
let mut state = self.state.lock().unwrap();
egui::SidePanel::left("frame").show(ctx, |ui| {
if let Some(frame) = &state.last_frame {
@ -175,6 +192,7 @@ impl eframe::App for MyApp {
));
}
if state.debug_frames {
ui.separator();
ui.heading("Raw OCR results");
let mut raw_data_sorted: Vec<_> = state.raw_data.iter().collect();
@ -182,12 +200,14 @@ impl eframe::App for MyApp {
for (key, val) in raw_data_sorted {
ui.label(format!("{}: {:?}", key, val));
}
}
ui.separator();
ui.checkbox(&mut state.debug_frames, "Debug OCR regions");
});
egui::CentralPanel::default().show(ctx, |ui| {
if let Some(race) = &state.current_race {
egui::ScrollArea::vertical().show(ui, |ui| {
if let Some(race) = &mut state.current_race {
ui.heading("Current Race");
show_race_state(ui, "current", race);
}
@ -204,13 +224,16 @@ impl eframe::App for MyApp {
ui.text_edit_singleline(&mut race.car);
ui.label("Track:");
ui.text_edit_singleline(&mut race.track);
ui.label("Comments:");
ui.text_edit_singleline(&mut race.comments);
if ui.button("Export").clicked() {
match export_race_stats(race) {
Ok(_) => {
race.exported = true;
}
Err(e) => {
race.export_error = Some(format!("failed to export race: {:?}", e));
race.export_error =
Some(format!("failed to export race: {:?}", e));
}
}
}
@ -222,9 +245,11 @@ impl eframe::App for MyApp {
}
}
});
});
if state.debug_frames {
egui::SidePanel::right("screenshots").show(ctx, |ui| {
egui::ScrollArea::vertical().show(ui, |ui| {
let mut screenshots_sorted: Vec<_> = state.saved_frames.iter().collect();
screenshots_sorted.sort_by_key(|(name, _)| name.clone());
for (name, image) in screenshots_sorted {
@ -242,7 +267,8 @@ impl eframe::App for MyApp {
self.config_load_err = None;
}
Err(e) => {
self.config_load_err = Some(format!("failed to load config: {:?}", e));
self.config_load_err =
Some(format!("failed to load config: {:?}", e));
}
}
}
@ -257,7 +283,9 @@ impl eframe::App for MyApp {
ui.text_edit_singleline(&mut self.value_to_learn);
if ui.button("Learn").clicked() {
let mut learned_config = (*state.learned).clone();
learned_config.learned_images.insert(self.hash_to_learn.clone(), self.value_to_learn.clone());
learned_config
.learned_images
.insert(self.hash_to_learn.clone(), self.value_to_learn.clone());
learned_config.save().unwrap();
state.learned = Arc::new(learned_config);
@ -265,6 +293,7 @@ impl eframe::App for MyApp {
self.value_to_learn = "".to_owned();
}
});
});
}
ctx.request_repaint();

View File

@ -76,8 +76,9 @@ pub async fn ocr_all_regions(
let locked = ocr_cache.read().unwrap();
locked.get(&hash).cloned()
};
if let Some(cached) = cached {
cached
let use_cache = region.use_ocr_cache.unwrap_or(true) && config.use_ocr_cache.unwrap_or(true);
if cached.is_some() && use_cache {
cached.unwrap()
} else {
match run_ocr(&filtered_image, &config.ocr_server_endpoint).await {
Ok(v) => {

View File

@ -17,6 +17,8 @@ pub struct ParsedFrame {
pub best_time: Option<Duration>,
pub lap_time: Option<Duration>,
pub striked: bool,
}
fn parse_duration(time: &str) -> Option<Duration> {
@ -54,6 +56,7 @@ impl ParsedFrame {
tyres: parse_to_0_100(raw.get("tyres")),
best_time: parse_to_duration(raw.get("best")),
lap_time: parse_to_duration(raw.get("lap_time")),
striked: false,
}
}
}
@ -71,6 +74,7 @@ pub struct RaceState {
pub car: String,
pub track: String,
pub comments: String,
}
impl RaceState {

View File

@ -19,6 +19,10 @@ pub fn export_race_stats(race_stats: &mut RaceState) -> Result<()> {
let mut csv_writer = csv::Writer::from_writer(writer);
for lap in &race_stats.laps {
if lap.striked {
continue;
}
csv_writer.write_record(vec![
race_name.clone(),
race_stats.track.clone(),
@ -45,6 +49,7 @@ pub fn export_race_stats(race_stats: &mut RaceState) -> Result<()> {
lap.tyres
.map(|x| x.to_string())
.unwrap_or_else(|| "".to_owned()),
race_stats.comments.clone(),
])?;
}

5
suggestions.md Normal file
View File

@ -0,0 +1,5 @@
- Recognize
- Penalties
- Pit stops
- ComboBox for car/track
- GLobal best time not current best

19
tracklist.txt Normal file
View File

@ -0,0 +1,19 @@
Whistle Valley
Sugar Hill
Maple Ridge
Rennvoort
Magdalena GP
Magdalena Club
Copperwood GP
Copperwood Club
Interstate
Buffalo Hill
Lost Lagoons
Bullseye Speedway
Speedopolis
Faenza
Siena
Thunder Point
Tilksport GP
Tilksport Club
Tilksport Rallycross