From 99d4aaa75b25e2270a8ea050ae48a6b8c41b5d5d Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 18 May 2021 18:12:19 -0600 Subject: [PATCH] Major Update - Regions 2.3 - Overhauled regions to use an id as id rather than name as id. This fixes all overwriting issues and issues with regions disappearing. - Moved Regions from multitracker to its own - Regions generate uuid as id if no id is given - Renamed radius in methods to regions - radius_regions renamed to region_dict for better description of the dictionary data structure - Moved people_tab from input_dialog to TrackerTab - people_tab now has uuid - Retain region now sets to false at default, working with read_only and other_room - Fixed export to print regions properly. Old method failed when duplicate regions were present - Multitracker.update(frame) in multitracker is now fixed, it looks unused but the function would have failed by calling non-existent value of show_frame when only frame was passed into the method and present. WARNING: Duplicate region names are now supported, this is up to user discretion to decide if this is valid or invalid. --- src/Regions.py | 153 +++++++++++++++++++++++++++++ src/TrackerTab.py | 232 ++++++++++++++++++++++++++++++++++++++++++++ src/multitracker.py | 189 +++++++----------------------------- src/qt_dialog.py | 219 +---------------------------------------- src/test_Regions.py | 71 +++++++------- 5 files changed, 461 insertions(+), 403 deletions(-) create mode 100644 src/Regions.py create mode 100644 src/TrackerTab.py diff --git a/src/Regions.py b/src/Regions.py new file mode 100644 index 0000000..6501492 --- /dev/null +++ b/src/Regions.py @@ -0,0 +1,153 @@ +from PyQt5.QtCore import QCoreApplication +from PyQt5.QtWidgets import (QApplication, QComboBox, QInputDialog, QLineEdit, + QMessageBox, QWidget,QSplashScreen) + +import uuid + +import cv2 +import math +class Regions(QWidget): + + def __init__(self, log = None): + super().__init__() + self.region_dict = dict() + self.log = log #logs the information + + def add_region(self, frame): + """ + Creates an ellipse given a rectangle ROI. + """ + name, okPressed = QInputDialog.getText(self, 'Region', 'Region Name:') + if okPressed and name != '': + if self.log is not None: + self.log(name) + key = uuid.uuid1() + point_x, point_y, width, height = cv2.selectROI("Frame", frame, fromCenter=False, showCrosshair=True) + self.region_dict[key] = (name, point_x, point_y, width, height) + + def add_moving_region(self, name, point, dimensions, id=None): + if name not in [self.region_dict.keys()]: + + if uuid is None: + id = uuid.uuid1() + + self.region_dict[id] = (name, point[0], point[1], dimensions[0], dimensions[1]) + else: + print("Radius already Exists") + + def set_moving_region(self, name, point, dimensions, id): + """ + Creates and sets a region with a given name, and given dimensions + Point (x, y) : (int, int) + Dimensions (width, height), (int, int) + """ + # if id in [self.region_dict.keys()]: + self.region_dict[id] = (name, point[0], point[1], dimensions[0], dimensions[1]) + + def del_region(self): + items = (self.region_dict.values()) + name_list = [] + for item in items: + name_list.append(item[0]) + # items = ("Red","Blue","Green") + item, okPressed = QInputDialog.getItem(self, "Select Region","Delete Regions:", name_list, 0, False) + if okPressed and item: + for index, value in enumerate(items): + print(value) + if item in value: + if self.log is not None: + self.log(str(("Deleting " + str(item)))) + + # key = items.index(index) + # print(items) + key = list(self.region_dict.keys())[index] + print("KEY", key) + del self.region_dict[key] + return + # name, okPressed = QInputDialog.getText(self, 'Region', 'Delete Region Name:') + + def del_moving_region(self, name, id=None): + if id in self.region_dict: + del self.region_dict[id] + # combo_box = QComboBox(self) + # for item in items: + # combo_box.addItem(item) + # combo_box.move(50, 250) + # combo_box.showPopup() + # selected = combo_box.activated[str] + # creating a combo box widget + + + # adding action to the button + # button.pressed.connect(self.action) + + + + # comboBox.activated[str].connect(lambda parameter_list: expression) + # del self.region_dict[selected] + # item, okPressed = QInputDialog.getItem(self.parent, "Get item","Region Name", items, 0, False) + # if okPressed and item: + # input_dialog.log(item) + + def display_region(self, frame): + """ + Displays all region created Radius on given frame + """ + for key, region in self.region_dict.items(): + name = region[0] + x, y, w, h = region[1], region[2], region[3], region[4] + ellipse_center = (int(x + (w/2)) ,int( y + (h/2))) + + frame = cv2.ellipse(frame, ellipse_center, (int((w/2)),(int(h/2))), 0, 0,360, (0,255,0) ) + # cv2.ellipse(frame, box=w/2,color=(0,255,0)) + cv2.rectangle(frame, (x + int(w/2.1) , y - 1), (x + int(w/2.1) + 10 * (len(name)) , y - 15),(255,255,255),-1) + cv2.putText(frame, name, (x + int(w/2.1) , y - 1), cv2.FONT_HERSHEY_PLAIN, 1, (0,0,0),1) + return frame + + def test_region(self, test_point): + """ + Tests weather a point value exists within an eclipse + p <= 1 exists in or on eclipse + + Equation: Given p = (x-h)^2/a^2 + (y-k)^2/b^2 + Where test_point is (x,y) and ellipse_center is (h,k), + radius_width, radius_height as a,b respectivly + + Returns list(regions), p + """ + #overlapping areas may result in multiple True tests + within_points = [] + test_x = test_point[0] + test_y = test_point[1] + p = float('inf') + for key, region in self.region_dict.items(): + name = region[0] + x, y, w, h = region[1], region[2], region[3], region[4] + + #Invalid inputs are areas with zero or less, return no region and invalid p value + if w <= 0 or h <= 0: + return [], float("inf") + + #handle if devisor == 0 + denom_x = math.pow((w/2), 2) + denom_y = math.pow((h/2), 2) + if denom_x == 0: + denom_x = 1 + elif denom_y == 0: + denom_y = 1 + ellipse_center = (x + (w/2) , y + (h/2)) + + # checking the equation of + # ellipse with the given point + try: + p = ((math.pow((test_x - ellipse_center[0]), 2) / denom_x) + + (math.pow((test_y - ellipse_center[1]), 2) / denom_y)) + except ZeroDivisionError as zerodiverr: + p = float("inf") # Does not count inside the eclipse + + if p <= 1: #point exists in or on eclipse + within_points.append(name) + return within_points, p + + def handle_inputs(): + pass \ No newline at end of file diff --git a/src/TrackerTab.py b/src/TrackerTab.py new file mode 100644 index 0000000..aa9f2af --- /dev/null +++ b/src/TrackerTab.py @@ -0,0 +1,232 @@ +from PyQt5.QtWidgets import QWidget, QInputDialog, QLineEdit, QLabel, QPushButton, QPlainTextEdit, QVBoxLayout, QHBoxLayout, QCheckBox + +from PyQt5.QtGui import QIntValidator, QPixmap +from PyQt5.QtCore import Qt + +import uuid + +class person_tab(): + def __init__(self, window): + self.id = uuid.uuid1() + self.parent = window + self.tab = QWidget() + + + #these are used to record metadata + self.name_line = QLineEdit(window) + self.id_line = QLineEdit(window) + self.group_line = QLineEdit(window) + self.group_line.setValidator(QIntValidator(0,999)) + # self.name_line.textChanged.connect(self.update_tab_name) + self.sex_line = QLineEdit(window) + self.desc_line = QPlainTextEdit(window) + + self.length_tracked = QLabel(window) + self.length_tracked.setText("00:00") + + self.active = True + self.read_only = False + self.other_room = False + self.beginning = False + self.is_region = False + self.is_chair = False + self.init_tab(window) + + def init_tab(self, parent_window): + try: + self.tab.layout = QVBoxLayout(parent_window) + + #Start Name + name_layout = QHBoxLayout() + self.tab.layout.addLayout(name_layout) + + name_btn = QPushButton('Name', parent_window) + name_btn.clicked.connect(lambda: self.getText(input_name="Name:",line=self.name_line)) + + name_layout.addWidget(name_btn) + + name_layout.addWidget(self.name_line) + + + id_btn = QPushButton('ID', parent_window) + id_btn.clicked.connect(lambda: self.getText(input_name="ID:",line=self.id_line)) + + name_layout.addWidget(id_btn) + + name_layout.addWidget(self.id_line) + # #Start Sex + # sex_layout = QHBoxLayout() + + sex_btn = QPushButton('Sex', parent_window) + sex_btn.clicked.connect(self.getChoice) + + name_layout.addWidget(sex_btn) + name_layout.addWidget(self.sex_line) + + group_size_btn = QPushButton('Group Size', parent_window) + group_size_btn.clicked.connect(lambda: self.getInteger()) + + name_layout.addWidget(group_size_btn) + name_layout.addWidget(self.group_line) + + #Start Description + desc_layout = QHBoxLayout() + + desc_btn = QPushButton('Description', parent_window) + desc_btn.clicked.connect(lambda: self.getText(input_name="Description:",line=self.desc_line)) + + desc_layout.addWidget(desc_btn) + desc_layout.addWidget(self.desc_line) + + self.image = QPixmap() + self.tab.layout.addLayout(desc_layout) + + #setup length of time tracked + length_layout = QHBoxLayout() + length_label = QLabel(parent_window) + length_label.setText("Total Tracked (mm:ss): ") + + length_layout.addWidget(length_label) + length_layout.addWidget(self.length_tracked) + length_layout.setAlignment(Qt.AlignCenter) + + # self.active_button = QCheckBox("Active") + # self.active_button.setChecked(True) + # self.active_button.stateChanged.connect(lambda:self.toggle_active()) + # self.active_button.setToolTip("Sets the current tracking to actively record. \nIf unchecked, no box will be processed, displayed or recorded.") + # length_layout.addWidget(self.active_button) + + self.read_only_button = QCheckBox("Read Only") + self.read_only_button.setChecked(False) + self.read_only_button.stateChanged.connect(lambda:self.toggle_read()) + self.read_only_button.setToolTip("Sets the person to read only. \nThis is useful for scrolling through the video without overwriting data.\n Also useful for people exiting the frame") + length_layout.addWidget(self.read_only_button) + + self.other_room_button = QCheckBox("Other Room") + self.other_room_button.setChecked(False) + self.other_room_button.stateChanged.connect(lambda:self.toggle_other_room()) + self.other_room_button.setToolTip("Sets the person to Other Room. \n This is useful for maintaining time without location.") + length_layout.addWidget(self.other_room_button) + + + self.beginning_button = QCheckBox("Beginning") + self.beginning_button.setChecked(False) + self.beginning_button.stateChanged.connect(lambda:self.toggle_beginning()) + self.beginning_button.setToolTip("Sets the 'present at beginning' to be True or False for this person.") + length_layout.addWidget(self.beginning_button) + self.tab.layout.addLayout(length_layout) + + self.is_region_button = QCheckBox("Is Region") + self.is_region_button.setChecked(False) + self.is_region_button.stateChanged.connect(lambda:self.toggle_region()) + length_layout.addWidget(self.is_region_button) + + self.is_chair_button = QCheckBox("Chair") + self.is_chair_button.setChecked(False) + self.is_chair_button.stateChanged.connect(lambda:self.toggle_chair()) + length_layout.addWidget(self.is_chair_button) + + + self.tab.layout.addLayout(length_layout) + + + self.tab.setLayout(self.tab.layout) + + except: + crashlogger.log(str(traceback.format_exc())) + + # #inital load of variables + # self.getText(input_name="Name:",line=self.name_line) + # self.getChoice() + # self.getText(input_name="Description:",line=self.desc_line) + + + + def getInteger(self): + i, okPressed = QInputDialog.getInt(self.parent, "QInputDialog().getInteger()", + "Number:", 1, 0, 999, 1) + self.group_line.setText(str(i)) + if okPressed: + return i + + # def getDouble(self): + # d, okPressed = QInputDialog.getDouble(self.parent, "Get double","Value:", 10.50, 0, 100, 10) + # if okPressed: + # return d + + def getChoice(self): + items = ("Female","Male", "Other") + item, okPressed = QInputDialog.getItem(self.parent, "Get item","Sex:", items, 0, False) + if okPressed and item: + # print(item) + self.sex_line.setText(item) + # return item + + def getText(self, input_name, line): + text, okPressed = QInputDialog.getText(self.parent, "Get text", input_name, QLineEdit.Normal, "") + if okPressed and text != '': + if type(line) == type(QPlainTextEdit()): + line.setPlainText(text) + else: + line.setText(text) + # print(text) + + # return text + + def get_uuid(self): + return self.id + + def get_beginning(self): + return self.beginning + + def get_is_region(self): + return self.is_region + + # def get_region_state(self): + # return self.region_state + + def get_is_chair(self): + return self.is_chair + + def get_read_only(self): + return self.read_only + + def get_other_room(self): + return self.other_room + + def toggle_active(self): + self.parent.log("Setting Active to " + str(not self.active)) + self.active = not self.active + return self.active + + def toggle_read(self): + self.parent.log("Setting Read only to " + str(not self.read_only)) + self.read_only = not self.read_only + return self.read_only + + def toggle_beginning(self): + self.parent.log("Setting person present at beginning to " + str(not self.beginning)) + self.beginning = not self.beginning + return self.beginning + + def toggle_region(self): + # self.region_state = True + self.parent.log("Setting person to a region " + str(not self.is_region)) + self.is_region = not self.is_region + return self.is_region + + def toggle_other_room(self): + self.parent.log("Setting person to other room " + str(not self.other_room)) + self.other_room = not self.other_room + return self.other_room + + def toggle_chair(self): + self.parent.log("Setting person in chair " + str(not self.is_region)) + self.is_chair = not self.is_chair + return self.is_chair + + def update_length_tracked(self, time): + self.length_tracked.setText("00:00") + seconds = round((time)%60,2) + minutes = int(((time)/60)%60) + self.length_tracked.setText( str(minutes) + ":" + str(seconds)) \ No newline at end of file diff --git a/src/multitracker.py b/src/multitracker.py index aaffefb..fdbb084 100644 --- a/src/multitracker.py +++ b/src/multitracker.py @@ -18,6 +18,7 @@ import imutils import numpy as np import pandas as pd + from PyQt5.QtCore import QCoreApplication from PyQt5.QtWidgets import (QApplication, QComboBox, QInputDialog, QLineEdit, QMessageBox, QWidget,QSplashScreen) @@ -29,11 +30,12 @@ from Video import FileVideoStream, STFileVideoStream import filters import regression +from Regions import Regions CPU_COUNT = multiprocessing.cpu_count() #start tracking version at 1.0 -PEOPLETRACKER_VERSION = 2.26 +PEOPLETRACKER_VERSION = 2.3 # For extracting video metadata # import mutagen @@ -47,18 +49,21 @@ def __init__(self, tab, name="Person", colour=(255,255,255)): # self.location_data = [] #(x, y) self.distance_data = [] #(CLOSE, MED, FAR) (estimated) self.time_data = [] #(frames tracked) - # self.radius_regions = dict + # self.region_dict = dict self.data_dict = dict() self.previous_time = 0 + #Set these variables to getter functions (to update whenever accessed) self.sex = tab.sex_line self.group = tab.group_line self.description = tab.desc_line self.beginning = tab.get_beginning self.is_region = tab.get_is_region + # self.region_state = tab.get_region_state self.is_chair = tab.get_is_chair self.read_only = tab.get_read_only + self.id = tab.get_uuid # self.other_room = tab.get_other_room self.record_state = False @@ -239,18 +244,18 @@ def update_tracker(self, frame): # check to see if we are currently tracking an object if self.init_bounding_box is not None: # grab the new bounding box coordinates of the object - (success, box) = self.tracker.update(show_frame) + (success, box) = self.tracker.update(frame) # check to see if the tracking was a success if success: (x, y, w, h) = [int(v) for v in box] - cv2.rectangle(show_frame, (x, y), (x + w, y + h), + cv2.rectangle(frame, (x, y), (x + w, y + h), self.colour, 2) - cv2.rectangle(show_frame, (x , y - 1), (x + 10 * (len(self.get_name())) , y - 15),(255,255,255),-1) - cv2.putText(show_frame,self.get_name(), (x , y - 1), cv2.FONT_HERSHEY_PLAIN, 1, (0,0,0),1) + cv2.rectangle(frame, (x , y - 1), (x + 10 * (len(self.get_name())) , y - 15),(255,255,255),-1) + cv2.putText(frame,self.get_name(), (x , y - 1), cv2.FONT_HERSHEY_PLAIN, 1, (0,0,0),1) # cv2.putText(frame,self.get_name(), (x , y - 1), cv2.FONT_HERSHEY_COMPLEX_SMALL, 0.75, (0,0,0),1) - return success, box, show_frame + return success, box, frame def remove(self): """ @@ -362,11 +367,13 @@ def export_data(self, vid_width, vid_height, vid_name, fps): region_string = "" - for text in data[1]: - if text == data[1][-1]: + for index, text in enumerate(data[1]): + #If there is no item, 1 item or the item is the last item in the list, do not append comma and space + if len(data[1]) <= 1 or index == len(data[1]) -1: region_string += (text) else: region_string += (text + ", ") + region_list.append(region_string) other_room_list.append(data[3]) total_people_list.append(data[4]) @@ -586,122 +593,6 @@ def calculate_total_time(self, total_frames, fps=30, segmented=True): return total_time -class Regions(QWidget): - - def __init__(self): - super().__init__() - self.radius_regions = dict() - - def add_radius(self): - """ - Creates a circle given a rectangle ROI. - """ - name, okPressed = QInputDialog.getText(self, 'Region', 'Region Name:') - if okPressed and name != '': - input_dialog.log(name) - self.radius_regions[name] = (cv2.selectROI("Frame", frame, fromCenter=False, showCrosshair=True)) - - def set_moving_radius(self, name, point, dimensions): - """ - Creates and sets a radius with a given name, and given dimensions - Point (x, y) : (int, int) - Dimensions (width, height), (int, int) - """ - self.radius_regions[name] = (point[0], point[1], dimensions[0], dimensions[1]) - - def del_radius(self): - items = (self.radius_regions.keys()) - # items = ("Red","Blue","Green") - item, okPressed = QInputDialog.getItem(self, "Get item","Delete Regions:", items, 0, False) - if okPressed and item: - input_dialog.log(item) - del self.radius_regions[item] - # name, okPressed = QInputDialog.getText(self, 'Region', 'Delete Region Name:') - - def del_moving_radius(self, name): - if name in self.radius_regions: - del self.radius_regions[name] - # combo_box = QComboBox(self) - # for item in items: - # combo_box.addItem(item) - # combo_box.move(50, 250) - # combo_box.showPopup() - # selected = combo_box.activated[str] - # creating a combo box widget - - - # adding action to the button - # button.pressed.connect(self.action) - - - - # comboBox.activated[str].connect(lambda parameter_list: expression) - # del self.radius_regions[selected] - # item, okPressed = QInputDialog.getItem(self.parent, "Get item","Region Name", items, 0, False) - # if okPressed and item: - # input_dialog.log(item) - - def display_radius(self, frame): - """ - Displays all radius created Radius on given frame - """ - for key, region in self.radius_regions.items(): - x, y, w, h = region[0], region[1], region[2], region[3] - ellipse_center = (int(x + (w/2)) ,int( y + (h/2))) - - frame = cv2.ellipse(frame, ellipse_center, (int((w/2)),(int(h/2))), 0, 0,360, (0,255,0) ) - # cv2.ellipse(frame, box=w/2,color=(0,255,0)) - cv2.rectangle(frame, (x + int(w/2.1) , y - 1), (x + int(w/2.1) + 10 * (len(key)) , y - 15),(255,255,255),-1) - cv2.putText(frame,key, (x + int(w/2.1) , y - 1), cv2.FONT_HERSHEY_PLAIN, 1, (0,0,0),1) - return frame - - def test_radius(self, test_point): - """ - Tests weather a point value exists within an eclipse - p <= 1 exists in or on eclipse - - Equation: Given p = (x-h)^2/a^2 + (y-k)^2/b^2 - Where test_point is (x,y) and ellipse_center is (h,k), - radius_width, radius_height as a,b respectivly - - Returns list(regions), p - """ - #overlapping areas may result in multiple True tests - within_points = [] - test_x = test_point[0] - test_y = test_point[1] - p = float('inf') - for key, region in self.radius_regions.items(): - - x, y, w, h = region[0], region[1], region[2], region[3] - - #Invalid inputs are areas with zero or less, return no region and invalid p value - if w <= 0 or h <= 0: - return [], float("inf") - - #handle if devisor == 0 - denom_x = math.pow((w/2), 2) - denom_y = math.pow((h/2), 2) - if denom_x == 0: - denom_x = 1 - elif denom_y == 0: - denom_y = 1 - ellipse_center = (x + (w/2) , y + (h/2)) - - # checking the equation of - # ellipse with the given point - try: - p = ((math.pow((test_x - ellipse_center[0]), 2) / denom_x) + - (math.pow((test_y - ellipse_center[1]), 2) / denom_y)) - except ZeroDivisionError as zerodiverr: - p = float("inf") # Does not count inside the eclipse - - if p <= 1: #point exists in or on eclipse - within_points.append(key) - return within_points, p - - def handle_inputs(): - pass def export_null_meta(vid_dir): #Create path string for exporting the data. It's just a change of extention. export_filename = str(videoPath[:-4]) + ".csv" @@ -909,7 +800,7 @@ def export_meta(vid_dir): selected_tracker = 1 #initialize regions object to store all regions - regions = Regions() + regions = Regions(log=input_dialog.log) #Initialize video, get the first frame and setup the scrollbar to the video length cap = cv2.VideoCapture(videoPath) @@ -935,10 +826,6 @@ def export_meta(vid_dir): input_dialog.scrollbar_changed = True previous_frame = frame - # print(frame_num) - # frame = imutils.resize(frame, width=450) - # frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) - # frame = np.dstack([frame, frame, frame]) frame = cv2.resize(frame, (input_dialog.resolution_x, input_dialog.resolution_y), 0, 0, cv2.INTER_CUBIC) show_frame = frame.copy() @@ -947,10 +834,8 @@ def export_meta(vid_dir): #get the video's FPS vid_fps = cap.get(cv2.CAP_PROP_FPS) input_dialog.log("Video FPS set to " + str(vid_fps)) - # vid_fps = 30 input_dialog.set_fps_info(vid_fps) - skip_frame = 10 input_dialog.log("Gathering frames...") @@ -1226,12 +1111,12 @@ def export_meta(vid_dir): elif input_dialog.region_state is True: input_dialog.log("Adding region... Write name and then draw boundaries") - regions.add_radius() + regions.add_region(show_frame) input_dialog.region_state = False input_dialog.log("Adding region complete.") elif input_dialog.del_region_state is True: input_dialog.log("Select a region to remove...") - regions.del_radius() + regions.del_region() input_dialog.del_region_state = False input_dialog.log("Removing region complete.") @@ -1252,12 +1137,13 @@ def export_meta(vid_dir): for tracker in enumerate(tracker_list): tracker_num = tracker[0] tracker = tracker[1] - # try: - # frame_number = fvs.frame_number + frame_number = frame_num try: if input_dialog.tab_list[tracker_num].other_room: tracker.record_data(frame_number, input_dialog.num_people.value(), other_room=True) + regions.del_moving_region(tracker.get_name(), id=tracker.id()) + elif tracker.init_bounding_box is not None and input_dialog.tab_list[tracker_num].active is True and input_dialog.tab_list[tracker_num].read_only is False: #allocate frames on GPU, reducing CPU load. @@ -1273,9 +1159,6 @@ def export_meta(vid_dir): # tracker.assign(frame, trackerName) #caluclate info needed this frame - # frame_number = cap.get(cv2.CAP_PROP_POS_FRAMES) - # frame_number = fvs.frame_number - # frame_number = frame_num top_left_x = box[0] top_left_y = box[1] width = box[2] @@ -1291,17 +1174,18 @@ def export_meta(vid_dir): if tracker.is_region() is True and tracker.get_name() != "": - regions.set_moving_radius(name = tracker.get_name(), + regions.set_moving_region(name = tracker.get_name(), point = (int(center_x - width), int(center_y - height)), - dimensions = (int(width*2), int(height*2)) + dimensions = (int(width*2), int(height*2)), + id=tracker.id() ) - regions.display_radius(show_frame) + regions.display_region(show_frame) elif tracker.is_region() is False: - # If tracker region is no longer selected, delete moving radius - regions.del_moving_radius(tracker.get_name()) + # If tracker region is no longer selected, delete moving region + regions.del_moving_region(tracker.get_name(), id=tracker.id()) if pred_dict and input_dialog.mcrnn_options.get_active() is True: if input_dialog.get_scrollbar_value() in pred_dict.keys(): @@ -1355,7 +1239,7 @@ def export_meta(vid_dir): # bottom = (int(center_x), int(center_y - height/2)) # cv2.circle(frame, top, 3, (0,255,255),-1) # cv2.circle(frame, bottom, 3, (0,255,255),-1) - in_region, p = regions.test_radius((center_x, center_y)) + in_region, p = regions.test_region((center_x, center_y)) if input_dialog.play_state == True and input_dialog.tab_list[tracker_num].read_only is False: #record all the data collected from that frame @@ -1466,7 +1350,7 @@ def export_meta(vid_dir): # frame_number = frame_num # print(frame_number) frame_number = input_dialog.get_scrollbar_value() - # regions.del_moving_radius(tracker.get_name()) + # regions.del_moving_region(tracker.get_name()) # print(selected_tracker, tracker_num) if frame_number in tracker.data_dict: # print("Exists") @@ -1478,13 +1362,13 @@ def export_meta(vid_dir): point = (int(center[0] - dim[0]), int(center[1] - dim[1])) dim = (int(dim[0]*2), int(dim[1]*2)) - regions.set_moving_radius(tracker.get_name(), point, dim) + regions.set_moving_region(tracker.get_name(), point, dim) # top = (int(center[0]) - dim[0], int(center[1], - dim[1]/2)) # bottom = (int(center[0]) - dim[0], int(center[1], + dim[1]/2)) if tracker.is_region() is False: - # If tracker region is no longer selected, delete moving radius - regions.del_moving_radius(tracker.get_name()) + # If tracker region is no longer selected, delete moving region + regions.del_moving_region(tracker.get_name(), id=tracker.id()) if selected_tracker == tracker_num: @@ -1504,7 +1388,8 @@ def export_meta(vid_dir): #Exclude if you want regions to not exist elif not input_dialog.retain_region: - regions.del_moving_radius(tracker.get_name()) + regions.del_moving_region(tracker.get_name(), id=tracker.id()) + except Exception as e: crashlogger.log(str(e)) input_dialog.log("Could not handle read only. List index out of range, Continuing") @@ -1564,8 +1449,8 @@ def export_meta(vid_dir): pass #Display all regions on screen if they exist - if len(regions.radius_regions) > 0: - show_frame = regions.display_radius(show_frame) + if len(regions.region_dict) > 0: + show_frame = regions.display_region(show_frame) # print("FRAMES", frame_num, fvs.frame_number, input_dialog.get_scrollbar_value()) #When done processing each tracker, view the frame diff --git a/src/qt_dialog.py b/src/qt_dialog.py index 8a310db..a049e6e 100644 --- a/src/qt_dialog.py +++ b/src/qt_dialog.py @@ -7,6 +7,7 @@ import webbrowser import crashlogger import traceback +from TrackerTab import person_tab import cv2 import numpy as np @@ -41,7 +42,7 @@ def __init__(self): self.vid_fps = 30 self.snap_state = None self.set_tracker_state = False - self.retain_region = True + self.retain_region = False self.quit_State = False self.image = None @@ -588,223 +589,7 @@ def toggle_retain_region(self): self.retain_region = not self.retain_region -class person_tab(): - def __init__(self, window): - self.parent = window - self.tab = QWidget() - - - #these are used to record metadata - self.name_line = QLineEdit(window) - self.id_line = QLineEdit(window) - self.group_line = QLineEdit(window) - self.group_line.setValidator(QIntValidator(0,999)) - # self.name_line.textChanged.connect(self.update_tab_name) - self.sex_line = QLineEdit(window) - self.desc_line = QPlainTextEdit(window) - - self.length_tracked = QLabel(window) - self.length_tracked.setText("00:00") - - self.active = True - self.read_only = False - self.other_room = False - self.beginning = False - self.is_region = False - self.is_chair = False - self.init_tab(window) - - def init_tab(self, parent_window): - try: - self.tab.layout = QVBoxLayout(parent_window) - - #Start Name - name_layout = QHBoxLayout() - self.tab.layout.addLayout(name_layout) - - name_btn = QPushButton('Name', parent_window) - name_btn.clicked.connect(lambda: self.getText(input_name="Name:",line=self.name_line)) - - name_layout.addWidget(name_btn) - - name_layout.addWidget(self.name_line) - - - id_btn = QPushButton('ID', parent_window) - id_btn.clicked.connect(lambda: self.getText(input_name="ID:",line=self.id_line)) - - name_layout.addWidget(id_btn) - - name_layout.addWidget(self.id_line) - # #Start Sex - # sex_layout = QHBoxLayout() - - sex_btn = QPushButton('Sex', parent_window) - sex_btn.clicked.connect(self.getChoice) - - name_layout.addWidget(sex_btn) - name_layout.addWidget(self.sex_line) - - group_size_btn = QPushButton('Group Size', parent_window) - group_size_btn.clicked.connect(lambda: self.getInteger()) - - name_layout.addWidget(group_size_btn) - name_layout.addWidget(self.group_line) - - #Start Description - desc_layout = QHBoxLayout() - - desc_btn = QPushButton('Description', parent_window) - desc_btn.clicked.connect(lambda: self.getText(input_name="Description:",line=self.desc_line)) - - desc_layout.addWidget(desc_btn) - desc_layout.addWidget(self.desc_line) - - self.image = QPixmap() - self.tab.layout.addLayout(desc_layout) - - #setup length of time tracked - length_layout = QHBoxLayout() - length_label = QLabel(parent_window) - length_label.setText("Total Tracked (mm:ss): ") - - length_layout.addWidget(length_label) - length_layout.addWidget(self.length_tracked) - length_layout.setAlignment(Qt.AlignCenter) - - # self.active_button = QCheckBox("Active") - # self.active_button.setChecked(True) - # self.active_button.stateChanged.connect(lambda:self.toggle_active()) - # self.active_button.setToolTip("Sets the current tracking to actively record. \nIf unchecked, no box will be processed, displayed or recorded.") - # length_layout.addWidget(self.active_button) - - self.read_only_button = QCheckBox("Read Only") - self.read_only_button.setChecked(False) - self.read_only_button.stateChanged.connect(lambda:self.toggle_read()) - self.read_only_button.setToolTip("Sets the person to read only. \nThis is useful for scrolling through the video without overwriting data.\n Also useful for people exiting the frame") - length_layout.addWidget(self.read_only_button) - - self.other_room_button = QCheckBox("Other Room") - self.other_room_button.setChecked(False) - self.other_room_button.stateChanged.connect(lambda:self.toggle_other_room()) - self.other_room_button.setToolTip("Sets the person to Other Room. \n This is useful for maintaining time without location.") - length_layout.addWidget(self.other_room_button) - - self.beginning_button = QCheckBox("Beginning") - self.beginning_button.setChecked(False) - self.beginning_button.stateChanged.connect(lambda:self.toggle_beginning()) - self.beginning_button.setToolTip("Sets the 'present at beginning' to be True or False for this person.") - length_layout.addWidget(self.beginning_button) - self.tab.layout.addLayout(length_layout) - - self.is_region_button = QCheckBox("Is Region") - self.is_region_button.setChecked(False) - self.is_region_button.stateChanged.connect(lambda:self.toggle_region()) - length_layout.addWidget(self.is_region_button) - - self.is_chair_button = QCheckBox("Chair") - self.is_chair_button.setChecked(False) - self.is_chair_button.stateChanged.connect(lambda:self.toggle_chair()) - length_layout.addWidget(self.is_chair_button) - - - self.tab.layout.addLayout(length_layout) - - - self.tab.setLayout(self.tab.layout) - - except: - crashlogger.log(str(traceback.format_exc())) - - # #inital load of variables - # self.getText(input_name="Name:",line=self.name_line) - # self.getChoice() - # self.getText(input_name="Description:",line=self.desc_line) - - - - def getInteger(self): - i, okPressed = QInputDialog.getInt(self.parent, "QInputDialog().getInteger()", - "Number:", 1, 0, 999, 1) - self.group_line.setText(str(i)) - if okPressed: - return i - - # def getDouble(self): - # d, okPressed = QInputDialog.getDouble(self.parent, "Get double","Value:", 10.50, 0, 100, 10) - # if okPressed: - # return d - - def getChoice(self): - items = ("Female","Male", "Other") - item, okPressed = QInputDialog.getItem(self.parent, "Get item","Sex:", items, 0, False) - if okPressed and item: - # print(item) - self.sex_line.setText(item) - # return item - - def getText(self, input_name, line): - text, okPressed = QInputDialog.getText(self.parent, "Get text", input_name, QLineEdit.Normal, "") - if okPressed and text != '': - if type(line) == type(QPlainTextEdit()): - line.setPlainText(text) - else: - line.setText(text) - # print(text) - - # return text - - def get_beginning(self): - return self.beginning - - def get_is_region(self): - return self.is_region - - def get_is_chair(self): - return self.is_chair - - def get_read_only(self): - return self.read_only - - def get_other_room(self): - return self.other_room - - def toggle_active(self): - self.parent.log("Setting Active to " + str(not self.active)) - self.active = not self.active - return self.active - - def toggle_read(self): - self.parent.log("Setting Read only to " + str(not self.read_only)) - self.read_only = not self.read_only - return self.read_only - - def toggle_beginning(self): - self.parent.log("Setting person present at beginning to " + str(not self.beginning)) - self.beginning = not self.beginning - return self.beginning - - def toggle_region(self): - self.parent.log("Setting person to a region " + str(not self.is_region)) - self.is_region = not self.is_region - return self.is_region - - def toggle_other_room(self): - self.parent.log("Setting person to other room " + str(not self.other_room)) - self.other_room = not self.other_room - return self.other_room - - def toggle_chair(self): - self.parent.log("Setting person in chair " + str(not self.is_region)) - self.is_chair = not self.is_chair - return self.is_chair - - def update_length_tracked(self, time): - self.length_tracked.setText("00:00") - seconds = round((time)%60,2) - minutes = int(((time)/60)%60) - self.length_tracked.setText( str(minutes) + ":" + str(seconds)) diff --git a/src/test_Regions.py b/src/test_Regions.py index 261594d..398d501 100644 --- a/src/test_Regions.py +++ b/src/test_Regions.py @@ -17,35 +17,38 @@ def new_region(self): self.assertNotEqual(region, None, "Should not be None") return region - def test_set_moving_radius(self): + def test_set_moving_region(self): regions = self.new_region() #Test Null Entry or Invalid - regions.set_moving_radius(name="Joe", point=(0,0), dimensions=(0,0)) - regions.set_moving_radius(name="Joe", point=(0,0), dimensions=(-1,-1)) - regions.set_moving_radius(name="Joe", point=(0,0), dimensions=(10,10)) + regions.set_moving_region(name="Joe", point=(0,0), dimensions=(0,0), id = "1") + regions.set_moving_region(name="Joe", point=(0,0), dimensions=(-1,-1),id = "2") + regions.set_moving_region(name="Joe", point=(0,0), dimensions=(10,10), id = "3") + # regions.set_moving_region - def del_moving_radius(self): + + def del_moving_region(self): regions = self.new_region() # Test Removal of name - regions.set_moving_radius(name="Test", point=(0,0), dimensions=(0,0)) - regions.del_moving_radius(name="Test") - self.assertEqual(regions.radius_regions, dict()) + regions.set_moving_region(name="Test", point=(0,0), dimensions=(0,0), id="1") + regions.del_moving_region(name="Test") + self.assertEqual(regions.region_dict, dict()) # Test does not remove subset - regions.set_moving_radius(name="Test", point=(0,0), dimensions=(0,0)) - regions.del_moving_radius(name="Tes") - self.assertEqual(regions.radius_regions.keys(), dict("Test").keys()) + regions.set_moving_region(name="Test", point=(0,0), dimensions=(0,0), id="1") + regions.del_moving_region(name="Tes") + + self.assertEqual(regions.region_dict.keys(), dict("1").keys()) # Test does not remove superset - regions.set_moving_radius(name="Test", point=(0,0), dimensions=(0,0)) - regions.del_moving_radius(name="Tests") - self.assertEqual(regions.radius_regions.keys(), dict("Test").keys()) + regions.set_moving_region(name="Test", point=(0,0), dimensions=(0,0), id="1") + regions.del_moving_region(name="Tests") + self.assertEqual(regions.region_dict.keys(), dict("1").keys()) - def test_test_radius(self): + def test_test_region(self): """ if p <= 1: #point exists in or on ellipse Otherwise the point exists outside of the ellipse @@ -55,35 +58,35 @@ def test_test_radius(self): regions = self.new_region() # Test on Empty - regions.set_moving_radius(name="Test", point=(0,0), dimensions=(0,0)) + regions.set_moving_region(name="Test", point=(0,0), dimensions=(0,0), id="1") # Zero Area cube has no center, therefore should not be in eclipse - self.assertEqual(regions.test_radius((0,0))[0], []) - self.assertGreaterEqual(regions.test_radius((0,0))[1], 1) + self.assertEqual(regions.test_region((0,0))[0], []) + self.assertGreaterEqual(regions.test_region((0,0))[1], 1) # Point outside of zero area eclispe should not be in or on the eclipse - self.assertEqual(regions.test_radius((10,10))[0], []) - self.assertGreaterEqual(regions.test_radius((10,10))[1], 1) + self.assertEqual(regions.test_region((10,10))[0], []) + self.assertGreaterEqual(regions.test_region((10,10))[1], 1) # Test Invalid - regions.set_moving_radius(name="Test", point=(-1,-1), dimensions=(-1,-1)) + regions.set_moving_region(name="Test", point=(-1,-1), dimensions=(-1,-1), id="1") - self.assertEqual(regions.test_radius((0,0))[0], []) - self.assertGreaterEqual(regions.test_radius((0,0))[1], 1) + self.assertEqual(regions.test_region((0,0))[0], []) + self.assertGreaterEqual(regions.test_region((0,0))[1], 1) - self.assertEqual(regions.test_radius((-10,-10))[0], []) - self.assertGreaterEqual(regions.test_radius((-10,-10))[1], 1) + self.assertEqual(regions.test_region((-10,-10))[0], []) + self.assertGreaterEqual(regions.test_region((-10,-10))[1], 1) # Test Valid - regions.set_moving_radius(name="Test", point=(100,100), dimensions=(5,10)) - self.assertGreaterEqual(regions.test_radius((0,0))[1], 1) - self.assertGreaterEqual(regions.test_radius((100,100))[1], 1) # The top left of a rectangle which describes an eclipse is not included in ellipse - - self.assertLessEqual(regions.test_radius((102.5,100))[1], 1) # Tangent Edge should exist within ellipse (top) - self.assertLessEqual(regions.test_radius((102.5,110))[1], 1) # Tangent Edge should exist within ellipse (bottom) - self.assertLessEqual(regions.test_radius((100,105))[1], 1) # Tangent Edge should exist within ellipse (left) - self.assertLessEqual(regions.test_radius((105,105))[1], 1) # Tangent Edge should exist within ellipse (right) - # self.assertEqual(regions.test_radius((,-10)), -1) + regions.set_moving_region(name="Test", point=(100,100), dimensions=(5,10),id="1") + self.assertGreaterEqual(regions.test_region((0,0))[1], 1) + self.assertGreaterEqual(regions.test_region((100,100))[1], 1) # The top left of a rectangle which describes an eclipse is not included in ellipse + + self.assertLessEqual(regions.test_region((102.5,100))[1], 1) # Tangent Edge should exist within ellipse (top) + self.assertLessEqual(regions.test_region((102.5,110))[1], 1) # Tangent Edge should exist within ellipse (bottom) + self.assertLessEqual(regions.test_region((100,105))[1], 1) # Tangent Edge should exist within ellipse (left) + self.assertLessEqual(regions.test_region((105,105))[1], 1) # Tangent Edge should exist within ellipse (right) + # self.assertEqual(regions.test_region((,-10)), -1) if __name__ == '__main__': unittest.main()