diff --git a/cvview.py b/cvview.py index 07f0e35..57a8639 100644 --- a/cvview.py +++ b/cvview.py @@ -2,6 +2,7 @@ import os import cv2 import argparse import numpy as np +from datetime import datetime # Set up the argument parser parser = argparse.ArgumentParser(description="Visualize image files and display pixel values on hover.") @@ -28,51 +29,31 @@ def calibrate(x): #print('{}..{}'.format(ret.max(), ret.min())) return ret - -class state: - calibrate = False - - -# Function to display the image and pixel values along with the frame index -def show_pixel_values(image_path): - def mouse_event(event, x, y, flags, param): - if event == cv2.EVENT_MOUSEMOVE: - pixel_value = img[y, x] - text = f'Value: {pixel_value}, Location: ({x},{y})' - img_text = img.copy() - # Overlay the frame index - frame_index = get_frame_index(image_path) - cv2.putText(img_text, f'Frame: {frame_index}', (10, img_text.shape[0] - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 1, cv2.LINE_AA) - cv2.putText(img_text, text, (50, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 1, cv2.LINE_AA) - cv2.imshow('Image', img_text) - - img = cv2.imread(image_path, cv2.IMREAD_UNCHANGED) - if img is None: - print(f"Failed to load image at {image_path}. Check the file path and integrity.") - return False - if state.calibrate: - img = calibrate(img) - cv2.namedWindow('Image') - cv2.setMouseCallback('Image', mouse_event) - cv2.imshow('Image', img) - return True +# Global variables for the last mouse position +last_x, last_y = 0, 0 +img, calibrated_img = None, None # Function to get the frame index from the filename def get_frame_index(filename): - return os.path.splitext(os.path.basename(filename))[0][-4:] + return os.path.splitext(os.path.basename(filename))[0][-5:] # Function to modify the numeric part of the filename -def modify_filename(filename, increment=True): +def modify_filename(filename, frame_increment=1): directory, basename = os.path.split(filename) basename_no_ext, ext = os.path.splitext(basename) print(f"Modifying filename {basename_no_ext} in directory {directory}.") - if len(basename_no_ext) < 4 or not basename_no_ext[-4:].isdigit(): + if len(basename_no_ext) < 5 or not basename_no_ext[-5:].isdigit(): raise ValueError("Filename does not end with five digits.") - num_part = basename_no_ext[-4:] - num = int(num_part) + (1 if increment else -1) - new_name = f"{basename_no_ext[:-4]}{num:04d}{ext}" + + num_part = basename_no_ext[-5:] + num = int(num_part) + frame_increment + + # Handle rollover + num = num % 100000 # Modulo 100000 for 5 digits + + new_name = f"{basename_no_ext[:-5]}{num:05d}{ext}" new_path = os.path.join(directory, new_name) if not os.path.exists(new_path): print(f"No file found at {new_path}.") @@ -80,6 +61,49 @@ def modify_filename(filename, increment=True): return new_path +# Function to display the image and pixel values along with the frame index +def show_pixel_values(image_path): + global img, calibrated_img, last_x, last_y + + def mouse_event(event, x, y, flags, param): + global last_x, last_y + if event == cv2.EVENT_MOUSEMOVE: + last_x, last_y = x, y + update_display(x, y) + + img = cv2.imread(image_path, cv2.IMREAD_UNCHANGED) + if img is None: + print(f"Failed to load image at {image_path}. Check the file path and integrity.") + return False + + calibrated_img = calibrate(img) # Calibrate the image for display + + cv2.namedWindow('Image') + cv2.setMouseCallback('Image', mouse_event) + update_display(last_x, last_y) # Initial display update + return True + +# Function to update the display with pixel values +def update_display(x, y): + global img, calibrated_img + original_pixel_value = img[y, x] + calibrated_pixel_value = calibrated_img[y, x] + text_original = f'Original: {original_pixel_value}, Loc: ({x},{y})' + text_calibrated = f'Calibrated: {calibrated_pixel_value}' + img_text = img.copy() + frame_index = get_frame_index(img_path) + cv2.putText(img_text, f'Frame: {frame_index}', (10, img_text.shape[0] - 20), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1, cv2.LINE_AA) + cv2.putText(img_text, text_original, (5, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1, cv2.LINE_AA) + cv2.putText(img_text, text_calibrated+"c", (5, 50), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1, cv2.LINE_AA) + cv2.imshow('Image', img_text) + return img_text # Return the image with text for saving + +def save_frame(img_text): + current_time = datetime.now().strftime("%Y%m%d_%H%M%S") + save_path = f"frame_{current_time}.png" + cv2.imwrite(save_path, img_text) + print(f"Frame saved as {save_path}") + # Ensure the provided path is a valid file if not os.path.isfile(img_path): print("The provided path is not a valid file.") @@ -94,15 +118,25 @@ while True: key = cv2.waitKey(0) if key == 27: # ESC key to exit break - elif key == 91: # '[' key - img_path = modify_filename(img_path, increment=False) - elif key == 93: # ']' key - img_path = modify_filename(img_path, increment=True) - elif key == ord('c'): - state.calibrate = not state.calibrate + elif key in [91, 93, ord('{'), ord('}')]: # Keys for frame navigation + if key == 91: # '[' key + img_path = modify_filename(img_path, frame_increment=-1) + elif key == 93: # ']' key + img_path = modify_filename(img_path, frame_increment=1) + elif key == ord('{'): # Shift + '[' + img_path = modify_filename(img_path, frame_increment=-50) + elif key == ord('}'): # Shift + ']' + img_path = modify_filename(img_path, frame_increment=50) + + if not show_pixel_values(img_path): + break # Exit if the new image cannot be loaded + else: + update_display(last_x, last_y) # Update display with last known mouse position - # Show the new image - if not show_pixel_values(img_path): - break # Exit the loop if the new image cannot be loaded + elif key == ord('s'): # 's' key for saving + # Update the display to get the latest overlay and save it + img_text_with_overlays = update_display(last_x, last_y) + save_frame(img_text_with_overlays) + continue # Skip the frame reload if saving -cv2.destroyAllWindows() +cv2.destroyAllWindows() \ No newline at end of file diff --git a/examples/main.rs b/examples/main.rs index a8a5cad..0b7cb8f 100644 --- a/examples/main.rs +++ b/examples/main.rs @@ -6,7 +6,6 @@ fn main() -> anyhow::Result<()> { let mut arg = env::args(); arg.next(); // skip executable let filename = arg.next().ok_or(anyhow::anyhow!("unexpected"))?; - let frames = arg.next().unwrap_or("frames".into()); - decode_to_files(&filename, &frames)?; + decode_to_files(&filename)?; Ok(()) } diff --git a/rustdecode.sh b/rustdecode.sh new file mode 100755 index 0000000..58c00f7 --- /dev/null +++ b/rustdecode.sh @@ -0,0 +1,2 @@ +#!/bin/bash +cargo run --release --example main -- "$@" diff --git a/src/lib.rs b/src/lib.rs index 4731b9a..f3ea571 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -251,15 +251,26 @@ fn decode(filename: &str) -> PyResult { Ok(iter.into()) } -pub fn decode_to_files(filename: &str, frames_root: &str) -> anyhow::Result<()> { +/// writes to frames/ +pub fn decode_to_files(filename: &str) -> anyhow::Result<()> { let frameiter = Decoder::new(filename)?; + let basename = std::path::Path::new(filename) + .file_stem() + .ok_or(anyhow::anyhow!("cannot get basename"))? + .to_str() + .ok_or(anyhow::anyhow!("cannot convert to utf-8 from os name"))?; + let target_dir = format!("frames/{}", basename); + let target_dir = std::path::Path::new(&target_dir); + if !target_dir.exists() { + std::fs::create_dir(target_dir)?; + } for (i, frame) in frameiter.enumerate() { - let name = format!("{}/{:05}.png", frames_root, i); + let name = format!("frames/{}/{:05}.png", basename, i); if let Err(_e) = write_raw_frame(&name, &frame.raw) { println!("skipping bad frame {}", i); continue; } - let name = format!("{}/temp_{:05}.png", frames_root, i); + let name = format!("{}/temp_{:05}.png", target_dir.display(), i); let pixels = frame.pixels(); write_calibrated_frame(&name, &pixels)?; } diff --git a/thermal.mp4 b/thermal.mp4 index 4665d9b..cd76de9 100644 Binary files a/thermal.mp4 and b/thermal.mp4 differ