dark mode release
This commit is contained in:
parent
4a920b4b42
commit
34a2b61757
|
@ -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
|
|
@ -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": {
|
||||
|
|
|
@ -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
|
|
|
@ -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": {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
55
src/main.rs
55
src/main.rs
|
@ -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();
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(),
|
||||
])?;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
- Recognize
|
||||
- Penalties
|
||||
- Pit stops
|
||||
- ComboBox for car/track
|
||||
- GLobal best time not current best
|
|
@ -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
|
Loading…
Reference in New Issue