diff --git a/.gitignore b/.gitignore index 985406b..a41f15b 100644 --- a/.gitignore +++ b/.gitignore @@ -67,4 +67,10 @@ target/ # Project directory project_dir/* -*.IDdict \ No newline at end of file +*.IDdict +application/test_app.py +application/test_cloud.py + +*.dmg + +*.zip diff --git a/application/.application b/application/.application deleted file mode 100644 index fd80b70..0000000 --- a/application/.application +++ /dev/null @@ -1,5 +0,0 @@ -[info] -default_project_dir = ../project_dir -# If the next line gives problems, this could be changed to, for example, /home/svaughan/Traffic -ti_install_dir = ~/Traffic - diff --git a/application/SantosBuild b/application/SantosBuild new file mode 100755 index 0000000..6ccea1a --- /dev/null +++ b/application/SantosBuild @@ -0,0 +1,13 @@ +#!/bin/bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +echo $DIR + +cd $DIR +pyinstaller $DIR/app.spec +chmod 777 $DIR/dist/app + +echo “[Press any button to exit]” +read -rsn1 +exit \ No newline at end of file diff --git a/application/SantosBuild.sh b/application/SantosBuild.sh new file mode 100644 index 0000000..6ccea1a --- /dev/null +++ b/application/SantosBuild.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +echo $DIR + +cd $DIR +pyinstaller $DIR/app.spec +chmod 777 $DIR/dist/app + +echo “[Press any button to exit]” +read -rsn1 +exit \ No newline at end of file diff --git a/application/app.py b/application/app.py index e22f4ac..fd52352 100755 --- a/application/app.py +++ b/application/app.py @@ -4,10 +4,9 @@ import cv2 from custom.video_frame_player import VideoFramePlayer from PyQt4 import QtGui, QtCore -from safety_main import Ui_TransportationSafety +from views.safety_main import Ui_TransportationSafety import subprocess - -from plotting.make_object_trajectories import main as db_make_objtraj +import zipfile ##############################################3 # testing feature objects @@ -16,7 +15,6 @@ ############################################### import os -from PyQt4.phonon import Phonon import ConfigParser from PyQt4.QtGui import * @@ -27,35 +25,33 @@ from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar import numpy as np -import cvutils -from app_config import AppConfig as ac -import app_config as pm -import pm -import cloud_api as capi - -import qt_plot +from app_config import get_base_project_dir, get_project_path, update_config_with_sections, get_config_with_sections, get_config_path, get_identifier, projects_exist +import pm +import message_helper +import project_selector +from cloud_api import api +from cloud_api import StatusPoller +from video import convert_video_to_frames -class Organizer(object): # TODO: Phase out. - def __init__(self): - super(Organizer, self).__init__() class MainGUI(QtGui.QMainWindow): + test_feature_callback_signal = QtCore.pyqtSignal() + test_object_callback_signal = QtCore.pyqtSignal() + analysis_callback_signal = QtCore.pyqtSignal() + results_callback_signal = QtCore.pyqtSignal() def __init__(self): super(MainGUI, self).__init__() self.ui = Ui_TransportationSafety() self.ui.setupUi(self) self.newp = pm.ProjectWizard(self) - self.api = capi.CloudWizard('server',None,'192.168.1.1') - - # Experimenting with organizational objects - self.feature_tracking = Organizer() - self.results = Organizer() + self.pselector = project_selector.ProjectSelectionWizard(self) # Connect Menu actions self.ui.actionOpen_Project.triggered.connect(self.open_project) self.ui.actionNew_Project.triggered.connect(self.create_new_project) + self.ui.actionFeedback.triggered.connect(self.open_feedback) self.ui.actionAdd_Replace_Aerial_Image.triggered.connect(self.homography_open_image_aerial) # TODO: New method. Check which tab is open. Move to homography tab if not already there. Then call open_image_aerial. self.ui.actionAdd_Replace_Aerial_Image.triggered.connect(self.homography_open_image_camera) self.ui.main_tab_widget.setCurrentIndex(0) # Start on the first tab @@ -69,6 +65,12 @@ def __init__(self): self.ui.feature_tracking_continue_button.clicked.connect(self.show_next_tab) self.ui.feature_tracking_back_button.clicked.connect(self.show_prev_tab) + # Connect callback signals + self.test_feature_callback_signal.connect(self.get_feature_video) + self.test_object_callback_signal.connect(self.get_object_video) + self.analysis_callback_signal.connect(self.runResults) + self.results_callback_signal.connect(self.retrieveResults) + ########################################################################################################################################### self.ui.roadusers_tracking_back_button.clicked.connect(self.show_prev_tab) @@ -84,7 +86,6 @@ def __init__(self): # config self.configGui_features = configGui_features() - self.ui.actionOpen_Config.triggered.connect(self.configGui_features.openConfig) self.ui.feature_tracking_parameter_layout.addWidget(self.configGui_features) # test button @@ -101,17 +102,15 @@ def __init__(self): # config self.configGui_object = configGui_object() - self.ui.actionOpen_Config.triggered.connect(self.configGui_object.openConfig) self.ui.roadusers_tracking_parameter_layout.addWidget(self.configGui_object) # test button self.ui.button_roadusers_tracking_test.clicked.connect(self.test_object) - # run button - self.ui.button_roadusers_tracking_run.clicked.connect(self.run) + # runResults button + self.ui.runAnalysisButton.clicked.connect(self.runAnalysis) - qt_plot.plot_results(self) ########################################################################################################################################### # self.ui.track_image.mousePressEvent = self.get_image_position @@ -124,288 +123,154 @@ def __init__(self): self.ui.homography_compute_button.clicked.connect(self.homography_compute) self.show() + if projects_exist(): + self.pselector.show() + else: + self.newp.show() + ###################################################################################################### def test_feature(self): - tracking_path = os.path.join(ac.CURRENT_PROJECT_PATH, ".temp", "test", "test_feature", "feature_tracking.cfg") - db_path = os.path.join(ac.CURRENT_PROJECT_PATH, ".temp", "test", "test_feature", "test1.sqlite") - if os.path.exists(db_path): - os.remove(db_path) + frame_start = get_config_with_sections(get_config_path(), "config", "frame_start") + num_frames = get_config_with_sections(get_config_path(), "config", "num_frames") + api.testConfig('feature',\ + get_identifier(),\ + frame_start = frame_start,\ + num_frames = num_frames) + StatusPoller(get_identifier(), 'feature_test', 5, self.test_feature_callback).start() - images_folder = "feature_images" - self.delete_images(images_folder) + self.show_message('Your test of feature tracking has begun. When it has completed, a video will be shown in the window on the left. Please wait, this will only take about a minute.') - # Get the frame information for the test - configuration = self.configGui_features.getConfig_features() - frame1 = int(configuration["frame1"]) - nframes = int(configuration["nframes"]) - fps = float(configuration["video-fps"]) + def test_feature_callback(self): + # Emitting the signal will call get_feature_video on the main thread + self.test_feature_callback_signal.emit() - subprocess.call(["feature-based-tracking", tracking_path, "--tf", "--database-filename", db_path]) - subprocess.call(["display-trajectories.py", "-i", ac.CURRENT_PROJECT_VIDEO_PATH, "-d", db_path, "-o", ac.CURRENT_PROJECT_PATH + "/homography/homography.txt", "-t", "feature", "--save-images", "-f", str(frame1), "--last-frame", str(frame1+nframes)]) + def get_feature_video(self): + project_path = get_project_path() + api.getTestConfig('feature', get_identifier(), project_path) - self.move_images_to_project_dir_folder(images_folder) + images_prefix = 'feature_images-' + extension = 'png' + images_folder = os.path.join(project_path, 'feature_video', 'images') + video_path = os.path.join(project_path, 'feature_video', 'feature_video.mp4') - self.feature_tracking_video_player.loadFrames(os.path.join(ac.CURRENT_PROJECT_PATH, images_folder), fps) + convert_video_to_frames(video_path, images_folder, prefix=images_prefix, extension=extension) + + video = cv2.VideoCapture(video_path) + fps = video.get(cv2.cv.CV_CAP_PROP_FPS) + self.feature_tracking_video_player.loadFrames(images_folder, fps, prefix=images_prefix, extension=extension) def test_object(self): - tracking_path = os.path.join(ac.CURRENT_PROJECT_PATH, ".temp", "test", "test_object", "object_tracking.cfg") - obj_db_path = os.path.join(ac.CURRENT_PROJECT_PATH,".temp", "test", "test_object", "test1.sqlite") - feat_db_path = os.path.join(ac.CURRENT_PROJECT_PATH, ".temp", "test", "test_feature", "test1.sqlite") - if os.path.exists(obj_db_path): - os.remove(obj_db_path) - shutil.copyfile(feat_db_path, obj_db_path) - - images_folder = "object_images" - self.delete_images(images_folder) - - # Get the frame information for the test - configuration = self.configGui_object.getConfig_objects() - frame1 = int(configuration["frame1"]) - nframes = int(configuration["nframes"]) - fps = float(configuration["video-fps"]) - - subprocess.call(["feature-based-tracking",tracking_path,"--gf","--database-filename",obj_db_path]) - subprocess.call(["classify-objects.py", "--cfg", tracking_path, "-d", obj_db_path]) # Classify road users - subprocess.call(["display-trajectories.py", "-i", ac.CURRENT_PROJECT_VIDEO_PATH,"-d", obj_db_path, "-o", ac.CURRENT_PROJECT_PATH + "/homography/homography.txt", "-t", "object", "--save-images", "-f", str(frame1), "--last-frame", str(frame1+nframes)]) - - self.move_images_to_project_dir_folder(images_folder) - - self.roadusers_tracking_video_player.loadFrames(os.path.join(ac.CURRENT_PROJECT_PATH, images_folder), fps) - - def delete_images(self, folder): - images_folder = os.path.join(ac.CURRENT_PROJECT_PATH, folder) - if os.path.exists(images_folder): - for file in os.listdir(images_folder): - if file[0:6] == 'image-' and file[-4:] == '.png': - os.remove(os.path.join(images_folder, file)) - for file in os.listdir(os.getcwd()): - if file[0:6] == 'image-' and file[-4:] == '.png': - os.remove(os.path.join(os.getcwd(), file)) - - def move_images_to_project_dir_folder(self, folder): - images_folder = os.path.join(ac.CURRENT_PROJECT_PATH, folder) - if not os.path.exists(images_folder): - os.makedirs(images_folder) - for file in os.listdir(os.getcwd()): - if file[0:6] == 'image-' and file[-4:] == '.png': - os.rename(file, os.path.join(images_folder, file)) - - def images_exist(self, folder): - images_folder = os.path.join(ac.CURRENT_PROJECT_PATH, folder) - if os.path.exists(images_folder): - for file in os.listdir(images_folder): - if file[0:6] == 'image-' and file[-4:] == '.png': - return True - return False - -# for the run button - def run(self): + frame_start = get_config_with_sections(get_config_path(), "config", "frame_start") + num_frames = get_config_with_sections(get_config_path(), "config", "num_frames") + api.testConfig('object',\ + get_identifier(),\ + frame_start = frame_start,\ + num_frames = num_frames) + StatusPoller(get_identifier(), 'object_test', 5, self.test_object_callback).start() + + self.show_message('Your test of object tracking has begun. When it has completed, a video will be shown in the window on the left. Please wait, this will only take about a minute.') + + def test_object_callback(self): + # Emitting the signal will call get_object_video on the main thread + self.test_object_callback_signal.emit() + + def get_object_video(self): + project_path = get_project_path() + api.getTestConfig('object', get_identifier(), project_path) + + images_prefix = 'object_images-' + extension = 'png' + images_folder = os.path.join(project_path, 'object_video', 'images') + video_path = os.path.join(project_path, 'object_video', 'object_video.mp4') + + convert_video_to_frames(video_path, images_folder, prefix=images_prefix, extension=extension) + + video = cv2.VideoCapture(video_path) + fps = video.get(cv2.cv.CV_CAP_PROP_FPS) + self.roadusers_tracking_video_player.loadFrames(images_folder, fps, prefix=images_prefix, extension=extension) + + # for the runAnalysis button + def runAnalysis(self): """ Runs TrafficIntelligence trackers and support scripts. """ - # create test folder - self.api.upload() + email = get_config_with_sections(get_config_path(), 'info', 'email') + api.analysis(get_identifier(), email=email) - return + StatusPoller(get_identifier(), 'safety_analysis', 15, self.analysisCallback).start() - i = raw_input("") + self.show_message('Object tracking and safety analysis is now running. This will take a few minutes. After it is done, creating a safety report will run, which will take some additional time. \n\nPlease keep the application open during analysis. If it is closed, a safety report will not be generated.\n\nIf you entered an email on the first screen, you will be notified when each step has been completed.') - if not os.path.exists(ac.CURRENT_PROJECT_PATH + "/run"): - os.mkdir(ac.CURRENT_PROJECT_PATH + "/run") + def analysisCallback(self): + # Emitting this signal will call self.runResults on the main thread + self.analysis_callback_signal.emit() - # removes object tracking.cfg - if os.path.exists(ac.CURRENT_PROJECT_PATH + "/run/run_tracking.cfg"): - os.remove(ac.CURRENT_PROJECT_PATH + "/run/run_tracking.cfg") + def runResults(self): + """Runs server methods that generate safety metric results and visualizations""" + identifier = get_identifier() + ttc_threshold = self.ui.timeToCollisionLineEdit.text() + vehicle_only = self.ui.vehiclesOnlyCheckBox.isChecked() + speed_limit = self.ui.speedLimitLineEdit.text() + api.results(identifier, ttc_threshold, vehicle_only, speed_limit) - # creates new config file - shutil.copyfile(ac.CURRENT_PROJECT_PATH + "/.temp/test/test_object/object_tracking.cfg", ac.CURRENT_PROJECT_PATH + "/run/run_tracking.cfg") + StatusPoller(identifier, 'highlight_video', 15, self.resultsCallback).start() - path1 = ac.CURRENT_PROJECT_PATH + "/run/run_tracking.cfg" + self.show_message('Creating a safety report now. This will take around five minutes.\n\nPlease keep the application open during this. If you close the application, your results will not be automatically downloaded') + + def resultsCallback(self): + # Emitting this signal will call self.runResults on the main thread + self.results_callback_signal.emit() + + def retrieveResults(self): + api.retrieveResults(get_identifier(), get_project_path()) + + results_dir = os.path.join(get_project_path(), "results") + + with zipfile.ZipFile(os.path.join(results_dir, "results.zip"), "r") as zip_file: + zip_file.extractall(results_dir) + + self.show_message('Results have been retrieved! This program will now open the folder containing the results.') + + # Open the file location + if sys.platform == 'darwin': + subprocess.Popen(['open', '--', results_dir]) + elif sys.platform == 'linux2': + subprocess.Popen(['xdg-open', '--', results_dir]) + elif sys.platform == 'win32': + subprocess.Popen(['explorer', results_dir]) - f = open(path1, 'r') - lines = f.readlines() - f.close() - with open(path1, "w") as wf: - for line in lines: - line_param = line.split('=')[0].strip() - if "frame1" == line_param: # Replace parameter "frame1" - wf.write("frame1 = 0\n") - elif "nframes" == line_param: # Remove parameter "nframes" - wf.write("nframes = 0\n") - elif "database-filename" == line_param: - wf.write("database-filename = results.sqlite\n") - else: - wf.write(line) - - db_path = os.path.join(ac.CURRENT_PROJECT_PATH, "run", "results.sqlite") - tracking_path = os.path.join(ac.CURRENT_PROJECT_PATH, "run", "run_tracking.cfg") - - if os.path.exists(db_path): # If results database already exists, - os.remove(db_path) # then remove it--it'll be recreated. - subprocess.call(["feature-based-tracking", tracking_path, "--tf", "--database-filename", db_path]) - subprocess.call(["feature-based-tracking", tracking_path, "--gf", "--database-filename", db_path]) - - subprocess.call(["classify-objects.py", "--cfg", tracking_path, "-d", db_path]) # Classify road users - - db_make_objtraj(db_path) # Make our object_trajectories db table - - self.create_video() - - def create_video(self): - count = 0 - num_frames_per_vid = 60 - images_folder = os.path.join(ac.CURRENT_PROJECT_PATH, "final_images") - videos_folder = os.path.join(ac.CURRENT_PROJECT_PATH, "final_videos") - - # Make the videos and images folder if it doesn't exists - if not os.path.exists(videos_folder): - os.makedirs(videos_folder) - if not os.path.exists(images_folder): - os.makedirs(images_folder) - db_path = os.path.join(ac.CURRENT_PROJECT_PATH, "run", "results.sqlite") - self.delete_videos("final_videos") - - while True: - # Delete old images, and recreate them in the right place - self.delete_images(images_folder) - subprocess.call(["display-trajectories.py", "-i", ac.CURRENT_PROJECT_VIDEO_PATH,"-d", db_path, "-o", ac.CURRENT_PROJECT_PATH + "/homography/homography.txt", "-t", "object", "--save-images", "-f", str(count*num_frames_per_vid), "--last-frame", str((count + 1)*num_frames_per_vid - 1)]) - self.move_images_to_project_dir_folder(images_folder) - - # If we got to the end of the video, break - if not self.images_exist(images_folder): - print 'No more images' - break - - # Get the frames, and create a short video out of them - self.renumber_frames(images_folder, count*num_frames_per_vid) - self.convert_frames_to_video(images_folder, videos_folder, "video-"+str(count)+".mp4") - - count += 1 - - self.combine_videos(videos_folder, "final_videos") - - def renumber_frames(self, folder, frame): - images_folder = os.path.join(ac.CURRENT_PROJECT_PATH, folder) - - # Rename them all to 'new-image-x' in order to not interfere with the current 'image-x' - for file in os.listdir(images_folder): - if file[0:6] == 'image-' and file[-4:] == '.png': - number = file[6:-4] - new_number = int(number) - frame - new_file = 'new-image-'+str(new_number)+'.png' - os.rename(os.path.join(images_folder, file), os.path.join(images_folder, new_file)) - - # Rename the 'new-image-x' to 'image-x' - for file in os.listdir(images_folder): - if file[0:10] == 'new-image-' and file[-4:] == '.png': - new_file = file[4:] - os.rename(os.path.join(images_folder, file), os.path.join(images_folder, new_file)) - - def convert_frames_to_video(self, images_folder, videos_folder, filename): - subprocess.call(["ffmpeg", "-framerate", "30", "-i", os.path.join(images_folder, "image-%d.png"), "-c:v", "libx264", "-pix_fmt", "yuv420p", os.path.join(videos_folder, filename)]) - - def delete_videos(self, folder): - videos_folder = os.path.join(ac.CURRENT_PROJECT_PATH, folder) - file_extensions = ['.mp4', '.mpg'] - - for extension in file_extensions: - if os.path.exists(videos_folder): - for file in os.listdir(videos_folder): - if file[0:6] == 'video-' and file[-4:] == extension: - os.remove(os.path.join(videos_folder, file)) - if file == 'output' + extension: - os.remove(os.path.join(videos_folder, file)) - for file in os.listdir(os.getcwd()): - if file[0:6] == 'video-' and file[-4:] == extension: - os.remove(os.path.join(os.getcwd(), file)) - if file == 'output' + extension: - os.remove(os.path.join(videos_folder, file)) - - def move_videos_to_folder(self, folder): - videos_folder = os.path.join(ac.CURRENT_PROJECT_PATH, folder) - if not os.path.exists(videos_folder): - os.makedirs(videos_folder) - for file in os.listdir(os.getcwd()): - if file[0:6] == 'video-' and file[-4:] == '.mp4': - os.rename(file, os.path.join(videos_folder, file)) - - def combine_videos(self, folder, filename): - # The only way I could find to join videos was to convert the videos to .mpg format, and then join them. - # This seems to be the only way to keep ffmpeg happy. - videos_folder = os.path.join(ac.CURRENT_PROJECT_PATH, folder) - self.convert_to_mpeg(videos_folder) - - # Using Popen seems to be necessary in order to pipe the output of one into the other - p1 = subprocess.Popen(['cat']+self.get_videos(videos_folder), stdout=subprocess.PIPE) - p2 = subprocess.Popen(['ffmpeg', '-f', 'mpeg', '-i', '-', '-qscale', '0', '-vcodec', 'mpeg4', os.path.join(videos_folder, 'output.mp4')], stdin=p1.stdout, stdout=subprocess.PIPE) - p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits. - - def convert_to_mpeg(self, folder): - videos_folder = os.path.join(ac.CURRENT_PROJECT_PATH, folder) - count = 0 - - while os.path.exists(os.path.join(videos_folder, "video-"+str(count)+".mp4")): - subprocess.call(['ffmpeg', '-i', os.path.join(videos_folder, 'video-'+str(count)+'.mp4'), '-qscale', '0', os.path.join(folder, "video-"+str(count)+".mpg")]) - count += 1 - - def get_videos(self, folder): - videos_folder = os.path.join(ac.CURRENT_PROJECT_PATH, folder) - count = 0 - videos = [] - - while os.path.exists(os.path.join(videos_folder, "video-"+str(count)+".mpg")): - videos.append(os.path.join(videos_folder, "video-"+str(count)+".mpg")) - count += 1 - - return videos ################################################################################################ - def homography_load_aerial_image(self): - pass def show_next_tab(self): curr_i = self.ui.main_tab_widget.currentIndex() new_i = curr_i + 1 self.ui.main_tab_widget.setCurrentIndex(new_i) - if new_i is 3: # If we are moving to the plots page - qt_plot.plot_results(self) def show_prev_tab(self): curr_i = self.ui.main_tab_widget.currentIndex() self.ui.main_tab_widget.setCurrentIndex(curr_i - 1) - def results_plot_plot1(self): - data = [random.random() for i in range(10)] - # create an axis - ax = self.figure1.add_subplot(111) - # discards the old graph - ax.hold(False) - # plot data - ax.plot(data, '*-') - # refresh canvas - self.canvas1.draw() - - def results_plot_plot2(self): - data1 = [random.random() for i in range(50)] - # create an axis - ax = self.figure2.add_subplot(111) - # discards the old graph - ax.hold(False) - # plot data - ax.plot(data1, '*-') - # refresh canvas - self.canvas2.draw() + def show_message(self, message): + helper = message_helper.MessageHelper(self) + helper.show_message(message) def open_project(self): - fname = str(QtGui.QFileDialog.getExistingDirectory(self, "Open Existing Project Folder...", ac.PROJECT_DIR)) + fname = str(QtGui.QFileDialog.getExistingDirectory(self, "Open Existing Project Folder...", get_base_project_dir())) # TODO: Instead of select folder, perhaps select config file? if fname: - pm.load_project(fname, self) + project_name = os.path.basename(fname) + pm.load_project(project_name, self) else: pass # If no folder selected, don't load anything. + def open_feedback(self): + url = QtCore.QUrl('https://docs.google.com/forms/d/e/1FAIpQLSeTRwZlMUwNrbv9Nw-BddsOBGrCQjR5YXHbloPirRzB3-QoFA/viewform') + if not QtGui.QDesktopServices.openUrl(url): + QtGui.QMessageBox.warning(self, 'Connecting to Feedback', 'Could not open feedback form') + def create_new_project(self): self.newp.restart() self.newp.show() @@ -457,9 +322,18 @@ def homography_compute(self): #TODO: display error message if points are < 4 px_text = self.ui.unit_px_input.text() self.unitPixRatio = float(unicode(px_text)) + + homography_path = os.path.join(get_project_path(), "homography") + api.configHomography(\ + get_identifier(),\ + self.unitPixRatio,\ + self.ui.homography_aerialview.list_points(),\ + self.ui.homography_cameraview.list_points()) + self.unscaled_world_pts = (np.array(self.ui.homography_aerialview.list_points())) self.worldPts = self.unitPixRatio * self.unscaled_world_pts self.videoPts = np.array(self.ui.homography_cameraview.list_points()) + if len(self.worldPts) >= 4: if len(self.worldPts) == len(self.videoPts): self.homography, self.mask = cv2.findHomography(self.videoPts, self.worldPts) @@ -474,7 +348,7 @@ def homography_compute(self): else: error = QtGui.QErrorMessage() error.showMessage('''\ - To compute the homography, please choose at least 4 points on + To compute the homography, please choose at least 4 points on each image.''') error.exec_() return @@ -482,9 +356,8 @@ def homography_compute(self): if self.homography is None: return - pm.update_project_cfg("homography", "unitpixelratio", str(self - .unitPixRatio)) - homography_path = os.path.join(ac.CURRENT_PROJECT_PATH, "homography") + update_config_with_sections(get_config_path(), "homography", "unitpixelratio", str(self.unitPixRatio)) + homography_path = os.path.join(get_project_path(), "homography") if self.homography.size > 0: txt_path = os.path.join(homography_path, "homography.txt") @@ -505,25 +378,27 @@ def homography_compute(self): self.homography_display_results() def homography_display_results(self): - homography_path = os.path.join(ac.CURRENT_PROJECT_PATH, "homography") + cvBlue = (0,0,255) + cvRed = (255,0,0) + homography_path = os.path.join(get_project_path(), "homography") worldImg = cv2.imread(os.path.join(homography_path, "aerial.png")) videoImg = cv2.imread(os.path.join(homography_path, "camera.png")) invHomography = np.linalg.inv(self.homography) - projectedWorldPts = cvutils.projectArray(invHomography, self.worldPts.T).T - projectedVideoPts = cvutils.projectArray(self.homography, self.videoPts.T).T + projectedWorldPts = projectArray(invHomography, self.worldPts.T).T + projectedVideoPts = projectArray(self.homography, self.videoPts.T).T # TODO: Nicer formatting for computed goodness images for i in range(self.worldPts.shape[0]): # world image - cv2.circle(worldImg, tuple(np.int32(np.round(self.worldPts[i] / self.unitPixRatio))), 2, cvutils.cvBlue) - cv2.circle(worldImg, tuple(np.int32(np.round(projectedVideoPts[i] / self.unitPixRatio))), 2, cvutils.cvRed) - cv2.putText(worldImg, str(i+1), tuple(np.int32(np.round(self.worldPts[i]/self.unitPixRatio)) + 5), cv2.FONT_HERSHEY_PLAIN, 2., cvutils.cvBlue, 2) + cv2.circle(worldImg, tuple(np.int32(np.round(self.worldPts[i] / self.unitPixRatio))), 2, cvBlue) + cv2.circle(worldImg, tuple(np.int32(np.round(projectedVideoPts[i] / self.unitPixRatio))), 2, cvRed) + cv2.putText(worldImg, str(i+1), tuple(np.int32(np.round(self.worldPts[i]/self.unitPixRatio)) + 5), cv2.FONT_HERSHEY_PLAIN, 2., cvBlue, 2) # video image - cv2.circle(videoImg, tuple(np.int32(np.round(self.videoPts[i]))), 2, cvutils.cvBlue) - cv2.circle(videoImg, tuple(np.int32(np.round(projectedWorldPts[i]))), 2, cvutils.cvRed) - cv2.putText(videoImg, str(i+1), tuple(np.int32(np.round(self.videoPts[i]) + 5)), cv2.FONT_HERSHEY_PLAIN, 2., cvutils.cvBlue, 2) + cv2.circle(videoImg, tuple(np.int32(np.round(self.videoPts[i]))), 2, cvBlue) + cv2.circle(videoImg, tuple(np.int32(np.round(projectedWorldPts[i]))), 2, cvRed) + cv2.putText(videoImg, str(i+1), tuple(np.int32(np.round(self.videoPts[i]) + 5)), cv2.FONT_HERSHEY_PLAIN, 2., cvBlue, 2) aerial_goodness_path = os.path.join(homography_path, "homography_goodness_aerial.png") camera_goodness_path = os.path.join(homography_path, "homography_goodness_camera.png") @@ -545,7 +420,7 @@ def initUI(self): self.btn = QtGui.QPushButton('Set Config', self) # self.btn.move(20, 20) - self.btn.clicked.connect(self.createConfig_features) + self.btn.clicked.connect(self.saveConfig_features) self.label1 = QtGui.QLabel("first frame to process") # input box @@ -580,6 +455,8 @@ def initUI(self): self.label10 = QtGui.QLabel("a feature for grouping") self.input10 = QtGui.QLineEdit() + self.loadConfig_features() + grid = QtGui.QGridLayout() grid.setSpacing(10) @@ -617,113 +494,78 @@ def initUI(self): self.setWindowTitle('Input config') # self.show() - # opens a cofig file - def openConfig(self): - - # path = QFileDialog.getOpenFileName(self, 'Open File', '/') - # global path1 - path1= str(path) - - # path1 = "../project_dir/test1" - - def createConfig_features(self, path): - """ - Create a config file - """ - - # global path1 - # path1= str(path) - - # update test1 name with file chose - - - - config = ConfigParser.ConfigParser() - - if not os.path.exists(ac.CURRENT_PROJECT_PATH + "/.temp/test"): - os.mkdir(ac.CURRENT_PROJECT_PATH + "/.temp/test") - - # create test folder - if not os.path.exists(ac.CURRENT_PROJECT_PATH + "/.temp/test/test_feature"): - os.mkdir(ac.CURRENT_PROJECT_PATH + "/.temp/test/test_feature") - - # removes feature_tracking.cfg - if os.path.exists(ac.CURRENT_PROJECT_PATH + "/.temp/test/test_feature/feature_tracking.cfg"): - os.remove(ac.CURRENT_PROJECT_PATH + "/.temp/test/test_feature/feature_tracking.cfg") - - # creates new config file - proj_tracking_path = os.path.join(ac.CURRENT_PROJECT_PATH, "tracking.cfg") - shutil.copyfile(proj_tracking_path, ac.CURRENT_PROJECT_PATH + "/.temp/test/test_feature/feature_tracking.cfg") - - path1 = ac.CURRENT_PROJECT_PATH + "/.temp/test/test_feature/feature_tracking.cfg" - - - # add new content to config file - config.add_section("added") - config.set("added", "video-filename",ac.CURRENT_PROJECT_VIDEO_PATH) - config.set("added", "homography-filename",ac.CURRENT_PROJECT_PATH + "/homography/homography.txt") - config.set("added", "frame1", self.input1.text()) - config.set("added", "nframes", self.input2.text()) - config.set("added", "max-nfeatures", self.input3.text()) - config.set("added", "ndisplacements", self.input5.text()) - config.set("added", "min-feature-displacement", self.input6.text()) - config.set("added", "max-number-iterations", self.input8.text()) - config.set("added", "min-feature-time", self.input10.text()) - - try: - path1 - except NameError: - # self.player.load(Phonon.MediaSource("")) - error = QtGui.QErrorMessage() - error.showMessage('''\ - no config files chosen''') - error.exec_() - print "no config chosen" - else: - with open(path1, "a") as config_file: - config.write(config_file) - - # to remove the section header from config file - - # opens the file to read - f = open(path1, "r") - lines = f.readlines() - f.close() - # opens the file to write - f = open(path1, "w") - for line in lines: - # removes the section header - if line != "[added]"+"\n": - f.write(line) - f.close() - - def getConfig_features(self): + def saveConfig_features(self): """ - Gets the features configuration file and returns the data as a dictionary. + Save configuration """ - path = os.path.join(ac.CURRENT_PROJECT_PATH, ".temp/test/test_feature/feature_tracking.cfg") - - f = open(path, "r") - lines = f.readlines() - f.close() - - final_dict = {} - for line in lines: - line = line.strip() - - # If it's a comment, ignore it - if len(line) > 0 and line[0] == '#': - continue - - arr = line.split(' = ') - - # Protect against things that aren't in "this = that" format - if len(arr) != 2: - continue - - final_dict[arr[0]] = arr[1] - - return final_dict + config_path = get_config_path() + + frame_start = str(self.input1.text()) + if frame_start != "": + update_config_with_sections(config_path, "config", "frame_start", frame_start) + + num_frames = str(self.input2.text()) + if num_frames != "": + update_config_with_sections(config_path, "config", "num_frames", num_frames) + + max_features_per_frame = str(self.input3.text()) + if max_features_per_frame != "": + update_config_with_sections(config_path, "config", "max_features_per_frame", max_features_per_frame) + else: max_features_per_frame = None + + num_displacement_frames = str(self.input5.text()) + if num_displacement_frames != "": + update_config_with_sections(config_path, "config", "num_displacement_frames", num_displacement_frames) + else: num_displacement_frames = None + + min_feature_displacement = str(self.input6.text()) + if min_feature_displacement != "": + update_config_with_sections(config_path, "config", "min_feature_displacement", min_feature_displacement) + else: min_feature_displacement = None + + max_iterations_to_persist = str(self.input8.text()) + if max_iterations_to_persist != "": + update_config_with_sections(config_path, "config", "max_iterations_to_persist", max_iterations_to_persist) + else: max_iterations_to_persist = None + + min_feature_frames = str(self.input10.text()) + if min_feature_frames != "": + update_config_with_sections(config_path, "config", "min_feature_frames", min_feature_frames) + else: min_feature_frames = None + + + api.configFiles(get_identifier(),\ + max_features_per_frame = max_features_per_frame,\ + num_displacement_frames = num_displacement_frames,\ + min_feature_displacement = min_feature_displacement,\ + max_iterations_to_persist = max_iterations_to_persist,\ + min_feature_frames = min_feature_frames) + + def loadConfig_features(self): + config_path = get_config_path() + + frame_start = get_config_with_sections(config_path, "config", "frame_start") + num_frames = get_config_with_sections(config_path, "config", "num_frames") + max_features_per_frame = get_config_with_sections(config_path, "config", "max_features_per_frame") + num_displacement_frames = get_config_with_sections(config_path, "config", "num_displacement_frames") + min_feature_displacement = get_config_with_sections(config_path, "config", "min_feature_displacement") + max_iterations_to_persist = get_config_with_sections(config_path, "config", "max_iterations_to_persist") + min_feature_frames = get_config_with_sections(config_path, "config", "min_feature_frames") + + if frame_start != None: + self.input1.setText(frame_start) + if num_frames != None: + self.input2.setText(num_frames) + if max_features_per_frame != None: + self.input3.setText(max_features_per_frame) + if num_displacement_frames != None: + self.input5.setText(num_displacement_frames) + if min_feature_displacement != None: + self.input6.setText(min_feature_displacement) + if max_iterations_to_persist != None: + self.input8.setText(max_iterations_to_persist) + if min_feature_frames != None: + self.input10.setText(min_feature_frames) ########################################################################################################################## @@ -739,7 +581,7 @@ def initUI(self): self.btn = QtGui.QPushButton('Set Config', self) # self.btn.move(20, 20) - self.btn.clicked.connect(self.createConfig_objects) + self.btn.clicked.connect(self.saveConfig_objects) self.label1 = QtGui.QLabel("first frame to process") @@ -758,6 +600,8 @@ def initUI(self): self.label4 = QtGui.QLabel("maximum segmentation-distance") self.input4 = QtGui.QLineEdit() + self.loadConfig_objects() + grid = QtGui.QGridLayout() grid.setSpacing(10) @@ -782,103 +626,72 @@ def initUI(self): self.setWindowTitle('Input config') # self.show() - # opens a config file - def openConfig(self): - path = QFileDialog.getOpenFileName(self, 'Open File', '/') - # global path1 - path1 = str(path) - - def createConfig_objects(self, path): - """ - Create a config file - """ - config = ConfigParser.ConfigParser() - object_folder = os.path.join(ac.CURRENT_PROJECT_PATH, ".temp", "test", "test_object") - feature_cfg = os.path.join(ac.CURRENT_PROJECT_PATH, ".temp", "test", "test_feature", "feature_tracking.cfg") - object_cfg = os.path.join(object_folder, "object_tracking.cfg") - - # create test folder - if not os.path.exists(object_folder): - os.mkdir(object_folder) - - # removes object tracking.cfg - if os.path.exists(object_cfg): - os.remove(object_cfg) - - # creates new config file - shutil.copyfile(feature_cfg, object_cfg) - - with open(object_cfg, 'r') as rf: - lines = rf.readlines() - - with open(object_cfg, 'w') as wf: - for line in lines: - line_param = line.split('=')[0].strip() - if "frame1" == line_param: # Remove parameter "frame1" - pass - elif "nframes" == line_param: # Remove parameter "nframes" - pass - else: - wf.write(line) - - # add new content to config file - - config.add_section("added") - config.set("added", "frame1", self.input1.text()) - config.set("added", "nframes", self.input2.text()) - config.set("added", "mm-connection-distance", self.input3.text()) - config.set("added", "mm-segmentation-distance", self.input4.text()) - - with open(object_cfg, "a") as config_file: - config.write(config_file) - - # to remove the section header from config file - - # opens the file to read - with open(object_cfg, "r") as f: - lines = f.readlines() - # opens the file to write - with open(object_cfg, 'w') as wf: - for line in lines: - # removes the section header - if line != "[added]\n": - wf.write(line) - - def getConfig_objects(self): + def saveConfig_objects(self): """ - Gets the features configuration file and returns the data as a dictionary. + Save configuration """ - path = os.path.join(ac.CURRENT_PROJECT_PATH, ".temp/test/test_feature/feature_tracking.cfg") - - f = open(path, "r") - lines = f.readlines() - f.close() - - final_dict = {} - for line in lines: - line = line.strip() - - # If it's a comment, ignore it - if len(line) > 0 and line[0] == '#': - continue - - arr = line.split(' = ') - - # Protect against things that aren't in "this = that" format - if len(arr) != 2: - continue - - final_dict[arr[0]] = arr[1] - - return final_dict + config_path = get_config_path() + + frame_start = str(self.input1.text()) + if frame_start != "": + update_config_with_sections(config_path, "config", "frame_start", frame_start) + + num_frames = str(self.input2.text()) + if num_frames != "": + update_config_with_sections(config_path, "config", "num_frames", num_frames) + + max_connection_distance = str(self.input3.text()) + if max_connection_distance != "": + update_config_with_sections(config_path, "config", "max_connection_distance", max_connection_distance) + else: max_connection_distance = None + + max_segmentation_distance = str(self.input4.text()) + if max_segmentation_distance != "": + update_config_with_sections(config_path, "config", "max_segmentation_distance", max_segmentation_distance) + else: max_segmentation_distance = None + + api.configFiles(get_identifier(),\ + max_connection_distance = max_connection_distance,\ + max_segmentation_distance = max_segmentation_distance) + + def loadConfig_objects(self): + config_path = get_config_path() + + frame_start = get_config_with_sections(config_path, "config", "frame_start") + num_frames = get_config_with_sections(config_path, "config", "num_frames") + max_connection_distance = get_config_with_sections(config_path, "config", "max_connection_distance") + max_segmentation_distance = get_config_with_sections(config_path, "config", "max_segmentation_distance") + + if frame_start != None: + self.input1.setText(frame_start) + if num_frames != None: + self.input2.setText(num_frames) + if max_connection_distance != None: + self.input3.setText(max_connection_distance) + if max_segmentation_distance != None: + self.input4.setText(max_segmentation_distance) ########################################################################################################################## +def projectArray(homography, points): + '''Returns the coordinates of the projected points through homography + (format: array 2xN points) + ''' + if points.shape[0] != 2: + raise Exception('points of dimension {0} {1}'.format(points.shape[0], points.shape[1])) + + if (homography is not None) and homography.size>0: + #alternatively, on could use cv2.convertpointstohomogeneous and other conversion to/from homogeneous coordinates + augmentedPoints = np.append(points,[[1]*points.shape[1]], 0) + prod = np.dot(homography, augmentedPoints) + return prod[0:2]/prod[2] + else: + return points +########################################################################################################################## def main(): app.exec_() if __name__ == '__main__': - ac.load_application_config() app = QtGui.QApplication(sys.argv) ex = MainGUI() sys.exit(main()) diff --git a/application/app.spec b/application/app.spec new file mode 100644 index 0000000..9d26c20 --- /dev/null +++ b/application/app.spec @@ -0,0 +1,28 @@ +# -*- mode: python -*- + +block_cipher = None + + +a = Analysis(['app.py'], + pathex=['/Users/user/Documents/SantosGUI/application'], + binaries=[], + datas=[], + hiddenimports=['sklearn','sklearn.neighbors.typedefs'], + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher) +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + name='app', + debug=False, + strip=False, + upx=True, + console=True ) diff --git a/application/app_config.py b/application/app_config.py index 7692421..a31059e 100644 --- a/application/app_config.py +++ b/application/app_config.py @@ -4,80 +4,171 @@ class AppConfig(object): - PROJECT_DIR = None - CURRENT_PROJECT_PATH = None # Path to base of currently open project's folder + PROJECT_DIR = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "project_dir")) CURRENT_PROJECT_NAME = None - CURRENT_PROJECT_CONFIG = None # Path - CURRENT_PROJECT_VIDEO_PATH = None - def __init__(self): - super(AppConfig, self).__init__() +def get_base_project_dir(): + return AppConfig.PROJECT_DIR - @classmethod - def load_application_config(cls): - config_parser = SafeConfigParser() - config_parser.read(os.path.join(os.path.dirname(os.path.realpath(__file__)), ".application")) - cls.PROJECT_DIR = config_parser.get("info", "default_project_dir") +def get_project_path(): + if AppConfig.CURRENT_PROJECT_NAME: + return os.path.join(AppConfig.PROJECT_DIR, AppConfig.CURRENT_PROJECT_NAME) + return None - # TODO: class method for writing to application config file +def get_config_path(): + if AppConfig.CURRENT_PROJECT_NAME: + return os.path.join(AppConfig.PROJECT_DIR, AppConfig.CURRENT_PROJECT_NAME, "config.cfg") + return None -def update_project_cfg(section, option, value): +def get_identifier(): + config_path = get_config_path() + if config_path: + return get_config_with_sections(config_path, "info", "identifier") + return None + +def get_project_video_path(): + config_path = get_config_path() + if config_path: + video = get_config_with_sections(config_path, "video", "name") + if video: + return os.path.join(get_project_path(), video) + else: + print("ERR: project_video(): Couldn't get video") + return None + +def projects_exist(): + if not os.path.exists(get_base_project_dir()): + return False + return (len(os.listdir(get_base_project_dir())) > 0) + +def update_config_with_sections(config_path, section, option, value): """ Updates a single value in the current open project's configuration file. Writes nothing and returns -1 if no project currently open. Creates sections in the config file if they do not already exist. Args: + config_path (str): Path to the config file section (str): Name of the section to write new option-value pair to write. option (str): Name of the option to write/update. value (str): Value to write/update assocaited with the specified option. """ - if not AppConfig.CURRENT_PROJECT_CONFIG: + if not os.path.exists(config_path): + print("ERR [update_config_with_sections()]: File {} does not exist.".format(config_path)) return -1 cfp = SafeConfigParser() - cfp.read(AppConfig.CURRENT_PROJECT_CONFIG) + cfp.read(config_path) if section not in cfp.sections(): # If the given section does not exist, cfp.add_section(section) # then create it. cfp.set(section, option, value) # Set the option-value pair - with open(AppConfig.CURRENT_PROJECT_CONFIG, "wb") as cfg_file: + with open(config_path, "wb") as cfg_file: cfp.write(cfg_file) # Write changes -def check_project_cfg_option(section, option): +def get_config_with_sections(config_path, section, option): """ - Checks the currently open project's configuration file for the specified option - in the specified section. If it exists, this returns (True, ). If it does not - exist, this returns (False, None). + Checks the configuration file for the specified option + in the specified section. If it exists, this returns . If it does not + exist, this returns None. Args: + config_path (str): Path to the config file to check section (str): Name of the section to check for option. option (str): Name of the option check/return. """ + if config_path == None: + print("ERR [get_config_with_sections()]: config_path was None.") + return None + if not os.path.exists(config_path): + print("ERR [get_config_with_sections()]: File {} does not exist.".format(config_path)) + return None cfp = SafeConfigParser() - cfp.read(AppConfig.CURRENT_PROJECT_CONFIG) + cfp.read(config_path) try: value = cfp.get(section, option) except NoSectionError: - print("ERR [check_project_cfg_option()]: Section {} is not available in {}.".format(section, AppConfig.CURRENT_PROJECT_CONFIG)) - return (False, None) + return None except NoOptionError: - print("Option {} is not available in {}.".format(option, AppConfig.CURRENT_PROJECT_CONFIG)) - return (False, None) + return None + else: + return value + +def get_config_section(config_path, section): + """ + Checks the configuration file for the specified option + in the specified section. If it exists, this returns dict_of_data. If it does not + exist, this returns None. + + Args: + config_path (str): Path to the config file to check + section (str): Name of the section whose data should be returned. + """ + if not os.path.exists(config_path): + print("ERR [get_config_section()]: File {} does not exist.".format(config_path)) + return None + cfp = SafeConfigParser() + cfp.read(config_path) + try: + tuples = cfp.items(section) + except NoSectionError: + print("ERR [get_config_section()]: Section {} is not available in {}.".format(section, config_path)) + return None else: - return (True, value) + d = {} + for (key, value) in tuples: + d[key] = value + return d -def check_project_cfg_section(section): +def config_section_exists(config_path, section): """ Checks the currently open project's configuration file for the section. If it exists, this returns True. If it does not exist, this returns False. Args: + config_path (str): Path to the config file. section (str): Name of the section to check existance of. """ cfp = SafeConfigParser() - cfp.read(AppConfig.CURRENT_PROJECT_CONFIG) + cfp.read(config_path) if section in cfp.sections(): # If the given section exists, return True # then return True. else: # Otherwise, - return False # then return False \ No newline at end of file + return False # then return False + +def update_config_without_sections(config_path, update_dict): + """helper function to edit cfg files that look like tracking.cfg + + update_dict: i.e. {'nframes': 10, 'video-filename': 'video.avi'} + """ + unused_keys = update_dict.keys() + with open(config_path, 'r') as rf: + lines = rf.readlines() + with open(config_path, 'w') as wf: + for line in lines: + line_param = line.split('=')[0].strip() + if line_param in update_dict.keys(): + wf.write("{} = {}\n".format(line_param, update_dict[line_param])) + unused_keys.remove(line_param) + else: + wf.write(line) + for unused_key in unused_keys: + wf.write("{} = {}\n".format(unused_key, update_dict[unused_key])) + + +def get_config_without_sections(config_path): + """helper function to get params and their values of cfg files that look like tracking.cfg + + get_dict: params to their values, as a dictionary + """ + get_dict = {} + with open(config_path, 'r') as rf: + lines = rf.readlines() + for line in lines: + # if not a comment line + if line[0] != "#": + line_param = line.split('=')[0].strip() + line_value = line.split('=')[1].strip() + get_dict[line_param] = line_value + return get_dict + diff --git a/application/build_settings.py b/application/build_settings.py new file mode 100644 index 0000000..9d2f78f --- /dev/null +++ b/application/build_settings.py @@ -0,0 +1,174 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import biplist +import os.path + +# +# Example settings file for dmgbuild +# + +# Use like this: dmgbuild -s build_settings.py "Test Volume" test.dmg + +# You can actually use this file for your own application (not just TextEdit) +# by doing e.g. +# +# dmgbuild -s settings.py -D app=/path/to/My.app "My Application" MyApp.dmg + +# .. Useful stuff .............................................................. + +application = defines.get('app', 'app.py') +appname = os.path.basename(application) + +def icon_from_app(app_path): + plist_path = os.path.join(app_path, 'Contents', 'Info.plist') + plist = biplist.readPlist(plist_path) + icon_name = plist['CFBundleIconFile'] + icon_root,icon_ext = os.path.splitext(icon_name) + if not icon_ext: + icon_ext = '.icns' + icon_name = icon_root + icon_ext + return os.path.join(app_path, 'Contents', 'Resources', icon_name) + +# .. Basics .................................................................... + +# Uncomment to override the output filename +filename = 'test.dmg' + +# Uncomment to override the output volume name +# volume_name = 'Test' + +# Volume format (see hdiutil create -help) +format = defines.get('format', 'UDBZ') + +# Volume size (must be large enough for your files) +size = defines.get('size', '100M') + +# Files to include +files = [ application ] + +# Symlinks to create +symlinks = { 'Applications': '/Applications' } + +# Volume icon +# +# You can either define icon, in which case that icon file will be copied to the +# image, *or* you can define badge_icon, in which case the icon file you specify +# will be used to badge the system's Removable Disk icon +# +#icon = '/path/to/icon.icns' +# badge_icon = icon_from_app(application) + +# Where to put the icons +# icon_locations = { +# appname: (140, 120), +# 'Applications': (500, 120) +# } + +# .. Window configuration ...................................................... + +# Background +# +# This is a STRING containing any of the following: +# +# #3344ff - web-style RGB color +# #34f - web-style RGB color, short form (#34f == #3344ff) +# rgb(1,0,0) - RGB color, each value is between 0 and 1 +# hsl(120,1,.5) - HSL (hue saturation lightness) color +# hwb(300,0,0) - HWB (hue whiteness blackness) color +# cmyk(0,1,0,0) - CMYK color +# goldenrod - X11/SVG named color +# builtin-arrow - A simple built-in background with a blue arrow +# /foo/bar/baz.png - The path to an image file +# +# The hue component in hsl() and hwb() may include a unit; it defaults to +# degrees ('deg'), but also supports radians ('rad') and gradians ('grad' +# or 'gon'). +# +# Other color components may be expressed either in the range 0 to 1, or +# as percentages (e.g. 60% is equivalent to 0.6). +background = 'builtin-arrow' + +show_status_bar = False +show_tab_view = False +show_toolbar = False +show_pathbar = False +show_sidebar = False +sidebar_width = 180 + +# Window position in ((x, y), (w, h)) format +window_rect = ((100, 100), (640, 280)) + +# Select the default view; must be one of +# +# 'icon-view' +# 'list-view' +# 'column-view' +# 'coverflow' +# +default_view = 'icon-view' + +# General view configuration +show_icon_preview = False + +# Set these to True to force inclusion of icon/list view settings (otherwise +# we only include settings for the default view) +include_icon_view_settings = 'auto' +include_list_view_settings = 'auto' + +# .. Icon view configuration ................................................... + +arrange_by = None +grid_offset = (0, 0) +grid_spacing = 100 +scroll_position = (0, 0) +label_pos = 'bottom' # or 'right' +text_size = 16 +icon_size = 128 + +# .. List view configuration ................................................... + +# Column names are as follows: +# +# name +# date-modified +# date-created +# date-added +# date-last-opened +# size +# kind +# label +# version +# comments +# +list_icon_size = 16 +list_text_size = 12 +list_scroll_position = (0, 0) +list_sort_by = 'name' +list_use_relative_dates = True +list_calculate_all_sizes = False, +list_columns = ('name', 'date-modified', 'size', 'kind', 'date-added') +list_column_widths = { + 'name': 300, + 'date-modified': 181, + 'date-created': 181, + 'date-added': 181, + 'date-last-opened': 181, + 'size': 97, + 'kind': 115, + 'label': 100, + 'version': 75, + 'comments': 300, + } +list_column_sort_directions = { + 'name': 'ascending', + 'date-modified': 'descending', + 'date-created': 'descending', + 'date-added': 'descending', + 'date-last-opened': 'descending', + 'size': 'descending', + 'kind': 'ascending', + 'label': 'ascending', + 'version': 'ascending', + 'comments': 'ascending', + } \ No newline at end of file diff --git a/application/choose_project.py b/application/choose_project.py new file mode 100644 index 0000000..97f4d3c --- /dev/null +++ b/application/choose_project.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'choose_project.ui' +# +# Created by: PyQt4 UI code generator 4.11.4 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + +class Ui_choose_project(object): + def setupUi(self, choose_project): + choose_project.setObjectName(_fromUtf8("choose_project")) + choose_project.setEnabled(True) + choose_project.resize(400, 150) + choose_project.setContextMenuPolicy(QtCore.Qt.NoContextMenu) + self.gridLayout = QtGui.QGridLayout(choose_project) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.verticalLayout = QtGui.QVBoxLayout() + self.verticalLayout.setSizeConstraint(QtGui.QLayout.SetDefaultConstraint) + self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) + self.label = QtGui.QLabel(choose_project) + self.label.setEnabled(True) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) + self.label.setSizePolicy(sizePolicy) + self.label.setAutoFillBackground(False) + self.label.setAlignment(QtCore.Qt.AlignCenter) + self.label.setObjectName(_fromUtf8("label")) + self.verticalLayout.addWidget(self.label) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setSizeConstraint(QtGui.QLayout.SetDefaultConstraint) + self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) + self.create_new_project_button = QtGui.QPushButton(choose_project) + self.create_new_project_button.setObjectName(_fromUtf8("create_new_project_button")) + self.horizontalLayout.addWidget(self.create_new_project_button) + self.open_project_button = QtGui.QPushButton(choose_project) + self.open_project_button.setObjectName(_fromUtf8("open_project_button")) + self.horizontalLayout.addWidget(self.open_project_button) + self.verticalLayout.addLayout(self.horizontalLayout) + self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1) + + self.retranslateUi(choose_project) + QtCore.QMetaObject.connectSlotsByName(choose_project) + + def retranslateUi(self, choose_project): + choose_project.setWindowTitle(_translate("choose_project", "Choose Project", None)) + self.label.setText(_translate("choose_project", "Would you like to create a new project or open an existing one?", None)) + self.create_new_project_button.setText(_translate("choose_project", "Create New Project", None)) + self.open_project_button.setText(_translate("choose_project", "Open Project", None)) + diff --git a/application/choose_project.ui b/application/choose_project.ui new file mode 100644 index 0000000..0d5d5db --- /dev/null +++ b/application/choose_project.ui @@ -0,0 +1,77 @@ + + + choose_project + + + true + + + + 0 + 0 + 400 + 150 + + + + Qt::NoContextMenu + + + Choose Project + + + + + + QLayout::SetDefaultConstraint + + + + + true + + + + 0 + 0 + + + + false + + + Would you like to create a new project or open an existing one? + + + Qt::AlignCenter + + + + + + + QLayout::SetDefaultConstraint + + + + + Create New Project + + + + + + + Open Project + + + + + + + + + + + + diff --git a/application/cloud_api.py b/application/cloud_api.py index 105dcdc..dd3c853 100644 --- a/application/cloud_api.py +++ b/application/cloud_api.py @@ -3,23 +3,43 @@ import pickle import uuid import requests +from app_config import AppConfig as ac +from app_config import get_project_path +import json +from threading import Timer -def CloudWizard(ip_addr,*arg): - return TrafficCloud(ip_addr) -class TrafficCloud: - def __init__(self,ip_addr): +from pprint import pprint + +class CloudWizard: + def __init__(self, ip_addr, port=8088): #TODO: Needs to be done in sync with server # not a uuid creation #self._ids = self.readIds() #self.project_path = project_path #self._ids[self.project_path] = uuid.uuid4() #self._writeIds() - if ip_addr == 'localhost': - self.server_ip = '127.0.0.1' - else: - self.server_ip = ip_addr + self.set_url(ip_addr, port=port) + + def set_url(self, ip_addr, port=8088): + protocol = self.protocol_from_url_string(ip_addr) + if protocol == None: + protocol = 'http://' + + (addr, p) = self.ip_and_port_from_url_string(ip_addr) + if addr == 'localhost': + addr = '127.0.0.1' + + # Use port specified by string, otherwise fall back to default port + if p == None: + p = port + + self.server_addr = protocol + addr + ':{}/'.format(port) +############################################################################### +# ID Storage Functions +############################################################################### + def _initializeIds(self): with open('.IDdict','wb') as blank_dict_file: pickle.dump({},blank_dict_file) @@ -27,7 +47,7 @@ def _initializeIds(self): def readIds(self): if os.path.isfile('.IDdict'): - with open('.Iddict','rb') as dict_file: + with open('.IDdict','rb') as dict_file: ids = pickle.loads(dict_file.read()) return ids else: @@ -41,64 +61,375 @@ def _writeIds(self): else: return self._initializeIds() - def uploadFiles(self, types, paths, callback): - project_name = ac.CURRENT_PROJECT_PATH.strip('/').split('/')[-1] - homography_path = os.path.join(ac.CURRENT_PROJECT_PATH, "homography") - - video_extn = ac.CURRENT_PROJECT_VIDEO_PATH.split('.')[-1] - - with open(os.path.join(homography_path, "aerial.png"), 'rb') as hg_aerial,\ - open(os.path.join(homography_path, "camera.png"), 'rb') as hg_camera,\ - open(os.path.join(homography_path, "homography.txt"), 'rb') as hg_txt,\ - open(os.path.join(ac.CURRENT_PROJECT_PATH, project_name + ".cfg"), 'rb') as cfg_prname,\ - open(os.path.join(ac.CURRENT_PROJECT_PATH, "tracking.cfg"), 'rb') as cfg_track,\ - open(os.path.join(ac.CURRENT_PROJECT_PATH, ".temp/test/test_object/object_tracking.cfg"), 'rb') as test_obj,\ - open(os.path.join(ac.CURRENT_PROJECT_PATH, ".temp/test/test_feature/feature_tracking.cfg"), 'rb') as test_track,\ - open(ac.CURRENT_PROJECT_VIDEO_PATH, 'rb') as video: - - files = { - 'homography/aerial.png': hg_aerial, - 'homography/camera.png': hg_camera, - 'homography/homography.txt': hg_txt, - 'project_name.cfg': cfg_prname, - 'tracking.cfg': cfg_track, - '.temp/test/test_object/object_tracking.cfg': test_obj, - ".temp/test/test_feature/feature_tracking.cfg": test_track, - 'video.%s'%video_extn : video - } - r = requests.post("http://" + self.server_ip + '/upload',files = files) - print r.text; - - def testFeatureAnalysis(self, config, frames, callback): - raise NotImplementedError("testFeatureAnalysis not yet implemented") - - def testObjectAnalysis(self, config, frames, callback): - raise NotImplementedError("testFeatureAnalysis not yet implemented") - - def runTrajectoryAnalysis(self, config, callback): - raise NotImplementedError("runTrajectoryAnalysis not yet implemented") - - def runSafetyAnalysis(self, prediction, db, callback): - raise NotImplementedError("runSafetyAnalysis not yet implemented") - - def runVisualization(self, db, callback): - raise NotImplementedError("runVisualization not yet implemented") - - def getDB(self, callback): - raise NotImplementedError("getDB not yet implemented") - - def getStatus(self, callback): - raise NotImplementedError("getStatus not yet implemented") - - def generateDefaultConfig(self, callback): - raise NotImplementedError("generateDefaultConfig not yet implemented") - -#FOR TESTING PURPOSES ONLY -if __name__ == '__main__': - remote = CloudWizard('10.7.90.25') - local = CloudWizard('localhost') - #server = TrafficCloud_Server("/project/path/goes/ahere","localhost") - print remote.server_ip - print remote.readIds() - print local.server_ip - print local.readIds() \ No newline at end of file +############################################################################### +# Upload Functions +############################################################################### + + def uploadVideo(self, video_path, identifier = None): + print "uploadVideo called with identifier = {}".format(identifier) + with open(video_path, 'rb') as video: + files = {'video' : video} + payload = {'identifier': identifier} + r = requests.post(\ + self.server_addr + 'uploadVideo',\ + data = payload, files = files, stream = True) + print "Status Code: {}".format(r.status_code) + print "Response Text: {}".format(r.text) + print "Response JSON: {}".format(r.json()) + return r.json()['identifier'] + +############################################################################### +# Configuration Functions +############################################################################### + + def configHomography(self, + identifier,\ + up_ratio,\ + aerial_pts,\ + camera_pts): + payload = { + 'identifier': identifier, + 'unit_pixel_ratio': up_ratio, + 'aerial_pts': json.dumps(aerial_pts), + 'camera_pts': json.dumps(camera_pts) + } + + r = requests.post(\ + self.server_addr + 'configHomography', data = payload) + print "Status Code: {}".format(r.status_code) + print "Response Text: {}".format(r.text) + + def configFiles(self, identifier, + max_features_per_frame = None,\ + num_displacement_frames = None,\ + min_feature_displacement = None,\ + max_iterations_to_persist = None,\ + min_feature_frames = None,\ + max_connection_distance = None,\ + max_segmentation_distance = None): + + print "configFiles called with identifier = {}"\ + .format(identifier) + + payload = { + 'identifier': identifier, + 'max_features_per_frame': max_features_per_frame, + 'num_displacement_frames': num_displacement_frames, + 'min_feature_displacement': min_feature_displacement, + 'max_iterations_to_persist': max_iterations_to_persist, + 'min_feature_frames': min_feature_frames, + 'max_connection_distance': max_connection_distance, + 'max_segmentation_distance': max_segmentation_distance + } + print "config_data is as follows:" + pprint(payload) + + r = requests.post(self.server_addr + 'config', data = payload) + print "Status Code: {}".format(r.status_code) + print "Response Text: {}".format(r.text) + + def testConfig(self, test_flag, identifier, + frame_start = None,\ + num_frames = None): + print "testConfig called with identifier = {},\ + test_flag = {}, frame_start = {}, and num_frames = {}"\ + .format(identifier,test_flag,frame_start,num_frames) + + status_dict = self.getProjectStatus(identifier) + if status_dict["config_homography"] != 2: + print "Check your homography and upload (again)." + return + + payload = { + 'test_flag': test_flag, + 'identifier': identifier, + 'frame_start': frame_start, + 'num_frames': num_frames + } + + r = requests.post(self.server_addr + 'testConfig', data = payload) + print "Status Code: {}".format(r.status_code) + print "Response Text: {}".format(r.text) + + def getTestConfig(self, test_flag, identifier, project_path): + print "getTestConfig called with identifier = {} and test_flag = {}".format(identifier,test_flag) + + payload = { + 'test_flag': test_flag, + 'identifier': identifier + } + + if test_flag == 'feature': + if not os.path.exists(os.path.join(project_path, 'feature_video')): + os.mkdir(os.path.join(project_path, 'feature_video')) + path = os.path.join(project_path, 'feature_video', 'feature_video.mp4') + elif test_flag == 'object': + if not os.path.exists(os.path.join(project_path, 'object_video')): + os.mkdir(os.path.join(project_path, 'object_video')) + path = os.path.join(project_path, 'object_video', 'object_video.mp4') + else: + print "ERROR: Invalid flag" + return + + r = requests.get(self.server_addr + 'testConfig', data = payload, stream=True) + + with open(path, 'wb') as f: + print('Dumping "{0}"...'.format(path)) + for chunk in r.iter_content(chunk_size=2048): + if chunk: + f.write(chunk) + print "Status Code: {}".format(r.status_code) + + def defaultConfig(self): + r = requests.get(self.server_addr + 'defaultConfig') + return r.json() + +############################################################################### +# Analysis Functions +############################################################################### + + def analysis(self, identifier, email=None): + print "analysis called with identifier = {} and email = {}".format(identifier, email) + + status_dict = self.getProjectStatus(identifier) + if status_dict["config_homography"] != 2: + print "Check your homography and upload (again)." + return + + payload = { + 'identifier': identifier, + 'email': email + } + + r = requests.post(self.server_addr + 'analysis', data = payload) + print "Status Code: {}".format(r.status_code) + print "Response Text: {}".format(r.text) + + def objectTracking(self, identifier, email=None): + print "objectTracking called with identifier = {} and email = {}".format(identifier, email) + + status_dict = self.getProjectStatus(identifier) + if status_dict["config_homography"] != 2: + print "Check your homography and upload (again)." + return + + payload = { + 'identifier': identifier, + 'email': email + } + r = requests.post(self.server_addr + 'objectTracking', data = payload) + print "Status Code: {}".format(r.status_code) + print "Response Text: {}".format(r.text) + + def safetyAnalysis(self, identifier, email=None): + print "safetyAnalysis called with identifier = {} and email = {}".format(identifier, email) + + status_dict = self.getProjectStatus(identifier) + if status_dict["config_homography"] != 2: + print "Check your homography and upload (again)." + return + elif status_dict["object_tracking"] != 2: + print "Check object tracking and run (again)." + return + + payload = { + 'identifier': identifier, + 'email': email + } + + r = requests.post(self.server_addr + 'safetyAnalysis', data = payload) + print "Status Code: {}".format(r.status_code) + print "Response Text: {}".format(r.text) + +############################################################################### +# Status Checking Functions +############################################################################### + + def getProjectStatus(self, identifier): + + payload = { + 'identifier': identifier, + } + + r = requests.post(self.server_addr + 'status', data = payload) + status_dict = r.json() + status_dict = {k:int(v) for (k,v) in status_dict.iteritems()} + print "Status Code: {}".format(r.status_code) + print "Response Text: {}".format(r.text) + return status_dict + +############################################################################### +# Results Functions +############################################################################### + + def results(self, identifier, ttc_threshold = None, vehicle_only = None, speed_limit = None): + print "results called with identifier = {}, ttc_threshold = {}, vehicle_only = {}, and speed_limit= {}"\ + .format(identifier, ttc_threshold, vehicle_only, speed_limit) + + # sync calls + self.roadUserCounts(identifier) + self.speedDistribution(identifier, speed_limit, vehicle_only) + + self.makeReport(identifier) + + # async calls + self.highlightVideo(identifier, ttc_threshold, vehicle_only) + + def highlightVideo(self, identifier, ttc_threshold = None, vehicle_only = None): + print "highlightVideo called with identifier = {}, ttc_threshold = {} and vehicle_only = {}"\ + .format(identifier, ttc_threshold, vehicle_only) + + status_dict = self.getProjectStatus(identifier) + if status_dict["config_homography"] != 2: + print "Check your homography and upload (again)." + return + elif status_dict["object_tracking"] != 2: + print "Check object tracking and run (again)." + return + elif status_dict["safety_analysis"] != 2: + print "Check safety analysis and run (again)." + return + + payload = { + 'identifier': identifier, + 'ttc_threshold': ttc_threshold, + 'vehicle_only': vehicle_only + } + + r = requests.post(self.server_addr + 'highlightVideo', data = payload) + print "Status Code: {}".format(r.status_code) + print "Response Text: {}".format(r.text) + + def makeReport(self, identifier): + print "makeReport called with identifier = {}".format(identifier) + + payload = { + 'identifier': identifier, + } + + r = requests.post(self.server_addr + 'makeReport', data = payload) + print "Status Code: {}".format(r.status_code) + print "Response Text: {}".format(r.text) + + def retrieveResults(self, identifier, project_path): + print "retrieveResults called with identifier = {}".format(identifier) + + payload = { + 'identifier': identifier, + } + path = os.path.join(project_path, 'results', 'results.zip') + if os.path.exists(path): + os.remove(path) + r = requests.get(self.server_addr + 'retrieveResults', data = payload, stream=True) + with open(path, 'wb') as f: + print('Dumping "{0}"...'.format(path)) + for chunk in r.iter_content(chunk_size=2048): + if chunk: + f.write(chunk) + print "Status Code: {}".format(r.status_code) + + def roadUserCounts(self, identifier): + print "roadUserCounts called with identifier = {}".format(identifier) + + payload = { + 'identifier': identifier, + } + + r = requests.post(self.server_addr + 'roadUserCounts', data = payload) + print "Status Code: {}".format(r.status_code) + print "Response Text: {}".format(r.text) + + def speedDistribution(self, identifier, speed_limit = None, vehicle_only = None): + print "speedDistribution called with identifier = {}, speed_limit = {} and vehicle_only = {}"\ + .format(identifier, speed_limit, vehicle_only) + + payload = { + 'identifier': identifier, + 'speed_limit': speed_limit, + 'vehicle_only': vehicle_only + } + + r = requests.post(self.server_addr + 'speedDistribution', data = payload) + print "Status Code: {}".format(r.status_code) + print "Response Text: {}".format(r.text) + + + +############################################################################### +# Helper Methods +############################################################################### + + @classmethod + def ip_and_port_from_url_string(cls, url): + # Strip protocol if exists + protocol = cls.protocol_from_url_string(url) + if protocol: + url = url[len(protocol):] + + # Now we should only have IP:PORT + if ':' in url: + l = url.split(':') + return (l[0], l[1]) + else: + return (url, None) + + @classmethod + def protocol_from_url_string(cls, url): + protocols = ['http://', 'https://'] + for protocol in protocols: + if url.startswith(protocol): + return protocol + return None + +# Define singleton to be used everywhere +api = CloudWizard('localhost') + +############################################################################### +# Poll for Status with Callback +############################################################################### + +class StatusPoller(object): + def __init__(self, identifier, status_name, interval, callback): + self._timer = None + self.identifier = identifier + self.status_name = status_name + self.interval = interval + self.callback = callback + self.is_running = False + self.has_run = False + + def _run(self): + self.is_running = False + self.start() + + self._poll_for_status() + + def _poll_for_status(self): + status_dict = api.getProjectStatus(self.identifier) + + if self.status_name not in status_dict.keys(): + print(self.status_name + ' not in status dictionary') + elif status_dict[self.status_name] == 2: + self.stop() + self.callback() + elif status_dict[self.status_name] == 1: + print(self.status_name + ' is still running') + else: + print(self.status_name + ' is not running, not continuing to poll for status') + self.stop() + + def start(self): + if not self.is_running: + # If it's the first time, run it immediately + if not self.has_run: + self._timer = Timer(0, self._run) + self.has_run = True + else: + self._timer = Timer(self.interval, self._run) + self._timer.start() + self.is_running = True + + def stop(self): + self._timer.cancel() + self.is_running = False + diff --git a/application/custom/homography.py b/application/custom/homography.py index 0acb019..cf16701 100644 --- a/application/custom/homography.py +++ b/application/custom/homography.py @@ -24,10 +24,12 @@ def load_image(self, image): """ self.scene_image = image new_scene = HomographyScene(self) - pmap = new_scene.addPixmap(QtGui.QPixmap().fromImage(image)) - new_scene.register_pixmap(pmap) + pmap = QtGui.QPixmap().fromImage(image) + pmapitem = new_scene.addPixmap(pmap) + new_scene.register_pixmap(pmapitem) new_scene.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(0, 0, 0))) self.setScene(new_scene) + self.fitInView(0, 0, pmap.width(), pmap.height(), Qt.KeepAspectRatio) self.show() self.image_loaded = True @@ -76,9 +78,11 @@ def load_image(self, image): """ self.scene_image = image new_scene = QtGui.QGraphicsScene(self) - pmap = new_scene.addPixmap(QtGui.QPixmap().fromImage(image)) + pmap = QtGui.QPixmap().fromImage(image) + pmapitem = new_scene.addPixmap(pmap) new_scene.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(0, 0, 0))) self.setScene(new_scene) + self.fitInView(0, 0, pmap.width(), pmap.height(), Qt.KeepAspectRatio) self.show() self.image_loaded = True diff --git a/application/custom/video_frame_player.py b/application/custom/video_frame_player.py index e75d567..888c79b 100644 --- a/application/custom/video_frame_player.py +++ b/application/custom/video_frame_player.py @@ -23,6 +23,7 @@ def __init__(self, parent = None): QtCore.Qt.AlignVCenter) # Sets the image frames for this player to display + self.frame_rate = 30 self.max_frame_rate = 5.0 self.currentFrame = 0 self.frames = [] @@ -40,8 +41,8 @@ def __init__(self, parent = None): self.reconfigurePlayer() - def setImage(self, imageFilename): - scaledImage = QtGui.QPixmap(os.path.join(os.getcwd(), imageFilename)).scaled(self.label.size(), QtCore.Qt.KeepAspectRatio) + def setImage(self, image_path): + scaledImage = QtGui.QPixmap(image_path).scaled(self.label.size(), QtCore.Qt.KeepAspectRatio) self.label.setPixmap(scaledImage) def playPause(self): @@ -73,7 +74,7 @@ def stepForward(): # We have to use a signal to redraw so that the redraw happens on the main thread self.redraw_signal.emit() - threading.Thread(target=stepForward).start() + threading.Thread(target=stepForward).start() self.timer = event.set def updateImage(self): @@ -105,17 +106,31 @@ def gettingTime(self): endTime = (endMinutes*60+endSeconds) self.player.seek(startTime*1000) - def loadFrames(self, directory, frame_rate): + def loadFrames(self, directory, frame_rate, prefix='image-', extension='png'): ''' This function takes a directory that holds a set of images, named 'image-001.png', 'image-002.png', etc. and loads them into the frame player ''' self.frame_rate = frame_rate frames = [] - for file in os.listdir(directory): - if file[0:6] == 'image-' and file[-4:] == '.png': - frames.append(os.path.join(directory, file)) - self.frames = sorted(frames) + count = 0 + success = True + while success: + if '.' in extension: + format_string = "%d" + else: + format_string = "%d." + filename = prefix + format_string + extension + path = os.path.join(directory, filename % count) + + if os.path.exists(path): + frames.append(path) + else: + success = False + + count += 1 + + self.frames = frames self.reconfigurePlayer() def reconfigurePlayer(self): diff --git a/application/custom/zoomslider.py b/application/custom/zoomslider.py index 88b355b..195f07d 100644 --- a/application/custom/zoomslider.py +++ b/application/custom/zoomslider.py @@ -1,6 +1,7 @@ # zoomslider.py from PyQt4 import QtGui +slider_start = 50. class ZoomSlider(QtGui.QSlider): """Slider used for zooming a HomographyView or a HomographyResultView. @@ -15,13 +16,13 @@ def __init__(self, parent): def valueChanged_handler(self): if self.zoom_target is None: - self.setValue(100) # If no target set, do not manipulate slider. + self.setValue(slider_start) # If no target set, do not manipulate slider. return elif not self.zoom_target.image_loaded: - self.setValue(100) + self.setValue(slider_start) return slider_val = self.value() - desired_percentage = slider_val / 100. + desired_percentage = slider_val / slider_start scale = desired_percentage / self.zoom_percentage self.zoom_percentage = desired_percentage self.zoom_target.scale(scale, scale) diff --git a/application/displaytrajectories2.py b/application/displaytrajectories2.py deleted file mode 100644 index fa64294..0000000 --- a/application/displaytrajectories2.py +++ /dev/null @@ -1,139 +0,0 @@ -import sys, argparse - -import storage, cvutils, utils - -from numpy.linalg.linalg import inv -from numpy import loadtxt -import cv2 - -############################################################################ -from app_config import AppConfig as ac -################################################################ - -def makeTrajectories(): - - configFilename = ac.CURRENT_PROJECT_PATH + "/.temp/test/test_feature/feature_tracking.cfg" - - if configFilename: # consider there is a configuration file - params = storage.ProcessParameters(configFilename) - videoFilename = params.videoFilename - databaseFilename = params.databaseFilename - if params.homography is not None: - homography = inv(params.homography) - else: - homography = None - intrinsicCameraMatrix = params.intrinsicCameraMatrix - distortionCoefficients = params.distortionCoefficients - undistortedImageMultiplication = params.undistortedImageMultiplication - undistort = params.undistort - firstFrameNum = params.firstFrameNum - else: - homography = None - undistort = False - intrinsicCameraMatrix = None - distortionCoefficients = [] - undistortedImageMultiplication = None - firstFrameNum = 0 - - - - - objects = storage.loadTrajectoriesFromSqlite(databaseFilename, 'feature') - #objects = storage.loadTrajectoriesFromSqlite(ac.CURRENT_PROJECT_PATH + "/.temp/test/test_feature/test1.sqlite", 'feature') - - boundingBoxes = storage.loadBoundingBoxTableForDisplay(databaseFilename) - #boundingBoxes = storage.loadBoundingBoxTableForDisplay(ac.CURRENT_PROJECT_PATH + "/.temp/test/test_feature/test1.sqlite") - - # cvutils.displayTrajectories(videoFilename, objects, boundingBoxes, homography, firstFrameNum, args.lastFrameNum, rescale = args.rescale, nFramesStep = args.nFramesStep, saveAllImages = args.saveAllImages, undistort = (undistort or args.undistort), intrinsicCameraMatrix = intrinsicCameraMatrix, distortionCoefficients = distortionCoefficients, undistortedImageMultiplication = undistortedImageMultiplication) - makeVideo(ac.CURRENT_PROJECT_VIDEO_PATH, objects, ac.CURRENT_PROJECT_PATH + "/.temp/test/test_feature/feature_video.avi", boundingBoxes, homography) - - -def makeVideo(videoFilename, objects, outputVideoPath, boundingBoxes = {}, homography = None, firstFrameNum = 0, lastFrameNumArg = None, printFrames = True, rescale = 1., nFramesStep = 1, saveAllImages = False, undistort = False, intrinsicCameraMatrix = None, distortionCoefficients = None, undistortedImageMultiplication = 1., annotations = [], gtMatches = {}, toMatches = {},writeVideo=True): - '''Displays the objects overlaid frame by frame over the video ''' - from moving import userTypeNames - from math import ceil, log10 - - capture = cv2.VideoCapture(videoFilename) - width = int(capture.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH)) - height = int(capture.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT)) - fourcc = cv2.cv.CV_FOURCC('D','I','V','X') - outputVideo = cv2.VideoWriter(outputVideoPath,fourcc, 30.0, (width,height)) - #outputVideo = cv2.VideoWriter('~/Documents/laurier/testVideo.avi', cv2.cv.CV_FOURCC('H','2','6','4'),30,(704,384),True) - - windowName = 'frame' - if rescale == 1.: - cv2.namedWindow(windowName, cv2.WINDOW_NORMAL) - - if undistort: # setup undistortion - [map1, map2] = computeUndistortMaps(width, height, undistortedImageMultiplication, intrinsicCameraMatrix, distortionCoefficients) - if capture.isOpened(): - key = -1 - ret = True - frameNum = firstFrameNum - capture.set(cv2.cv.CV_CAP_PROP_POS_FRAMES, firstFrameNum) - if lastFrameNumArg is None: - from sys import maxint - lastFrameNum = maxint - else: - lastFrameNum = lastFrameNumArg - nZerosFilename = int(ceil(log10(lastFrameNum))) - objectToDeleteIds = [] - while ret and frameNum <= lastFrameNum: - ret, img = capture.read() - if ret: - if undistort: - img = cv2.remap(img, map1, map2, interpolation=cv2.INTER_LINEAR) - if printFrames: - print('frame {0}'.format(frameNum)) - if len(objectToDeleteIds) > 0: - objects = [o for o in objects if o.getNum() not in objectToDeleteIds] - objectToDeleteIds = [] - # plot objects - for obj in objects: - if obj.existsAtInstant(frameNum): - if obj.getLastInstant() == frameNum: - objectToDeleteIds.append(obj.getNum()) - if not hasattr(obj, 'projectedPositions'): - if homography is not None: - obj.projectedPositions = obj.positions.project(homography) - else: - obj.projectedPositions = obj.positions - cvPlot(img, obj.projectedPositions, cvColors[obj.getNum()], frameNum-obj.getFirstInstant()) - if frameNum not in boundingBoxes.keys() and obj.hasFeatures(): - imgcrop, yCropMin, yCropMax, xCropMin, xCropMax = imageBox(img, obj, frameNum, homography, width, height) - cv2.rectangle(img, (xCropMin, yCropMin), (xCropMax, yCropMax), cvBlue, 1) - objDescription = '{} '.format(obj.num) - if userTypeNames[obj.userType] != 'unknown': - objDescription += userTypeNames[obj.userType][0].upper() - if len(annotations) > 0: # if we loaded annotations, but there is no match - if frameNum not in toMatches[obj.getNum()]: - objDescription += " FA" - cv2.putText(img, objDescription, obj.projectedPositions[frameNum-obj.getFirstInstant()].asint().astuple(), cv2.cv.CV_FONT_HERSHEY_PLAIN, 1, cvColors[obj.getNum()]) - # plot object bounding boxes - if frameNum in boundingBoxes.keys(): - for rect in boundingBoxes[frameNum]: - cv2.rectangle(img, rect[0].asint().astuple(), rect[1].asint().astuple(), cvColors[obj.getNum()]) - # plot ground truth - if len(annotations) > 0: - for gt in annotations: - if gt.existsAtInstant(frameNum): - if frameNum in gtMatches[gt.getNum()]: - color = cvColors[gtMatches[gt.getNum()][frameNum]] # same color as object - else: - color = cvRed - cv2.putText(img, 'Miss', gt.topLeftPositions[frameNum-gt.getFirstInstant()].asint().astuple(), cv2.cv.CV_FONT_HERSHEY_PLAIN, 1, cvRed) - cv2.rectangle(img, gt.topLeftPositions[frameNum-gt.getFirstInstant()].asint().astuple(), gt.bottomRightPositions[frameNum-gt.getFirstInstant()].asint().astuple(), color) - # saving images and going to next - #if not saveAllImages: - #cvImshow(windowName, img, rescale) - #key = cv2.waitKey() - if saveAllImages: - cv2.imwrite('image-{{:0{}}}.png'.format(nZerosFilename).format(frameNum), img) - if writeVideo: - outputVideo.write(img) - frameNum += nFramesStep - if nFramesStep > 1: - capture.set(cv2.cv.CV_CAP_PROP_POS_FRAMES, frameNum) - cv2.destroyAllWindows() - else: - print 'Cannot load file ' + videoFilename \ No newline at end of file diff --git a/application/message_helper.py b/application/message_helper.py new file mode 100644 index 0000000..fd6c30a --- /dev/null +++ b/application/message_helper.py @@ -0,0 +1,24 @@ +""" +Project management classes and functions +""" + +from PyQt4 import QtGui, QtCore +from views.message_dialog import Ui_message_dialog + +class MessageHelper(QtGui.QDialog): + + def __init__(self, parent): + super(MessageHelper, self).__init__(parent) + self.ui = Ui_message_dialog() + self.ui.setupUi(self) + + self.ui.close_button.clicked.connect(self.close) + + # Remove '?' icon + flags = self.windowFlags() & (~QtCore.Qt.WindowContextHelpButtonHint) + self.setWindowFlags(flags) + + def show_message(self, message): + self.ui.label.setText(message) + self.show() + diff --git a/application/playVideo.py b/application/playVideo.py deleted file mode 100644 index 33187d7..0000000 --- a/application/playVideo.py +++ /dev/null @@ -1,161 +0,0 @@ -#! /usr/bin/env python - -import sys, argparse - -import storage, cvutils, utils - -from numpy.linalg.linalg import inv -from numpy import loadtxt -from app_config import AppConfig as ac -from cvutils import * - -parser = argparse.ArgumentParser(description='The program displays feature or object trajectories overlaid over the video frames.', epilog = 'Either the configuration filename or the other parameters (at least video and database filenames) need to be provided.') -parser.add_argument('--cfg', dest = 'configFilename', help = 'name of the configuration file') -parser.add_argument('-d', dest = 'databaseFilename', help = 'name of the Sqlite database file') -parser.add_argument('-i', dest = 'videoFilename', help = 'name of the video file') -parser.add_argument('-t', dest = 'trajectoryType', help = 'type of trajectories to display', choices = ['feature', 'object'], default = 'feature') -parser.add_argument('-o', dest = 'homographyFilename', help = 'name of the image to world homography file') -parser.add_argument('--intrinsic', dest = 'intrinsicCameraMatrixFilename', help = 'name of the intrinsic camera file') -parser.add_argument('--distortion-coefficients', dest = 'distortionCoefficients', help = 'distortion coefficients', nargs = '*', type = float) -parser.add_argument('--undistorted-multiplication', dest = 'undistortedImageMultiplication', help = 'undistorted image multiplication', type = float) -parser.add_argument('-u', dest = 'undistort', help = 'undistort the video (because features have been extracted that way)', action = 'store_true') -parser.add_argument('-f', dest = 'firstFrameNum', help = 'number of first frame number to display', type = int) -parser.add_argument('-r', dest = 'rescale', help = 'rescaling factor for the displayed image', default = 1., type = float) -parser.add_argument('-s', dest = 'nFramesStep', help = 'number of frames between each display', default = 1, type = int) -parser.add_argument('--save-images', dest = 'saveAllImages', help = 'save all images', action = 'store_true') -parser.add_argument('--last-frame', dest = 'lastFrameNum', help = 'number of last frame number to save (for image saving, no display is made)', type = int) -parser.add_argument('--output', dest = 'outputVideoPath', help='outputVideoPath') - -args = parser.parse_args() - -if args.configFilename: # consider there is a configuration file - params = storage.ProcessParameters(args.configFilename) - videoFilename = params.videoFilename - databaseFilename = params.databaseFilename - if params.homography is not None: - homography = inv(params.homography) - else: - homography = None - intrinsicCameraMatrix = params.intrinsicCameraMatrix - distortionCoefficients = params.distortionCoefficients - undistortedImageMultiplication = params.undistortedImageMultiplication - undistort = params.undistort - firstFrameNum = params.firstFrameNum - outputVideoPath = parser.outputVideoPath -else: - homography = None - undistort = False - intrinsicCameraMatrix = None - distortionCoefficients = [] - undistortedImageMultiplication = None - firstFrameNum = 0 - -if not args.configFilename and args.videoFilename is not None: - videoFilename = args.videoFilename -if not args.configFilename and args.databaseFilename is not None: - databaseFilename = args.databaseFilename -if not args.configFilename and args.homographyFilename is not None: - homography = inv(loadtxt(args.homographyFilename)) -if not args.configFilename and args.intrinsicCameraMatrixFilename is not None: - intrinsicCameraMatrix = loadtxt(args.intrinsicCameraMatrixFilename) -if not args.configFilename and args.distortionCoefficients is not None: - distortionCoefficients = args.distortionCoefficients -if not args.configFilename and args.undistortedImageMultiplication is not None: - undistortedImageMultiplication = args.undistortedImageMultiplication -if args.firstFrameNum is not None: - firstFrameNum = args.firstFrameNum - - -objects = storage.loadTrajectoriesFromSqlite(databaseFilename, args.trajectoryType) -boundingBoxes = storage.loadBoundingBoxTableForDisplay(databaseFilename) -makeVideo(videoFilename, objects, boundingBoxes, homography, firstFrameNum, args.lastFrameNum, rescale = args.rescale, nFramesStep = args.nFramesStep, saveAllImages = args.saveAllImages, undistort = (undistort or args.undistort), intrinsicCameraMatrix = intrinsicCameraMatrix, distortionCoefficients = distortionCoefficients, undistortedImageMultiplication = undistortedImageMultiplication, writeVideo = True, outputVideoPath) - -def makeVideo(videoFilename, objects, boundingBoxes = {}, homography = None, firstFrameNum = 0, lastFrameNumArg = None, printFrames = True, rescale = 1., nFramesStep = 1, saveAllImages = False, undistort = False, intrinsicCameraMatrix = None, distortionCoefficients = None, undistortedImageMultiplication = 1., annotations = [], gtMatches = {}, toMatches = {},writeVideo=True, outputVideoPath): - '''Displays the objects overlaid frame by frame over the video ''' - from moving import userTypeNames - from math import ceil, log10 - - capture = cv2.VideoCapture(videoFilename) - width = int(capture.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH)) - height = int(capture.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT)) - fourcc = cv2.cv.CV_FOURCC('D','I','V','X') - outputVideo = cv2.VideoWriter(outputVideoPath,fourcc, 30.0, (width,height)) - #outputVideo = cv2.VideoWriter('~/Documents/laurier/testVideo.avi', cv2.cv.CV_FOURCC('H','2','6','4'),30,(704,384),True) - - windowName = 'frame' - if rescale == 1.: - cv2.namedWindow(windowName, cv2.WINDOW_NORMAL) - - if undistort: # setup undistortion - [map1, map2] = computeUndistortMaps(width, height, undistortedImageMultiplication, intrinsicCameraMatrix, distortionCoefficients) - if capture.isOpened(): - key = -1 - ret = True - frameNum = firstFrameNum - capture.set(cv2.cv.CV_CAP_PROP_POS_FRAMES, firstFrameNum) - if lastFrameNumArg is None: - from sys import maxint - lastFrameNum = maxint - else: - lastFrameNum = lastFrameNumArg - nZerosFilename = int(ceil(log10(lastFrameNum))) - objectToDeleteIds = [] - while ret and not quitKey(key) and frameNum <= lastFrameNum: - ret, img = capture.read() - if ret: - if undistort: - img = cv2.remap(img, map1, map2, interpolation=cv2.INTER_LINEAR) - if printFrames: - print('frame {0}'.format(frameNum)) - if len(objectToDeleteIds) > 0: - objects = [o for o in objects if o.getNum() not in objectToDeleteIds] - objectToDeleteIds = [] - # plot objects - for obj in objects: - if obj.existsAtInstant(frameNum): - if obj.getLastInstant() == frameNum: - objectToDeleteIds.append(obj.getNum()) - if not hasattr(obj, 'projectedPositions'): - if homography is not None: - obj.projectedPositions = obj.positions.project(homography) - else: - obj.projectedPositions = obj.positions - cvPlot(img, obj.projectedPositions, cvColors[obj.getNum()], frameNum-obj.getFirstInstant()) - if frameNum not in boundingBoxes.keys() and obj.hasFeatures(): - imgcrop, yCropMin, yCropMax, xCropMin, xCropMax = imageBox(img, obj, frameNum, homography, width, height) - cv2.rectangle(img, (xCropMin, yCropMin), (xCropMax, yCropMax), cvBlue, 1) - objDescription = '{} '.format(obj.num) - if userTypeNames[obj.userType] != 'unknown': - objDescription += userTypeNames[obj.userType][0].upper() - if len(annotations) > 0: # if we loaded annotations, but there is no match - if frameNum not in toMatches[obj.getNum()]: - objDescription += " FA" - cv2.putText(img, objDescription, obj.projectedPositions[frameNum-obj.getFirstInstant()].asint().astuple(), cv2.cv.CV_FONT_HERSHEY_PLAIN, 1, cvColors[obj.getNum()]) - # plot object bounding boxes - if frameNum in boundingBoxes.keys(): - for rect in boundingBoxes[frameNum]: - cv2.rectangle(img, rect[0].asint().astuple(), rect[1].asint().astuple(), cvColors[obj.getNum()]) - # plot ground truth - if len(annotations) > 0: - for gt in annotations: - if gt.existsAtInstant(frameNum): - if frameNum in gtMatches[gt.getNum()]: - color = cvColors[gtMatches[gt.getNum()][frameNum]] # same color as object - else: - color = cvRed - cv2.putText(img, 'Miss', gt.topLeftPositions[frameNum-gt.getFirstInstant()].asint().astuple(), cv2.cv.CV_FONT_HERSHEY_PLAIN, 1, cvRed) - cv2.rectangle(img, gt.topLeftPositions[frameNum-gt.getFirstInstant()].asint().astuple(), gt.bottomRightPositions[frameNum-gt.getFirstInstant()].asint().astuple(), color) - # saving images and going to next - if not saveAllImages: - cvImshow(windowName, img, rescale) - #key = cv2.waitKey() - if saveAllImages or saveKey(key): - cv2.imwrite('image-{{:0{}}}.png'.format(nZerosFilename).format(frameNum), img) - if writeVideo: - outputVideo.write(img) - frameNum += nFramesStep - if nFramesStep > 1: - capture.set(cv2.cv.CV_CAP_PROP_POS_FRAMES, frameNum) - cv2.destroyAllWindows() - else: - print 'Cannot load file ' + videoFilename \ No newline at end of file diff --git a/application/pm.py b/application/pm.py index 6be034d..6d59069 100644 --- a/application/pm.py +++ b/application/pm.py @@ -3,7 +3,7 @@ """ from PyQt4 import QtGui, QtCore -from new_project import Ui_create_new_project +from views.new_project import Ui_create_new_project import os from ConfigParser import SafeConfigParser, NoSectionError, NoOptionError import time @@ -14,13 +14,11 @@ from PIL import Image except: import Image -import cvutils import numpy as np from app_config import AppConfig as ac -from app_config import check_project_cfg_option, update_project_cfg, check_project_cfg_section -from qt_plot import plot_results - +from app_config import get_project_path, get_config_path, config_section_exists, get_config_with_sections, update_config_with_sections +from cloud_api import api class ProjectWizard(QtGui.QWizard): @@ -33,8 +31,9 @@ def __init__(self, parent): self.aerial_image_selected = False self.video_selected = False - # self.DEFAULT_PROJECT_DIR = os.path.join(os.getcwd(), os.pardir, "project_dir") - self.DEFAULT_PROJECT_DIR = ac.PROJECT_DIR + # Remove '?' icon + flags = self.windowFlags() & (~QtCore.Qt.WindowContextHelpButtonHint) + self.setWindowFlags(flags) self.ui.newp_start_creation.clicked.connect(self.start_create_project) self.config_parser = SafeConfigParser() @@ -45,7 +44,11 @@ def __init__(self, parent): self.ui.newp_p2.registerField("video_path*", self.ui.newp_video_input) self.ui.newp_p2.registerField("video_start_datetime", self.ui.newp_video_start_time_input) - self.ui.newp_p2.registerField("video_fps*", self.ui.newp_video_fps_input) + self.ui.newp_p2.registerField("video_server_input*", self.ui.newp_video_server_input) + self.ui.newp_p2.registerField("video_email", self.ui.newp_video_email_input) + + # Set default server + self.ui.newp_video_server_input.setText("http://localhost:8088") self.ui.newp_p2.registerField("aerial_image*", self.ui.newp_aerial_image_input) @@ -100,13 +103,16 @@ def start_create_project(self): self.create_project_dir() def create_project_dir(self): - self.project_name = str(self.ui.newp_projectname_input.text()) + ac.CURRENT_PROJECT_NAME = str(self.ui.newp_projectname_input.text()) progress_bar = self.ui.newp_creation_progress progress_msg = self.ui.newp_creation_status - directory_names = ["homography", ".temp", "run", "results"] - pr_path = os.path.join(self.DEFAULT_PROJECT_DIR, self.project_name) + directory_names = ["homography", "results"] + pr_path = get_project_path() if not os.path.exists(pr_path): - self.PROJECT_PATH = pr_path + # Set URL to use before doing anything + server = str(self.ui.newp_video_server_input.text()) + update_api(server) + progress_msg.setText("Creating project directories...") for new_dir in directory_names: progress_bar.setValue(progress_bar.value() + 5) @@ -115,29 +121,15 @@ def create_project_dir(self): progress_bar.setValue(progress_bar.value() + 5) progress_msg.setText("Writing configuration files...") self._write_to_project_config() - copy("default/tracking.cfg", os.path.join(pr_path, "tracking.cfg")) - copy("default/classifier.cfg", os.path.join(pr_path, "classifier.cfg")) - with open(os.path.join(pr_path, 'tracking.cfg') ,'r+') as trkcfg: - old = trkcfg.read() - trkcfg.seek(0) - newline = 'classifier-filename = ../project_dir/{}/classifier.cfg'.format(self.project_name) - trkcfg.write(newline+old) - with open(os.path.join(pr_path, 'classifier.cfg'),'r+') as newcfg: - old = newcfg.read() - newcfg.seek(0) - nline1 = 'pbv-svm-filename = ../project_dir/{}/modelPBV.xml\n'.format(self.project_name) - nline2 = 'bv-svm-filename = ../project_dir/{}/modelBV.xml\n'.format(self.project_name) - newcfg.write(nline1+nline2+old) - - progress_msg.setText("Copying object classification files...") - svms = ["modelBV.xml", "modelPB.xml", "modelPBV.xml", "modelPV.xml"] - for svm in svms: - copy("default/{}".format(svm), os.path.join(pr_path, svm)) - progress_bar.setValue(progress_bar.value() + 5) progress_msg.setText("Copying video file...") - video_dest = os.path.join(pr_path, os.path.basename(self.videopath)) + video_extension = self.videopath.split('.')[-1] + video_dest = os.path.join(pr_path, 'video.' + video_extension) copy(self.videopath, video_dest) + + progress_msg.setText("Uploading video file...") + identifier = api.uploadVideo(self.videopath) + update_config_with_sections(get_config_path(), 'info', 'identifier', identifier) progress_bar.setValue(80) progress_msg.setText("Extracting camera image...") @@ -158,7 +150,7 @@ def create_project_dir(self): progress_bar.setValue(95) progress_msg.setText("Complete.") - progress_msg.setText("Opening {} project...".format(self.project_name)) + progress_msg.setText("Opening {} project...".format(ac.CURRENT_PROJECT_NAME)) self.load_new_project() progress_bar.setValue(100) progress_msg.setText("Complete.") @@ -169,43 +161,58 @@ def create_project_dir(self): def _write_to_project_config(self): ts = time.time() vid_ts = self.ui.newp_video_start_time_input.dateTime().toPyDateTime() + email = str(self.ui.newp_video_email_input.text()) + server = str(self.ui.newp_video_server_input.text()) + timestamp = datetime.datetime.fromtimestamp(ts).strftime('%d-%m-%Y %H:%M:%S %Z') video_timestamp = vid_ts.strftime('%d-%m-%Y %H:%M:%S %Z') self.config_parser.add_section("info") - self.config_parser.set("info", "project_name", self.project_name) + self.config_parser.set("info", "project_name", ac.CURRENT_PROJECT_NAME) self.config_parser.set("info", "creation_date", timestamp) + self.config_parser.set("info", "server", server) + self.config_parser.set("info", "email", email) self.config_parser.add_section("video") - self.config_parser.set("video", "name", os.path.basename(self.videopath)) + video_extension = self.videopath.split('.')[-1] + self.config_parser.set("video", "name", 'video.'+video_extension) self.config_parser.set("video", "source", self.videopath) - self.config_parser.set("video", "framerate", str(self.ui.newp_video_fps_input.text())) self.config_parser.set("video", "start", video_timestamp) - with open(os.path.join(self.PROJECT_PATH, "{}.cfg".format(self.project_name)), 'wb') as configfile: + self.config_parser.add_section("config") + try: + config = api.defaultConfig() + for (key, value) in config.iteritems(): + self.config_parser.set("config", key, str(value)) + except Exception as e: + print("Failed to get default configuration") + print(str(e)) + + with open(os.path.join(get_config_path()), 'wb') as configfile: self.config_parser.write(configfile) def load_new_project(self): - load_project(self.PROJECT_PATH, self.parent()) + load_project(ac.CURRENT_PROJECT_NAME, self.parent()) - -def load_project(folder_path, main_window): - path = os.path.normpath(folder_path) # Clean path. May not be necessary. - project_name = os.path.basename(path) - project_cfg = os.path.join(path, "{}.cfg".format(project_name)) - ac.CURRENT_PROJECT_PATH = path # Set application-level variables indicating the currently open project +def load_project(project_name, main_window): ac.CURRENT_PROJECT_NAME = project_name - ac.CURRENT_PROJECT_CONFIG = project_cfg - config_parser = SafeConfigParser() - config_parser.read(project_cfg) # Read project config file. - ac.CURRENT_PROJECT_VIDEO_PATH = os.path.join(ac.CURRENT_PROJECT_PATH, config_parser.get("video", "name")) + load_homography(main_window) - load_results(main_window) + # Reload the URL for the project + addr = get_config_with_sections(get_config_path(), "info", "server") + update_api(addr) + + load_config(main_window) + +def loadPointCorrespondences(filename): + '''Loads and returns the corresponding points in world (first 2 lines) and image spaces (last 2 lines)''' + points = np.loadtxt(filename, dtype=np.float32) + return (points[:2,:].T, points[2:,:].T) # (world points, image points) def load_homography(main_window): """ Loads homography information into the specified main window. """ - path = ac.CURRENT_PROJECT_PATH + path = get_project_path() aerial_path = os.path.join(path, "homography", "aerial.png") camera_path = os.path.join(path, "homography", "camera.png") # TODO: Handle if above two paths do not exist @@ -226,13 +233,13 @@ def load_homography(main_window): corr_path = pt_corrs_path # Has a homography been previously computed? - if check_project_cfg_section("homography"): # If we can load homography unit-pix ratio load it + if config_section_exists(get_config_path(), "homography"): # If we can load homography unit-pix ratio load it # load unit-pixel ratio - upr_exists, upr = check_project_cfg_option("homography", "unitpixelratio") - if upr_exists: + upr = get_config_with_sections(get_config_path(), "homography", "unitpixelratio") + if upr: gui.unit_px_input.setText(upr) if os.path.exists(corr_path): # If points have been previously selected - worldPts, videoPts = cvutils.loadPointCorrespondences(corr_path) + worldPts, videoPts = loadPointCorrespondences(corr_path) main_window.homography = np.loadtxt(homo_path) if load_from is "image_pts": for point in worldPts: @@ -248,7 +255,14 @@ def load_homography(main_window): else: print ("{} does not exist. No points loaded.".format(corr_path)) -def load_results(main_window): - if os.path.exists(os.path.join(ac.CURRENT_PROJECT_PATH, "homography", "homography.txt")): - if os.path.exists(os.path.join(ac.CURRENT_PROJECT_PATH, "run", "results.sqlite")): - plot_results(main_window) +def update_api(addr): + if addr: + api.set_url(addr) + else: + print("No server, resorting to localhost") + api.set_url('localhost') + +def load_config(main_window): + main_window.configGui_features.loadConfig_features() + main_window.configGui_object.loadConfig_objects() + diff --git a/application/project_selector.py b/application/project_selector.py new file mode 100644 index 0000000..95d067d --- /dev/null +++ b/application/project_selector.py @@ -0,0 +1,29 @@ +""" +Project management classes and functions +""" + +from PyQt4 import QtGui, QtCore +from choose_project import Ui_choose_project + +class ProjectSelectionWizard(QtGui.QDialog): + + def __init__(self, parent): + super(ProjectSelectionWizard, self).__init__(parent) + self.ui = Ui_choose_project() + self.ui.setupUi(self) + + self.ui.create_new_project_button.clicked.connect(self.create_new_project) + self.ui.open_project_button.clicked.connect(self.open_project) + + # Remove '?' icon + flags = self.windowFlags() & (~QtCore.Qt.WindowContextHelpButtonHint) + self.setWindowFlags(flags) + + def create_new_project(self): + self.parent().create_new_project() + self.close() + + def open_project(self): + self.parent().open_project() + self.close() + diff --git a/application/qt_plot.py b/application/qt_plot.py index d11a4a4..e4e38c3 100644 --- a/application/qt_plot.py +++ b/application/qt_plot.py @@ -6,14 +6,7 @@ from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar from matplotlib.figure import Figure -# # from application_config import AppConfig as ac -# import application - -from app_config import AppConfig as ac -from app_config import check_project_cfg_option -# from .. import pm -# from .. import app_config -# ac = app_config.AppConfig +from app_config import get_project_path from plotting import visualization import random @@ -84,12 +77,12 @@ def draw(self): def plot_results(main_window): - if ac.CURRENT_PROJECT_PATH: - database_filename = os.path.join(ac.CURRENT_PROJECT_PATH, "run", "results.sqlite") - homography_filename = os.path.join(ac.CURRENT_PROJECT_PATH, "homography", "homography.txt") - camera_image = os.path.join(ac.CURRENT_PROJECT_PATH, "homography", "camera.png") - fps_exists, video_fps = check_project_cfg_option("video", "framerate") - video_fps = float(video_fps) + project_path = get_project_path() + if project_path: + database_filename = os.path.join(project_path, "run", "results.sqlite") + homography_filename = os.path.join(project_path, "homography", "homography.txt") + camera_image = os.path.join(project_path, "homography", "camera.png") + video_fps = 30 # TODO: What? plot0 = main_window.ui.results_plot0 fig0 = plot0.getFigure() diff --git a/application/test_cloud.py b/application/test_cloud.py new file mode 100644 index 0000000..5c798ba --- /dev/null +++ b/application/test_cloud.py @@ -0,0 +1,97 @@ +import cloud_api as api + +TEST_IP = 'localhost' +VIDEO_PATH = 'path/to/video' +UNIT_PIXEL_RATIO = 0.05 + +#TODO: Remove raw_input hotfix in favor of checking function call + +#SAMPLE POINTS PROVIDED +AERIAL_PTS = [ + (1002.2857055664062, 388.0), + (864.7857055664062, 575.5), + (1061.2142333984375, 389.78570556640625), + (1036.2142333984375, 291.5714416503906), + (757.6428833007812, 575.5), + (843.3571166992188, 391.5714416503906)] +CAMERA_PTS = [ + (508.5128173828125, 231.58973693847656), + (941.8461303710938, 416.20513916015625), + (493.1282043457031, 146.974365234375), + (316.20513916015625, 216.2051239013672), + (1000.8204956054688, 611.076904296875), + (577.7435913085938, 503.3846130371094)] + +EMAIL = None + +if __name__ == '__main__': + print "Syntax looks fine!" + + ########################################################################### + # Setup CloudWizard + ########################################################################### + remote = api.CloudWizard(TEST_IP) + + ########################################################################### + # Upload Video + ########################################################################### + identifier = remote.uploadVideo(VIDEO_PATH) + raw_input('Uploading Video, please wait...\n Press Enter to Continue') + + ########################################################################### + # Configure Homography + ########################################################################### + remote.configHomography(identifier, UNIT_PIXEL_RATIO, AERIAL_PTS, CAMERA_PTS) + raw_input('Configuring Homography, please wait...\n Press Enter to Continue') + + ########################################################################### + # Test Configs + ########################################################################### + + remote.configFiles(identifier,\ + max_features_per_frame = 1001,\ + num_displacement_frames = 11,\ + min_feature_displacement = 0.000099,\ + max_iterations_to_persist = 201,\ + min_feature_frames = 14,\ + max_connection_distance = 0.99,\ + max_segmentation_distance = 0.69) + raw_input('Updating Config Files, please wait...\n Press Enter to Continue') + + remote.testConfig('feature', identifier) + raw_input('Testing Feature Config, please wait...\n Press Enter to Continue') + + remote.testConfig('object', identifier) + raw_input('Testing object Config, please wait...\n Press Enter to Continue') + + ########################################################################### + # Run Analysis Routes + ########################################################################### + + #remote.objectTracking(identifier,EMAIL) + #raw_input('Running Object Tracking, please wait...\n Press Enter to Continue') + + #remote.safetyAnalysis(identifier,EMAIL) + #raw_input('Running Safety Analysis, please wait...\n Press Enter to Continue') + + remote.analysis(identifier,EMAIL) + raw_input('Running Analysis, please wait...\n Press Enter to Continue') + + ########################################################################### + # Run Result Routes + ########################################################################### + + remote.highlightVideo(identifier) + raw_input('Generating Highlight Video, please wait...\n Press Enter to Continue') + + remote.roadUserCounts(identifier) + raw_input('Generating User Counts, please wait...\n Press Enter to Continue') + + remote.speedDistribution(identifier) + raw_input('Generating Speed Distribution, please wait...\n Press Enter to Continue') + + remote.makeReport(identifier) + raw_input('Generating Report, please wait...\n Press Enter to Continue') + + remote.retrieveResults(identifier) + raw_input('Retrieving Results, please wait...\n Press Enter to Continue') diff --git a/application/video.py b/application/video.py new file mode 100644 index 0000000..d86f61f --- /dev/null +++ b/application/video.py @@ -0,0 +1,26 @@ + +import cv2 +import os + +def convert_video_to_frames(video_path, images_dir, prefix='image-', extension='png'): + if not os.path.exists(video_path): + return False + + if not os.path.exists(images_dir): + os.makedirs(images_dir) + + vidcap = cv2.VideoCapture(video_path) + count = 0 + success = True + while success: + success, image = vidcap.read() + if success: + if '.' in extension: + format_string = "%d" + else: + format_string = "%d." + filename = prefix + format_string + extension + cv2.imwrite(os.path.join(images_dir, filename % count), image) # save frame as JPEG file + count += 1 + + diff --git a/application/views/__init__.py b/application/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/application/views/message_dialog.py b/application/views/message_dialog.py new file mode 100644 index 0000000..a35745d --- /dev/null +++ b/application/views/message_dialog.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'message_dialog.ui' +# +# Created by: PyQt4 UI code generator 4.11.4 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + +class Ui_message_dialog(object): + def setupUi(self, message_dialog): + message_dialog.setObjectName(_fromUtf8("message_dialog")) + message_dialog.resize(400, 150) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(message_dialog.sizePolicy().hasHeightForWidth()) + message_dialog.setSizePolicy(sizePolicy) + message_dialog.setMaximumSize(QtCore.QSize(400, 16777215)) + self.gridLayout = QtGui.QGridLayout(message_dialog) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.verticalLayout = QtGui.QVBoxLayout() + self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) + self.label = QtGui.QLabel(message_dialog) + self.label.setText(_fromUtf8("")) + self.label.setAlignment(QtCore.Qt.AlignCenter) + self.label.setWordWrap(True) + self.label.setObjectName(_fromUtf8("label")) + self.verticalLayout.addWidget(self.label) + self.close_button = QtGui.QPushButton(message_dialog) + self.close_button.setObjectName(_fromUtf8("close_button")) + self.verticalLayout.addWidget(self.close_button) + self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1) + + self.retranslateUi(message_dialog) + QtCore.QMetaObject.connectSlotsByName(message_dialog) + + def retranslateUi(self, message_dialog): + message_dialog.setWindowTitle(_translate("message_dialog", "Message", None)) + self.close_button.setText(_translate("message_dialog", "Close", None)) + diff --git a/application/views/message_dialog.ui b/application/views/message_dialog.ui new file mode 100644 index 0000000..bcbccf8 --- /dev/null +++ b/application/views/message_dialog.ui @@ -0,0 +1,57 @@ + + + message_dialog + + + + 0 + 0 + 400 + 150 + + + + + 0 + 0 + + + + + 400 + 16777215 + + + + Message + + + + + + + + + + + Qt::AlignCenter + + + true + + + + + + + Close + + + + + + + + + + diff --git a/application/new_project.py b/application/views/new_project.py similarity index 80% rename from application/new_project.py rename to application/views/new_project.py index 319fe7f..8d451f4 100644 --- a/application/new_project.py +++ b/application/views/new_project.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'new_project.ui' +# Form implementation generated from reading ui file 'application/new_project.ui' # -# Created: Thu Apr 7 07:08:52 2016 +# Created: Mon Feb 13 23:10:23 2017 # by: PyQt4 UI code generator 4.10.4 # # WARNING! All changes made in this file will be lost! @@ -27,7 +27,7 @@ class Ui_create_new_project(object): def setupUi(self, create_new_project): create_new_project.setObjectName(_fromUtf8("create_new_project")) create_new_project.setWindowModality(QtCore.Qt.ApplicationModal) - create_new_project.resize(568, 406) + create_new_project.setMinimumSize(QtCore.QSize(550, 500)) create_new_project.setAutoFillBackground(False) create_new_project.setModal(True) create_new_project.setWizardStyle(QtGui.QWizard.ClassicStyle) @@ -65,6 +65,11 @@ def setupUi(self, create_new_project): self.verticalLayout.addWidget(self.newp_ldir_label) create_new_project.addPage(self.newp_p1) self.newp_p2 = QtGui.QWizardPage() + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.newp_p2.sizePolicy().hasHeightForWidth()) + self.newp_p2.setSizePolicy(sizePolicy) self.newp_p2.setObjectName(_fromUtf8("newp_p2")) self.verticalLayout_2 = QtGui.QVBoxLayout(self.newp_p2) self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) @@ -81,32 +86,48 @@ def setupUi(self, create_new_project): self.newp_p2_add_vido_description.setObjectName(_fromUtf8("newp_p2_add_vido_description")) self.verticalLayout_2.addWidget(self.newp_p2_add_vido_description) self.newp_p2_video_layout = QtGui.QFormLayout() - self.newp_p2_video_layout.setContentsMargins(-1, 8, -1, -1) + self.newp_p2_video_layout.setSizeConstraint(QtGui.QLayout.SetNoConstraint) + self.newp_p2_video_layout.setFieldGrowthPolicy(QtGui.QFormLayout.AllNonFixedFieldsGrow) self.newp_p2_video_layout.setObjectName(_fromUtf8("newp_p2_video_layout")) self.newp_video_label = QtGui.QLabel(self.newp_p2) + self.newp_video_label.setMinimumSize(QtCore.QSize(0, 28)) self.newp_video_label.setObjectName(_fromUtf8("newp_video_label")) self.newp_p2_video_layout.setWidget(0, QtGui.QFormLayout.LabelRole, self.newp_video_label) self.newp_p2_video_browse_layout = QtGui.QHBoxLayout() self.newp_p2_video_browse_layout.setObjectName(_fromUtf8("newp_p2_video_browse_layout")) self.newp_video_input = QtGui.QLineEdit(self.newp_p2) + self.newp_video_input.setMinimumSize(QtCore.QSize(0, 21)) self.newp_video_input.setObjectName(_fromUtf8("newp_video_input")) self.newp_p2_video_browse_layout.addWidget(self.newp_video_input) self.newp_video_browse = QtGui.QPushButton(self.newp_p2) + self.newp_video_browse.setMinimumSize(QtCore.QSize(0, 32)) self.newp_video_browse.setObjectName(_fromUtf8("newp_video_browse")) self.newp_p2_video_browse_layout.addWidget(self.newp_video_browse) self.newp_p2_video_layout.setLayout(0, QtGui.QFormLayout.FieldRole, self.newp_p2_video_browse_layout) self.newp_video_start_time_label = QtGui.QLabel(self.newp_p2) + self.newp_video_start_time_label.setMinimumSize(QtCore.QSize(0, 24)) self.newp_video_start_time_label.setObjectName(_fromUtf8("newp_video_start_time_label")) self.newp_p2_video_layout.setWidget(1, QtGui.QFormLayout.LabelRole, self.newp_video_start_time_label) self.newp_video_start_time_input = QtGui.QDateTimeEdit(self.newp_p2) + self.newp_video_start_time_input.setMinimumSize(QtCore.QSize(0, 24)) self.newp_video_start_time_input.setObjectName(_fromUtf8("newp_video_start_time_input")) self.newp_p2_video_layout.setWidget(1, QtGui.QFormLayout.FieldRole, self.newp_video_start_time_input) - self.newp_video_fps_label = QtGui.QLabel(self.newp_p2) - self.newp_video_fps_label.setObjectName(_fromUtf8("newp_video_fps_label")) - self.newp_p2_video_layout.setWidget(2, QtGui.QFormLayout.LabelRole, self.newp_video_fps_label) - self.newp_video_fps_input = QtGui.QLineEdit(self.newp_p2) - self.newp_video_fps_input.setObjectName(_fromUtf8("newp_video_fps_input")) - self.newp_p2_video_layout.setWidget(2, QtGui.QFormLayout.FieldRole, self.newp_video_fps_input) + self.newp_video_server_label = QtGui.QLabel(self.newp_p2) + self.newp_video_server_label.setMinimumSize(QtCore.QSize(0, 21)) + self.newp_video_server_label.setObjectName(_fromUtf8("newp_video_server_label")) + self.newp_p2_video_layout.setWidget(2, QtGui.QFormLayout.LabelRole, self.newp_video_server_label) + self.newp_video_server_input = QtGui.QLineEdit(self.newp_p2) + self.newp_video_server_input.setMinimumSize(QtCore.QSize(0, 21)) + self.newp_video_server_input.setObjectName(_fromUtf8("newp_video_server_input")) + self.newp_p2_video_layout.setWidget(2, QtGui.QFormLayout.FieldRole, self.newp_video_server_input) + self.newp_video_email_label = QtGui.QLabel(self.newp_p2) + self.newp_video_email_label.setMinimumSize(QtCore.QSize(0, 21)) + self.newp_video_email_label.setObjectName(_fromUtf8("newp_video_email_label")) + self.newp_p2_video_layout.setWidget(3, QtGui.QFormLayout.LabelRole, self.newp_video_email_label) + self.newp_video_email_input = QtGui.QLineEdit(self.newp_p2) + self.newp_video_email_input.setMinimumSize(QtCore.QSize(0, 21)) + self.newp_video_email_input.setObjectName(_fromUtf8("newp_video_email_input")) + self.newp_p2_video_layout.setWidget(3, QtGui.QFormLayout.FieldRole, self.newp_video_email_input) self.verticalLayout_2.addLayout(self.newp_p2_video_layout) self.newp_add_aerial_image_title = QtGui.QLabel(self.newp_p2) font = QtGui.QFont() @@ -175,11 +196,12 @@ def retranslateUi(self, create_new_project): self.newp_p1_title.setText(_translate("create_new_project", "New Safety Project", None)) self.newp_projectname_label.setText(_translate("create_new_project", "Project Name", None)) self.newp_p2_add_video_title.setText(_translate("create_new_project", "Add project video", None)) - self.newp_p2_add_vido_description.setText(_translate("create_new_project", "Browse and select a video file to analyze. Please also input the date and time when the video recording began as well as the framerate of the video in frames per second.", None)) + self.newp_p2_add_vido_description.setText(_translate("create_new_project", "Browse and select a video file to analyze. Please input the time when the video recording occurred. You may optionally enter an email that should be notified when long-running analysis is finished. You should also enter the IP address or URL of the server that should be used for analysis.", None)) self.newp_video_label.setText(_translate("create_new_project", "Selected video", None)) self.newp_video_browse.setText(_translate("create_new_project", "Browse...", None)) self.newp_video_start_time_label.setText(_translate("create_new_project", "Recording start time", None)) - self.newp_video_fps_label.setText(_translate("create_new_project", "Video framerate", None)) + self.newp_video_server_label.setText(_translate("create_new_project", "Server IP or URL", None)) + self.newp_video_email_label.setText(_translate("create_new_project", "Email", None)) self.newp_add_aerial_image_title.setText(_translate("create_new_project", "Add aerial image", None)) self.newp_p2_add_aerial_image_description.setText(_translate("create_new_project", "Browse and select an aerial image of the video\'s target. ", None)) self.newp_aerial_image_label.setText(_translate("create_new_project", "Aerial image", None)) diff --git a/application/new_project.ui b/application/views/new_project.ui similarity index 70% rename from application/new_project.ui rename to application/views/new_project.ui index 1305f0f..626618e 100644 --- a/application/new_project.ui +++ b/application/views/new_project.ui @@ -5,13 +5,11 @@ Qt::ApplicationModal - - - 0 - 0 - 568 - 406 - + + + 550 + 500 + Create New Project @@ -78,6 +76,12 @@ + + + 0 + 0 + + @@ -96,7 +100,7 @@ - Browse and select a video file to analyze. Please also input the date and time when the video recording began as well as the framerate of the video in frames per second. + Browse and select a video file to analyze. Please input the time when the video recording occurred. You may optionally enter an email that should be notified when long-running analysis is finished. You should also enter the IP address or URL of the server that should be used for analysis. true @@ -105,11 +109,26 @@ - - 8 + + QLayout::SetNoConstraint + + + QFormLayout::AllNonFixedFieldsGrow + + + -1 + + + -1 + + + 0 + 28 + + Selected video @@ -118,10 +137,23 @@ - + + + + 0 + 21 + + + + + + 0 + 32 + + Browse... @@ -131,23 +163,72 @@ + + + 0 + 24 + + Recording start time - + + + + 0 + 24 + + + - + + + + 0 + 21 + + - Video framerate + Server IP or URL - + + + + 0 + 21 + + + + + + + + + 0 + 21 + + + + Email + + + + + + + + 0 + 21 + + + diff --git a/application/safety_main.py b/application/views/safety_main.py similarity index 87% rename from application/safety_main.py rename to application/views/safety_main.py index 53777f0..e4ea936 100644 --- a/application/safety_main.py +++ b/application/views/safety_main.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'safety_main.ui' +# Form implementation generated from reading ui file 'application/safety_main.ui' # -# Created: Wed Apr 20 12:49:37 2016 +# Created: Mon Feb 13 17:43:32 2017 # by: PyQt4 UI code generator 4.10.4 # # WARNING! All changes made in this file will be lost! @@ -26,12 +26,17 @@ def _translate(context, text, disambig): class Ui_TransportationSafety(object): def setupUi(self, TransportationSafety): TransportationSafety.setObjectName(_fromUtf8("TransportationSafety")) - TransportationSafety.resize(1129, 808) + TransportationSafety.resize(1280, 800) self.centralWidget = QtGui.QWidget(TransportationSafety) self.centralWidget.setObjectName(_fromUtf8("centralWidget")) self.gridLayout_2 = QtGui.QGridLayout(self.centralWidget) self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) self.main_tab_widget = QtGui.QTabWidget(self.centralWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.main_tab_widget.sizePolicy().hasHeightForWidth()) + self.main_tab_widget.setSizePolicy(sizePolicy) self.main_tab_widget.setAcceptDrops(False) self.main_tab_widget.setAutoFillBackground(False) self.main_tab_widget.setTabShape(QtGui.QTabWidget.Rounded) @@ -205,6 +210,10 @@ def setupUi(self, TransportationSafety): self.button_feature_tracking_test.setObjectName(_fromUtf8("button_feature_tracking_test")) self.horizontalLayout_6.addWidget(self.button_feature_tracking_test) self.gridLayout.addWidget(self.feature_tracking_run_panel, 1, 1, 1, 1) + self.feature_tracking_video_layout = QtGui.QVBoxLayout() + self.feature_tracking_video_layout.setSizeConstraint(QtGui.QLayout.SetNoConstraint) + self.feature_tracking_video_layout.setObjectName(_fromUtf8("feature_tracking_video_layout")) + self.gridLayout.addLayout(self.feature_tracking_video_layout, 0, 0, 2, 1) self.feature_tracking_parameter_area = QtGui.QScrollArea(self.tab_features) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) @@ -216,8 +225,8 @@ def setupUi(self, TransportationSafety): self.feature_tracking_parameter_area.setWidgetResizable(True) self.feature_tracking_parameter_area.setObjectName(_fromUtf8("feature_tracking_parameter_area")) self.feature_tracking_parameter_widget = QtGui.QWidget() - self.feature_tracking_parameter_widget.setGeometry(QtCore.QRect(0, 0, 538, 568)) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) + self.feature_tracking_parameter_widget.setGeometry(QtCore.QRect(0, 0, 597, 541)) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.feature_tracking_parameter_widget.sizePolicy().hasHeightForWidth()) @@ -231,10 +240,6 @@ def setupUi(self, TransportationSafety): self.formLayout_2.setLayout(0, QtGui.QFormLayout.LabelRole, self.feature_tracking_parameter_layout) self.feature_tracking_parameter_area.setWidget(self.feature_tracking_parameter_widget) self.gridLayout.addWidget(self.feature_tracking_parameter_area, 0, 1, 1, 1) - self.feature_tracking_video_layout = QtGui.QVBoxLayout() - self.feature_tracking_video_layout.setSizeConstraint(QtGui.QLayout.SetNoConstraint) - self.feature_tracking_video_layout.setObjectName(_fromUtf8("feature_tracking_video_layout")) - self.gridLayout.addLayout(self.feature_tracking_video_layout, 0, 0, 2, 1) self.gridLayout_3.addLayout(self.gridLayout, 0, 0, 1, 1) self.feature_tracking_flow_control = QtGui.QFrame(self.tab_features) self.feature_tracking_flow_control.setMinimumSize(QtCore.QSize(0, 50)) @@ -278,9 +283,6 @@ def setupUi(self, TransportationSafety): self.button_roadusers_tracking_test = QtGui.QPushButton(self.roadusers_tracking_run_panel) self.button_roadusers_tracking_test.setObjectName(_fromUtf8("button_roadusers_tracking_test")) self.horizontalLayout_15.addWidget(self.button_roadusers_tracking_test) - self.button_roadusers_tracking_run = QtGui.QPushButton(self.roadusers_tracking_run_panel) - self.button_roadusers_tracking_run.setObjectName(_fromUtf8("button_roadusers_tracking_run")) - self.horizontalLayout_15.addWidget(self.button_roadusers_tracking_run) self.gridLayout_14.addWidget(self.roadusers_tracking_run_panel, 1, 1, 1, 1) self.roadusers_tracking_video_layout = QtGui.QVBoxLayout() self.roadusers_tracking_video_layout.setSizeConstraint(QtGui.QLayout.SetNoConstraint) @@ -296,8 +298,8 @@ def setupUi(self, TransportationSafety): self.roadusers_tracking_parameter_area.setWidgetResizable(True) self.roadusers_tracking_parameter_area.setObjectName(_fromUtf8("roadusers_tracking_parameter_area")) self.roadusers_tracking_parameter_widget = QtGui.QWidget() - self.roadusers_tracking_parameter_widget.setGeometry(QtCore.QRect(0, 0, 538, 568)) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) + self.roadusers_tracking_parameter_widget.setGeometry(QtCore.QRect(0, 0, 597, 541)) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.roadusers_tracking_parameter_widget.sizePolicy().hasHeightForWidth()) @@ -305,8 +307,11 @@ def setupUi(self, TransportationSafety): self.roadusers_tracking_parameter_widget.setObjectName(_fromUtf8("roadusers_tracking_parameter_widget")) self.formLayout_9 = QtGui.QFormLayout(self.roadusers_tracking_parameter_widget) self.formLayout_9.setFieldGrowthPolicy(QtGui.QFormLayout.AllNonFixedFieldsGrow) + self.formLayout_9.setLabelAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.formLayout_9.setObjectName(_fromUtf8("formLayout_9")) self.roadusers_tracking_parameter_layout = QtGui.QHBoxLayout() + self.roadusers_tracking_parameter_layout.setSizeConstraint(QtGui.QLayout.SetDefaultConstraint) + self.roadusers_tracking_parameter_layout.setContentsMargins(-1, -1, 0, -1) self.roadusers_tracking_parameter_layout.setObjectName(_fromUtf8("roadusers_tracking_parameter_layout")) self.formLayout_9.setLayout(0, QtGui.QFormLayout.LabelRole, self.roadusers_tracking_parameter_layout) self.roadusers_tracking_parameter_area.setWidget(self.roadusers_tracking_parameter_widget) @@ -335,24 +340,50 @@ def setupUi(self, TransportationSafety): self.verticalLayout_5.setObjectName(_fromUtf8("verticalLayout_5")) self.results_grid = QtGui.QGridLayout() self.results_grid.setObjectName(_fromUtf8("results_grid")) - self.results_plot2 = MatplotlibWidget(self.tab_results) - self.results_plot2.setObjectName(_fromUtf8("results_plot2")) - self.results_grid.addWidget(self.results_plot2, 1, 1, 1, 1) - self.results_plot1 = MatplotlibWidget(self.tab_results) - self.results_plot1.setObjectName(_fromUtf8("results_plot1")) - self.results_grid.addWidget(self.results_plot1, 0, 1, 1, 1) - self.results_plot0 = MatplotlibWidget(self.tab_results) - self.results_plot0.setObjectName(_fromUtf8("results_plot0")) - self.results_grid.addWidget(self.results_plot0, 0, 0, 1, 1) - self.results_plot3 = MatplotlibWidget(self.tab_results) - self.results_plot3.setObjectName(_fromUtf8("results_plot3")) - self.results_grid.addWidget(self.results_plot3, 1, 0, 1, 1) + self.results_parameter_area = QtGui.QScrollArea(self.tab_results) + self.results_parameter_area.setWidgetResizable(True) + self.results_parameter_area.setObjectName(_fromUtf8("results_parameter_area")) + self.results_parameter_widget = QtGui.QWidget() + self.results_parameter_widget.setGeometry(QtCore.QRect(0, 0, 1206, 661)) + self.results_parameter_widget.setObjectName(_fromUtf8("results_parameter_widget")) + self.formLayoutWidget = QtGui.QWidget(self.results_parameter_widget) + self.formLayoutWidget.setGeometry(QtCore.QRect(10, 10, 261, 111)) + self.formLayoutWidget.setObjectName(_fromUtf8("formLayoutWidget")) + self.results_parameter_form = QtGui.QFormLayout(self.formLayoutWidget) + self.results_parameter_form.setMargin(0) + self.results_parameter_form.setObjectName(_fromUtf8("results_parameter_form")) + self.speedLimitLabel = QtGui.QLabel(self.formLayoutWidget) + self.speedLimitLabel.setObjectName(_fromUtf8("speedLimitLabel")) + self.results_parameter_form.setWidget(0, QtGui.QFormLayout.LabelRole, self.speedLimitLabel) + self.speedLimitLineEdit = QtGui.QLineEdit(self.formLayoutWidget) + self.speedLimitLineEdit.setObjectName(_fromUtf8("speedLimitLineEdit")) + self.results_parameter_form.setWidget(0, QtGui.QFormLayout.FieldRole, self.speedLimitLineEdit) + self.timeToCollisionLabel = QtGui.QLabel(self.formLayoutWidget) + self.timeToCollisionLabel.setObjectName(_fromUtf8("timeToCollisionLabel")) + self.results_parameter_form.setWidget(2, QtGui.QFormLayout.LabelRole, self.timeToCollisionLabel) + self.timeToCollisionLineEdit = QtGui.QLineEdit(self.formLayoutWidget) + self.timeToCollisionLineEdit.setObjectName(_fromUtf8("timeToCollisionLineEdit")) + self.results_parameter_form.setWidget(2, QtGui.QFormLayout.FieldRole, self.timeToCollisionLineEdit) + self.vehiclesOnlyLabel = QtGui.QLabel(self.formLayoutWidget) + self.vehiclesOnlyLabel.setObjectName(_fromUtf8("vehiclesOnlyLabel")) + self.results_parameter_form.setWidget(3, QtGui.QFormLayout.LabelRole, self.vehiclesOnlyLabel) + self.vehiclesOnlyCheckBox = QtGui.QCheckBox(self.formLayoutWidget) + self.vehiclesOnlyCheckBox.setObjectName(_fromUtf8("vehiclesOnlyCheckBox")) + self.results_parameter_form.setWidget(3, QtGui.QFormLayout.FieldRole, self.vehiclesOnlyCheckBox) + self.results_parameter_layout = QtGui.QHBoxLayout() + self.results_parameter_layout.setObjectName(_fromUtf8("results_parameter_layout")) + self.results_parameter_form.setLayout(1, QtGui.QFormLayout.LabelRole, self.results_parameter_layout) + self.runAnalysisButton = QtGui.QPushButton(self.results_parameter_widget) + self.runAnalysisButton.setGeometry(QtCore.QRect(12, 130, 261, 32)) + self.runAnalysisButton.setObjectName(_fromUtf8("runAnalysisButton")) + self.results_parameter_area.setWidget(self.results_parameter_widget) + self.results_grid.addWidget(self.results_parameter_area, 0, 0, 1, 1) self.verticalLayout_5.addLayout(self.results_grid) self.main_tab_widget.addTab(self.tab_results, _fromUtf8("")) self.gridLayout_2.addWidget(self.main_tab_widget, 0, 0, 1, 1) TransportationSafety.setCentralWidget(self.centralWidget) self.menuBar = QtGui.QMenuBar(TransportationSafety) - self.menuBar.setGeometry(QtCore.QRect(0, 0, 1129, 25)) + self.menuBar.setGeometry(QtCore.QRect(0, 0, 1280, 22)) self.menuBar.setObjectName(_fromUtf8("menuBar")) self.menuTraffic_Analysis = QtGui.QMenu(self.menuBar) self.menuTraffic_Analysis.setObjectName(_fromUtf8("menuTraffic_Analysis")) @@ -391,10 +422,10 @@ def setupUi(self, TransportationSafety): self.actionUser_s_Guide.setObjectName(_fromUtf8("actionUser_s_Guide")) self.actionAbout = QtGui.QAction(TransportationSafety) self.actionAbout.setObjectName(_fromUtf8("actionAbout")) + self.actionFeedback = QtGui.QAction(TransportationSafety) + self.actionFeedback.setObjectName(_fromUtf8("actionFeedback")) self.actionOpen_Video = QtGui.QAction(TransportationSafety) self.actionOpen_Video.setObjectName(_fromUtf8("actionOpen_Video")) - self.actionOpen_Config = QtGui.QAction(TransportationSafety) - self.actionOpen_Config.setObjectName(_fromUtf8("actionOpen_Config")) self.actionNew_Project = QtGui.QAction(TransportationSafety) self.actionNew_Project.setObjectName(_fromUtf8("actionNew_Project")) self.menuTraffic_Analysis.addAction(self.actionNew_Project) @@ -412,7 +443,7 @@ def setupUi(self, TransportationSafety): self.menuBar.addAction(self.menuHelp.menuAction()) self.retranslateUi(TransportationSafety) - self.main_tab_widget.setCurrentIndex(0) + self.main_tab_widget.setCurrentIndex(1) QtCore.QMetaObject.connectSlotsByName(TransportationSafety) def retranslateUi(self, TransportationSafety): @@ -434,10 +465,13 @@ def retranslateUi(self, TransportationSafety): self.main_tab_widget.setTabText(self.main_tab_widget.indexOf(self.tab_features), _translate("TransportationSafety", "Track Features", None)) self.roadusers_tracking_run_test_progress.setFormat(_translate("TransportationSafety", "%p%", None)) self.button_roadusers_tracking_test.setText(_translate("TransportationSafety", "Test on Sample", None)) - self.button_roadusers_tracking_run.setText(_translate("TransportationSafety", "Run", None)) self.roadusers_tracking_back_button.setText(_translate("TransportationSafety", "", None)) self.main_tab_widget.setTabText(self.main_tab_widget.indexOf(self.roadusers_tab), _translate("TransportationSafety", "Track Road Users", None)) + self.speedLimitLabel.setText(_translate("TransportationSafety", "speed limit", None)) + self.timeToCollisionLabel.setText(_translate("TransportationSafety", "time to collision", None)) + self.vehiclesOnlyLabel.setText(_translate("TransportationSafety", "vehicles only", None)) + self.runAnalysisButton.setText(_translate("TransportationSafety", "Run Video Analysis", None)) self.main_tab_widget.setTabText(self.main_tab_widget.indexOf(self.tab_results), _translate("TransportationSafety", "Results", None)) self.menuTraffic_Analysis.setTitle(_translate("TransportationSafety", "File", None)) self.menuProject.setTitle(_translate("TransportationSafety", "Project", None)) @@ -456,11 +490,10 @@ def retranslateUi(self, TransportationSafety): self.actionAcquire_Aerial_Image.setText(_translate("TransportationSafety", "Acquire Aerial Image", None)) self.actionUser_s_Guide.setText(_translate("TransportationSafety", "User\'s Guide", None)) self.actionAbout.setText(_translate("TransportationSafety", "About", None)) + self.actionFeedback.setText(_translate("TransportationSafety", "Send Feedback", None)) self.actionOpen_Video.setText(_translate("TransportationSafety", "Open Video", None)) - self.actionOpen_Config.setText(_translate("TransportationSafety", "Open Config ", None)) self.actionNew_Project.setText(_translate("TransportationSafety", "New Project", None)) self.actionNew_Project.setShortcut(_translate("TransportationSafety", "Ctrl+N", None)) from custom.homography import HomographyResultView, HomographyView -from qt_plot import MatplotlibWidget from custom.zoomslider import ZoomSlider diff --git a/application/safety_main.ui b/application/views/safety_main.ui similarity index 87% rename from application/safety_main.ui rename to application/views/safety_main.ui index 2ba36ea..abc74d9 100644 --- a/application/safety_main.ui +++ b/application/views/safety_main.ui @@ -6,8 +6,8 @@ 0 0 - 1129 - 808 + 1280 + 800 @@ -17,6 +17,12 @@ + + + 0 + 0 + + false @@ -27,7 +33,7 @@ QTabWidget::Rounded - 0 + 1 Qt::ElideNone @@ -429,6 +435,13 @@ + + + + QLayout::SetNoConstraint + + + @@ -457,12 +470,12 @@ 0 0 - 538 - 568 + 597 + 541 - + 0 0 @@ -478,13 +491,6 @@ - - - - QLayout::SetNoConstraint - - - @@ -573,13 +579,6 @@ - - - - Run - - - @@ -612,12 +611,12 @@ 0 0 - 538 - 568 + 597 + 541 - + 0 0 @@ -626,8 +625,18 @@ QFormLayout::AllNonFixedFieldsGrow + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + - + + + QLayout::SetDefaultConstraint + + + 0 + + @@ -689,17 +698,80 @@ - - - - - - - - - - + + + true + + + + + 0 + 0 + 1206 + 661 + + + + + + 10 + 10 + 261 + 111 + + + + + + + speed limit + + + + + + + + + + time to collision + + + + + + + + + + vehicles only + + + + + + + + + + + + + + + 12 + 130 + 261 + 32 + + + + Run Video Analysis + + + + @@ -714,8 +786,8 @@ 0 0 - 1129 - 25 + 1280 + 22 @@ -822,14 +894,14 @@ About - + - Open Video + Send Feedback - + - Open Config + Open Video @@ -858,12 +930,6 @@ QGraphicsView
custom/homography.h
- - MatplotlibWidget - QWidget -
qt_plot.h
- 1 -