From 2e40a51e830b5ffffd97820d597b73360b716d12 Mon Sep 17 00:00:00 2001 From: Corwin Perren Date: Sun, 14 Jan 2018 03:01:11 +0000 Subject: [PATCH 1/9] Updated UDEV rules for camera names --- environment_reference/UDEV Rules/99-rover-cameras.rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment_reference/UDEV Rules/99-rover-cameras.rules b/environment_reference/UDEV Rules/99-rover-cameras.rules index 78cd323..f6a98cb 100644 --- a/environment_reference/UDEV Rules/99-rover-cameras.rules +++ b/environment_reference/UDEV Rules/99-rover-cameras.rules @@ -9,7 +9,7 @@ KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", ATTRS{idVendor}=="2b03", ATTRS{ KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="082d", ATTRS{serial}=="B9A8A5FF", SYMLINK+="rover/camera_undercarriage" # The second C920 Webcam -KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="082d", ATTRS{serial}=="A98AA5FF", SYMLINK+="rover/camera_gimbal" +KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="082d", ATTRS{serial}=="A98AA5FF", SYMLINK+="rover/camera_chassis" # The special main nav cam KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", ATTRS{idVendor}=="05a3", ATTRS{idProduct}=="9422", ATTRS{serial}=="SN0001", ATTR{index}=="0", SYMLINK+="rover/camera_main_navigation" From c49b90b038ad2b31b2b1643f9f55f86c5cdfa658 Mon Sep 17 00:00:00 2001 From: Corwin Perren Date: Sat, 20 Jan 2018 17:16:01 -0800 Subject: [PATCH 2/9] Added thrid resolution for cameras --- rover/rover_camera/src/rover_camera.cpp | 44 +++++++++++++++---------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/rover/rover_camera/src/rover_camera.cpp b/rover/rover_camera/src/rover_camera.cpp index a42588a..669d9dd 100644 --- a/rover/rover_camera/src/rover_camera.cpp +++ b/rover/rover_camera/src/rover_camera.cpp @@ -16,6 +16,8 @@ int main(int argc, char** argv) int large_image_width; int large_image_height; + int medium_image_width; + int medium_image_height; int small_image_width; int small_image_height; @@ -27,8 +29,10 @@ int main(int argc, char** argv) node_handle.param("large_image_width", large_image_width, 1280); node_handle.param("large_image_height", large_image_height, 720); - node_handle.param("small_image_width", small_image_width, 640); - node_handle.param("small_image_height", small_image_height, 360); + node_handle.param("medium_image_width", medium_image_width, 640); + node_handle.param("medium_image_height", medium_image_height, 360); + node_handle.param("small_image_width", small_image_width, 256); + node_handle.param("small_image_height", small_image_height, 144); cv::VideoCapture cap(capture_device_path); @@ -42,32 +46,38 @@ int main(int argc, char** argv) } std::string large_image_node_name = "image_" + std::to_string(large_image_width) + "x" + std::to_string(large_image_height); + std::string medium_image_node_name = "image_" + std::to_string(medium_image_width) + "x" + std::to_string(medium_image_height); std::string small_image_node_name = "image_" + std::to_string(small_image_width) + "x" + std::to_string(small_image_height); - image_transport::ImageTransport full_res_image_transport(node_handle); - image_transport::ImageTransport lower_res_image_transport(node_handle); + image_transport::ImageTransport large_image_transport(node_handle); + image_transport::ImageTransport medium_image_transport(node_handle); + image_transport::ImageTransport small_image_transport(node_handle); - image_transport::Publisher full_size_publisher = full_res_image_transport.advertise(large_image_node_name, 1); - image_transport::Publisher lower_size_publisher = lower_res_image_transport.advertise(small_image_node_name, 1); + image_transport::Publisher large_image_publisher = large_image_transport.advertise(large_image_node_name, 1); + image_transport::Publisher medium_image_publisher = medium_image_transport.advertise(medium_image_node_name, 1); + image_transport::Publisher small_image_publisher = small_image_transport.advertise(small_image_node_name, 1); - cv::Mat image; - cv::Mat image_smaller; - - ros::Rate loop_rate(fps + 5); + cv::Mat image_large; + cv::Mat image_medium; + cv::Mat image_small; + ros::Rate loop_rate(fps + 2); while (ros::ok()) { - cap.read(image); + cap.read(image_large); - if(!image.empty()){ - cv::resize(image, image_smaller, cv::Size(small_image_width, small_image_height)); + if(!image_large.empty()){ + cv::resize(image_large, image_medium, cv::Size(medium_image_width, medium_image_height)); + cv::resize(image_medium, image_small, cv::Size(small_image_width, small_image_height)); - sensor_msgs::ImagePtr full_res_message = cv_bridge::CvImage(std_msgs::Header(), "bgr8", image).toImageMsg(); - sensor_msgs::ImagePtr lower_res_message = cv_bridge::CvImage(std_msgs::Header(), "bgr8", image_smaller).toImageMsg(); + sensor_msgs::ImagePtr large_image_message = cv_bridge::CvImage(std_msgs::Header(), "bgr8", image_large).toImageMsg(); + sensor_msgs::ImagePtr medium_image_message = cv_bridge::CvImage(std_msgs::Header(), "bgr8", image_medium).toImageMsg(); + sensor_msgs::ImagePtr small_image_message = cv_bridge::CvImage(std_msgs::Header(), "bgr8", image_small).toImageMsg(); - full_size_publisher.publish(full_res_message); - lower_size_publisher.publish(lower_res_message); + large_image_publisher.publish(large_image_message); + medium_image_publisher.publish(medium_image_message); + small_image_publisher.publish(small_image_message); } ros::spinOnce(); From 901a8fe90c073a8041f7aa6efda35d83b90dab2b Mon Sep 17 00:00:00 2001 From: Nick McComb Date: Mon, 22 Jan 2018 17:34:44 -0800 Subject: [PATCH 3/9] Update circuitmaker_stuff.md --- electrical/schematics/circuitmaker_stuff.md | 68 +++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/electrical/schematics/circuitmaker_stuff.md b/electrical/schematics/circuitmaker_stuff.md index 22aea53..eee8c6e 100644 --- a/electrical/schematics/circuitmaker_stuff.md +++ b/electrical/schematics/circuitmaker_stuff.md @@ -62,3 +62,71 @@ Where `descriptor` is either `RAW` or `SYS`. `voltage` is represented as shown i For example: ![power_net_naming](http://nickmccomb.net/wp-content/uploads/2017/12/power_net_naming_iris.png "Power Net Naming for the Iris Project") + +### Exporting your Project + +#### Board Outline +Ensure you have your board outline connector setup. You need an outline defined in the "Outline" layer (mechanical 1) as well as an exact copy on Mechanical 4 (per [CircuitMaker Fourm Post](https://circuitmaker.com/forum/posts/220409)). + +We need both gerber files, and NC Drill files. Gerbers tell the manufacture where to put copper, silkscreen, etc, while the NC Drill files tell them where to drill. Your guess as to why they are different processes. + +#### Outputting Gerbers + +Make sure your file is 100% ready to go (do a final Design Rule Check), and then go to the output tab and select "Gerber". You will have to commit your project to export any manufacturing files. + +Choose the following settings: +![Gerber Settings 1](http://nickmccomb.net/wp-content/uploads/2018/01/2018-01-22-17_14_34-Gerber-Setup.png "Gerber Settings 1") +![Gerber Settings 2](http://nickmccomb.net/wp-content/uploads/2018/01/2018-01-22-17_14_55-Gerber-Setup.png "Gerber Settings 2") + + +Then hit OK, and save your project into the "Node Output Files" folder in the Google Drive (if outputting a node) under your folder's name. + +#### Outputting NC Drill Files + +Select "NC Drill Files" under the output menu. You will have to commit your project again. + +Choose the following settings: +![NC Drill Settings](http://nickmccomb.net/wp-content/uploads/2018/01/2018-01-22-17_18_01-NC-Drill-Setup.png "NC Drill Settings") + +Save this .zip in the same folder as the gerber ones. + +#### Assembling your files for being sent to the manufacturer + +Extract both of the drill files into their own folders. We're looking to establish the following group of files: + +![Gerber Files](https://sites.google.com/a/oregonstate.edu/osurcknowledgebase/_/rsrc/1506362541118/engineering-resources/electrical-engineering/pcb-design/altium-designer-to-df-robot/2015-10-18%2000_43_59-OSH%20Park%20~%20Design%20Submission%20Guidelines.png "Gerber Files") + +Make a folder with a basic board name (e.g. "IrisBoard") that will hold your finalized board generation files. This will be your staging folder. + +Grab the **.TXT** from the "\*\_NC\_Drill" folder, rename it "_boardname_.XLN" and move it into your staging folder. + +Rename the **.GM4** file from the "\_Gerber" folder to "_boardname_.GKO" and move it to your staging folder. + +From the same folder, move the following files into your staging folder: + +* .GBL +* .GBO +* .GBS +* .GTL +* .GTO +* .GTS + +You should now have all the files from the picture above in your staging folder. Make a .zip of this folder. This folder is all you have to send to the manufacturer to make your board. + +#### Verifying you've done this correctly + +Upload your design to [OshPark.com](www.oshpark.com) and make sure that they render it correctly. This is a great first pass indicator to make sure you've done this process correctly. PCBWAY will also check your boards, but this prevents some dumb mistakes in assemling your .zip file. + + +#### Ordering your boards + +Email the Team Lead with your .zip files, and he will place the order for you. Include the following information: + +* Board dimensions in mm +* Desired color for the board (if not Red for Rover) +* Desired copper weight (1oz is the default, unless you have a reason for it to be different) + + + + + From 1938bea5c6c21c604e2d6e8a4fd51f785e153143 Mon Sep 17 00:00:00 2001 From: Dylan Thrush Date: Mon, 22 Jan 2018 19:40:30 -0800 Subject: [PATCH 4/9] Update circuitmaker_stuff.md --- electrical/schematics/circuitmaker_stuff.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electrical/schematics/circuitmaker_stuff.md b/electrical/schematics/circuitmaker_stuff.md index eee8c6e..0dcf5db 100644 --- a/electrical/schematics/circuitmaker_stuff.md +++ b/electrical/schematics/circuitmaker_stuff.md @@ -115,7 +115,7 @@ You should now have all the files from the picture above in your staging folder. #### Verifying you've done this correctly -Upload your design to [OshPark.com](www.oshpark.com) and make sure that they render it correctly. This is a great first pass indicator to make sure you've done this process correctly. PCBWAY will also check your boards, but this prevents some dumb mistakes in assemling your .zip file. +Upload your design to [OshPark.com](https://oshpark.com/) and make sure that they render it correctly. This is a great first pass indicator to make sure you've done this process correctly. PCBWAY will also check your boards, but this prevents some dumb mistakes in assemling your .zip file. #### Ordering your boards From 08fc436fa277f839826840774823bae37c9bc45e Mon Sep 17 00:00:00 2001 From: Corwin Perren Date: Mon, 22 Jan 2018 22:42:17 -0800 Subject: [PATCH 5/9] Updated UI Files --- ground_station/resources/ui/left_screen.ui | 214 ++++++++++++++++-- ground_station/resources/ui/right_screen.ui | 237 +++++++++++++------- 2 files changed, 352 insertions(+), 99 deletions(-) diff --git a/ground_station/resources/ui/left_screen.ui b/ground_station/resources/ui/left_screen.ui index 08f5409..bdafc73 100644 --- a/ground_station/resources/ui/left_screen.ui +++ b/ground_station/resources/ui/left_screen.ui @@ -39,38 +39,204 @@ color: #DCDCDC; - + 0 - - 0 - - - 0 - - - 0 - - + 0 - - - - 74 - 75 - true - + + + 0 - - UI File 1 (Left Screen) + + + + + 0 + 0 + + + + + 640 + 540 + + + + + 640 + 540 + + + + background-color:lightgreen; + + + + + + + + 0 + 0 + + + + + 640 + 540 + + + + + 640 + 540 + + + + 0 + + + + Recording + + + + + Settings + + + + + + + + + + 0 - - Qt::AlignCenter - - + + + + + 0 + 0 + + + + + 1280 + 720 + + + + + 1280 + 720 + + + + background-color:lightblue; + + + + + + + + + + 0 + + + + + + 0 + 0 + + + + + 640 + 360 + + + + + 640 + 360 + + + + background-color:salmon; + + + + 0 + + + 0 + + + + + + + + + 0 + 0 + + + + + 320 + 360 + + + + + 320 + 360 + + + + background-color:teal; + + + + + + + + 0 + 0 + + + + + 320 + 360 + + + + + 320 + 360 + + + + background-color:lightgreen + + + + + + diff --git a/ground_station/resources/ui/right_screen.ui b/ground_station/resources/ui/right_screen.ui index 0df80bf..8f8e907 100644 --- a/ground_station/resources/ui/right_screen.ui +++ b/ground_station/resources/ui/right_screen.ui @@ -39,65 +39,136 @@ color: #DCDCDC; - - - 0 - - - 0 - - - 0 - - - 0 - + 0 - - - - - 1280 - 720 - - - - - 1280 - 720 - - - - background-color: darkblue; - - - - - - - - + + QLayout::SetDefaultConstraint + + + 0 + + + 0 - + + + 0 + + + + + + 0 + 0 + + + + + 640 + 720 + + + + + 640 + 720 + + + + background-color:orange; + + + + + + + 0 + + + QLayout::SetNoConstraint + + + + + + 0 + 0 + + + + + 320 + 360 + + + + + 320 + 360 + + + + background-color:lightgreen;; + + + + + + + + 0 + 0 + + + + + 320 + 360 + + + + + 320 + 360 + + + + background-color:salmon; + + + + + + + + + + + + + 0 + + + - 640 - 360 + 1280 + 720 - 640 - 360 + 1280 + 720 - background-color:darkgreen; + background-color: darkblue; @@ -105,42 +176,58 @@ color: #DCDCDC; - - - - 640 - 360 - + + + 0 - - - 640 - 360 - - - - background-color:maroon; - - - - - + + + + + 640 + 360 + + + + + 640 + 360 + + + + background-color:darkgreen; + + + + + + + + + + + 640 + 360 + + + + + 640 + 360 + + + + background-color:maroon; + + + + + + + - - - - Qt::Horizontal - - - - 40 - 20 - - - - From bd940996248b7709df3ba08473185871f64cd865 Mon Sep 17 00:00:00 2001 From: Nick McComb Date: Mon, 22 Jan 2018 22:54:36 -0800 Subject: [PATCH 6/9] Update circuitmaker_stuff.md --- electrical/schematics/circuitmaker_stuff.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electrical/schematics/circuitmaker_stuff.md b/electrical/schematics/circuitmaker_stuff.md index 0dcf5db..01f8826 100644 --- a/electrical/schematics/circuitmaker_stuff.md +++ b/electrical/schematics/circuitmaker_stuff.md @@ -92,7 +92,7 @@ Save this .zip in the same folder as the gerber ones. #### Assembling your files for being sent to the manufacturer -Extract both of the drill files into their own folders. We're looking to establish the following group of files: +Extract both of the .zip files into their own folders. We're looking to establish the following group of files: ![Gerber Files](https://sites.google.com/a/oregonstate.edu/osurcknowledgebase/_/rsrc/1506362541118/engineering-resources/electrical-engineering/pcb-design/altium-designer-to-df-robot/2015-10-18%2000_43_59-OSH%20Park%20~%20Design%20Submission%20Guidelines.png "Gerber Files") From 4d071d304ed3781c89f4a5067644981131a33226 Mon Sep 17 00:00:00 2001 From: Corwin Perren Date: Tue, 23 Jan 2018 09:07:06 -0800 Subject: [PATCH 7/9] Updated layout of the main launcher, as well as ui files. --- .../DriveSystems/RoverDriveSender.py | 50 ++++ .../Framework/DriveSystems/__init__.py | 0 .../VideoSystems/RoverVideoCoordinator.py | 65 +++++ .../VideoSystems/RoverVideoReceiver.py | 136 ++++++++++ .../VideoSystems/RoverVideoReceiverOld.py | 134 ++++++++++ .../Framework/VideoSystems/__init__.py | 0 ground_station/Framework/__init__.py | 0 .../ui => Resources/Ui}/left_screen.ui | 0 .../ui => Resources/Ui}/right_screen.ui | 0 ground_station/RoverGroundStation.py | 246 ++++-------------- 10 files changed, 435 insertions(+), 196 deletions(-) create mode 100644 ground_station/Framework/DriveSystems/RoverDriveSender.py create mode 100644 ground_station/Framework/DriveSystems/__init__.py create mode 100644 ground_station/Framework/VideoSystems/RoverVideoCoordinator.py create mode 100644 ground_station/Framework/VideoSystems/RoverVideoReceiver.py create mode 100644 ground_station/Framework/VideoSystems/RoverVideoReceiverOld.py create mode 100644 ground_station/Framework/VideoSystems/__init__.py create mode 100644 ground_station/Framework/__init__.py rename ground_station/{resources/ui => Resources/Ui}/left_screen.ui (100%) rename ground_station/{resources/ui => Resources/Ui}/right_screen.ui (100%) diff --git a/ground_station/Framework/DriveSystems/RoverDriveSender.py b/ground_station/Framework/DriveSystems/RoverDriveSender.py new file mode 100644 index 0000000..363808a --- /dev/null +++ b/ground_station/Framework/DriveSystems/RoverDriveSender.py @@ -0,0 +1,50 @@ +import sys +from PyQt5 import QtWidgets, QtCore, QtGui, uic +import signal +import rospy +import time +from cv_bridge import CvBridge, CvBridgeError +import cv2 +import qimage2ndarray +import numpy as np + +from geometry_msgs.msg import Twist +from sensor_msgs.msg import CompressedImage +#from sensor_msgs.msg import Image, CompressedImage + + +class DriveTest(QtCore.QThread): + publish_message_signal = QtCore.pyqtSignal() + + def __init__(self): + super(DriveTest, self).__init__() + + self.not_abort = True + + self.message = None + self.publisher = rospy.Publisher("/cmd_vel", Twist, queue_size=10) + + rospy.init_node("test") + + def run(self): + # TODO: Thread starting message here + while self.not_abort: + self.message = Twist() + + self.message.linear.x = 1.0 + self.message.angular.z = 1.0 + + self.publisher.publish(self.message) + + self.msleep(100) + # TODO: Thread ending message here + + def __publish_message(self): + pass + + def setup_start_and_kill_signals(self, start_signal, signals_and_slots_signal, kill_signal): + start_signal.connect(self.start) + kill_signal.connect(self.on_kill_threads_requested__slot) + + def on_kill_threads_requested__slot(self): + self.not_abort = False diff --git a/ground_station/Framework/DriveSystems/__init__.py b/ground_station/Framework/DriveSystems/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ground_station/Framework/VideoSystems/RoverVideoCoordinator.py b/ground_station/Framework/VideoSystems/RoverVideoCoordinator.py new file mode 100644 index 0000000..24ef9c4 --- /dev/null +++ b/ground_station/Framework/VideoSystems/RoverVideoCoordinator.py @@ -0,0 +1,65 @@ +##################################### +# Imports +##################################### +# Python native imports +from PyQt5 import QtCore, QtGui, QtWidgets +import logging +import cv2 +import numpy as np +import qimage2ndarray +import pprint + +import rospy +from cv_bridge import CvBridge +from sensor_msgs.msg import CompressedImage + +# Custom Imports + +##################################### +# Global Variables +##################################### +FONT = cv2.FONT_HERSHEY_TRIPLEX + + +##################################### +# RoverVideoReceiver Class Definition +##################################### +class RoverVideoReceiver(QtCore.QThread): + def __init__(self, shared_objects): + super(RoverVideoReceiver, self).__init__() + + # ########## Reference to class init variables ########## + self.shared_objects = shared_objects + self.right_screen = self.shared_objects["screens"]["right_screen"] + self.primary_video_display_label = self.right_screen.primary_video_label # type:QtWidgets.QLabel + self.primary_video_display_label = self.right_screen.secondary_video_label # type:QtWidgets.QLabel + self.primary_video_display_label = self.right_screen.tertiary_video_label # type:QtWidgets.QLabel + + # ########## Get the settings instance ########## + self.settings = QtCore.QSettings() + + # ########## Get the Pick And Plate instance of the logger ########## + self.logger = logging.getLogger("groundstation") + + # ########## Thread Flags ########## + self.run_thread_flag = True + + def run(self): + self.logger.debug("Starting Video Coordinator Thread") + + while self.run_thread_flag: + + self.msleep(100) + + self.logger.debug("Stopping Video Coordinator Thread") + + def connect_signals_and_slots(self): + pass + + def setup_signals(self, start_signal, signals_and_slots_signal, kill_signal): + start_signal.connect(self.start) + signals_and_slots_signal.connect(self.connect_signals_and_slots) + kill_signal.connect(self.on_kill_threads_requested__slot) + + def on_kill_threads_requested__slot(self): + self.run_thread_flag = False \ No newline at end of file diff --git a/ground_station/Framework/VideoSystems/RoverVideoReceiver.py b/ground_station/Framework/VideoSystems/RoverVideoReceiver.py new file mode 100644 index 0000000..56b4ded --- /dev/null +++ b/ground_station/Framework/VideoSystems/RoverVideoReceiver.py @@ -0,0 +1,136 @@ +##################################### +# Imports +##################################### +# Python native imports +from PyQt5 import QtCore, QtGui, QtWidgets +import logging +import cv2 +import numpy as np +import qimage2ndarray +import pprint + +import rospy +from cv_bridge import CvBridge +from sensor_msgs.msg import CompressedImage + +# Custom Imports + +##################################### +# Global Variables +##################################### +FONT = cv2.FONT_HERSHEY_TRIPLEX + + +##################################### +# RoverVideoReceiver Class Definition +##################################### +class RoverVideoReceiver(QtCore.QThread): + publish_message_signal = QtCore.pyqtSignal() + image_ready_signal = QtCore.pyqtSignal() + + def __init__(self, shared_objects, video_display_label, topic_path): + super(RoverVideoReceiver, self).__init__() + + # ########## Reference to class init variables ########## + self.shared_objects = shared_objects + self.video_display_label = video_display_label # type:QtWidgets.QLabel + self.topic_path = topic_path + + # ########## Get the settings instance ########## + self.settings = QtCore.QSettings() + + # ########## Get the Pick And Plate instance of the logger ########## + self.logger = logging.getLogger("groundstation") + + # ########## Thread Flags ########## + self.run_thread_flag = True + + # ########## Class Variables ########## + # Subscription variables + # self.video_subscriber = rospy.Subscriber(self.topic_path, CompressedImage, + # self.__image_data_received_callback) # type: rospy.Subscriber + + topics = rospy.get_published_topics(self.topic_path) + + for group in topics: + if "image_" in group[0]: + print group[0] + + self.subscription_queue_size = 10 + + # Steam name variable + self.video_name = self.topic_path.split("/")[2].replace("_", " ").title() + + # Image variables + self.raw_image = None + self.opencv_image = None + self.pixmap = None + + # Processing variables + self.bridge = CvBridge() # OpenCV ROS Video Data Processor + self.video_enabled = False + self.new_frame = False + + # Assign local callbacks + self.__set_local_callbacks() + + def run(self): + self.logger.debug("Starting \"%s\" Thread") + + while self.run_thread_flag: + if self.video_enabled: + self.__show_video_enabled() + else: + self.__show_video_disabled() + + self.msleep(18) + + self.logger.debug("Stopping \"%s\" Thread") + + def __show_video_enabled(self): + if self.new_frame: + self.opencv_image = self.bridge.compressed_imgmsg_to_cv2(self.raw_image, "rgb8") + self.opencv_image = cv2.resize(self.opencv_image, (1280, 720)) + self.pixmap = QtGui.QPixmap.fromImage(qimage2ndarray.array2qimage(self.opencv_image)) + self.image_ready_signal.emit() + self.new_frame = False + + def __show_video_disabled(self): + if self.new_frame: + fps_image = np.zeros((720, 1280, 3), np.uint8) + + self.pixmap = QtGui.QPixmap.fromImage(qimage2ndarray.array2qimage(fps_image)) + self.image_ready_signal.emit() + self.new_frame = False + + + def __on_image_update_ready(self): + self.video_display_label.setPixmap(self.pixmap) + + def __image_data_received_callback(self, raw_image): + self.raw_image = raw_image + self.new_frame = True + + def __set_local_callbacks(self): + self.video_display_label.mouseDoubleClickEvent = self.toggle_video_display + + def toggle_video_display(self, _): + if self.video_enabled: + self.video_subscriber.unregister() + self.new_frame = True + self.video_enabled = False + else: + self.video_subscriber = rospy.Subscriber(self.topic_path, CompressedImage, + self.__image_data_received_callback) + self.video_enabled = True + + def connect_signals_and_slots(self): + self.image_ready_signal.connect(self.__on_image_update_ready) + + def setup_signals(self, start_signal, signals_and_slots_signal, kill_signal): + start_signal.connect(self.start) + signals_and_slots_signal.connect(self.connect_signals_and_slots) + kill_signal.connect(self.on_kill_threads_requested__slot) + + def on_kill_threads_requested__slot(self): + self.run_thread_flag = False diff --git a/ground_station/Framework/VideoSystems/RoverVideoReceiverOld.py b/ground_station/Framework/VideoSystems/RoverVideoReceiverOld.py new file mode 100644 index 0000000..7bc1435 --- /dev/null +++ b/ground_station/Framework/VideoSystems/RoverVideoReceiverOld.py @@ -0,0 +1,134 @@ +import sys +from PyQt5 import QtWidgets, QtCore, QtGui, uic +import signal +import rospy +import time +from cv_bridge import CvBridge, CvBridgeError +import cv2 +import qimage2ndarray +import numpy as np + +from geometry_msgs.msg import Twist +from sensor_msgs.msg import CompressedImage +#from sensor_msgs.msg import Image, CompressedImage + + +class VideoTest(QtCore.QThread): + publish_message_signal = QtCore.pyqtSignal() + image_ready_signal = QtCore.pyqtSignal() + + def __init__(self, shared_objects, screen_label, video_size=None, sub_path=None): + super(VideoTest, self).__init__() + + self.not_abort = True + + self.shared_objects = shared_objects + + self.right_screen_label = screen_label # type: QtGui.QPixmap + self.video_size = video_size + + self.message = None + + self.publisher = rospy.Subscriber(sub_path, CompressedImage, self.__receive_message) + + self.raw_image = None + self.cv_image = None + self.pixmap = None + self.bridge = CvBridge() + # self.bridge.com + + self.new_frame = False + self.frame_count = 0 + self.last_frame_time = time.time() + self.fps = 0 + + self.name = sub_path.split("/")[2].replace("_", " ").title() + + self.font = cv2.FONT_HERSHEY_TRIPLEX + + thickness = 1 + baseline = 0 + + text_size = cv2.getTextSize(self.name, self.font, thickness, baseline) + print text_size + + text_width, text_height = text_size[0] + + width = text_width + 10 + height = text_height + 20 + + self.blank_image = np.zeros((height, width, 3), np.uint8) + cv2.putText(self.blank_image, self.name, ((width - text_width) / 2, int((height * 2) / 3)), self.font, 1, (255, 255, 255), 1, cv2.LINE_AA) + self.blank_image = cv2.resize(self.blank_image, (width / 2, height / 2)) + + def run(self): + # TODO: Thread starting message here + y_offset = 0 + x_offset = 0 + + while self.not_abort: + if self.raw_image and self.new_frame: + self.cv_image = self.bridge.compressed_imgmsg_to_cv2(self.raw_image, "rgb8") + + self.cv_image = self.__show_fps(self.cv_image) + + self.cv_image[y_offset:y_offset + self.blank_image.shape[0], x_offset:x_offset + self.blank_image.shape[1]] = self.blank_image + + if self.video_size: + self.cv_image = cv2.resize(self.cv_image, self.video_size) + self.pixmap = QtGui.QPixmap.fromImage(qimage2ndarray.array2qimage(self.cv_image)) + self.image_ready_signal.emit() + self.new_frame = False + + if (time.time() - self.last_frame_time) >= 0.5: + self.fps = int(self.frame_count / (time.time() - self. last_frame_time)) + + self.last_frame_time = time.time() + self.frame_count = 0 + + self.msleep(18) + # TODO: Thread ending message here + + def __show_fps(self, image): + thickness = 1 + baseline = 0 + + fps_string = str(self.fps) + + text_size = cv2.getTextSize(fps_string, self.font, thickness, baseline) + + text_width, text_height = text_size[0] + + width = text_width + 10 + height = text_height + 20 + + fps_image = np.zeros((height, width, 3), np.uint8) + + cv2.putText(fps_image, fps_string, ((width - text_width) / 2, int((height * 2) / 3)), self.font, 1, (255, 255, 255), 1, cv2.LINE_AA) + fps_image = cv2.resize(fps_image, (width / 2, height / 2)) + + y_offset = 0 + x_offset = (image.shape[1] - fps_image.shape[1]) / 2 + + image[y_offset:y_offset + fps_image.shape[0], x_offset:x_offset + fps_image.shape[1]] = fps_image + + return image + + def __on_image_update_ready(self): + self.right_screen_label.setPixmap(self.pixmap) + + def __receive_message(self, message): + self.raw_image = message + self.new_frame = True + self.frame_count += 1 + + def connect_signals_and_slots(self): + self.image_ready_signal.connect(self.__on_image_update_ready) + + def setup_signals(self, start_signal, signals_and_slots_signal, kill_signal): + start_signal.connect(self.start) + signals_and_slots_signal.connect(self.connect_signals_and_slots) + kill_signal.connect(self.on_kill_threads_requested__slot) + + def on_kill_threads_requested__slot(self): + self.not_abort = False \ No newline at end of file diff --git a/ground_station/Framework/VideoSystems/__init__.py b/ground_station/Framework/VideoSystems/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ground_station/Framework/__init__.py b/ground_station/Framework/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ground_station/resources/ui/left_screen.ui b/ground_station/Resources/Ui/left_screen.ui similarity index 100% rename from ground_station/resources/ui/left_screen.ui rename to ground_station/Resources/Ui/left_screen.ui diff --git a/ground_station/resources/ui/right_screen.ui b/ground_station/Resources/Ui/right_screen.ui similarity index 100% rename from ground_station/resources/ui/right_screen.ui rename to ground_station/Resources/Ui/right_screen.ui diff --git a/ground_station/RoverGroundStation.py b/ground_station/RoverGroundStation.py index ed665b5..ca1bb63 100755 --- a/ground_station/RoverGroundStation.py +++ b/ground_station/RoverGroundStation.py @@ -1,10 +1,4 @@ #!/usr/bin/env python - -""" - Main file used to launch the Rover Base Station - No other files should be used for launching this application. -""" - ##################################### # Imports ##################################### @@ -13,190 +7,27 @@ import sys from PyQt5 import QtWidgets, QtCore, QtGui, uic import signal import rospy -import time -from cv_bridge import CvBridge, CvBridgeError -import cv2 -import qimage2ndarray -import numpy as np -from geometry_msgs.msg import Twist -from sensor_msgs.msg import CompressedImage -#from sensor_msgs.msg import Image, CompressedImage # Custom Imports +import Framework.VideoSystems.RoverVideoCoordinator as RoverVideoCoordinator ##################################### # Global Variables ##################################### -UI_FILE_LEFT = "resources/ui/left_screen.ui" -UI_FILE_RIGHT = "resources/ui/right_screen.ui" +UI_FILE_LEFT = "Resources/Ui/left_screen.ui" +UI_FILE_RIGHT = "Resources/Ui/right_screen.ui" ##################################### # Class Organization ##################################### # Class Name: # "init" +# "run (if there)" - personal pref # "private methods" # "public methods, minus slots" # "slot methods" # "static methods" - - -class VideoTest(QtCore.QThread): - ROS_CORE_COMMAND = ["roscore"] - - publish_message_signal = QtCore.pyqtSignal() - image_ready_signal = QtCore.pyqtSignal() - - def __init__(self, screen_label, video_size=None, sub_path=None): - super(VideoTest, self).__init__() - - self.not_abort = True - - self.right_screen_label = screen_label # type: QtGui.QPixmap - self.video_size = video_size - - #rospy.init_node("video_test") - - self.message = None - - self.publisher = rospy.Subscriber(sub_path, CompressedImage, self.__receive_message) - - self.raw_image = None - self.cv_image = None - self.pixmap = None - self.bridge = CvBridge() - # self.bridge.com - - self.image_ready_signal.connect(self.__on_image_update_ready) - - self.new_frame = False - self.frame_count = 0 - self.last_frame_time = time.time() - self.fps = 0 - - self.name = sub_path.split("/")[2].replace("_", " ").title() - - self.font = cv2.FONT_HERSHEY_TRIPLEX - - thickness = 1 - baseline = 0 - - text_size = cv2.getTextSize(self.name, self.font, thickness, baseline) - print text_size - - text_width, text_height = text_size[0] - - width = text_width + 10 - height = text_height + 20 - - self.blank_image = np.zeros((height, width, 3), np.uint8) - cv2.putText(self.blank_image, self.name, ((width - text_width) / 2, int((height * 2) / 3)), self.font, 1, (255, 255, 255), 1, cv2.LINE_AA) - self.blank_image = cv2.resize(self.blank_image, (width / 2, height / 2)) - - def run(self): - # TODO: Thread starting message here - y_offset = 0 - x_offset = 0 - - while self.not_abort: - if self.raw_image and self.new_frame: - self.cv_image = self.bridge.compressed_imgmsg_to_cv2(self.raw_image, "rgb8") - - self.cv_image = self.__show_fps(self.cv_image) - - self.cv_image[y_offset:y_offset + self.blank_image.shape[0], x_offset:x_offset + self.blank_image.shape[1]] = self.blank_image - - if self.video_size: - self.cv_image = cv2.resize(self.cv_image, self.video_size) - self.pixmap = QtGui.QPixmap.fromImage(qimage2ndarray.array2qimage(self.cv_image)) - self.image_ready_signal.emit() - self.new_frame = False - - if (time.time() - self.last_frame_time) >= 0.5: - self.fps = int(self.frame_count / (time.time() - self. last_frame_time)) - - self.last_frame_time = time.time() - self.frame_count = 0 - - self.msleep(18) - # TODO: Thread ending message here - - def __show_fps(self, image): - thickness = 1 - baseline = 0 - - fps_string = str(self.fps) - - text_size = cv2.getTextSize(fps_string, self.font, thickness, baseline) - - text_width, text_height = text_size[0] - - width = text_width + 10 - height = text_height + 20 - - fps_image = np.zeros((height, width, 3), np.uint8) - - cv2.putText(fps_image, fps_string, ((width - text_width) / 2, int((height * 2) / 3)), self.font, 1, (255, 255, 255), 1, cv2.LINE_AA) - fps_image = cv2.resize(fps_image, (width / 2, height / 2)) - - y_offset = 0 - x_offset = (image.shape[1] - fps_image.shape[1]) / 2 - - image[y_offset:y_offset + fps_image.shape[0], x_offset:x_offset + fps_image.shape[1]] = fps_image - - return image - - def __on_image_update_ready(self): - self.right_screen_label.setPixmap(self.pixmap) - - def __receive_message(self, message): - self.raw_image = message - self.new_frame = True - self.frame_count += 1 - - def setup_start_and_kill_signals(self, start_signal, kill_signal): - start_signal.connect(self.start) - kill_signal.connect(self.on_kill_threads_requested__slot) - - def on_kill_threads_requested__slot(self): - self.not_abort = False - - -class DriveTest(QtCore.QThread): - publish_message_signal = QtCore.pyqtSignal() - - def __init__(self): - super(DriveTest, self).__init__() - - self.not_abort = True - - self.message = None - self.publisher = rospy.Publisher("/cmd_vel", Twist, queue_size=10) - - rospy.init_node("test") - - def run(self): - # TODO: Thread starting message here - while self.not_abort: - self.message = Twist() - - self.message.linear.x = 1.0 - self.message.angular.z = 1.0 - - self.publisher.publish(self.message) - - self.msleep(100) - # TODO: Thread ending message here - - def __publish_message(self): - pass - - def setup_start_and_kill_signals(self, start_signal, kill_signal): - start_signal.connect(self.start) - kill_signal.connect(self.on_kill_threads_requested__slot) - - def on_kill_threads_requested__slot(self): - self.not_abort = False +# "run (if there)" - personal pref ##################################### @@ -223,47 +54,70 @@ class GroundStation(QtCore.QObject): RIGHT_SCREEN_ID = 1 start_threads_signal = QtCore.pyqtSignal() + connect_signals_and_slots_signal = QtCore.pyqtSignal() kill_threads_signal = QtCore.pyqtSignal() def __init__(self, parent=None,): # noinspection PyArgumentList super(GroundStation, self).__init__(parent) - # self.left_screen = self.create_application_window(UI_FILE_LEFT, "Rover Ground Station Left Screen", - # self.LEFT_SCREEN_ID) # type: ApplicationWindow - self.right_screen = self.create_application_window(UI_FILE_RIGHT, "Rover Ground Station Right Screen", - self.LEFT_SCREEN_ID) # type: ApplicationWindow + rospy.init_node("ground_station") - # Start ROSCORE - self.video_test = VideoTest(self.right_screen.primary_video_label, (1280, 720), sub_path="/cameras/main_navigation/image_640x360/compressed") - self.video_test_1 = VideoTest(self.right_screen.secondary_video_label, (640, 360), sub_path="/cameras/chassis/image_640x360/compressed") - self.video_test_2 = VideoTest(self.right_screen.tertiary_video_label, (640, 360), sub_path="/cameras/undercarriage/image_640x360/compressed") - self.drive_test = DriveTest() + self.shared_objects = { + "screens": {}, + "regular_classes": {}, + "threaded_classes": {} + } - # Keep track of all threads - self.threads = [] - self.threads.append(self.drive_test) - self.threads.append(self.video_test) - self.threads.append(self.video_test_1) - self.threads.append(self.video_test_2) + # ###### Instantiate Left And Right Screens ##### + self.shared_objects["screens"]["left_screen"] = \ + self.create_application_window(UI_FILE_LEFT, "Rover Ground Station Left Screen", + self.LEFT_SCREEN_ID) # type: ApplicationWindow - # Connect signals - for thread in self.threads: - thread.setup_start_and_kill_signals(self.start_threads_signal, self.kill_threads_signal) + self.shared_objects["screens"]["right_screen"] = \ + self.create_application_window(UI_FILE_RIGHT, "Rover Ground Station Right Screen", + self.RIGHT_SCREEN_ID) # type: ApplicationWindow + # ##### Instantiate Simple Classes ##### + + # ##### Instantiate Threaded Classes ##### + self + # self.__add_thread("Primary Video", + # RoverVideoReceiver.RoverVideoReceiver( + # self.shared_objects, + # self.shared_objects["screens"]["right_screen"].primary_video_label, + # "/cameras/main_navigation/")) + # self.__add_thread("Secondary Video", + # RoverVideoReceiverOld.VideoTest( + # self.shared_objects, + # self.shared_objects["screens"]["right_screen"].secondary_video_label, + # (640, 360), + # sub_path="/cameras/chassis/image_640x360/compressed")) + # self.__add_thread("Tertiary Video", + # RoverVideoReceiverOld.VideoTest( + # self.shared_objects, + # self.shared_objects["screens"]["right_screen"].tertiary_video_label, + # (640, 360), + # sub_path="/cameras/undercarriage/image_640x360/compressed")) + + self.connect_signals_and_slots_signal.emit() self.__connect_signals_to_slots() self.start_threads_signal.emit() + def __add_thread(self, thread_name, instance): + self.shared_objects["threaded_classes"][thread_name] = instance + instance.setup_signals(self.start_threads_signal, self.connect_signals_and_slots_signal, self.kill_threads_signal) + def __connect_signals_to_slots(self): - # self.left_screen.exit_requested_signal.connect(self.on_exit_requested__slot) - self.right_screen.exit_requested_signal.connect(self.on_exit_requested__slot) + self.shared_objects["screens"]["left_screen"].exit_requested_signal.connect(self.on_exit_requested__slot) + self.shared_objects["screens"]["right_screen"].exit_requested_signal.connect(self.on_exit_requested__slot) def on_exit_requested__slot(self): self.kill_threads_signal.emit() # Wait for Threads - for thread in self.threads: - thread.wait() + for thread in self.threads["threaded_classes"]: + self.threads["threaded_classes"][thread].wait() QtGui.QGuiApplication.exit() From 60ea2d9339e11a6ec749b47d5b5487ccb39d8f09 Mon Sep 17 00:00:00 2001 From: Corwin Perren Date: Tue, 23 Jan 2018 09:27:40 -0800 Subject: [PATCH 8/9] Added a dark stylesheet so things look nice --- ground_station/.idea/deployment.xml | 2 +- .../inspectionProfiles/profiles_settings.xml | 7 - ground_station/.idea/webServers.xml | 2 +- ground_station/.idea/workspace.xml | 130 +++++++++++------- ground_station/RoverGroundStation.py | 7 +- 5 files changed, 87 insertions(+), 61 deletions(-) delete mode 100644 ground_station/.idea/inspectionProfiles/profiles_settings.xml diff --git a/ground_station/.idea/deployment.xml b/ground_station/.idea/deployment.xml index cc6653c..46cc65e 100644 --- a/ground_station/.idea/deployment.xml +++ b/ground_station/.idea/deployment.xml @@ -5,7 +5,7 @@ - + diff --git a/ground_station/.idea/inspectionProfiles/profiles_settings.xml b/ground_station/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index c23ecac..0000000 --- a/ground_station/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/ground_station/.idea/webServers.xml b/ground_station/.idea/webServers.xml index d47e248..52f47c3 100644 --- a/ground_station/.idea/webServers.xml +++ b/ground_station/.idea/webServers.xml @@ -3,7 +3,7 @@