0% found this document useful (0 votes)
11 views19 pages

Camera Roi

The document outlines the implementation of an RTSPManager class for managing camera connections and frame capture using OpenCV and threading. It includes methods for starting cameras, checking connections, capturing frames, and handling errors, as well as a CameraRoiInterface class for the user interface that allows users to select cameras and draw regions of interest (ROI) on captured frames. The code employs a GUI built with tkinter and customtkinter, integrating camera management functionalities within a user-friendly interface.

Uploaded by

elimelech03
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
11 views19 pages

Camera Roi

The document outlines the implementation of an RTSPManager class for managing camera connections and frame capture using OpenCV and threading. It includes methods for starting cameras, checking connections, capturing frames, and handling errors, as well as a CameraRoiInterface class for the user interface that allows users to select cameras and draw regions of interest (ROI) on captured frames. The code employs a GUI built with tkinter and customtkinter, integrating camera management functionalities within a user-friendly interface.

Uploaded by

elimelech03
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 19

import tkinter as tk

from customtkinter import CTkFrame, CTkLabel, CTkButton, CTkOptionMenu, CTkEntry


import cv2
from PIL import Image, ImageTk
import threading
import time
import logging

from FrameOperation.CameraManager import CameraStateManager

import cv2
import logging
import threading
import time
from PIL import Image, ImageTk
from queue import Queue
from threading import Event

class RTSPManager:
def __init__(self):
self._capture_lock = threading.Lock()
self._frame_queue = Queue(maxsize=1)
self._stop_event = Event()
self._current_camera = None
self._current_url = None
self._frame = None
self._error = None
self.connection_timeout = 3
self.frame_retry_attempts = 3
self.frame_retry_delay = 0.5

def start_camera(self, camera_name, rtsp_url, callback_on_success=None,


callback_on_error=None):
"""
Start capturing from a new camera in a non-blocking way
"""
self._stop_current_camera()
self._current_camera = camera_name
self._current_url = rtsp_url
self._error = None

def camera_thread():
try:
if self._check_rtsp_connection():
# Start frame capture thread if connection is successful
self._start_frame_capture()
if callback_on_success:
callback_on_success()
else:
if callback_on_error:
callback_on_error("Failed to connect to camera. Invalid
RTSP URL or network issues.")
except Exception as e:
logging.error(f"Camera thread error: {str(e)}")
if callback_on_error:
callback_on_error(f"Camera error: {str(e)}")

# Start camera operations in separate thread


threading.Thread(target=camera_thread, daemon=True).start()

def _check_rtsp_connection(self):
"""
Check if RTSP connection can be established
"""
try:
cap = cv2.VideoCapture(self._current_url, cv2.CAP_FFMPEG)
start_time = time.time()

while not cap.isOpened() and time.time() - start_time <


self.connection_timeout:
time.sleep(0.1)

is_opened = cap.isOpened()
cap.release()
return is_opened
except Exception as e:
logging.error(f"RTSP connection check failed: {str(e)}")
return False

def _start_frame_capture(self):
"""
Start continuous frame capture in background
"""

def capture_thread():
cap = None
try:
cap = cv2.VideoCapture(self._current_url, cv2.CAP_FFMPEG)
while not self._stop_event.is_set():
with self._capture_lock:
ret, frame = cap.read()
if ret and frame is not None:
# Clear queue and put new frame
while not self._frame_queue.empty():
try:
self._frame_queue.get_nowait()
except:
pass
self._frame_queue.put(frame)
else:
time.sleep(0.1)
except Exception as e:
self._error = str(e)
logging.error(f"Frame capture error: {str(e)}")
finally:
if cap:
cap.release()

self._stop_event.clear()
threading.Thread(target=capture_thread, daemon=True).start()

def get_latest_frame(self):
"""
Get the most recent frame non-blocking
Returns: (frame, error_message)
"""
try:
if not self._frame_queue.empty():
return self._frame_queue.get_nowait(), None
return None, "No frame available"
except Exception as e:
return None, str(e)

def _stop_current_camera(self):
"""
Stop current camera operations
"""
self._stop_event.set()
with self._capture_lock:
while not self._frame_queue.empty():
try:
self._frame_queue.get_nowait()
except:
pass

def resize_frame(self, frame, width, height):


"""
Resize frame to specified dimensions
"""
if frame is not None:
return cv2.resize(frame, (width, height),
interpolation=cv2.INTER_LANCZOS4)
return None

def convert_frame_to_photo(self, frame):


"""
Convert OpenCV frame to PhotoImage
"""
if frame is not None:
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
image = Image.fromarray(frame_rgb)
return ImageTk.PhotoImage(image=image)
return None

def cleanup(self):
"""
Clean up resources
"""
self._stop_current_camera()

class CameraRoiInterface(CTkFrame):
def __init__(self, master, root_width=None, root_height=None, **kwargs):
if 'root_width' in kwargs:
del kwargs['root_width']
if 'root_height' in kwargs:
del kwargs['root_height']

super().__init__(master, **kwargs)

# Set minimum dimensions


self.min_width = 800
self.min_height = 600

# Initialize instance variables


self._roi_state = {}
self.camera_list = {}
self.current_camera = None
self.rtsp_url = None
self.obj_Core = None
self.rtsp_manager = RTSPManager()

self._initialized = False
self.pending_roi_restore = False

# Get screen dimensions


self.screen_width = self.winfo_screenwidth()
self.screen_height = self.winfo_screenheight()

# Set height with extra room for buttons


self.form_height = int(min(self.screen_height * 0.85, self.screen_height -
100))
self.form_width = self.screen_width

self.running = True
self.roi_selected = False
self.roi_coords = None
self.frame = None
self.drawing = False

# Configure main frame


self.configure(fg_color="#F1F5FA", corner_radius=0)

# Main form frame


self.frame_form = CTkFrame(
self,
fg_color="#F1F5FA",
corner_radius=2
)
self.frame_form.pack(expand=True, fill="both", padx=5, pady=5)

# Header frame
self.header_frame = CTkFrame(
self.frame_form,
fg_color="#F1F5FA",
corner_radius=0,
height=40
)
self.header_frame.pack(fill="x", padx=10, pady=(5, 0))
self.header_frame.pack_propagate(False)

# Header elements
self.label_heading = CTkLabel(
self.header_frame,
text="Camera ROI Selection",
text_color="#000000",
font=("Arial", 16, "bold")
)
self.label_heading.pack(side="left", pady=5)

# Dropdown
self.dropdown_values = []
self.camera_dropdown = CTkLabel(
self.header_frame,
text='',
fg_color="#FFFFFF",
text_color="#000000",
width=160,
height=30,
corner_radius=2
)
self.camera_dropdown.pack(side="left", padx=(20, 0), pady=5)

self.status_label = CTkLabel(
self.header_frame,
text="Draw ROI on the image",
text_color="#FFA500",
font=("Arial", 12)
)
self.status_label.pack(side="right", pady=5, padx=5)

# Canvas frame with fixed maximum height


canvas_max_height = int(self.form_height * 0.65) # 65% of form height
self.canvas_frame = CTkFrame(
self.frame_form,
fg_color="#FFFFFF",
corner_radius=0,
height=canvas_max_height
)
self.canvas_frame.pack(expand=True, fill="both", padx=15, pady=5)

# Canvas
self.canvas = tk.Canvas(
self.canvas_frame,
bg="#FFFFFF",
highlightthickness=1,
highlightbackground="#DEDEDE",
height=canvas_max_height - 10 # Account for padding
)
self.canvas.pack(expand=True, fill="both", padx=5, pady=5)

# Control frame with fixed height


self.control_frame = CTkFrame(
self.frame_form,
fg_color="#F1F5FA",
corner_radius=0,
height=90 # Increased height for better button visibility
)
self.control_frame.pack(fill="x", padx=10, pady=5)
self.control_frame.pack_propagate(False)

# Buttons with padding from the bottom


self.button_frame = CTkFrame(
self.control_frame,
fg_color="#F1F5FA"
)
self.button_frame.pack(expand=True, pady=10)

# Buttons
self.save_button = CTkButton(
self.button_frame,
text="Save ROI",
command=self.save_roi,
fg_color="#3A36F5",
hover_color="#218838",
font=("Arial", 12, "bold"),
corner_radius=4,
state="disabled",
height=35,
width=100
)
self.save_button.pack(side="left", padx=5)

self.cancel_button = CTkButton(
self.button_frame,
text="Cancel ROI",
command=self.cancel_roi,
fg_color="#DC3545",
hover_color="#C82333",
font=("Arial", 12, "bold"),
corner_radius=4,
height=35,
width=100
)
self.cancel_button.pack(side="left", padx=5)

# Initialize variables for ROI drawing


self.start_x = None
self.start_y = None
self.rect_id = None
self.temp_rect_id = None
self.can_draw = True
self.roi_color = "#DAA520"

# Bind mouse events


self.canvas.bind("<ButtonPress-1>", self.start_draw)
self.canvas.bind("<B1-Motion>", self.draw_rectangle)
self.canvas.bind("<ButtonRelease-1>", self.end_draw)

# Initialize frame and ROI


self.initialize_frame_and_roi()

self.camera_manager = CameraStateManager()

def update_camera_list(self, new_camera_list):


"""Update the camera list and dropdown with new values from database"""
try:
self.camera_list = new_camera_list
print("Updating camera list with:", new_camera_list)

# Update dropdown values with checkbox symbols


self.dropdown_values = [f"☐ {camera}" for camera in
self.camera_list.keys()]

# If we have a current camera, restore its state


if self.current_camera in self.camera_list:
self.change_camera(self.current_camera)
elif self.camera_list:
# Select first camera if current_camera is not in list
first_camera = list(self.camera_list.keys())[0]
self.change_camera(first_camera)
if not self.dropdown_values:
self.status_label.configure(text="No cameras available",
text_color="#FF0000")

except Exception as e:
print(f"Error updating camera list: {e}")
self.status_label.configure(text="Error updating camera list",
text_color="#FF0000")

def initialize_frame_and_roi(self):
"""Initialize the frame and restore ROI if available"""
# Clear any existing ROI
if hasattr(self, 'canvas'):
self.canvas.delete("all")

# If we have a current camera, try to restore its ROI


if self.current_camera:
# Try to get ROI data from the database
try:
roi_data =
self.obj_Core.obj_Camera.get_roi_coordinates(self.current_camera)
if roi_data and not roi_data.get("str_error_msg"):
coordinates = roi_data.get("coordinates", {})
if coordinates:
# Extract coordinates
x1, y1 = coordinates["point1"]
x2, y2 = coordinates["point4"]
self.roi_coords = (int(x1), int(y1), int(x2), int(y2))
self.roi_selected = True
self._roi_state[self.current_camera] = {
"coords": self.roi_coords,
"selected": True
}
self.pending_roi_restore = True
except Exception as e:
print(f"Error restoring ROI from database: {e}")

if not self._initialized:
# Create a message on the canvas
canvas_width = self.canvas.winfo_width()
canvas_height = self.canvas.winfo_height()
self.canvas.create_text(
canvas_width / 2,
canvas_height / 2,
font=("Arial", 14),
fill="#666666"
)
self._initialized = True

def check_rtsp_connection(self, rtsp_url, timeout=3):


"""Check the RTSP stream connection with a timeout to avoid hanging."""
cap = cv2.VideoCapture(rtsp_url, cv2.CAP_FFMPEG)

# Start a timer for timeout


start_time = time.time()

# Try to open the connection until timeout


while not cap.isOpened():
if time.time() - start_time > timeout:
print(f"Failed to open RTSP URL {rtsp_url} within {timeout}
seconds.")
cap.release()
print("failed -----------------")
return False
time.sleep(0.1)

cap.release()
print("passed -----------------")
return True

def capture_frame(self):
"""Capture a single frame from the RTSP stream with robust error
handling."""
if not self.rtsp_url:
self.handle_camera_error("No camera selected")
return

try:
print(f"Attempting to open RTSP URL:
{self.rtsp_url}-----------------------------------------")

# Check if the RTSP connection is valid


if not self.check_rtsp_connection(self.rtsp_url, timeout=2):
self.handle_camera_error("Failed to connect to camera. Invalid RTSP
URL or Network issues.")
return

cap = cv2.VideoCapture(self.rtsp_url, cv2.CAP_FFMPEG)


if not cap.isOpened():
cap.release()
self.handle_camera_error("Could not open RTSP stream. Check your
network or camera settings.")
return

print("Successfully opened RTSP URL")

ret, frame = False, None


max_retries = 3 # Try multiple times before giving up
for attempt in range(max_retries):
print(f"Attempt {attempt + 1}: Trying to read a frame...")
ret, frame = cap.read()
if ret and frame is not None:
print("Frame read successfully!")
break
time.sleep(0.5) # Delay between retries

cap.release()

# Check if frame retrieval was successful


if not ret or frame is None:
self.handle_camera_error("Could not read frame from camera. Please
try again.")
return

print("Successfully read a frame")

# Resize frame to fit the canvas


canvas_width = self.canvas.winfo_width()
canvas_height = self.canvas.winfo_height()
self.frame = cv2.resize(frame, (canvas_width, canvas_height),
interpolation=cv2.INTER_LANCZOS4)

self._last_frame = self.frame
self._last_camera = self.current_camera
self.display_frame()
self.status_label.configure(text="Frame captured",
text_color="#00FF00")

except cv2.error as e:
logging.error(f"OpenCV error: {e}", exc_info=True)
self.handle_camera_error(f"OpenCV error occurred: {str(e)}")

except Exception as e:
logging.error(f"Unexpected error: {e}", exc_info=True)
self.handle_camera_error("An unexpected error occurred. Please try
again.")

def handle_camera_error(self, error_msg):


"""Centralized error handling for camera-related issues"""
print(f"Camera capture error: {error_msg}")

# Set error message and update UI


self.status_label.configure(text=f"Camera error: {error_msg}",
text_color="#FF0000")

# Clear the frame and display error message on canvas


self.frame = None
self.canvas.delete("all")

# Create error message on canvas


canvas_width = self.canvas.winfo_width()
canvas_height = self.canvas.winfo_height()
self.canvas.create_text(
canvas_width / 2,
canvas_height / 2,
text=f"Camera Error:\n{error_msg}\n\nPlease select a different camera",
font=("Arial", 14),
fill="#FF0000",
justify="center"
)

# Reset ROI state


self.roi_coords = None
self.roi_selected = False
self.save_button.configure(state="disabled")

def display_frame(self):
"""Display the current frame on the canvas"""
if self.frame is not None:
# Get current canvas dimensions
canvas_width = self.canvas.winfo_width()
canvas_height = self.canvas.winfo_height()

# Ensure frame matches canvas size


if self.frame.shape[1] != canvas_width or self.frame.shape[0] !=
canvas_height:
self.frame = cv2.resize(self.frame, (canvas_width, canvas_height),
interpolation=cv2.INTER_LANCZOS4)

frame_rgb = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)


image = Image.fromarray(frame_rgb)
photo = ImageTk.PhotoImage(image=image)
self.canvas.delete("all") # Clear canvas before creating new image
self.canvas.create_image(0, 0, image=photo, anchor="nw")
self.canvas.image = photo

# If there's a pending ROI restore, do it now


if self.pending_roi_restore and self.roi_selected and self.roi_coords:
self.restore_roi()
self.pending_roi_restore = False
def change_camera(self, camera_name):
"""Handle camera change from dropdown"""
try:

print("chnage camera name : ",camera_name)


# Remove checkbox symbol to get actual camera name
# actual_camera = camera_name.split(" ", 1)[1]
actual_camera=camera_name
# Save current ROI state before switching
if self.current_camera:
self._roi_state[self.current_camera] = {
"coords": self.roi_coords,
"selected": self.roi_selected
}
print(f"Saved state for {self.current_camera}:
{self._roi_state[self.current_camera]}")

# Update UI to show loading state


self.status_label.configure(text="Switching camera...",
text_color="#FFA500")
self.update()

# Switch camera
self.current_camera = actual_camera
self.rtsp_url = self.camera_list[actual_camera]

# Clear canvas and reset drawing state


self.canvas.delete("all")
self.drawing = False

# Capture new frame first


self.capture_frame()

# After frame is captured, restore ROI state if it exists


camera_state = self._roi_state.get(actual_camera, {})
print(f"Loading state for {actual_camera}: {camera_state}")

self.roi_coords = camera_state.get("coords")
self.roi_selected = camera_state.get("selected", False)

# Update dropdown values


self.dropdown_values = [
f"☑ {cam}" if cam == actual_camera else f"☐ {cam}"
for cam in self.camera_list.keys()
]
self.camera_dropdown.configure(text=actual_camera)
#self.camera_dropdown.set(f"☑ {actual_camera}")

# Update save button state based on ROI


if self.roi_selected and self.roi_coords:
self.save_button.configure(state="normal")
self.restore_roi()
else:
self.save_button.configure(state="disabled")
# self.roi_coords_label.configure(text="ROI: None")
self.status_label.configure(text="Draw ROI on the image",
text_color="#FFA500")

except Exception as e:
error_msg = f"Camera switch error: {str(e)}"
print(error_msg)
self.status_label.configure(text=error_msg, text_color="#FF0000")

def restore_roi(self):
"""Restore previously drawn ROI"""
print(f"Attempting to restore ROI for camera {self.current_camera}")
print(f"ROI coordinates: {self.roi_coords}")
print(f"Current ROI state: {self._roi_state}")

if self.roi_coords:
# Create ROI rectangle with grey overlay
self.canvas.create_rectangle(
self.roi_coords[0], self.roi_coords[1],
self.roi_coords[2], self.roi_coords[3],
fill='grey', stipple='gray50', tags="roi"
)
self.rect_id = self.canvas.create_rectangle(
self.roi_coords[0], self.roi_coords[1],
self.roi_coords[2], self.roi_coords[3],
outline=self.roi_color, width=2, tags="roi"
)

# Update the label


# x1, y1, x2, y2 = self.roi_coords
# self.roi_coords_label.configure(
# text=f"ROI: ({x1},{y1}),({x2},{y1}),({x1},{y2}),({x2},{y2})"
# )

# Switch to save/cancel buttons if selected


if self.roi_selected:
self.save_button.configure(state="normal")

# def start_draw(self, event):


# """Start drawing the rectangle with initial validation"""
# # First check if a camera is selected
# if not self.current_camera:
# self.status_label.configure(
# text="Please select a camera before drawing ROI",
# text_color="#FF0000"
# )
# return
#
# # Get canvas height and calculate line positions
# canvas_height = self.canvas.winfo_height()
# line1_y = int(canvas_height * 0.25) # 25% height
# line2_y = int(canvas_height * 0.75) # 75% height
#
# # Check if initial click is between the lines (invalid area)
# if line1_y < event.y < line2_y:
# self.status_label.configure(
# text="Error: Cannot start ROI between the lines",
# text_color="#FF0000"
# )
# return
#
# if self.roi_selected:
# self.canvas.delete("roi")
# self.roi_coords = None
# self.roi_selected = False
#
# self.drawing = True
# self.start_x = event.x
# self.start_y = event.y
# self.temp_rect_id = self.canvas.create_rectangle(
# self.start_x, self.start_y, self.start_x, self.start_y,
# outline=self.roi_color, width=4, tags="temp_roi"
# )
# def draw_rectangle(self, event):
# """Update the rectangle as the mouse moves with line validation"""
# if self.drawing and self.temp_rect_id and self.can_draw:
# # Constrain cursor position within canvas bounds
# cur_x = min(max(event.x, 0), self.canvas.winfo_width())
# cur_y = min(max(event.y, 0), self.canvas.winfo_height())
#
# # Update rectangle coordinates
# self.canvas.coords(self.temp_rect_id, self.start_x, self.start_y,
cur_x, cur_y)
#
# def end_draw(self, event):
# if self.drawing:
# self.drawing = False
# x1, y1, x2, y2 = self.canvas.coords(self.temp_rect_id)
#
# canvas_height = self.canvas.winfo_height()
# line1_y = int(canvas_height * 0.25)
# line2_y = int(canvas_height * 0.75)
#
# min_y = min(y1, y2)
# max_y = max(y1, y2)
#
# if (line1_y < min_y < line2_y) or (line1_y < max_y < line2_y):
# self.canvas.delete("temp_roi")
# self.status_label.configure(
# text="Error: ROI cannot cross or be inside the lines",
# text_color="#FF0000"
# )
# self.save_button.configure(state="disabled")
# return
#
# if max_y < line1_y or min_y > line2_y:
# self.canvas.delete("temp_roi")
# self.status_label.configure(
# text="Error: ROI must span both regions (above and below the
lines)",
# text_color="#FF0000"
# )
# self.save_button.configure(state="disabled")
# return
#
# self.roi_coords = (
# int(min(x1, x2)),
# int(min(y1, y2)),
# int(max(x1, x2)),
# int(max(y1, y2))
# )
# self.roi_selected = True
#
# # Clear any existing ROI
# self.canvas.delete("roi")
#
# # Solid grey overlay replaced with black lines
# line_spacing = 2 # Adjust the spacing between lines
#
# # Draw horizontal lines
# for y in range(self.roi_coords[1], self.roi_coords[3], line_spacing):
# self.canvas.create_line(
# self.roi_coords[0], y,
# self.roi_coords[2], y,
# fill="black", tags="roi"
# )
#
# # Draw vertical lines
# for x in range(self.roi_coords[0], self.roi_coords[2], line_spacing):
# self.canvas.create_line(
# x, self.roi_coords[1],
# x, self.roi_coords[3],
# fill="black", tags="roi"
# )
#
# # Border
# self.rect_id = self.canvas.create_rectangle(
# self.roi_coords[0], self.roi_coords[1],
# self.roi_coords[2], self.roi_coords[3],
# outline=self.roi_color, width=3, tags="roi"
# )
#
# self.save_button.configure(state="normal")
# self.status_label.configure(text="ROI selected",
text_color="#00FF00")
#
# def save_roi(self):
# """Save the ROI coordinates and calculate percentages based on frame
height"""
# if self.roi_selected and self.frame is not None:
# try:
# # Update ROI state
# self._roi_state[self.current_camera] = {
# "coords": self.roi_coords,
# "selected": True
# }
#
# # Get frame dimensions
# frame_height = self.frame.shape[0]
#
# # Get ROI coordinates
# x1, y1, x2, y2 = self.roi_coords
#
# # Calculate percentages based on frame height
# start_percentage = (y1 / frame_height) * 100
# end_percentage = (y2 / frame_height) * 100
#
# # Calculate guide line percentages for validation
# line1_percentage = 25 # 25% line
# line2_percentage = 75 # 75% line
#
# # Validate ROI position relative to guide lines
# if not ((start_percentage < line1_percentage and end_percentage >
line2_percentage) or
# (start_percentage < line2_percentage and end_percentage >
line1_percentage)):
# raise Exception("ROI must span both regions (above and below
the guide lines)")
#
# # Prepare coordinates dictionary
# coordinates = {
# "point1": [x1, y1],
# "point2": [x2, y1],
# "point3": [x1, y2],
# "point4": [x2, y2],
# "frame_height": frame_height,
# "start_percentage": start_percentage,
# "end_percentage": end_percentage
# }
#
# # Save to database through Core object
# result = self.obj_Core.obj_Camera.add_roi_coordinates(
# self.current_camera,
# start_percentage,
# end_percentage,
# coordinates
# )
#
# if result["str_error_msg"]:
# raise Exception(result["str_error_msg"])
#
# # Log the saved data
# print(f"\nSaved ROI coordinates and percentages:")
# print(f"Frame height: {frame_height}")
# print(f"ROI coordinates: ({x1},{y1}),({x2},{y1}),({x1},{y2}),
({x2},{y2})")
# print(f"Start Percentage: {start_percentage:.2f}%")
# print(f"End Percentage: {end_percentage:.2f}%")
#
# # Redraw frame to ensure it's displayed
# self.display_frame()
#
# # Clear any existing ROI
# self.canvas.delete("roi")
#
# # Draw horizontal lines
# line_spacing = 2 # Adjust the spacing between lines
# for y in range(self.roi_coords[1], self.roi_coords[3],
line_spacing):
# self.canvas.create_line(
# self.roi_coords[0], y,
# self.roi_coords[2], y,
# fill="black", tags="roi"
# )
#
# # Draw vertical lines
# for x in range(self.roi_coords[0], self.roi_coords[2],
line_spacing):
# self.canvas.create_line(
# x, self.roi_coords[1],
# x, self.roi_coords[3],
# fill="black", tags="roi"
# )
#
# # Border
# self.rect_id = self.canvas.create_rectangle(
# self.roi_coords[0], self.roi_coords[1],
# self.roi_coords[2], self.roi_coords[3],
# outline=self.roi_color, width=3, tags="roi"
# )
#
# self.save_button.configure(state="disabled")
# self.status_label.configure(text="ROI saved successfully",
text_color="#00FF00")
# self.restore_roi()
#
#
#
# except Exception as e:
# self.status_label.configure(text=f"Save error: {str(e)}",
text_color="#FF0000")
# print(f"Error saving ROI: {str(e)}")

def start_draw(self, event):


"""Start drawing the rectangle"""
# First check if a camera is selected
if not self.current_camera:
self.status_label.configure(
text="Please select a camera before drawing ROI",
text_color="#FF0000"
)
return

if self.roi_selected:
self.canvas.delete("roi")
self.roi_coords = None
self.roi_selected = False

self.drawing = True
self.start_x = event.x
self.start_y = event.y
self.temp_rect_id = self.canvas.create_rectangle(
self.start_x, self.start_y, self.start_x, self.start_y,
outline=self.roi_color, width=4, tags="temp_roi"
)
def draw_rectangle(self, event):
"""Update the rectangle as the mouse moves"""
if self.drawing and self.temp_rect_id and self.can_draw:
# Constrain cursor position within canvas bounds
cur_x = min(max(event.x, 0), self.canvas.winfo_width())
cur_y = min(max(event.y, 0), self.canvas.winfo_height())

# Update rectangle coordinates


self.canvas.coords(self.temp_rect_id, self.start_x, self.start_y,
cur_x, cur_y)

def end_draw(self, event):


if self.drawing:
self.drawing = False
x1, y1, x2, y2 = self.canvas.coords(self.temp_rect_id)

self.roi_coords = (
int(min(x1, x2)),
int(min(y1, y2)),
int(max(x1, x2)),
int(max(y1, y2))
)
self.roi_selected = True

# Clear any existing ROI


self.canvas.delete("roi")

# Draw horizontal lines


line_spacing = 2 # Adjust the spacing between lines
for y in range(self.roi_coords[1], self.roi_coords[3], line_spacing):
self.canvas.create_line(
self.roi_coords[0], y,
self.roi_coords[2], y,
fill="black", tags="roi"
)

# Draw vertical lines


for x in range(self.roi_coords[0], self.roi_coords[2], line_spacing):
self.canvas.create_line(
x, self.roi_coords[1],
x, self.roi_coords[3],
fill="black", tags="roi"
)

# Border
self.rect_id = self.canvas.create_rectangle(
self.roi_coords[0], self.roi_coords[1],
self.roi_coords[2], self.roi_coords[3],
outline=self.roi_color, width=3, tags="roi"
)

self.save_button.configure(state="normal")
self.status_label.configure(text="ROI selected", text_color="#00FF00")

def save_roi(self):
"""Save the ROI coordinates and calculate percentages based on frame
dimensions"""
if self.roi_selected and self.frame is not None:
try:
# Update ROI state
self._roi_state[self.current_camera] = {
"coords": self.roi_coords,
"selected": True
}

# Get frame dimensions


frame_height = self.frame.shape[0]
frame_width = self.frame.shape[1]

# Get ROI coordinates


x1, y1, x2, y2 = self.roi_coords

# Calculate percentages based on frame dimensions


ROIStartPercentageWidth = (x1 / frame_width) * 100
ROIEndPercentageWidth = (x2 / frame_width) * 100
ROIStartPercentageHeight = (y1 / frame_height) * 100
ROIEndPercentageHeight = (y2 / frame_height) * 100

# Prepare coordinates dictionary


ROICoordinates = {
"point1": [x1, y1],
"point2": [x2, y1],
"point3": [x1, y2],
"point4": [x2, y2],
"frame_height": frame_height,
"frame_width": frame_width
}

# Save to database through Core object


result = self.obj_Core.obj_Camera.add_roi_coordinates(
self.current_camera,
ROIStartPercentageHeight,
ROIEndPercentageHeight,

ROIStartPercentageWidth,
ROIEndPercentageWidth,
ROICoordinates,
)

if result["str_error_msg"]:
raise Exception(result["str_error_msg"])

# Log the saved data


print(f"\nSaved ROI coordinates and percentages:")
print(f"Frame dimensions: {frame_width}x{frame_height}")
print(f"ROI coordinates: ({x1},{y1}),({x2},{y1}),({x1},{y2}),({x2},
{y2})")
print(f"X-axis percentages: {ROIStartPercentageWidth:.2f}% -
{ROIEndPercentageWidth:.2f}%")
print(f"Y-axis percentages: {ROIStartPercentageHeight:.2f}% -
{ROIEndPercentageHeight:.2f}%")

# Redraw frame to ensure it's displayed


self.display_frame()

# Clear any existing ROI


self.canvas.delete("roi")
# Draw horizontal lines
line_spacing = 2
for y in range(self.roi_coords[1], self.roi_coords[3],
line_spacing):
self.canvas.create_line(
self.roi_coords[0], y,
self.roi_coords[2], y,
fill="black", tags="roi"
)

# Draw vertical lines


for x in range(self.roi_coords[0], self.roi_coords[2],
line_spacing):
self.canvas.create_line(
x, self.roi_coords[1],
x, self.roi_coords[3],
fill="black", tags="roi"
)

# Border
self.rect_id = self.canvas.create_rectangle(
self.roi_coords[0], self.roi_coords[1],
self.roi_coords[2], self.roi_coords[3],
outline=self.roi_color, width=3, tags="roi"
)

self.save_button.configure(state="disabled")
self.status_label.configure(text="ROI saved successfully",
text_color="#00FF00")
self.restore_roi()

except Exception as e:
self.status_label.configure(text=f"Save error: {str(e)}",
text_color="#FF0000")
print(f"Error saving ROI: {str(e)}")

def cancel_roi(self):
"""Restore the previously saved ROI"""
if self.current_camera:
# Check if there's a saved ROI for this camera
saved_roi = self._roi_state.get(self.current_camera)

if saved_roi and saved_roi.get('coords') and saved_roi.get('selected'):


# Clear existing drawings
self.canvas.delete("all")

# Redisplay the current frame


self.display_frame()

# Restore saved coordinates


x1, y1, x2, y2 = saved_roi['coords']

# Recreate ROI exactly as it was saved


line_spacing = 2
for y in range(y1, y2, line_spacing):
self.canvas.create_line(x1, y, x2, y, fill="black", tags="roi")

for x in range(x1, x2, line_spacing):


self.canvas.create_line(x, y1, x, y2, fill="black", tags="roi")
# Draw border
self.canvas.create_rectangle(
x1, y1, x2, y2,
outline=self.roi_color, width=3, tags="roi"
)

# Update current state


self.roi_coords = saved_roi['coords']
self.roi_selected = True
self.save_button.configure(state="normal")
self.status_label.configure(text="Saved ROI restored",
text_color="#00FF00")
else:
# No saved ROI exists
self.canvas.delete("all")
self.display_frame()
self.roi_coords = None
self.roi_selected = False
self.save_button.configure(state="disabled")
self.status_label.configure(text="Draw ROI on the image",
text_color="#FFA500")

You might also like