diff --git a/parallax/__init__.py b/parallax/__init__.py index ea068cb..88f2bfe 100644 --- a/parallax/__init__.py +++ b/parallax/__init__.py @@ -4,7 +4,7 @@ import os -__version__ = "0.37.22" +__version__ = "0.37.23" # allow multiple OpenMP instances os.environ["KMP_DUPLICATE_LIB_OK"] = "True" diff --git a/parallax/calculator.py b/parallax/calculator.py new file mode 100644 index 0000000..98b0248 --- /dev/null +++ b/parallax/calculator.py @@ -0,0 +1,186 @@ +import os +import logging +import numpy as np +from PyQt5.QtWidgets import QWidget, QGroupBox, QLineEdit, QPushButton +from PyQt5.uic import loadUi +from PyQt5.QtCore import Qt + +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + +package_dir = os.path.dirname(os.path.abspath(__file__)) +debug_dir = os.path.join(os.path.dirname(package_dir), "debug") +ui_dir = os.path.join(os.path.dirname(package_dir), "ui") + +class Calculator(QWidget): + def __init__(self, model): + super().__init__() + self.model = model + + self.ui = loadUi(os.path.join(ui_dir, "calc.ui"), self) + self.setWindowTitle(f"Calculator") + self.setWindowFlags(Qt.Window | Qt.WindowMinimizeButtonHint | \ + Qt.WindowMaximizeButtonHint | Qt.WindowCloseButtonHint) + + # Create the number of GroupBox for the number of stages + self.create_stage_groupboxes() + self.model.add_calc_instance(self) + + def show(self): + # Refresh the list of stage to show + self.set_calc_functions() + # Show + super().show() # Show the widget + + def set_calc_functions(self): + for stage_sn, item in self.model.transforms.items(): + transM, scale = item[0], item[1] + if transM is not None: # Set calc function for calibrated stages + push_button = self.findChild(QPushButton, f"convert_{stage_sn}") + if not push_button: + logger.warning(f"Error: QPushButton for {stage_sn} not found") + continue + self.enable(stage_sn) + push_button.clicked.connect(self.create_convert_function(stage_sn, transM, scale)) + else: # Block calc functions for uncalibrated stages + self.disable(stage_sn) + + def create_convert_function(self, stage_sn, transM, scale): + logger.debug(f"\n=== Creating convert function ===") + logger.debug(f"Stage SN: {stage_sn}") + logger.debug(f"transM: {transM}") + logger.debug(f"scale: {scale}") + return lambda: self.convert(stage_sn, transM, scale) + + def convert(self, sn, transM, scale): + # Enable the groupBox for the stage + globalX = self.findChild(QLineEdit, f"globalX_{sn}").text() + globalY = self.findChild(QLineEdit, f"globalY_{sn}").text() + globalZ = self.findChild(QLineEdit, f"globalZ_{sn}").text() + localX = self.findChild(QLineEdit, f"localX_{sn}").text() + localY = self.findChild(QLineEdit, f"localY_{sn}").text() + localZ = self.findChild(QLineEdit, f"localZ_{sn}").text() + + logger.debug("- Convert -") + logger.debug(f"Global: {globalX}, {globalY}, {globalZ}") + logger.debug(f"Local: {localX}, {localY}, {localZ}") + trans_type, local_pts, global_pts = self.get_transform_type(globalX, globalY, globalZ, localX, localY, localZ) + if trans_type == "global_to_local": + local_pts_ret = self.apply_inverse_transformation(global_pts, transM, scale) + self.show_local_pts_result(sn, local_pts_ret) + elif trans_type == "local_to_global": + global_pts_ret = self.apply_transformation(local_pts, transM, scale) + self.show_global_pts_result(sn, global_pts_ret) + else: + logger.warning(f"Error: Invalid transforsmation type for {sn}") + return + + def show_local_pts_result(self, sn, local_pts): + # Show the local points in the QLineEdit + self.findChild(QLineEdit, f"localX_{sn}").setText(f"{local_pts[0]:.2f}") + self.findChild(QLineEdit, f"localY_{sn}").setText(f"{local_pts[1]:.2f}") + self.findChild(QLineEdit, f"localZ_{sn}").setText(f"{local_pts[2]:.2f}") + + def show_global_pts_result(self, sn, global_pts): + self.findChild(QLineEdit, f"globalX_{sn}").setText(f"{global_pts[0]:.2f}") + self.findChild(QLineEdit, f"globalY_{sn}").setText(f"{global_pts[1]:.2f}") + self.findChild(QLineEdit, f"globalZ_{sn}").setText(f"{global_pts[2]:.2f}") + + def get_transform_type(self, globalX, globalY, globalZ, localX, localY, localZ): + def is_valid_number(s): + try: + float(s) + return True + except ValueError: + return False + + # Strip any whitespace or tabs from the inputs + globalX = globalX.strip() if globalX else "" + globalY = globalY.strip() if globalY else "" + globalZ = globalZ.strip() if globalZ else "" + localX = localX.strip() if localX else "" + localY = localY.strip() if localY else "" + localZ = localZ.strip() if localZ else "" + + # Check if all global and local coordinates are valid numbers + global_valid = all(is_valid_number(val) for val in [globalX, globalY, globalZ]) + local_valid = all(is_valid_number(val) for val in [localX, localY, localZ]) + + if global_valid and local_valid: + # Convert to float + global_pts = [float(globalX), float(globalY), float(globalZ)] + local_pts = [float(localX), float(localY), float(localZ)] + return "global_to_local", local_pts, global_pts + elif local_valid: + local_pts = [float(localX), float(localY), float(localZ)] + return "local_to_global", local_pts, None + elif global_valid: + global_pts = [float(globalX), float(globalY), float(globalZ)] + return "global_to_local", None, global_pts + else: + return None, None, None + + def apply_transformation(self, local_point_, transM_LR, scale): + local_point = local_point_ * scale + local_point = np.append(local_point, 1) + global_point = np.dot(transM_LR, local_point) + logger.debug(f"local_to_global: {local_point_} -> {global_point[:3]}") + logger.debug(f"R: {transM_LR[:3, :3]}\nT: {transM_LR[:3, 3]}") + return global_point[:3] + + def apply_inverse_transformation(self, global_point, transM_LR, scale): + # Transpose the 3x3 rotation part + R_T = transM_LR[:3, :3].T + local_point = np.dot(R_T, global_point - transM_LR[:3, 3]) + logger.debug(f"global_to_local {global_point} -> {local_point / scale}") + logger.debug(f"R.T: {R_T}\nT: {transM_LR[:3, 3]}") + return local_point / scale + + def disable(self, sn): + # Clear the QLineEdit for the stage + self.findChild(QLineEdit, f"localX_{sn}").setText(f"") + self.findChild(QLineEdit, f"localY_{sn}").setText(f"") + self.findChild(QLineEdit, f"localZ_{sn}").setText(f"") + self.findChild(QLineEdit, f"globalX_{sn}").setText(f"") + self.findChild(QLineEdit, f"globalY_{sn}").setText(f"") + self.findChild(QLineEdit, f"globalZ_{sn}").setText(f"") + + # Find the QGroupBox for the stage + group_box = self.findChild(QGroupBox, f"groupBox_{sn}") + group_box.setEnabled(False) + group_box.setStyleSheet("background-color: #333333;") + group_box.setTitle(f"{sn} (Uncalibrated)") + + def enable(self, sn): + # Find the QGroupBox for the stage + group_box = self.findChild(QGroupBox, f"groupBox_{sn}") + if not group_box.isEnabled(): + group_box.setEnabled(True) + group_box.setStyleSheet("background-color: black;") + group_box.setTitle(f"{sn}") + + def create_stage_groupboxes(self): + # Loop through the number of stages and create copies of groupBoxStage + for sn in self.model.stages.keys(): + # Load the QGroupBox from the calc_QGroupBox.ui file + group_box = QGroupBox(self) + loadUi(os.path.join(ui_dir, "calc_QGroupBox.ui"), group_box) + + # Set the visible title of the QGroupBox to sn + group_box.setTitle(f"{sn}") + + # Append _{sn} to the QGroupBox object name + group_box.setObjectName(f"groupBox_{sn}") + + # Find all QLineEdits and QPushButtons in the group_box and rename them + # globalX -> globalX_{sn} .. + # localX -> localX_{sn} .. + for line_edit in group_box.findChildren(QLineEdit): + line_edit.setObjectName(f"{line_edit.objectName()}_{sn}") + + # convert -> convert_{sn} + for push_button in group_box.findChildren(QPushButton): + push_button.setObjectName(f"{push_button.objectName()}_{sn}") + + # Add the newly created QGroupBox to the layout + self.ui.verticalLayout_QBox.addWidget(group_box) \ No newline at end of file diff --git a/parallax/main_window_wip.py b/parallax/main_window_wip.py index a5016e0..8dda4bb 100644 --- a/parallax/main_window_wip.py +++ b/parallax/main_window_wip.py @@ -183,6 +183,7 @@ def refresh_stages(self): """Search for connected stages""" if not self.dummy: self.model.scan_for_usb_stages() + self.model.init_transforms() def record_button_handler(self): """ @@ -775,4 +776,5 @@ def save_user_configs(self): def closeEvent(self, event): self.model.close_all_point_meshes() + self.model.close_clac_instance() event.accept() \ No newline at end of file diff --git a/parallax/model.py b/parallax/model.py index efb9e37..1960e59 100755 --- a/parallax/model.py +++ b/parallax/model.py @@ -28,6 +28,9 @@ def __init__(self, version="V1", bundle_adjustment=False): # point mesh self.point_mesh_instances = {} + # Calculator + self.calc_instance = None + # stage self.nStages = 0 self.stages = {} @@ -46,18 +49,9 @@ def __init__(self, version="V1", bundle_adjustment=False): self.calibrations = {} self.coords_debug = {} - self.cal_in_progress = False - self.accutest_in_progress = False - self.lcorr, self.rcorr = False, False - - self.img_point_last = None - self.obj_point_last = None + # Transformation matrices of stages to global coords self.transforms = {} - def set_last_object_point(self, obj_point): - """Set the last object point.""" - self.obj_point_last = obj_point - def add_calibration(self, cal): """Add a calibration.""" self.calibrations[cal.name] = cal @@ -66,27 +60,15 @@ def set_calibration(self, calibration): """Set the calibration.""" self.calibration = calibration - def set_lcorr(self, xc, yc): - """Set left coordinates.""" - self.lcorr = [xc, yc] - - def clear_lcorr(self): - """Clear left coordinates.""" - self.lcorr = False - - def set_rcorr(self, xc, yc): - """Set right coordinates.""" - self.rcorr = [xc, yc] - - def clear_rcorr(self): - """Clear right coordinates.""" - self.rcorr = False - def init_stages(self): """Initialize stages.""" self.stages = {} self.stages_calib = {} + def init_transforms(self): + for stage_sn in self.stages.keys(): + self.transforms[stage_sn] = [None, None] + def add_video_source(self, video_source): """Add a video source.""" self.cameras.append(video_source) @@ -134,7 +116,16 @@ def get_stage(self, stage_sn): return self.stages.get(stage_sn) def add_stage_calib_info(self, stage_sn, info): - """Add a stage.""" + """Add a stage. + info['detection_status'] + info['transM'] + info['L2_err'] + info['scale'] + info['dist_traveled'] + info['status_x'] + info['status_y'] + info['status_z'] + """ self.stages_calib[stage_sn] = info def get_stage_calib_info(self, stage_sn): @@ -145,6 +136,10 @@ def reset_stage_calib_info(self): """Reset stage calibration info.""" self.stages_calib = {} + def add_transform(self, stage_sn, transform, scale): + """Add transformation matrix between local to global coordinates.""" + self.transforms[stage_sn] = [transform, scale] + def add_probe_detector(self, probeDetector): """Add a probe detector.""" self.probeDetectors.append(probeDetector) @@ -154,7 +149,7 @@ def reset_coords_intrinsic_extrinsic(self): self.coords_axis = {} self.camera_intrinsic = {} self.camera_extrinsic = {} - + def add_pos_x(self, camera_name, pt): """Add position x.""" self.pos_x[camera_name] = pt @@ -220,4 +215,12 @@ def add_point_mesh_instance(self, instance): def close_all_point_meshes(self): for instance in self.point_mesh_instances.values(): instance.close() - self.point_mesh_instances.clear() \ No newline at end of file + self.point_mesh_instances.clear() + + def add_calc_instance(self, instance): + self.calc_instance = instance + + def close_clac_instance(self): + if self.calc_instance is not None: + self.calc_instance.close() + self.calc_instance = None \ No newline at end of file diff --git a/parallax/point_mesh.py b/parallax/point_mesh.py index 2d54236..ecf0f75 100644 --- a/parallax/point_mesh.py +++ b/parallax/point_mesh.py @@ -71,7 +71,6 @@ def _parse_csv(self): self.local_pts_org = self.df[['local_x', 'local_y', 'local_z']].values self.local_pts = self._local_to_global(self.local_pts_org, self.R[self.sn], self.T[self.sn], self.S[self.sn]) self.points_dict['local_pts'] = self.local_pts - print(self.R[self.sn], self.T[self.sn], self.S[self.sn]) self.global_pts = self.df[['global_x', 'global_y', 'global_z']].values self.points_dict['global_pts'] = self.global_pts diff --git a/parallax/probe_calibration.py b/parallax/probe_calibration.py index 2a5485d..c1bd8ad 100644 --- a/parallax/probe_calibration.py +++ b/parallax/probe_calibration.py @@ -152,14 +152,16 @@ def clear(self, sn = None): """ Clears all stored data and resets the transformation matrix to its default state. """ + self.model_LR, self.transM_LR, self.transM_LR_prev = None, None, None + self.scale = np.array([1, 1, 1]) + if sn is None: self._create_file() else: self.df = pd.read_csv(self.csv_file) self.df = self.df[self.df["sn"] != sn] self.df.to_csv(self.csv_file, index=False) - self.model_LR, self.transM_LR, self.transM_LR_prev = None, None, None - self.scale = np.array([1, 1, 1]) + self.model.add_transform(sn, self.transM_LR, self.scale) def _remove_duplicates(self, df): # Drop duplicate rows based on 'ts_local_coords', 'global_x', 'global_y', 'global_z' columns @@ -264,6 +266,7 @@ def _get_transM_LR_orthogonal(self, local_points, global_points, remove_noise=Tr def _get_transM(self, df, remove_noise=True, save_to_csv=False, file_name=None, noise_threshold=40): local_points, global_points = self._get_local_global_points(df) + valid_indices = np.ones(len(local_points), dtype=bool) # Initialize valid_indices as a mask with all True values if remove_noise: if self._is_criteria_met_points_min_max() and len(local_points) > 10 \ @@ -598,7 +601,8 @@ def update(self, stage, debug_info=None): self._update_local_global_point(debug_info) # Do no update if it is duplicates filtered_df = self._filter_df_by_sn(self.stage.sn) - self.transM_LR = self._get_transM(filtered_df) + self.transM_LR = self._get_transM(filtered_df, noise_threshold=100) # TODO original + #self.transM_LR = self._get_transM(filtered_df, remove_noise=False) # Test if self.transM_LR is None: return @@ -615,7 +619,9 @@ def update(self, stage, debug_info=None): def complete_calibration(self, filtered_df): # save the filtered points to a new file self.file_name = f"points_{self.stage.sn}.csv" - self.transM_LR = self._get_transM(filtered_df, save_to_csv=True, file_name=self.file_name, noise_threshold=20) + self.transM_LR = self._get_transM(filtered_df, save_to_csv=True, file_name=self.file_name, noise_threshold=20) # TODO original + #self.transM_LR = self._get_transM(filtered_df, save_to_csv=True, file_name=self.file_name, remove_noise=False) # Test + if self.transM_LR is None: return @@ -638,6 +644,9 @@ def complete_calibration(self, filtered_df): else: return + # Register into model + self.model.add_transform(self.stage.sn, self.transM_LR, self.scale) + # Emit the signal to indicate that calibration is complete self.calib_complete.emit(self.stage.sn, self.transM_LR, self.scale) logger.debug( diff --git a/parallax/probe_detect_manager.py b/parallax/probe_detect_manager.py index 96fcbbd..2b863f5 100644 --- a/parallax/probe_detect_manager.py +++ b/parallax/probe_detect_manager.py @@ -264,11 +264,11 @@ def run(self): logger.debug("probe_detect_manager running ") while self.running: if self.new: - self.frame = self.process_draw_reticle(self.frame) if self.is_detection_on: self.frame, self.timestamp = self.process( self.frame, self.timestamp ) + self.frame = self.process_draw_reticle(self.frame) self.frame_processed.emit(self.frame) self.new = False time.sleep(0.001) diff --git a/parallax/probe_detector.py b/parallax/probe_detector.py index 06248a1..586ae1c 100644 --- a/parallax/probe_detector.py +++ b/parallax/probe_detector.py @@ -315,19 +315,22 @@ def _get_probe_point(self, mask, p1, p2, img_fname=None): - probe_base (tuple): Coordinates of the probe base. """ + if mask is None: + mask = np.zeros((self.IMG_SIZE[1], self.IMG_SIZE[0]), dtype=np.uint8) + + mask = cv2.copyMakeBorder( mask, 1, 1, 1, 1, cv2.BORDER_CONSTANT, value=[0, 0, 0] ) - dist_transform = cv2.distanceTransform(mask, cv2.DIST_L2, 3) - """ - if img_fname: - mask_ = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR) - cv2.circle(mask_, p1, 5, (0, 255, 0), -1) # Green circle - cv2.circle(mask_, p2, 5, (0, 0, 255), -1) # Green circle - - output_fname = os.path.basename(img_fname).replace('.', '_mask.') - cv2.imwrite('output/' + output_fname, mask_) + """ + # TODO Draw a border inside the mask with the specified thickness + border_size = 200 + cv2.rectangle(mask, (border_size, border_size), + (mask.shape[1] - border_size - 1, mask.shape[0] - border_size - 1), + 0, border_size) """ + dist_transform = cv2.distanceTransform(mask, cv2.DIST_L2, 3) + dist_p1 = dist_transform[p1[1], p1[0]] # [y, x] dist_p2 = dist_transform[p2[1], p2[0]] logger.debug(f"dist_p1: {dist_p1}, dist_p2: {dist_p2}") diff --git a/parallax/probe_fine_tip_detector.py b/parallax/probe_fine_tip_detector.py index aae7599..b3b9ec5 100644 --- a/parallax/probe_fine_tip_detector.py +++ b/parallax/probe_fine_tip_detector.py @@ -46,12 +46,18 @@ def _is_valid(cls, img): f"get_probe_precise_tip fail. N of contours_boundary :{len(contours_boundary)}" ) return False - + """ boundary_img = np.zeros_like(img) boundary_img[0, 0] = 255 boundary_img[width - 1, 0] = 255 boundary_img[0, height - 1] = 255 boundary_img[width - 1, height - 1] = 255 + """ + boundary_img[0, 0] = 255 + boundary_img[0, width - 1] = 255 + boundary_img[height - 1, 0] = 255 + boundary_img[height - 1, width - 1] = 255 + and_result = cv2.bitwise_and(and_result, boundary_img) contours_boundary, _ = cv2.findContours( and_result, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE diff --git a/parallax/stage_widget.py b/parallax/stage_widget.py index dd26365..3a12c65 100644 --- a/parallax/stage_widget.py +++ b/parallax/stage_widget.py @@ -19,6 +19,7 @@ from .probe_calibration import ProbeCalibration from .stage_listener import StageListener from .stage_ui import StageUI +from .calculator import Calculator logger = logging.getLogger(__name__) logger.setLevel(logging.WARNING) @@ -89,6 +90,14 @@ def __init__(self, model, ui_dir, screen_widgets): self.view_trajectory_button_handler ) + # Calculation Button + self.calculation_btn = self.probe_calib_widget.findChild( + QPushButton, "calculation_btn" + ) + self.calculation_btn.clicked.connect( + self.calculation_button_handler + ) + # Reticle Widget self.reticle_detection_status = ( "default" # options: default, process, detected, accepted, request_axis @@ -151,6 +160,10 @@ def __init__(self, model, ui_dir, screen_widgets): self.filter = "no_filter" logger.debug(f"filter: {self.filter}") + # Calculator + self.calculation_btn.hide() + self.calculator = Calculator(self.model) + def reticle_detection_button_handler(self): """ Handles clicks on the reticle detection button, initiating or canceling reticle detection. @@ -738,6 +751,7 @@ def probe_detect_default_status_ui(self, sn = None): """) self.hide_x_y_z() self.hide_trajectory_btn() + self.hide_calculation_btn() self.probeCalibrationLabel.setText("") self.probe_calibration_btn.setChecked(False) @@ -759,10 +773,18 @@ def probe_detect_default_status_ui(self, sn = None): self.filter = "no_filter" logger.debug(f"filter: {self.filter}") - # Reset the probe calibration status - self.probeCalibration.clear(self.selected_stage_id) - # update global coords. Set to '-' on UI - self.stageListener.requestClearGlobalDataTransformM(sn = sn) + if sn is not None: + # Reset the probe calibration status + self.probeCalibration.clear(self.selected_stage_id) + # update global coords. Set to '-' on UI + self.stageListener.requestClearGlobalDataTransformM(sn = sn) + else: # Reset all probel calibration status + for sn in self.model.stages.keys(): + self.probeCalibration.clear(sn) + self.stageListener.requestClearGlobalDataTransformM(sn = sn) + + # Set as Uncalibrated + self.calculator.set_calc_functions() def probe_detect_default_status(self, sn = None): """ @@ -840,6 +862,8 @@ def probe_detect_accepted_status(self, stage_sn, transformation_matrix, scale, s self.hide_x_y_z() if not self.viewTrajectory_btn.isVisible(): self.viewTrajectory_btn.show() + if not self.calculation_btn.isVisible(): + self.calculation_btn.show() if self.filter == "probe_detection": for screen in self.screen_widgets: camera_name = screen.get_camera_name() @@ -895,6 +919,10 @@ def hide_trajectory_btn(self): if self.viewTrajectory_btn.isVisible(): self.viewTrajectory_btn.hide() + def hide_calculation_btn(self): + if self.calculation_btn.isVisible(): + self.calculation_btn.hide() + def calib_x_complete(self, switch_probe = False): """ Updates the UI to indicate that the calibration for the X-axis is complete. @@ -1073,4 +1101,7 @@ def update_stages(self, prev_stage_id, curr_stage_id): self.probe_detection_status = probe_detection_status def view_trajectory_button_handler(self): - self.probeCalibration.view_3d_trajectory(self.selected_stage_id) \ No newline at end of file + self.probeCalibration.view_3d_trajectory(self.selected_stage_id) + + def calculation_button_handler(self): + self.calculator.show() \ No newline at end of file diff --git a/ui/calc.ui b/ui/calc.ui new file mode 100644 index 0000000..6a5315a --- /dev/null +++ b/ui/calc.ui @@ -0,0 +1,155 @@ + + + Form + + + + 0 + 0 + 819 + 653 + + + + Form + + + + resources/calc.pngresources/calc.png + + + QWidget{ +background-color: rgb(00,00,00); +color: #FFFFFF; +} +QPushButton{ + background-color: black; +} + QPushButton:pressed { + background-color: rgb(224, 0, 0); +} +QPushButton:hover { + background-color: rgb(100, 30, 30); +} +QPushButton#startButton:disabled:checked { + color: gray; +} +QPushButton#startButton:disabled:checked { + background-color: #ffaaaa; +} +QPushButton#startButton:disabled:!checked { + background-color: lightGreen; +} + + + + + 10 + 10 + 781 + 621 + + + + + 10 + + + + + + + + 16777215 + 50 + + + + QLabel { + color: yellow; +} + + + Global + + + Qt::AlignCenter + + + + + + + + 16777215 + 50 + + + + Local + + + Qt::AlignCenter + + + + + + + + + + + QLabel { + color: yellow; +} + + + (x, y, z) + + + Qt::AlignCenter + + + + + + + (x, y, z) + + + Qt::AlignCenter + + + + + + + + + 20 + + + 10 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + diff --git a/ui/calc_QGroupBox.ui b/ui/calc_QGroupBox.ui new file mode 100644 index 0000000..9bf4901 --- /dev/null +++ b/ui/calc_QGroupBox.ui @@ -0,0 +1,241 @@ + + + SN + + + + 0 + 0 + 803 + 123 + + + + + 700 + 100 + + + + GroupBox + + + QWidget{ +background-color: rgb(00,00,00); +color: #FFFFFF; +} +QPushButton{ + background-color: black; +} + QPushButton:pressed { + background-color: rgb(224, 0, 0); +} +QPushButton:hover { + background-color: rgb(100, 30, 30); +} +QPushButton#startButton:disabled:checked { + color: gray; +} +QPushButton#startButton:disabled:checked { + background-color: #ffaaaa; +} +QPushButton#startButton:disabled:!checked { + background-color: lightGreen; +} + + + + + 140 + 40 + 100 + 40 + + + + + 100 + 40 + + + + + 8 + + + + QLineEdit { + color: yellow; +} + + + + + + 250 + 40 + 100 + 40 + + + + + 100 + 40 + + + + + 8 + + + + QLineEdit { + color: yellow; +} + + + + + + 30 + 40 + 100 + 40 + + + + + 100 + 40 + + + + + 8 + + + + QLineEdit { + color: yellow; +} + + + + + + 410 + 40 + 100 + 40 + + + + + 100 + 40 + + + + + 8 + + + + + + + 360 + 50 + 40 + 21 + + + + + 40 + 0 + + + + + 40 + 16777215 + + + + + 12 + + + + + + + + + + 630 + 40 + 100 + 40 + + + + + 100 + 40 + + + + + 200 + 16777215 + + + + + 8 + + + + + + + 520 + 40 + 100 + 40 + + + + + 100 + 40 + + + + + 200 + 16777215 + + + + + 8 + + + + + + globalX + globalY + globalZ + convert + localX + localY + localZ + + + + diff --git a/ui/point_mesh.ui b/ui/point_mesh.ui index 4299064..c755488 100644 --- a/ui/point_mesh.ui +++ b/ui/point_mesh.ui @@ -25,6 +25,10 @@ Form + + + resources/3D_map.pngresources/3D_map.png + QWidget{ background-color: rgb(0,0,0); diff --git a/ui/probe_calib.ui b/ui/probe_calib.ui index 53b875f..35d0833 100644 --- a/ui/probe_calib.ui +++ b/ui/probe_calib.ui @@ -7,7 +7,7 @@ 0 0 541 - 784 + 783 @@ -26,8 +26,8 @@ 1 - - + + false @@ -44,7 +44,7 @@ - Y + Z @@ -61,8 +61,21 @@ - - + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + false @@ -79,12 +92,40 @@ - Z + X - - + + + + + 0 + 0 + + + + + 200 + 40 + + + + + 300 + 40 + + + + Probe Calibration + + + true + + + + + false @@ -101,25 +142,12 @@ - X + Y - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - + + 0 @@ -138,6 +166,9 @@ 30 + + <html><head/><body><p><span style=" font-size:7pt;">Calculator</span></p></body></html> + QPushButton{ @@ -154,14 +185,18 @@ QPushButton:hover { } - 3D + + + + + resources/calc.pngresources/calc.png false - + @@ -177,8 +212,8 @@ QPushButton:hover { - - + + 0 @@ -187,21 +222,43 @@ QPushButton:hover { - 200 - 40 + 40 + 30 - 300 - 40 + 40 + 30 + + <html><head/><body><p><span style=" font-size:7pt;">Trajectory</span></p></body></html> + + + +QPushButton{ + color: white; + background-color: black; +} + +QPushButton:pressed { + background-color: rgb(224, 0, 0); +} + +QPushButton:hover { + background-color: rgb(100, 30, 30); +} + - Probe Calibration + + + + + resources/3D_map.pngresources/3D_map.png - true + false diff --git a/ui/resources/3D_map.png b/ui/resources/3D_map.png new file mode 100644 index 0000000..3ddfd93 Binary files /dev/null and b/ui/resources/3D_map.png differ diff --git a/ui/resources/calc.png b/ui/resources/calc.png new file mode 100644 index 0000000..0c53f0b Binary files /dev/null and b/ui/resources/calc.png differ diff --git a/ui/stage_info.ui b/ui/stage_info.ui index 8215993..102a3bf 100644 --- a/ui/stage_info.ui +++ b/ui/stage_info.ui @@ -199,6 +199,11 @@ + + QLabel { + color: yellow; +} + - @@ -263,6 +268,11 @@ + + QLabel { + color: yellow; +} + - @@ -370,6 +380,11 @@ + + QLabel { + color: yellow; +} + -