From f014f1b44025ef8786f296b3f398da40baa19d19 Mon Sep 17 00:00:00 2001 From: ch-sa Date: Sun, 24 Oct 2021 22:23:23 +0200 Subject: [PATCH] Cleanup - add return type hints - sort import - remove unecessary methods - split drawing strategies --- config.ini | 3 +- labelCloud/control/alignmode.py | 44 ++- labelCloud/control/bbox_controller.py | 63 +++-- labelCloud/control/config_manager.py | 10 +- labelCloud/control/controller.py | 45 ++- labelCloud/control/drawing_manager.py | 335 +---------------------- labelCloud/control/drawing_strategies.py | 260 ++++++++++++++++++ labelCloud/control/label_manager.py | 36 +-- labelCloud/control/pcd_manager.py | 51 ++-- labelCloud/model/bbox.py | 46 ++-- labelCloud/model/point_cloud.py | 48 ++-- labelCloud/ressources/interface.ui | 20 -- labelCloud/utils/math3d.py | 6 +- labelCloud/utils/oglhelper.py | 6 +- labelCloud/view/gui.py | 61 ++--- labelCloud/view/settings_dialog.py | 11 +- labelCloud/view/viewer.py | 35 ++- 17 files changed, 493 insertions(+), 587 deletions(-) create mode 100644 labelCloud/control/drawing_strategies.py diff --git a/config.ini b/config.ini index a063f5e..8929a92 100644 --- a/config.ini +++ b/config.ini @@ -55,5 +55,4 @@ viewing_precision = 2 near_plane = 0.1 far_plane = 300 ; keep last perspective between point clouds -keep_perspective = False - +keep_perspective = False \ No newline at end of file diff --git a/labelCloud/control/alignmode.py b/labelCloud/control/alignmode.py index ef3583a..2129180 100644 --- a/labelCloud/control/alignmode.py +++ b/labelCloud/control/alignmode.py @@ -3,22 +3,22 @@ three points on the plane that serves as the ground. Then the old point cloud will be saved up and the aligned current will overwrite the old. """ -from typing import Union, TYPE_CHECKING +from typing import TYPE_CHECKING, Union import numpy as np - import utils.oglhelper as ogl + from .pcd_manager import PointCloudManger if TYPE_CHECKING: from view.gui import GUI -class AlignMode: - def __init__(self, pcd_manager: PointCloudManger): +class AlignMode(object): + def __init__(self, pcd_manager: PointCloudManger) -> None: self.pcd_manager = pcd_manager self.view: Union[GUI, None] = None - self.activated = False + self.is_active = False self.point_color = (1, 1, 0, 1) self.area_color = (1, 1, 0, 0.6) self.plane1 = None @@ -28,40 +28,37 @@ def __init__(self, pcd_manager: PointCloudManger): self.tmp_p2 = None self.tmp_p3 = None - def set_view(self, view: "GUI"): + def set_view(self, view: "GUI") -> None: self.view = view self.view.glWidget.align_mode = self - def is_active(self): - return self.activated - - def change_activation(self, force=None): + def change_activation(self, force=None) -> None: if force is not None: - self.activated = force - elif self.activated: - self.activated = False + self.is_active = force + elif self.is_active: + self.is_active = False self.reset() else: - self.activated = True + self.is_active = True - if self.activated: + if self.is_active: self.view.update_status( "Select three points on the plane that should be the floor.", "alignment", ) - self.view.action_alignpcd.setChecked(self.activated) + self.view.action_alignpcd.setChecked(self.is_active) self.view.activate_draw_modes( - not self.activated + not self.is_active ) # Prevent bbox drawing while aligning - print("Alignmode was changed to %s!" % self.activated) + print(f"Alignmode was changed to {self.is_active}!") - def reset(self, points_only: bool = False): + def reset(self, points_only: bool = False) -> None: self.plane1, self.plane2, self.plane3 = (None, None, None) self.tmp_p2, self.tmp_p3 = (None, None) if not points_only: self.change_activation(force=False) - def register_point(self, new_point): + def register_point(self, new_point) -> None: if self.plane1 is None: self.plane1 = new_point elif not self.plane2: @@ -75,13 +72,13 @@ def register_point(self, new_point): else: print("Cannot register point.") - def register_tmp_point(self, new_tmp_point): + def register_tmp_point(self, new_tmp_point) -> None: if self.plane1 and (not self.plane2): self.tmp_p2 = new_tmp_point elif self.plane2 and (not self.plane3): self.tmp_p3 = new_tmp_point - def draw_preview(self): + def draw_preview(self) -> None: if not self.plane3: if self.plane1: ogl.draw_points([self.plane1], color=self.point_color) @@ -104,7 +101,6 @@ def draw_preview(self): ) elif self.plane1 and self.plane2 and self.plane3: - ogl.draw_points( [self.plane1, self.plane2, self.plane3], color=self.point_color ) @@ -112,7 +108,7 @@ def draw_preview(self): [self.plane1, self.plane2, self.plane3], color=self.area_color ) - def calculate_angles(self): + def calculate_angles(self) -> None: # Calculate plane normal with self.plane1 as origin plane_normal = np.cross( np.subtract(self.plane2, self.plane1), np.subtract(self.plane3, self.plane1) diff --git a/labelCloud/control/bbox_controller.py b/labelCloud/control/bbox_controller.py index bc29a12..2ccf395 100644 --- a/labelCloud/control/bbox_controller.py +++ b/labelCloud/control/bbox_controller.py @@ -4,13 +4,12 @@ Bounding Box Management: adding, selecting updating, deleting bboxes; Possible Active Bounding Box Manipulations: rotation, translation, scaling """ -from typing import TYPE_CHECKING, Union, List +from typing import TYPE_CHECKING, List, Optional, Union import numpy as np - - -from utils import oglhelper from model.bbox import BBox +from utils import oglhelper + from .config_manager import config if TYPE_CHECKING: @@ -46,41 +45,41 @@ def wrapper(*args, **kwargs): return wrapper -class BoundingBoxController: +class BoundingBoxController(object): STD_SCALING = config.getfloat("LABEL", "std_scaling") - def __init__(self): + def __init__(self) -> None: self.view = None self.bboxes = [] self.active_bbox_id = -1 # -1 means zero bboxes self.pcdc = None # GETTERS - def has_active_bbox(self): + def has_active_bbox(self) -> bool: return 0 <= self.active_bbox_id < self.get_no_of_bboxes() - def get_active_bbox(self) -> Union[BBox, None]: + def get_active_bbox(self) -> Optional[BBox]: if self.has_active_bbox(): return self.bboxes[self.active_bbox_id] else: return None - def get_bboxes(self): + def get_bboxes(self) -> List[BBox]: return self.bboxes - def get_no_of_bboxes(self): + def get_no_of_bboxes(self) -> int: return len(self.bboxes) @has_active_bbox_decorator - def get_classname(self): + def get_classname(self) -> str: return self.get_active_bbox().get_classname() # SETTERS - def set_view(self, view: "GUI"): + def set_view(self, view: "GUI") -> None: self.view = view - def add_bbox(self, bbox: BBox): + def add_bbox(self, bbox: BBox) -> None: if isinstance(bbox, BBox): self.bboxes.append(bbox) self.set_active_bbox(self.bboxes.index(bbox)) @@ -88,12 +87,12 @@ def add_bbox(self, bbox: BBox): "Bounding Box added, it can now be corrected.", mode="correction" ) - def update_bbox(self, bbox_id: int, bbox: BBox): + def update_bbox(self, bbox_id: int, bbox: BBox) -> None: if isinstance(bbox, BBox) and (0 <= bbox_id < len(self.bboxes)): self.bboxes[bbox_id] = bbox self.update_label_list() - def delete_bbox(self, bbox_id: int): + def delete_bbox(self, bbox_id: int) -> None: if 0 <= bbox_id < len(self.bboxes): del self.bboxes[bbox_id] if bbox_id == self.active_bbox_id: @@ -101,11 +100,11 @@ def delete_bbox(self, bbox_id: int): else: self.update_label_list() - def delete_current_bbox(self): + def delete_current_bbox(self) -> None: selected_item_id = self.view.label_list.currentRow() self.delete_bbox(selected_item_id) - def set_active_bbox(self, bbox_id: int): + def set_active_bbox(self, bbox_id: int) -> None: if 0 <= bbox_id < len(self.bboxes): self.active_bbox_id = bbox_id self.update_all() @@ -116,31 +115,31 @@ def set_active_bbox(self, bbox_id: int): self.deselect_bbox() @has_active_bbox_decorator - def set_classname(self, new_class: str): + def set_classname(self, new_class: str) -> None: self.get_active_bbox().set_classname(new_class) self.update_label_list() @has_active_bbox_decorator - def set_center(self, cx: float, cy: float, cz: float): + def set_center(self, cx: float, cy: float, cz: float) -> None: self.get_active_bbox().center = (cx, cy, cz) - def set_bboxes(self, bboxes: List[BBox]): + def set_bboxes(self, bboxes: List[BBox]) -> None: self.bboxes = bboxes self.deselect_bbox() self.update_label_list() - def reset(self): + def reset(self) -> None: self.deselect_bbox() self.set_bboxes([]) - def deselect_bbox(self): + def deselect_bbox(self) -> None: self.active_bbox_id = -1 self.update_all() self.view.update_status("", mode="navigation") # MANIPULATORS @has_active_bbox_decorator - def update_position(self, axis: str, value: float): + def update_position(self, axis: str, value: float) -> None: if axis == "pos_x": self.get_active_bbox().set_x_translation(value) elif axis == "pos_y": @@ -151,7 +150,7 @@ def update_position(self, axis: str, value: float): raise Exception("Wrong axis describtion.") @has_active_bbox_decorator - def update_dimension(self, dimension: str, value: float): + def update_dimension(self, dimension: str, value: float) -> None: if dimension == "length": self.get_active_bbox().set_length(value) elif dimension == "width": @@ -162,7 +161,7 @@ def update_dimension(self, dimension: str, value: float): raise Exception("Wrong dimension describtion.") @has_active_bbox_decorator - def update_rotation(self, axis: str, value: float): + def update_rotation(self, axis: str, value: float) -> None: if axis == "rot_x": self.get_active_bbox().set_x_rotation(value) elif axis == "rot_y": @@ -174,7 +173,7 @@ def update_rotation(self, axis: str, value: float): @only_zrotation_decorator @has_active_bbox_decorator - def rotate_around_x(self, dangle: float = None, clockwise: bool = False): + def rotate_around_x(self, dangle: float = None, clockwise: bool = False) -> None: dangle = dangle or config.getfloat("LABEL", "std_rotation") if clockwise: dangle *= -1 @@ -184,7 +183,7 @@ def rotate_around_x(self, dangle: float = None, clockwise: bool = False): @only_zrotation_decorator @has_active_bbox_decorator - def rotate_around_y(self, dangle: float = None, clockwise: bool = False): + def rotate_around_y(self, dangle: float = None, clockwise: bool = False) -> None: dangle = dangle or config.getfloat("LABEL", "std_rotation") if clockwise: dangle *= -1 @@ -195,7 +194,7 @@ def rotate_around_y(self, dangle: float = None, clockwise: bool = False): @has_active_bbox_decorator def rotate_around_z( self, dangle: float = None, clockwise: bool = False, absolute: bool = False - ): + ) -> None: dangle = dangle or config.getfloat("LABEL", "std_rotation") if clockwise: dangle *= -1 @@ -210,7 +209,7 @@ def rotate_around_z( @has_active_bbox_decorator def rotate_with_mouse( self, x_angle: float, y_angle: float - ): # TODO: Make more intuitive + ) -> None: # TODO: Make more intuitive # Get bbox perspective pcd_z_rotation = self.pcdc.get_pointcloud().rot_z bbox_z_rotation = self.get_active_bbox().get_z_rotation() @@ -224,7 +223,7 @@ def rotate_with_mouse( self.rotate_around_z(x_angle) @has_active_bbox_decorator - def translate_along_x(self, distance: float = None, left: bool = False): + def translate_along_x(self, distance: float = None, left: bool = False) -> None: distance = distance or config.getfloat("LABEL", "std_translation") if left: distance *= -1 @@ -237,7 +236,7 @@ def translate_along_x(self, distance: float = None, left: bool = False): ) @has_active_bbox_decorator - def translate_along_y(self, distance: float = None, forward: bool = False): + def translate_along_y(self, distance: float = None, forward: bool = False) -> None: distance = distance or config.getfloat("LABEL", "std_translation") if forward: distance *= -1 @@ -250,7 +249,7 @@ def translate_along_y(self, distance: float = None, forward: bool = False): ) @has_active_bbox_decorator - def translate_along_z(self, distance: float = None, down: bool = False): + def translate_along_z(self, distance: float = None, down: bool = False) -> None: distance = distance or config.getfloat("LABEL", "std_translation") if down: distance *= -1 diff --git a/labelCloud/control/config_manager.py b/labelCloud/control/config_manager.py index 70d23ca..721cb36 100644 --- a/labelCloud/control/config_manager.py +++ b/labelCloud/control/config_manager.py @@ -1,7 +1,5 @@ """Load configuration from .ini file.""" import configparser - - import os from typing import List @@ -27,21 +25,21 @@ class ConfigManager(object): PATH_TO_CONFIG = "config.ini" PATH_TO_DEFAULT_CONFIG = "ressources/default_config.ini" - def __init__(self): + def __init__(self) -> None: self.config = ExtendedConfigParser(comment_prefixes="/", allow_no_value=True) self.read_from_file() - def read_from_file(self): + def read_from_file(self) -> None: if os.path.isfile(ConfigManager.PATH_TO_CONFIG): self.config.read(ConfigManager.PATH_TO_CONFIG) else: self.config.read(ConfigManager.PATH_TO_DEFAULT_CONFIG) - def write_into_file(self): + def write_into_file(self) -> None: with open(ConfigManager.PATH_TO_CONFIG, "w") as configfile: self.config.write(configfile, space_around_delimiters=True) - def reset_to_default(self): + def reset_to_default(self) -> None: self.config.read(ConfigManager.PATH_TO_DEFAULT_CONFIG) def get_file_settings(self, key: str) -> str: diff --git a/labelCloud/control/controller.py b/labelCloud/control/controller.py index bf209b7..1b94ae0 100644 --- a/labelCloud/control/controller.py +++ b/labelCloud/control/controller.py @@ -1,11 +1,10 @@ from typing import Union -from PyQt5 import QtGui, QtCore - - +from PyQt5 import QtCore, QtGui from utils import oglhelper from model.bbox import BBox from view.gui import GUI + from .alignmode import AlignMode from .bbox_controller import BoundingBoxController from .drawing_manager import DrawingManager @@ -15,7 +14,7 @@ class Controller: MOVEMENT_THRESHOLD = 0.1 - def __init__(self): + def __init__(self) -> None: """Initializes all controllers and managers.""" self.view: Union["GUI", None] = None self.pcd_manager = PointCloudManger() @@ -35,7 +34,7 @@ def __init__(self): self.side_mode = False self.selected_side = None - def startup(self, view: "GUI"): + def startup(self, view: "GUI") -> None: """Sets the view in all controllers and dependent modules; Loads labels from file.""" self.view = view self.bbox_controller.set_view(self.view) @@ -49,14 +48,14 @@ def startup(self, view: "GUI"): self.pcd_manager.read_pointcloud_folder() self.next_pcd(save=False) - def loop_gui(self): + def loop_gui(self) -> None: """Function collection called during each event loop iteration.""" self.set_crosshair() self.set_selected_side() self.view.glWidget.updateGL() # POINT CLOUD METHODS - def next_pcd(self, save: bool = True): + def next_pcd(self, save: bool = True) -> None: if save: self.save() if self.pcd_manager.pcds_left(): @@ -67,7 +66,7 @@ def next_pcd(self, save: bool = True): self.view.update_progress(len(self.pcd_manager.pcds)) self.view.button_next_pcd.setEnabled(False) - def prev_pcd(self): + def prev_pcd(self) -> None: self.save() if self.pcd_manager.current_id > 0: self.pcd_manager.get_prev_pcd() @@ -75,18 +74,18 @@ def prev_pcd(self): self.bbox_controller.set_bboxes(self.pcd_manager.get_labels_from_file()) # CONTROL METHODS - def save(self): + def save(self) -> None: """Saves all bounding boxes in the label file.""" self.pcd_manager.save_labels_into_file(self.bbox_controller.get_bboxes()) - def reset(self): + def reset(self) -> None: """Resets the controllers and bounding boxes from the current screen.""" self.bbox_controller.reset() self.drawing_mode.reset() self.align_mode.reset() # CORRECTION METHODS - def set_crosshair(self): + def set_crosshair(self) -> None: """Sets the crosshair position in the glWidget to the current cursor position.""" if self.curr_cursor_pos: self.view.glWidget.crosshair_col = [0, 1, 0] @@ -95,7 +94,7 @@ def set_crosshair(self): self.curr_cursor_pos.y(), ) - def set_selected_side(self): + def set_selected_side(self) -> None: """Sets the currently hovered bounding box side in the glWidget.""" if ( (not self.side_mode) @@ -124,7 +123,7 @@ def set_selected_side(self): self.view.glWidget.selected_side_vertices = [] # EVENT PROCESSING - def mouse_clicked(self, a0: QtGui.QMouseEvent): + def mouse_clicked(self, a0: QtGui.QMouseEvent) -> None: """Triggers actions when the user clicks the mouse.""" self.last_cursor_pos = a0.pos() @@ -135,7 +134,7 @@ def mouse_clicked(self, a0: QtGui.QMouseEvent): ): self.drawing_mode.register_point(a0.x(), a0.y(), correction=True) - elif self.align_mode.is_active() and (not self.ctrl_pressed): + elif self.align_mode.is_active and (not self.ctrl_pressed): self.align_mode.register_point( self.view.glWidget.get_world_coords(a0.x(), a0.y(), correction=False) ) @@ -143,11 +142,11 @@ def mouse_clicked(self, a0: QtGui.QMouseEvent): elif self.selected_side: self.side_mode = True - def mouse_double_clicked(self, a0: QtGui.QMouseEvent): + def mouse_double_clicked(self, a0: QtGui.QMouseEvent) -> None: """Triggers actions when the user double clicks the mouse.""" self.bbox_controller.select_bbox_by_ray(a0.x(), a0.y()) - def mouse_move_event(self, a0: QtGui.QMouseEvent): + def mouse_move_event(self, a0: QtGui.QMouseEvent) -> None: """Triggers actions when the user moves the mouse.""" self.curr_cursor_pos = a0.pos() # Updates the current mouse cursor position @@ -157,7 +156,7 @@ def mouse_move_event(self, a0: QtGui.QMouseEvent): a0.x(), a0.y(), correction=True, is_temporary=True ) - elif self.align_mode.is_active() and (not self.ctrl_pressed): + elif self.align_mode.is_active and (not self.ctrl_pressed): self.align_mode.register_tmp_point( self.view.glWidget.get_world_coords(a0.x(), a0.y(), correction=False) ) @@ -171,7 +170,7 @@ def mouse_move_event(self, a0: QtGui.QMouseEvent): if ( self.ctrl_pressed and (not self.drawing_mode.is_active()) - and (not self.align_mode.is_active()) + and (not self.align_mode.is_active) ): if a0.buttons() & QtCore.Qt.LeftButton: # bbox rotation self.bbox_controller.rotate_with_mouse(-dx, -dy) @@ -196,9 +195,8 @@ def mouse_move_event(self, a0: QtGui.QMouseEvent): self.scroll_mode = False self.last_cursor_pos = a0.pos() - def mouse_scroll_event(self, a0: QtGui.QWheelEvent): + def mouse_scroll_event(self, a0: QtGui.QWheelEvent) -> None: """Triggers actions when the user scrolls the mouse wheel.""" - if self.selected_side: self.side_mode = True @@ -212,9 +210,8 @@ def mouse_scroll_event(self, a0: QtGui.QWheelEvent): self.pcd_manager.zoom_into(a0.angleDelta().y()) self.scroll_mode = True - def key_press_event(self, a0: QtGui.QKeyEvent): + def key_press_event(self, a0: QtGui.QKeyEvent) -> None: """Triggers actions when the user presses a key.""" - # Reset position to intial value if a0.key() == QtCore.Qt.Key_Control: self.ctrl_pressed = True @@ -235,7 +232,7 @@ def key_press_event(self, a0: QtGui.QKeyEvent): if self.drawing_mode.is_active(): self.drawing_mode.reset() print("Resetted drawn points!") - elif self.align_mode.is_active(): + elif self.align_mode.is_active: self.align_mode.reset() print("Resetted selected points!") @@ -277,7 +274,7 @@ def key_press_event(self, a0: QtGui.QKeyEvent): # move down self.bbox_controller.translate_along_z(down=True) - def key_release_event(self, a0: QtGui.QKeyEvent): + def key_release_event(self, a0: QtGui.QKeyEvent) -> None: """Triggers actions when the user releases a key.""" if a0.key() == QtCore.Qt.Key_Control: self.ctrl_pressed = False diff --git a/labelCloud/control/drawing_manager.py b/labelCloud/control/drawing_manager.py index ccbddb9..a68e1c7 100644 --- a/labelCloud/control/drawing_manager.py +++ b/labelCloud/control/drawing_manager.py @@ -1,43 +1,31 @@ -from abc import ABCMeta, abstractmethod, ABC -from typing import Union, List, TYPE_CHECKING +from typing import TYPE_CHECKING, Union -import numpy as np - -import utils.math3d as math3d -import utils.oglhelper as ogl -from model.bbox import BBox from .bbox_controller import BoundingBoxController -from .config_manager import config +from .drawing_strategies import IDrawingStrategy if TYPE_CHECKING: from view.gui import GUI -class DrawingManager: - def __init__(self, bbox_controller: BoundingBoxController): +class DrawingManager(object): + def __init__(self, bbox_controller: BoundingBoxController) -> None: self.bbox_controller = bbox_controller self.view: Union["GUI", None] = None self.drawing_strategy: Union[IDrawingStrategy, None] = None - def set_view(self, view: "GUI"): + def set_view(self, view: "GUI") -> None: self.view = view self.view.glWidget.drawing_mode = self def is_active(self) -> bool: return isinstance(self.drawing_strategy, IDrawingStrategy) - def is_mode(self, mode: str = None) -> bool: - if self.is_active(): - return self.drawing_strategy.__class__.__name__ == mode - else: - return False - def has_preview(self) -> bool: if self.is_active(): return self.drawing_strategy.__class__.PREVIEW - def set_drawing_strategy(self, strategy: str) -> None: - if self.is_active() and self.is_mode(strategy): + def set_drawing_strategy(self, strategy: IDrawingStrategy) -> None: + if self.is_active() and self.drawing_strategy == strategy: self.reset() print("Deactivated drawing!") else: @@ -45,32 +33,12 @@ def set_drawing_strategy(self, strategy: str) -> None: self.reset() print("Resetted previous active drawing mode!") - if strategy == "PickingStrategy": - self.drawing_strategy = PickingStrategy(self.view) - self.view.update_status( - "Please pick the location for the bounding box front center.", - mode="drawing", - ) - elif strategy == "RectangleStrategy": - self.drawing_strategy = RectangleStrategy(self.view) - self.view.update_status( - "Please select a corner for the 2D bounding box.", mode="drawing" - ) - elif strategy == "SpanStrategy": - self.drawing_strategy = SpanStrategy(self.view) - self.view.update_status( - "Begin by selecting a vertex of the bounding box.", mode="drawing" - ) + self.drawing_strategy = strategy def register_point( self, x, y, correction: bool = False, is_temporary: bool = False ) -> None: - if self.is_mode("RectangleStrategy"): - world_point = self.view.glWidget.get_world_coords(x, y, z=0) - else: - world_point = self.view.glWidget.get_world_coords( - x, y, correction=correction - ) + world_point = self.view.glWidget.get_world_coords(x, y, correction=correction) if is_temporary: self.drawing_strategy.register_tmp_point(world_point) else: @@ -85,291 +53,8 @@ def register_point( def draw_preview(self) -> None: self.drawing_strategy.draw_preview() - def reset(self, points_only: bool = False): + def reset(self, points_only: bool = False) -> None: if self.is_active(): self.drawing_strategy.reset() if not points_only: self.drawing_strategy = None - - -class IDrawingStrategy: - __metaclass__ = ABCMeta - POINTS_NEEDED: int - PREVIEW: bool = False - - def __init__(self, view: "GUI"): - self.view = view - self.points_registered = 0 - self.point_1 = None - - def is_bbox_finished(self) -> bool: - return self.points_registered >= self.__class__.POINTS_NEEDED - - @abstractmethod - def register_point(self, new_point: List[float]) -> None: - raise NotImplementedError - - def register_tmp_point(self, new_tmp_point: List[float]) -> None: - pass - - def register_scrolling(self, distance: float) -> None: - pass - - @abstractmethod - def get_bbox(self) -> BBox: - raise NotImplementedError - - def draw_preview(self) -> None: - pass - - def reset(self) -> None: - self.points_registered = 0 - self.point_1 = None - - -class PickingStrategy(IDrawingStrategy, ABC): - POINTS_NEEDED = 1 - PREVIEW = True - - def __init__(self, view: "GUI"): - super().__init__(view) - print("Enabled drawing mode.") - self.tmp_p1 = None - self.bbox_z_rotation = 0 - - def register_point(self, new_point: List[float]) -> None: - self.point_1 = new_point - print("registered point " + str(self.point_1)) - self.points_registered += 1 - - def register_tmp_point(self, new_tmp_point: List[float]) -> None: - self.tmp_p1 = new_tmp_point - - def register_scrolling(self, distance: float) -> None: - self.bbox_z_rotation += distance // 30 - - def draw_preview(self) -> None: # TODO: Refactor - if self.tmp_p1: - tmp_bbox = BBox( - *np.add( - self.tmp_p1, - [ - 0, - config.getfloat("LABEL", "STD_BOUNDINGBOX_WIDTH") / 2, - -config.getfloat("LABEL", "STD_BOUNDINGBOX_HEIGHT") / 3, - ], - ) - ) - tmp_bbox.set_z_rotation(self.bbox_z_rotation) - ogl.draw_cuboid( - tmp_bbox.get_vertices(), draw_vertices=True, vertex_color=(1, 1, 0, 1) - ) - - # Draw bbox with fixed dimensions and rotation at x,y in world space - def get_bbox(self) -> BBox: # TODO: Refactor - final_bbox = BBox( - *np.add( - self.point_1, - [ - 0, - config.getfloat("LABEL", "STD_BOUNDINGBOX_WIDTH") / 2, - -config.getfloat("LABEL", "STD_BOUNDINGBOX_HEIGHT") / 3, - ], - ) - ) - final_bbox.set_z_rotation(self.bbox_z_rotation) - return final_bbox - - def reset(self) -> None: - super().reset() - self.tmp_p1 = None - self.view.button_activate_picking.setChecked(False) - - -class SpanStrategy(IDrawingStrategy, ABC): - POINTS_NEEDED = 4 - PREVIEW = True - CORRECTION = False # Increases dimensions after drawing - - def __init__(self, view: "GUI"): - super().__init__(view) - print("Enabled spanning mode.") - self.preview_color = (1, 1, 0, 1) - self.point_2 = None # second edge - self.point_3 = None # width - self.point_4 = None # height - self.tmp_p2 = None # tmp points for preview - self.tmp_p3 = None - self.tmp_p4 = None - self.p1_w = None # p1 + dir_vector - self.p2_w = None # p2 + dir_vector - self.dir_vector = None # p1 + dir_vector - - def reset(self) -> None: - super().reset() - self.point_2, self.point_3, self.point_4 = (None, None, None) - self.tmp_p2, self.tmp_p3, self.tmp_p4, self.p1_w, self.p2_w = ( - None, - None, - None, - None, - None, - ) - self.view.button_activate_spanning.setChecked(False) - - def register_point(self, new_point: List[float]) -> None: - if self.point_1 is None: - self.point_1 = new_point - self.view.update_status( - "Select a point representing the length of the bounding box." - ) - elif not self.point_2: - self.point_2 = new_point - self.view.update_status( - "Select any point for the depth of the bounding box." - ) - elif not self.point_3: - self.point_3 = new_point - self.view.update_status( - "Select any point for the height of the bounding box." - ) - elif not self.point_4: - self.point_4 = new_point - else: - print("Cannot register point.") - self.points_registered += 1 - - def register_tmp_point(self, new_tmp_point: List[float]) -> None: - if self.point_1 and (not self.point_2): - self.tmp_p2 = new_tmp_point - elif self.point_2 and (not self.point_3): - self.tmp_p3 = new_tmp_point - elif self.point_3: - self.tmp_p4 = new_tmp_point - - def get_bbox(self) -> BBox: - length = math3d.vector_length(np.subtract(self.point_1, self.point_2)) - width = math3d.vector_length(self.dir_vector) - height = self.point_4[2] - self.point_1[2] # can also be negative - - line_center = np.add(self.point_1, self.point_2) / 2 - area_center = np.add(line_center * 2, self.dir_vector) / 2 - center = np.add(area_center, [0, 0, height / 2]) - - # Calculating z-rotation - len_vec_2d = np.subtract(self.point_1, self.point_2) - z_angle = np.arctan(len_vec_2d[1] / len_vec_2d[0]) - - if SpanStrategy.CORRECTION: - length *= 1.1 - width *= 1.1 - height *= 1.1 - - bbox = BBox(*center, length=length, width=width, height=abs(height)) - bbox.set_z_rotation(math3d.radians_to_degrees(z_angle)) - - if not config.getboolean("USER_INTERFACE", "z_rotation_only"): - # Also calculate y_angle - y_angle = np.arctan(len_vec_2d[2] / len_vec_2d[0]) - bbox.set_y_rotation(-math3d.radians_to_degrees(y_angle)) - return bbox - - def draw_preview(self) -> None: - if not self.tmp_p4: - if self.point_1: - ogl.draw_points([self.point_1], color=self.preview_color) - - if self.point_1 and (self.point_2 or self.tmp_p2): - if self.point_2: - self.tmp_p2 = self.point_2 - ogl.draw_points([self.tmp_p2], color=(1, 1, 0, 1)) - ogl.draw_lines([self.point_1, self.tmp_p2], color=self.preview_color) - - if self.point_1 and self.point_2 and (self.tmp_p3 or self.point_3): - if self.point_3: - self.tmp_p3 = self.point_3 - # Get x-y-aligned vector from line to point with intersection - self.dir_vector, intersection = math3d.get_line_perpendicular( - self.point_1, self.point_2, self.tmp_p3 - ) - # Calculate projected vertices - self.p1_w = np.add(self.point_1, self.dir_vector) - self.p2_w = np.add(self.point_2, self.dir_vector) - ogl.draw_points([self.p1_w, self.p2_w], color=self.preview_color) - ogl.draw_rectangles( - [self.point_1, self.point_2, self.p2_w, self.p1_w], - color=(1, 1, 0, 0.5), - ) - - elif ( - self.point_1 - and self.point_2 - and self.point_3 - and self.tmp_p4 - and (not self.point_4) - ): - height1 = self.tmp_p4[2] - self.point_1[2] - p1_t = np.add(self.point_1, [0, 0, height1]) - p2_t = np.add(self.point_2, [0, 0, height1]) - p1_wt = np.add(self.p1_w, [0, 0, height1]) - p2_wt = np.add(self.p2_w, [0, 0, height1]) - - ogl.draw_cuboid( - [ - self.p1_w, - self.point_1, - self.point_2, - self.p2_w, - p1_wt, - p1_t, - p2_t, - p2_wt, - ], - color=(1, 1, 0, 0.5), - draw_vertices=True, - vertex_color=self.preview_color, - ) - - -class RectangleStrategy(IDrawingStrategy, ABC): - POINTS_NEEDED = 3 - PREVIEW = True - - def __init__(self, view: "GUI"): - super().__init__(view) - self.point_2 = None - self.tmp_p2 = None - - def register_point(self, new_point: List[float]): - if self.point_1 is None: - self.point_1 = new_point - self.view.update_status("Select the opposing point of the 2D bounding box.") - elif not self.point_2: - self.point_2 = new_point - else: - print("Cannot register point.") - self.points_registered += 1 - - def register_tmp_point(self, new_tmp_point: List[float]): - if self.point_1 and (not self.point_2): - self.tmp_p2 = new_tmp_point - - def get_bbox(self): - # self.view.controller.pcd_manager. - pass - - def draw_preview(self): - if self.point_1: - ogl.draw_points([self.point_1]) - if self.point_2 or self.tmp_p2: - if self.point_2: - self.tmp_p2 = self.point_2 - bottom_left = [self.point_1[0], self.tmp_p2[1], self.point_1[2]] - top_right = [self.tmp_p2[0], self.point_1[1], self.tmp_p2[2]] - - ogl.draw_rectangles([self.point_1, bottom_left, self.tmp_p2, top_right]) - - def reset(self) -> None: - super().reset() - self.point_2, self.tmp_p2 = (None, None) - self.view.button_activate_drag.setChecked(False) diff --git a/labelCloud/control/drawing_strategies.py b/labelCloud/control/drawing_strategies.py new file mode 100644 index 0000000..4065ef3 --- /dev/null +++ b/labelCloud/control/drawing_strategies.py @@ -0,0 +1,260 @@ +from abc import ABC, ABCMeta, abstractmethod +from typing import TYPE_CHECKING, List + +import numpy as np +import utils.math3d as math3d +import utils.oglhelper as ogl +from model.bbox import BBox + +from .config_manager import config + +if TYPE_CHECKING: + from view.gui import GUI + +import numpy as np + + +class IDrawingStrategy: + __metaclass__ = ABCMeta + POINTS_NEEDED: int + PREVIEW: bool = False + + def __init__(self, view: "GUI") -> None: + self.view = view + self.points_registered = 0 + self.point_1 = None + + def is_bbox_finished(self) -> bool: + return self.points_registered >= self.__class__.POINTS_NEEDED + + @abstractmethod + def register_point(self, new_point: List[float]) -> None: + raise NotImplementedError + + def register_tmp_point(self, new_tmp_point: List[float]) -> None: + pass + + def register_scrolling(self, distance: float) -> None: + pass + + @abstractmethod + def get_bbox(self) -> BBox: + raise NotImplementedError + + def draw_preview(self) -> None: + pass + + def reset(self) -> None: + self.points_registered = 0 + self.point_1 = None + + +class PickingStrategy(IDrawingStrategy, ABC): + POINTS_NEEDED = 1 + PREVIEW = True + + def __init__(self, view: "GUI") -> None: + super().__init__(view) + print("Enabled drawing mode.") + self.view.update_status( + "Please pick the location for the bounding box front center.", + mode="drawing", + ) + self.tmp_p1 = None + self.bbox_z_rotation = 0 + + def register_point(self, new_point: List[float]) -> None: + self.point_1 = new_point + print("registered point " + str(self.point_1)) + self.points_registered += 1 + + def register_tmp_point(self, new_tmp_point: List[float]) -> None: + self.tmp_p1 = new_tmp_point + + def register_scrolling(self, distance: float) -> None: + self.bbox_z_rotation += distance // 30 + + def draw_preview(self) -> None: # TODO: Refactor + if self.tmp_p1: + tmp_bbox = BBox( + *np.add( + self.tmp_p1, + [ + 0, + config.getfloat("LABEL", "STD_BOUNDINGBOX_WIDTH") / 2, + -config.getfloat("LABEL", "STD_BOUNDINGBOX_HEIGHT") / 3, + ], + ) + ) + tmp_bbox.set_z_rotation(self.bbox_z_rotation) + ogl.draw_cuboid( + tmp_bbox.get_vertices(), draw_vertices=True, vertex_color=(1, 1, 0, 1) + ) + + # Draw bbox with fixed dimensions and rotation at x,y in world space + def get_bbox(self) -> BBox: # TODO: Refactor + final_bbox = BBox( + *np.add( + self.point_1, + [ + 0, + config.getfloat("LABEL", "STD_BOUNDINGBOX_WIDTH") / 2, + -config.getfloat("LABEL", "STD_BOUNDINGBOX_HEIGHT") / 3, + ], + ) + ) + final_bbox.set_z_rotation(self.bbox_z_rotation) + return final_bbox + + def reset(self) -> None: + super().reset() + self.tmp_p1 = None + self.view.button_activate_picking.setChecked(False) + + +class SpanStrategy(IDrawingStrategy, ABC): + POINTS_NEEDED = 4 + PREVIEW = True + CORRECTION = False # Increases dimensions after drawing + + def __init__(self, view: "GUI") -> None: + super().__init__(view) + print("Enabled spanning mode.") + self.view.update_status( + "Begin by selecting a vertex of the bounding box.", mode="drawing" + ) + self.preview_color = (1, 1, 0, 1) + self.point_2 = None # second edge + self.point_3 = None # width + self.point_4 = None # height + self.tmp_p2 = None # tmp points for preview + self.tmp_p3 = None + self.tmp_p4 = None + self.p1_w = None # p1 + dir_vector + self.p2_w = None # p2 + dir_vector + self.dir_vector = None # p1 + dir_vector + + def reset(self) -> None: + super().reset() + self.point_2, self.point_3, self.point_4 = (None, None, None) + self.tmp_p2, self.tmp_p3, self.tmp_p4, self.p1_w, self.p2_w = ( + None, + None, + None, + None, + None, + ) + self.view.button_activate_spanning.setChecked(False) + + def register_point(self, new_point: List[float]) -> None: + if self.point_1 is None: + self.point_1 = new_point + self.view.update_status( + "Select a point representing the length of the bounding box." + ) + elif not self.point_2: + self.point_2 = new_point + self.view.update_status( + "Select any point for the depth of the bounding box." + ) + elif not self.point_3: + self.point_3 = new_point + self.view.update_status( + "Select any point for the height of the bounding box." + ) + elif not self.point_4: + self.point_4 = new_point + else: + print("Cannot register point.") + self.points_registered += 1 + + def register_tmp_point(self, new_tmp_point: List[float]) -> None: + if self.point_1 and (not self.point_2): + self.tmp_p2 = new_tmp_point + elif self.point_2 and (not self.point_3): + self.tmp_p3 = new_tmp_point + elif self.point_3: + self.tmp_p4 = new_tmp_point + + def get_bbox(self) -> BBox: + length = math3d.vector_length(np.subtract(self.point_1, self.point_2)) + width = math3d.vector_length(self.dir_vector) + height = self.point_4[2] - self.point_1[2] # can also be negative + + line_center = np.add(self.point_1, self.point_2) / 2 + area_center = np.add(line_center * 2, self.dir_vector) / 2 + center = np.add(area_center, [0, 0, height / 2]) + + # Calculating z-rotation + len_vec_2d = np.subtract(self.point_1, self.point_2) + z_angle = np.arctan(len_vec_2d[1] / len_vec_2d[0]) + + if SpanStrategy.CORRECTION: + length *= 1.1 + width *= 1.1 + height *= 1.1 + + bbox = BBox(*center, length=length, width=width, height=abs(height)) + bbox.set_z_rotation(math3d.radians_to_degrees(z_angle)) + + if not config.getboolean("USER_INTERFACE", "z_rotation_only"): + # Also calculate y_angle + y_angle = np.arctan(len_vec_2d[2] / len_vec_2d[0]) + bbox.set_y_rotation(-math3d.radians_to_degrees(y_angle)) + return bbox + + def draw_preview(self) -> None: + if not self.tmp_p4: + if self.point_1: + ogl.draw_points([self.point_1], color=self.preview_color) + + if self.point_1 and (self.point_2 or self.tmp_p2): + if self.point_2: + self.tmp_p2 = self.point_2 + ogl.draw_points([self.tmp_p2], color=(1, 1, 0, 1)) + ogl.draw_lines([self.point_1, self.tmp_p2], color=self.preview_color) + + if self.point_1 and self.point_2 and (self.tmp_p3 or self.point_3): + if self.point_3: + self.tmp_p3 = self.point_3 + # Get x-y-aligned vector from line to point with intersection + self.dir_vector, intersection = math3d.get_line_perpendicular( + self.point_1, self.point_2, self.tmp_p3 + ) + # Calculate projected vertices + self.p1_w = np.add(self.point_1, self.dir_vector) + self.p2_w = np.add(self.point_2, self.dir_vector) + ogl.draw_points([self.p1_w, self.p2_w], color=self.preview_color) + ogl.draw_rectangles( + [self.point_1, self.point_2, self.p2_w, self.p1_w], + color=(1, 1, 0, 0.5), + ) + + elif ( + self.point_1 + and self.point_2 + and self.point_3 + and self.tmp_p4 + and (not self.point_4) + ): + height1 = self.tmp_p4[2] - self.point_1[2] + p1_t = np.add(self.point_1, [0, 0, height1]) + p2_t = np.add(self.point_2, [0, 0, height1]) + p1_wt = np.add(self.p1_w, [0, 0, height1]) + p2_wt = np.add(self.p2_w, [0, 0, height1]) + + ogl.draw_cuboid( + [ + self.p1_w, + self.point_1, + self.point_2, + self.p2_w, + p1_wt, + p1_t, + p2_t, + p2_wt, + ], + color=(1, 1, 0, 0.5), + draw_vertices=True, + vertex_color=self.preview_color, + ) diff --git a/labelCloud/control/label_manager.py b/labelCloud/control/label_manager.py index 10fa592..fa0afeb 100644 --- a/labelCloud/control/label_manager.py +++ b/labelCloud/control/label_manager.py @@ -1,13 +1,13 @@ import json import ntpath import os -from abc import ABCMeta, abstractmethod, ABC +from abc import ABC, ABCMeta, abstractmethod from typing import List import numpy as np - from model.bbox import BBox from utils import math3d + from .config_manager import config @@ -25,7 +25,7 @@ def get_label_strategy(export_format: str, label_folder: str) -> "IFormattingInt return CentroidFormat(label_folder, relative_rotation=False) -class LabelManager: +class LabelManager(object): LABEL_FORMATS = [ "vertices", "centroid_rel", @@ -37,7 +37,7 @@ class LabelManager: def __init__( self, strategy: str = STD_LABEL_FORMAT, path_to_label_folder: str = None - ): + ) -> None: self.label_folder = path_to_label_folder or config.get("FILE", "label_folder") if not os.path.isdir(self.label_folder): os.mkdir(self.label_folder) @@ -60,7 +60,7 @@ def import_labels(self, pcd_name: str) -> List[BBox]: ) return [] - def export_labels(self, pcd_path: str, bboxes: List[BBox]): + def export_labels(self, pcd_path: str, bboxes: List[BBox]) -> None: pcd_name = ntpath.basename(pcd_path) pcd_folder = os.path.dirname(pcd_path) self.label_strategy.export_labels(bboxes, pcd_name, pcd_folder, pcd_path) @@ -71,7 +71,7 @@ def export_labels(self, pcd_path: str, bboxes: List[BBox]): # -def save_to_label_file(path_to_file, data): +def save_to_label_file(path_to_file, data) -> None: if os.path.isfile(path_to_file): print("File %s already exists, replacing file ..." % path_to_file) if os.path.splitext(path_to_file)[1] == ".json": @@ -82,7 +82,7 @@ def save_to_label_file(path_to_file, data): write_file.write(data) -def round_dec(x, decimal_places: int = LabelManager.EXPORT_PRECISION): +def round_dec(x, decimal_places: int = LabelManager.EXPORT_PRECISION) -> List[float]: return np.round(x, decimal_places).tolist() @@ -110,10 +110,10 @@ def rel2abs_rotation(rel_rotation: float) -> float: return abs_rotation -class IFormattingInterface: +class IFormattingInterface(object): __metaclass__ = ABCMeta - def __init__(self, label_folder, relative_rotation=False): + def __init__(self, label_folder, relative_rotation=False) -> None: self.label_folder = label_folder print("Set export strategy to %s." % self.__class__.__name__) self.relative_rotation = relative_rotation @@ -127,19 +127,19 @@ def __init__(self, label_folder, relative_rotation=False): print("Saving rotations absolutely to positve x-axis in degrees (0..360°).") @abstractmethod - def import_labels(self, pcd_name_stripped): + def import_labels(self, pcd_name_stripped) -> List[BBox]: raise NotImplementedError @abstractmethod - def export_labels(self, bboxes, pcd_name, pcd_folder, pcd_path): + def export_labels(self, bboxes, pcd_name, pcd_folder, pcd_path) -> None: raise NotImplementedError - def update_label_folder(self, new_label_folder): + def update_label_folder(self, new_label_folder) -> None: self.label_folder = new_label_folder class VerticesFormat(IFormattingInterface, ABC): - def import_labels(self, pcd_name_stripped): + def import_labels(self, pcd_name_stripped) -> List[BBox]: labels = [] path_to_label = os.path.join(self.label_folder, pcd_name_stripped + ".json") @@ -170,7 +170,7 @@ def import_labels(self, pcd_name_stripped): print("Imported %s labels from %s." % (len(data["objects"]), path_to_label)) return labels - def export_labels(self, bboxes, pcd_name, pcd_folder, pcd_path): + def export_labels(self, bboxes, pcd_name, pcd_folder, pcd_path) -> None: data = dict() # Header data["folder"] = pcd_folder @@ -198,7 +198,7 @@ def export_labels(self, bboxes, pcd_name, pcd_folder, pcd_path): class CentroidFormat(IFormattingInterface, ABC): - def import_labels(self, pcd_name_stripped): + def import_labels(self, pcd_name_stripped) -> List[BBox]: labels = [] path_to_label = os.path.join(self.label_folder, pcd_name_stripped + ".json") if os.path.isfile(path_to_label): @@ -218,7 +218,7 @@ def import_labels(self, pcd_name_stripped): def export_labels( self, bboxes: List[BBox], pcd_name: str, pcd_folder: str, pcd_path: str - ): + ) -> None: data = dict() # Header data["folder"] = pcd_folder @@ -262,7 +262,7 @@ def export_labels( class KittiFormat(IFormattingInterface, ABC): - def import_labels(self, pcd_name_stripped): + def import_labels(self, pcd_name_stripped) -> List[BBox]: labels = [] path_to_label = os.path.join(self.label_folder, pcd_name_stripped + ".txt") if os.path.isfile(path_to_label): @@ -282,7 +282,7 @@ def import_labels(self, pcd_name_stripped): def export_labels( self, bboxes: List[BBox], pcd_name: str, pcd_folder: str, pcd_path: str - ): + ) -> None: data = str() # Labels diff --git a/labelCloud/control/pcd_manager.py b/labelCloud/control/pcd_manager.py index c7a43e5..cdeabb1 100644 --- a/labelCloud/control/pcd_manager.py +++ b/labelCloud/control/pcd_manager.py @@ -4,30 +4,29 @@ """ import ntpath import os +from dataclasses import dataclass from shutil import copyfile -from typing import List, Tuple, TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, List, Optional, Tuple import numpy as np import open3d as o3d - from model.bbox import BBox from model.point_cloud import PointCloud + from .config_manager import config from .label_manager import LabelManager -from dataclasses import dataclass - if TYPE_CHECKING: from view.gui import GUI @dataclass -class Perspective: +class Perspective(object): zoom: float rotation: Tuple[float, float, float] -def color_pointcloud(points, z_min, z_max): +def color_pointcloud(points, z_min, z_max) -> np.ndarray: palette = np.loadtxt("labelCloud/ressources/rocket-palette.txt") palette_len = len(palette) - 1 @@ -37,14 +36,14 @@ def color_pointcloud(points, z_min, z_max): return colors -class PointCloudManger: +class PointCloudManger(object): PCD_EXTENSIONS = [".pcd", ".ply", ".pts", ".xyz", ".xyzn", ".xyzrgb", ".bin"] ORIGINALS_FOLDER = "original_pointclouds" TRANSLATION_FACTOR = config.getfloat("POINTCLOUD", "STD_TRANSLATION") ZOOM_FACTOR = config.getfloat("POINTCLOUD", "STD_ZOOM") COLORIZE = config.getboolean("POINTCLOUD", "COLORLESS_COLORIZE") - def __init__(self): + def __init__(self) -> None: # Point cloud management self.pcd_folder = config.get("FILE", "pointcloud_folder") self.pcds = [] @@ -59,7 +58,7 @@ def __init__(self): self.collected_object_classes = set() self.saved_perspective: Perspective = None - def read_pointcloud_folder(self): + def read_pointcloud_folder(self) -> None: """Checks point cloud folder and sets self.pcds to all valid point cloud file names.""" if os.path.isdir(self.pcd_folder): self.pcds = [] @@ -93,7 +92,7 @@ def read_pointcloud_folder(self): def pcds_left(self) -> bool: return self.current_id + 1 < len(self.pcds) - def get_next_pcd(self): + def get_next_pcd(self) -> None: print("Loading next point cloud...") if self.pcds_left(): self.current_id += 1 @@ -102,7 +101,7 @@ def get_next_pcd(self): else: print("No point clouds left!") - def get_prev_pcd(self): + def get_prev_pcd(self) -> None: print("Loading previous point cloud...") if self.current_id > 0: self.current_id -= 1 @@ -111,7 +110,7 @@ def get_prev_pcd(self): else: raise Exception("No point cloud left for loading!") - def get_pointcloud(self): + def get_pointcloud(self) -> PointCloud: return self.pointcloud def get_current_name(self) -> str: @@ -125,7 +124,7 @@ def get_current_details(self) -> Tuple[str, int, int]: def get_current_path(self) -> str: return os.path.join(self.pcd_folder, self.pcds[self.current_id]) - def get_labels_from_file(self): + def get_labels_from_file(self) -> List[BBox]: bboxes = self.label_manager.import_labels(self.get_current_name()) print("Loaded %s bboxes!" % len(bboxes)) return bboxes @@ -135,7 +134,7 @@ def set_view(self, view: "GUI") -> None: self.view = view self.view.glWidget.set_pointcloud_controller(self) - def save_labels_into_file(self, bboxes: List[BBox]): + def save_labels_into_file(self, bboxes: List[BBox]) -> None: if self.pcds: self.label_manager.export_labels(self.get_current_path(), bboxes) self.collected_object_classes.update( @@ -222,41 +221,41 @@ def load_pointcloud(self, path_to_pointcloud: str) -> PointCloud: print("=" * 65) return tmp_pcd - def rotate_around_x(self, dangle): + def rotate_around_x(self, dangle) -> None: self.pointcloud.set_rot_x(self.pointcloud.rot_x - dangle) - def rotate_around_y(self, dangle): + def rotate_around_y(self, dangle) -> None: self.pointcloud.set_rot_y(self.pointcloud.rot_y - dangle) - def rotate_around_z(self, dangle): + def rotate_around_z(self, dangle) -> None: self.pointcloud.set_rot_z(self.pointcloud.rot_z - dangle) - def translate_along_x(self, distance): + def translate_along_x(self, distance) -> None: self.pointcloud.set_trans_x( self.pointcloud.trans_x - distance * PointCloudManger.TRANSLATION_FACTOR ) - def translate_along_y(self, distance): + def translate_along_y(self, distance) -> None: self.pointcloud.set_trans_y( self.pointcloud.trans_y + distance * PointCloudManger.TRANSLATION_FACTOR ) - def translate_along_z(self, distance): + def translate_along_z(self, distance) -> None: self.pointcloud.set_trans_z( self.pointcloud.trans_z - distance * PointCloudManger.TRANSLATION_FACTOR ) - def zoom_into(self, distance): + def zoom_into(self, distance) -> None: zoom_distance = distance * PointCloudManger.ZOOM_FACTOR self.pointcloud.set_trans_z(self.pointcloud.trans_z + zoom_distance) - def reset_translation(self): + def reset_translation(self) -> None: self.pointcloud.reset_translation() - def reset_rotation(self): + def reset_rotation(self) -> None: self.pointcloud.rot_x, self.pointcloud.rot_y, self.pointcloud.rot_z = (0, 0, 0) - def reset_transformations(self): + def reset_transformations(self) -> None: self.reset_translation() self.reset_rotation() @@ -300,7 +299,7 @@ def rotate_pointcloud( # HELPER - def get_perspective(self): + def get_perspective(self) -> Tuple[float, float, float]: x_rotation = self.pointcloud.rot_x z_rotation = self.pointcloud.rot_z @@ -315,7 +314,7 @@ def get_perspective(self): # UPDATE GUI - def update_pcd_infos(self, pointcloud_label: str = None): + def update_pcd_infos(self, pointcloud_label: str = None) -> None: self.view.set_pcd_label(pointcloud_label or self.get_current_name()) self.view.update_progress(self.current_id) diff --git a/labelCloud/model/bbox.py b/labelCloud/model/bbox.py index a027b4a..c664d11 100644 --- a/labelCloud/model/bbox.py +++ b/labelCloud/model/bbox.py @@ -1,14 +1,12 @@ -from typing import Tuple, List +from typing import List, Tuple -import OpenGL.GL as GL import numpy as np - +import OpenGL.GL as GL from control.config_manager import config -from utils import math3d -from utils import oglhelper +from utils import math3d, oglhelper -class BBox: +class BBox(object): # order in which the bounding box edges are drawn BBOX_EDGES = [ (0, 1), @@ -44,7 +42,7 @@ def __init__( length: float = None, width: float = None, height: float = None, - ): + ) -> None: self.center = cx, cy, cz self.length = length or config.getfloat("LABEL", "STD_BOUNDINGBOX_LENGTH") self.width = width or config.getfloat("LABEL", "STD_BOUNDINGBOX_WIDTH") @@ -98,29 +96,29 @@ def get_volume(self) -> float: # SETTERS - def set_classname(self, classname): + def set_classname(self, classname) -> None: if classname: self.classname = classname - def set_length(self, length): + def set_length(self, length) -> None: if length > 0: self.length = length else: print("New length is too small.") - def set_width(self, width): + def set_width(self, width) -> None: if width > 0: self.width = width else: print("New width is too small.") - def set_height(self, height): + def set_height(self, height) -> None: if height > 0: self.height = height else: print("New height is too small.") - def set_dimensions(self, length, width, height): + def set_dimensions(self, length, width, height) -> None: if (length > 0) and (width > 0) and (height > 0): self.length = length self.width = width @@ -128,13 +126,13 @@ def set_dimensions(self, length, width, height): else: print("New dimensions are too small.") - def set_x_rotation(self, angle): + def set_x_rotation(self, angle) -> None: self.x_rotation = angle % 360 - def set_y_rotation(self, angle): + def set_y_rotation(self, angle) -> None: self.y_rotation = angle % 360 - def set_z_rotation(self, angle): + def set_z_rotation(self, angle) -> None: self.z_rotation = angle % 360 def set_rotations(self, x_angle, y_angle, z_angle): @@ -142,17 +140,17 @@ def set_rotations(self, x_angle, y_angle, z_angle): self.y_rotation = y_angle self.z_rotation = z_angle - def set_x_translation(self, x_translation): + def set_x_translation(self, x_translation) -> None: self.center = (x_translation, *self.center[1:]) - def set_y_translation(self, y_translation): + def set_y_translation(self, y_translation) -> None: self.center = (self.center[0], y_translation, self.center[2]) - def set_z_translation(self, z_translation): + def set_z_translation(self, z_translation) -> None: self.center = (*self.center[:2], z_translation) # Updates the dimension of the BBox (important after scaling!) - def set_axis_aligned_verticies(self): + def set_axis_aligned_verticies(self) -> None: self.verticies = np.array( [ [-self.length / 2, -self.width / 2, -self.height / 2], @@ -167,7 +165,7 @@ def set_axis_aligned_verticies(self): ) # Draw the BBox using verticies - def draw_bbox(self, highlighted=False): + def draw_bbox(self, highlighted=False) -> None: self.set_axis_aligned_verticies() GL.glPushMatrix() @@ -184,7 +182,7 @@ def draw_bbox(self, highlighted=False): oglhelper.draw_lines(drawing_sequence, color=bbox_color) GL.glPopMatrix() - def draw_orientation(self, crossed_side: bool = True): + def draw_orientation(self, crossed_side: bool = True) -> None: # Get object coordinates for arrow arrow_length = self.length * 0.4 bp2 = [arrow_length, 0, 0] @@ -227,11 +225,11 @@ def draw_orientation(self, crossed_side: bool = True): # MANIPULATORS # Translate bbox by cx, cy, cz - def translate_bbox(self, dx, dy, dz): + def translate_bbox(self, dx, dy, dz) -> None: self.center = math3d.translate_point(list(self.center), dx, dy, dz) # Translate bbox away from extension by half distance - def translate_side(self, p_id_s, p_id_o, distance): + def translate_side(self, p_id_s, p_id_o, distance) -> None: direction = np.subtract( self.get_vertices()[p_id_s], self.get_vertices()[p_id_o] ) @@ -239,7 +237,7 @@ def translate_side(self, p_id_s, p_id_o, distance): self.center = math3d.translate_point(self.center, *translation_vector) # Extend bbox side by distance - def change_side(self, side, distance): # ToDo: Move to controller? + def change_side(self, side, distance) -> None: # ToDo: Move to controller? if side == "right" and self.length + distance > BBox.MIN_DIMENSION: self.length += distance self.translate_side(3, 0, distance) # TODO: Make dependend from side list diff --git a/labelCloud/model/point_cloud.py b/labelCloud/model/point_cloud.py index 7c60bb3..1c6262c 100644 --- a/labelCloud/model/point_cloud.py +++ b/labelCloud/model/point_cloud.py @@ -1,8 +1,8 @@ import ctypes +from typing import List, Tuple import numpy as np import OpenGL.GL as GL - from control.config_manager import config # Get size of float (4 bytes) for VBOs @@ -10,7 +10,7 @@ # Creates an array buffer in a VBO -def create_buffer(attributes): +def create_buffer(attributes) -> GL.glGenBuffers: bufferdata = (ctypes.c_float * len(attributes))(*attributes) # float buffer buffersize = len(attributes) * SIZE_OF_FLOAT # buffer size in bytes @@ -21,8 +21,8 @@ def create_buffer(attributes): return vbo -class PointCloud: - def __init__(self, path): +class PointCloud(object): + def __init__(self, path) -> None: self.path_to_pointcloud = path self.points = None self.colors = None @@ -42,59 +42,59 @@ def __init__(self, path): self.trans_z = 0.0 # GETTERS AND SETTERS - def get_no_of_points(self): + def get_no_of_points(self) -> int: return len(self.points) - def get_no_of_colors(self): + def get_no_of_colors(self) -> int: return len(self.colors) - def get_rotations(self): + def get_rotations(self) -> List[float]: return [self.rot_x, self.rot_y, self.rot_z] - def get_translations(self): + def get_translations(self) -> List[float]: return [self.trans_x, self.trans_y, self.trans_z] - def get_mins_maxs(self): + def get_mins_maxs(self) -> Tuple[float, float]: return self.pcd_mins, self.pcd_maxs - def get_min_max_height(self): + def get_min_max_height(self) -> Tuple[float, float]: return self.pcd_mins[2], self.pcd_maxs[2] - def set_mins_maxs(self): + def set_mins_maxs(self) -> None: self.pcd_mins = np.amin(self.points, axis=0) self.pcd_maxs = np.amax(self.points, axis=0) - def set_rot_x(self, angle): + def set_rot_x(self, angle) -> None: self.rot_x = angle % 360 - def set_rot_y(self, angle): + def set_rot_y(self, angle) -> None: self.rot_y = angle % 360 - def set_rot_z(self, angle): + def set_rot_z(self, angle) -> None: self.rot_z = angle % 360 - def set_rotations(self, x: float, y: float, z: float): + def set_rotations(self, x: float, y: float, z: float) -> None: self.rot_x = x % 360 self.rot_y = y % 360 self.rot_z = z % 360 - def set_trans_x(self, val): + def set_trans_x(self, val) -> None: self.trans_x = val - def set_trans_y(self, val): + def set_trans_y(self, val) -> None: self.trans_y = val - def set_trans_z(self, val): + def set_trans_z(self, val) -> None: self.trans_z = val - def set_translations(self, x: float, y: float, z: float): + def set_translations(self, x: float, y: float, z: float) -> None: self.trans_x = x self.trans_y = y self.trans_z = z # MANIPULATORS - def transform_data(self): + def transform_data(self) -> np.ndarray: if self.colorless: attributes = self.points else: @@ -103,11 +103,11 @@ def transform_data(self): return attributes.flatten() # flatten to single list - def write_vbo(self): + def write_vbo(self) -> None: v_array = self.transform_data() self.vbo = create_buffer(v_array) - def draw_pointcloud(self): + def draw_pointcloud(self) -> None: GL.glTranslate( self.trans_x, self.trans_y, self.trans_z ) # third, pcd translation @@ -153,10 +153,10 @@ def draw_pointcloud(self): GL.glDisableClientState(GL.GL_COLOR_ARRAY) GL.glBindBuffer(GL.GL_ARRAY_BUFFER, 0) - def reset_translation(self): + def reset_translation(self) -> None: self.trans_x, self.trans_y, self.trans_z = self.init_translation - def print_details(self): + def print_details(self) -> None: print("Point Cloud Center:\t\t%s" % np.round(self.center, 2)) print("Point Cloud Minimums:\t%s" % np.round(self.pcd_mins, 2)) print("Point Cloud Maximums:\t%s" % np.round(self.pcd_maxs, 2)) diff --git a/labelCloud/ressources/interface.ui b/labelCloud/ressources/interface.ui index cb5203a..133a3ba 100644 --- a/labelCloud/ressources/interface.ui +++ b/labelCloud/ressources/interface.ui @@ -715,25 +715,6 @@ QListWidget#label_list::item:selected { - - - - true - - - Drag Bounding Box - - - - 20 - 20 - - - - true - - - @@ -1668,7 +1649,6 @@ QListWidget#label_list::item:selected { button_bbox_decr dial_bbox_zrotation button_pick_bbox - button_drag_bbox button_span_bbox button_save_label current_class_lineedit diff --git a/labelCloud/utils/math3d.py b/labelCloud/utils/math3d.py index e3d9c66..e7131cf 100644 --- a/labelCloud/utils/math3d.py +++ b/labelCloud/utils/math3d.py @@ -1,5 +1,5 @@ import math -from typing import List, Tuple +from typing import List, Optional, Tuple import numpy as np @@ -146,7 +146,7 @@ def vertices2rotations( def get_line_perpendicular( line_start: List[float], line_end: List[float], point: List[float] -): +) -> Tuple[tuple, tuple]: """Get line perpendicular to point parallel to x-y-plane Returns: @@ -170,7 +170,7 @@ def get_line_perpendicular( # Calculates intersection between vector (p0, p1) and plane (p_co, p_no) def get_line_plane_intersection( p0: List[float], p1: List[float], p_co: List[float], p_no: List[float], epsilon=1e-6 -): +) -> Optional[np.ndarray]: """Calculate the intersection between a point and a plane. :param p0: Point on the line diff --git a/labelCloud/utils/oglhelper.py b/labelCloud/utils/oglhelper.py index de4d2db..c7835f9 100644 --- a/labelCloud/utils/oglhelper.py +++ b/labelCloud/utils/oglhelper.py @@ -1,12 +1,12 @@ from typing import List, Tuple, Union -import OpenGL.GL as GL import numpy as np +import OpenGL.GL as GL +from model.bbox import BBox +from model.point_cloud import PointCloud from OpenGL import GLU from utils import math3d -from model.bbox import BBox -from model.point_cloud import PointCloud Color4f = Tuple[float, float, float, float] # type alias for type hinting PointList = List[List[float]] diff --git a/labelCloud/view/gui.py b/labelCloud/view/gui.py index b177c7d..4fcd220 100644 --- a/labelCloud/view/gui.py +++ b/labelCloud/view/gui.py @@ -1,11 +1,12 @@ import os from typing import TYPE_CHECKING, List, Set -from PyQt5 import QtWidgets, uic, QtCore, QtGui +from control.config_manager import config +from control.drawing_strategies import PickingStrategy, SpanStrategy +from PyQt5 import QtCore, QtGui, QtWidgets, uic from PyQt5.QtCore import QEvent, Qt -from PyQt5.QtWidgets import QCompleter, QFileDialog, QActionGroup, QAction, QMessageBox +from PyQt5.QtWidgets import QAction, QActionGroup, QCompleter, QFileDialog, QMessageBox -from control.config_manager import config from .settings_dialog import SettingsDialog from .viewer import GLWidget @@ -38,7 +39,7 @@ def set_zrotation_only(state: bool) -> None: class GUI(QtWidgets.QMainWindow): - def __init__(self, control: "Controller"): + def __init__(self, control: "Controller") -> None: super(GUI, self).__init__() print(os.getcwd()) uic.loadUi("labelCloud/ressources/interface.ui", self) @@ -122,10 +123,6 @@ def __init__(self, control: "Controller"): self.button_activate_picking = self.findChild( QtWidgets.QPushButton, "button_pick_bbox" ) - self.button_activate_drag = self.findChild( - QtWidgets.QPushButton, "button_drag_bbox" - ) # ToDo Remove? - self.button_activate_drag.setVisible(False) self.button_activate_spanning = self.findChild( QtWidgets.QPushButton, "button_span_bbox" ) @@ -191,7 +188,7 @@ def __init__(self, control: "Controller"): self.timer.start() # Event connectors - def connect_events(self): + def connect_events(self) -> None: # POINTCLOUD CONTROL self.button_next_pcd.clicked.connect( lambda: self.controller.next_pcd(save=True) @@ -244,14 +241,13 @@ def connect_events(self): # LABEL CONTROL self.button_activate_picking.clicked.connect( - lambda: self.controller.drawing_mode.set_drawing_strategy("PickingStrategy") + lambda: self.controller.drawing_mode.set_drawing_strategy( + PickingStrategy(self) + ) ) self.button_activate_spanning.clicked.connect( - lambda: self.controller.drawing_mode.set_drawing_strategy("SpanStrategy") - ) - self.button_activate_drag.clicked.connect( lambda: self.controller.drawing_mode.set_drawing_strategy( - "RectangleStrategy" + SpanStrategy(self) ) ) self.button_save_labels.clicked.connect(self.controller.save) @@ -307,7 +303,7 @@ def connect_events(self): ) self.action_change_settings.triggered.connect(self.show_settings_dialog) - def set_checkbox_states(self): + def set_checkbox_states(self) -> None: self.action_showfloor.setChecked( config.getboolean("USER_INTERFACE", "show_floor") ) @@ -319,7 +315,7 @@ def set_checkbox_states(self): ) # Collect, filter and forward events to viewer - def eventFilter(self, event_object, event): + def eventFilter(self, event_object, event) -> bool: # Keyboard Events # if (event.type() == QEvent.KeyPress) and (not self.line_edited_activated()): if (event.type() == QEvent.KeyPress) and ( @@ -361,11 +357,13 @@ def closeEvent(self, a0: QtGui.QCloseEvent) -> None: self.timer.stop() a0.accept() - def show_settings_dialog(self): + def show_settings_dialog(self) -> None: dialog = SettingsDialog(self) dialog.exec() - def show_no_pointcloud_dialog(self, pcd_folder: str, pcd_extensions: List[str]): + def show_no_pointcloud_dialog( + self, pcd_folder: str, pcd_extensions: List[str] + ) -> None: msg = QMessageBox(self) msg.setIcon(QMessageBox.Warning) msg.setText( @@ -389,10 +387,10 @@ def init_progress(self, min_value, max_value): self.progressbar_pcd.setMinimum(min_value) self.progressbar_pcd.setMaximum(max_value) - def update_progress(self, value): + def update_progress(self, value) -> None: self.progressbar_pcd.setValue(value) - def update_curr_class_edit(self, force: str = None): + def update_curr_class_edit(self, force: str = None) -> None: if force is not None: self.curr_class_edit.setText(force) else: @@ -400,14 +398,14 @@ def update_curr_class_edit(self, force: str = None): self.controller.bbox_controller.get_active_bbox().get_classname() ) - def update_label_completer(self, classnames=None): + def update_label_completer(self, classnames=None) -> None: if classnames is None: classnames = set() classnames.update(config.getlist("LABEL", "object_classes")) print("COMPLETER CLASSNAMES: %s" % str(classnames)) self.curr_class_edit.setCompleter(QCompleter(classnames)) - def update_bbox_stats(self, bbox): + def update_bbox_stats(self, bbox) -> None: viewing_precision = config.getint("USER_INTERFACE", "viewing_precision") if bbox and not self.line_edited_activated(): self.pos_x_edit.setText(str(round(bbox.get_center()[0], viewing_precision))) @@ -430,7 +428,7 @@ def update_bbox_stats(self, bbox): self.volume_label.setText(str(round(bbox.get_volume(), viewing_precision))) - def update_bbox_parameter(self, parameter: str): + def update_bbox_parameter(self, parameter: str) -> None: str_value = None self.setFocus() # Changes the focus from QLineEdit to the window @@ -466,18 +464,17 @@ def update_bbox_parameter(self, parameter: str): self.controller.bbox_controller.update_rotation(parameter, float(str_value)) return True - def save_new_length(self): + def save_new_length(self) -> None: new_length = self.length_edit.text() self.controller.bbox_controller.get_active_bbox().length = float(new_length) print(f"New length for bounding box submitted → {new_length}.") # Enables, disables the draw mode - def activate_draw_modes(self, state: bool): + def activate_draw_modes(self, state: bool) -> None: self.button_activate_picking.setEnabled(state) - self.button_activate_drag.setEnabled(state) self.button_activate_spanning.setEnabled(state) - def update_status(self, message: str, mode: str = None): + def update_status(self, message: str, mode: str = None) -> None: self.tmp_status.setText(message) if mode: self.update_mode_status(mode) @@ -488,7 +485,7 @@ def line_edited_activated(self) -> bool: return True return False - def update_mode_status(self, mode: str): + def update_mode_status(self, mode: str) -> None: self.action_alignpcd.setEnabled(True) if mode == "drawing": text = "Drawing Mode" @@ -501,7 +498,7 @@ def update_mode_status(self, mode: str): text = "Navigation Mode" self.mode_status.setText(text) - def change_pointcloud_folder(self): + def change_pointcloud_folder(self) -> None: path_to_folder = QFileDialog.getExistingDirectory( self, "Change Point Cloud Folder", @@ -515,7 +512,7 @@ def change_pointcloud_folder(self): self.controller.pcd_manager.get_next_pcd() print("Changed point cloud folder to %s!" % path_to_folder) - def change_label_folder(self): + def change_label_folder(self) -> None: path_to_folder = QFileDialog.getExistingDirectory( self, "Change Label Folder", directory=config.get("FILE", "label_folder") ) @@ -528,7 +525,7 @@ def change_label_folder(self): ) print("Changed label folder to %s!" % path_to_folder) - def update_default_object_class_menu(self, new_classes: Set[str] = None): + def update_default_object_class_menu(self, new_classes: Set[str] = None) -> None: object_classes = set(config.getlist("LABEL", "object_classes")) object_classes.update(new_classes or []) existing_classes = { @@ -544,6 +541,6 @@ def update_default_object_class_menu(self, new_classes: Set[str] = None): self.menu_setdefaultclass.addActions(self.actiongroup_defaultclass.actions()) - def change_default_object_class(self, action: QAction): + def change_default_object_class(self, action: QAction) -> None: config.set("LABEL", "std_object_class", action.text()) print(f"Changed default object class to {action.text()}.") diff --git a/labelCloud/view/settings_dialog.py b/labelCloud/view/settings_dialog.py index 1661ac3..a567ab1 100644 --- a/labelCloud/view/settings_dialog.py +++ b/labelCloud/view/settings_dialog.py @@ -1,12 +1,11 @@ -from PyQt5 import uic -from PyQt5.QtWidgets import QDialog - from control.config_manager import config, config_manager from control.label_manager import LabelManager +from PyQt5 import uic +from PyQt5.QtWidgets import QDialog class SettingsDialog(QDialog): - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super().__init__(parent) self.parent_gui = parent uic.loadUi("labelCloud/ressources/settings_interface.ui", self) @@ -16,7 +15,7 @@ def __init__(self, parent=None): self.buttonBox.rejected.connect(self.chancel) self.reset_button.clicked.connect(self.reset) - def fill_with_current_settings(self): + def fill_with_current_settings(self) -> None: # File self.lineEdit_pointcloudfolder.setText(config.get("FILE", "pointcloud_folder")) self.lineEdit_labelfolder.setText(config.get("FILE", "label_folder")) @@ -163,7 +162,7 @@ def save(self) -> None: ) print("Saved and activated new configuration!") - def reset(self): + def reset(self) -> None: config_manager.reset_to_default() self.fill_with_current_settings() diff --git a/labelCloud/view/viewer.py b/labelCloud/view/viewer.py index eeff31c..46977d6 100644 --- a/labelCloud/view/viewer.py +++ b/labelCloud/view/viewer.py @@ -1,16 +1,15 @@ -from typing import Union +from typing import Tuple, Union import numpy as np import OpenGL.GL as GL -from OpenGL import GLU -from PyQt5 import QtOpenGL, QtGui - -from utils import oglhelper -from control.config_manager import config from control.alignmode import AlignMode from control.bbox_controller import BoundingBoxController -from control.pcd_manager import PointCloudManger +from control.config_manager import config from control.drawing_manager import DrawingManager +from control.pcd_manager import PointCloudManger +from OpenGL import GLU +from PyQt5 import QtGui, QtOpenGL +from utils import oglhelper # Main widget for presenting the point cloud @@ -18,7 +17,7 @@ class GLWidget(QtOpenGL.QGLWidget): NEAR_PLANE = config.getfloat("USER_INTERFACE", "near_plane") FAR_PLANE = config.getfloat("USER_INTERFACE", "far_plane") - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: self.parent = parent QtOpenGL.QGLWidget.__init__(self, parent) self.setMouseTracking( @@ -44,15 +43,15 @@ def __init__(self, parent=None): self.drawing_mode: Union[DrawingManager, None] = None self.align_mode: Union[AlignMode, None] = None - def set_pointcloud_controller(self, pcd_manager: PointCloudManger): + def set_pointcloud_controller(self, pcd_manager: PointCloudManger) -> None: self.pcd_manager = pcd_manager - def set_bbox_controller(self, bbox_controller: BoundingBoxController): + def set_bbox_controller(self, bbox_controller: BoundingBoxController) -> None: self.bbox_controller = bbox_controller # QGLWIDGET METHODS - def initializeGL(self): + def initializeGL(self) -> None: bg_color = [ int(fl_color) for fl_color in config.getlist("USER_INTERFACE", "BACKGROUND_COLOR") @@ -66,7 +65,7 @@ def initializeGL(self): # Must be written again, due to buffer clearing self.pcd_manager.get_pointcloud().write_vbo() - def resizeGL(self, width, height): + def resizeGL(self, width, height) -> None: print("Resized widget.") GL.glViewport(0, 0, width, height) GL.glMatrixMode(GL.GL_PROJECTION) @@ -76,7 +75,7 @@ def resizeGL(self, width, height): GLU.gluPerspective(45.0, aspect, GLWidget.NEAR_PLANE, GLWidget.FAR_PLANE) GL.glMatrixMode(GL.GL_MODELVIEW) - def paintGL(self): + def paintGL(self) -> None: GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) GL.glPushMatrix() # push the current matrix to the current stack @@ -102,7 +101,7 @@ def paintGL(self): self.drawing_mode.draw_preview() if self.align_mode is not None: - if self.align_mode.is_active(): + if self.align_mode.is_active: self.align_mode.draw_preview() # Highlight selected side with filled rectangle @@ -126,7 +125,7 @@ def paintGL(self): # Translates the 2D cursor position from screen plane into 3D world space coordinates def get_world_coords( self, x: int, y: int, z: float = None, correction: bool = False - ): + ) -> Tuple[float, float, float]: x *= self.DEVICE_PIXEL_RATIO # For fixing mac retina bug y *= self.DEVICE_PIXEL_RATIO @@ -159,7 +158,7 @@ def get_world_coords( # Creates a circular mask with radius around center -def circular_mask(arr_length, center, radius): +def circular_mask(arr_length, center, radius) -> np.ndarray: dx = np.arange(arr_length) return (dx[np.newaxis, :] - center) ** 2 + ( dx[:, np.newaxis] - center @@ -167,7 +166,7 @@ def circular_mask(arr_length, center, radius): # Returns the minimum (closest) depth for a specified radius around the center -def depth_min(depths, center, r=4): +def depth_min(depths, center, r=4) -> float: selected_depths = depths[circular_mask(len(depths), center, r)] filtered_depths = selected_depths[(0 < selected_depths) & (selected_depths < 1)] if 0 in depths: # Check if cursor is at widget border @@ -179,7 +178,7 @@ def depth_min(depths, center, r=4): # Returns the mean depth for a specified radius around the center -def depth_smoothing(depths, center, r=15): +def depth_smoothing(depths, center, r=15) -> float: selected_depths = depths[circular_mask(len(depths), center, r)] if 0 in depths: # Check if cursor is at widget border return 1