From 0e49ce17fda165cb436d8fa2b68c8dc5645cfec4 Mon Sep 17 00:00:00 2001 From: Adi Vardi <57910756+adivardi@users.noreply.github.com> Date: Tue, 30 Apr 2024 20:35:20 +0200 Subject: [PATCH] Add keyboard shortcuts for scaling, allow loading files recursively (#161) * Add keyboard shortcuts for scaling * Find pointclouds files recursively * rm duplicate keys and improve readme * rename size_increase to step * apply black formatting and set pip to use correct black version * Fix use in for single element, restore moved type:ignore --- README.md | 17 +++++++----- docs/shortcuts.md | 23 ++++++++++----- labelCloud/control/alignmode.py | 1 + labelCloud/control/bbox_controller.py | 40 +++++++++++++++++++++++++++ labelCloud/control/config_manager.py | 1 + labelCloud/control/controller.py | 25 +++++++++++++++-- labelCloud/control/pcd_manager.py | 8 +++--- labelCloud/model/point_cloud.py | 22 +++++++++------ labelCloud/utils/oglhelper.py | 12 ++++---- requirements.txt | 4 +-- 10 files changed, 117 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 6d98b7b..8785db0 100644 --- a/README.md +++ b/README.md @@ -116,13 +116,16 @@ All rotations are counterclockwise (i.e. a z-rotation of 90°/π is from the pos | `W`, `A`, `S`, `D` | Translates the Bounding Box back, left, front, right | | `Ctrl` + Right Mouse Button | Translates the Bounding Box in all dimensions | | `Q`, `E` | Lifts the Bounding Box up, down | -| `Z`, `X` | Rotates the Boundign Box around z-Axis | -| `C`, `V` | Rotates the Boundign Box around y-Axis | -| `B`, `N` | Rotates the Boundign Box around x-Axis | +| `Z`, `X` | Rotates the Bounding Box around z-Axis | +| `C`, `V` | Rotates the Bounding Box around y-Axis | +| `B`, `N` | Rotates the Bounding Box around x-Axis | +| `I`/ `O` | Increase/Decrease the Bounding Box length | +| `K`/ `L` | Increase/Decrease the Bounding Box width | +| `,`/ `.` | Increase/Decrease the Bounding Box height | | Scrolling with the Cursor above a Bounding Box Side ("Side Pulling") | Changes the Dimension of the Bounding Box | | `R`/`Left`, `F`/`Right` | Previous/Next sample | | `T`/`Up`, `G`/`Down` | Previous/Next bbox | -| `Y`/`,`,`H`/`.` | Change current bbox class to previous/next in list | +| `Y`, `H` | Change current bbox class to previous/next in list | | `1`-`9` | Select any of first 9 bboxes with number keys | | *General* | | | `Del` | Deletes Current Bounding Box | @@ -150,11 +153,11 @@ If you are using the tool for a scientific project please consider citing our [p author = {Christoph Sager and Patrick Zschech and Niklas Kuhl}, title = {{labelCloud}: A Lightweight Labeling Tool for Domain-Agnostic 3D Object Detection in Point Clouds}, journal = {Computer-Aided Design and Applications} - } - + } + # CAD Conference @misc{sager2021labelcloud, - title={labelCloud: A Lightweight Domain-Independent Labeling Tool for 3D Object Detection in Point Clouds}, + title={labelCloud: A Lightweight Domain-Independent Labeling Tool for 3D Object Detection in Point Clouds}, author={Christoph Sager and Patrick Zschech and Niklas Kühl}, year={2021}, eprint={2103.04970}, diff --git a/docs/shortcuts.md b/docs/shortcuts.md index 8addd32..a6b019b 100644 --- a/docs/shortcuts.md +++ b/docs/shortcuts.md @@ -5,16 +5,25 @@ There are a number of shortcuts supported for frequently used actions. | Shortcut | Description | | :------------------------------------------------------------------: | ---------------------------------------------------- | | *Navigation* | | -| Left Mouse Button | Rotates the Point Cloud | -| Right Mouse Button | Translates the Point Cloud | +| Left Mouse Button | Rotates the camera around Point Cloud centroid | +| Right Mouse Button | Translates the camera | | Mouse Wheel | Zooms into the Point Cloud | | *Correction* | | -| `W`, `A`, `S`, `D`
`Ctrl` + Right Mouse Button | Translates the Bounding Box back, left, front, right | +| `W`, `A`, `S`, `D` | Translates the Bounding Box back, left, front, right | +| `Ctrl` + Right Mouse Button | Translates the Bounding Box in all dimensions | | `Q`, `E` | Lifts the Bounding Box up, down | -| `X`, `Y` | Rotates the Boundign Box around z-Axis | +| `Z`, `X` | Rotates the Bounding Box around z-Axis | +| `C`, `V` | Rotates the Bounding Box around y-Axis | +| `B`, `N` | Rotates the Bounding Box around x-Axis | +| `I`/ `O` | Increase/Decrease the Bounding Box length | +| `K`/ `L` | Increase/Decrease the Bounding Box width | +| `,`/ `.` | Increase/Decrease the Bounding Box height | | Scrolling with the Cursor above a Bounding Box Side ("Side Pulling") | Changes the Dimension of the Bounding Box | -| `C` & `V`, `B` & `N` | Rotates the Bounding Box around y-Axis, x-Axis | +| `R`/`Left`, `F`/`Right` | Previous/Next sample | +| `T`/`Up`, `G`/`Down` | Previous/Next bbox | +| `Y`, `H` | Change current bbox class to previous/next in list | +| `1`-`9` | Select any of first 9 bboxes with number keys | | *General* | | | `Del` | Deletes Current Bounding Box | -| `R` | Resets Perspective | -| `Esc` | Cancels Selected Points | \ No newline at end of file +| `P`/`Home` | Resets Perspective | +| `Esc` | Cancels Selected Points | diff --git a/labelCloud/control/alignmode.py b/labelCloud/control/alignmode.py index 0980635..4328f1d 100644 --- a/labelCloud/control/alignmode.py +++ b/labelCloud/control/alignmode.py @@ -3,6 +3,7 @@ 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. """ + import logging from typing import TYPE_CHECKING, Optional diff --git a/labelCloud/control/bbox_controller.py b/labelCloud/control/bbox_controller.py index d163711..c50a709 100644 --- a/labelCloud/control/bbox_controller.py +++ b/labelCloud/control/bbox_controller.py @@ -4,6 +4,7 @@ Bounding Box Management: adding, selecting updating, deleting bboxes; Possible Active Bounding Box Manipulations: rotation, translation, scaling """ + import logging from functools import wraps from typing import TYPE_CHECKING, List, Optional @@ -296,6 +297,45 @@ def scale( self.get_active_bbox().set_dimensions(new_length, new_width, new_height) # type: ignore + @has_active_bbox_decorator + def scale_along_length( + self, step: Optional[float] = None, decrease: bool = False + ) -> None: + step = step or config.getfloat("LABEL", "std_scaling") + if decrease: + step *= -1 + + active_bbox: Bbox = self.get_active_bbox() # type: ignore + length, width, height = active_bbox.get_dimensions() + new_length = length + step + active_bbox.set_dimensions(new_length, width, height) + + @has_active_bbox_decorator + def scale_along_width( + self, step: Optional[float] = None, decrease: bool = False + ) -> None: + step = step or config.getfloat("LABEL", "std_scaling") + if decrease: + step *= -1 + + active_bbox: Bbox = self.get_active_bbox() # type: ignore + length, width, height = active_bbox.get_dimensions() + new_width = width + step + active_bbox.set_dimensions(length, new_width, height) + + @has_active_bbox_decorator + def scale_along_height( + self, step: Optional[float] = None, decrease: bool = False + ) -> None: + step = step or config.getfloat("LABEL", "std_scaling") + if decrease: + step *= -1 + + active_bbox: Bbox = self.get_active_bbox() # type: ignore + length, width, height = active_bbox.get_dimensions() + new_height = height + step + active_bbox.set_dimensions(length, width, new_height) + def select_bbox_by_ray(self, x: int, y: int) -> None: intersected_bbox_id = oglhelper.get_intersected_bboxes( x, diff --git a/labelCloud/control/config_manager.py b/labelCloud/control/config_manager.py index 75463b0..1bf1cd0 100644 --- a/labelCloud/control/config_manager.py +++ b/labelCloud/control/config_manager.py @@ -1,4 +1,5 @@ """Load configuration from .ini file.""" + import configparser from pathlib import Path from typing import List, Union diff --git a/labelCloud/control/controller.py b/labelCloud/control/controller.py index 0026586..7e6f7f4 100644 --- a/labelCloud/control/controller.py +++ b/labelCloud/control/controller.py @@ -311,6 +311,27 @@ def key_press_event(self, a0: QtGui.QKeyEvent) -> None: elif a0.key() == Keys.Key_E: # move down self.bbox_controller.translate_along_z(down=True) + + # BBOX Scaling + elif a0.key() == Keys.Key_I: + # increase length + self.bbox_controller.scale_along_length() + elif a0.key() == Keys.Key_O: + # decrease length + self.bbox_controller.scale_along_length(decrease=True) + elif a0.key() == Keys.Key_K: + # increase width + self.bbox_controller.scale_along_width() + elif a0.key() == Keys.Key_L: + # decrease width + self.bbox_controller.scale_along_width(decrease=True) + elif a0.key() == Keys.Key_Comma: + # increase height + self.bbox_controller.scale_along_height() + elif a0.key() == Keys.Key_Period: + # decrease height + self.bbox_controller.scale_along_height(decrease=True) + elif a0.key() in [Keys.Key_R, Keys.Key_Left]: # load previous sample self.prev_pcd() @@ -323,10 +344,10 @@ def key_press_event(self, a0: QtGui.QKeyEvent) -> None: elif a0.key() in [Keys.Key_G, Keys.Key_Down]: # select previous bbox self.select_relative_bbox(1) - elif a0.key() in [Keys.Key_Y, Keys.Key_Comma]: + elif a0.key() == Keys.Key_Y: # change bbox class to previous available class self.select_relative_class(-1) - elif a0.key() in [Keys.Key_H, Keys.Key_Period]: + elif a0.key() == Keys.Key_H: # change bbox class to next available class self.select_relative_class(1) elif a0.key() in list(range(49, 58)): diff --git a/labelCloud/control/pcd_manager.py b/labelCloud/control/pcd_manager.py index c0093ed..2c2db24 100644 --- a/labelCloud/control/pcd_manager.py +++ b/labelCloud/control/pcd_manager.py @@ -2,6 +2,7 @@ Module to manage the point clouds (loading, navigation, floor alignment). Sets the point cloud and original point cloud path. Initiate the writing to the virtual object buffer. """ + import logging from pathlib import Path from shutil import copyfile @@ -41,9 +42,8 @@ def __init__(self) -> None: # Point cloud control self.pointcloud: Optional[PointCloud] = None - self.collected_object_classes: Set[ - str - ] = set() # TODO: this should integrate with the new label definition setup. + # TODO: this should integrate with the new label definition setup. + self.collected_object_classes: Set[str] = set() self.saved_perspective: Optional[Perspective] = None @property @@ -60,7 +60,7 @@ def read_pointcloud_folder(self) -> None: """Checks point cloud folder and sets self.pcds to all valid point cloud file names.""" if self.pcd_folder.is_dir(): self.pcds = [] - for file in sorted(self.pcd_folder.iterdir()): + for file in sorted(self.pcd_folder.rglob("*")): if file.suffix in PointCloudManger.PCD_EXTENSIONS: self.pcds.append(file) else: diff --git a/labelCloud/model/point_cloud.py b/labelCloud/model/point_cloud.py index 7f0db60..a3c6154 100644 --- a/labelCloud/model/point_cloud.py +++ b/labelCloud/model/point_cloud.py @@ -378,19 +378,25 @@ def print_details(self) -> None: print_column( [ "Number of Points:", - green(len(self.points)) - if len(self.points) > 0 - else red(len(self.points)), + ( + green(len(self.points)) + if len(self.points) > 0 + else red(len(self.points)) + ), ] ) print_column( [ "Number of Colors:", - yellow("None") - if self.colorless - else green(len(self.colors)) # type: ignore - if len(self.colors) == len(self.points) # type: ignore - else red(len(self.colors)), # type: ignore + ( + yellow("None") + if self.colorless + else ( + green(len(self.colors)) # type: ignore + if len(self.colors) == len(self.points) # type: ignore + else red(len(self.colors)) # type: ignore + ) + ), ] ) print_column(["Point Cloud Center:", str(np.round(self.center, 2))]) diff --git a/labelCloud/utils/oglhelper.py b/labelCloud/utils/oglhelper.py index b7428ff..f533f7a 100644 --- a/labelCloud/utils/oglhelper.py +++ b/labelCloud/utils/oglhelper.py @@ -13,9 +13,9 @@ from ..model import BBox, PointCloud -DEVICE_PIXEL_RATIO: Optional[ - float -] = None # is set once and for every window resize (retina display fix) +DEVICE_PIXEL_RATIO: Optional[float] = ( + None # is set once and for every window resize (retina display fix) +) def draw_points( @@ -178,9 +178,9 @@ def get_intersected_sides( p0, p1 = get_pick_ray(x, y, modelview, projection) # Calculate picking ray vertices = bbox.get_vertices() - intersections: List[ - Tuple[list, str] - ] = list() # (intersection_point, bounding box side) + intersections: List[Tuple[list, str]] = ( + list() + ) # (intersection_point, bounding box side) for side, indices in BBOX_SIDES.items(): # Calculate plane equation pl1 = vertices[indices[0]] # point in plane diff --git a/requirements.txt b/requirements.txt index 059b76b..e0ec63a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,8 +9,8 @@ pytest~=7.3.1 pytest-qt~=4.2.0 # Development -black~=23.1.0 +black>=23.1.0 mypy~=1.3.0 PyQt5-stubs~=5.15.6 types-setuptools~=67.8.0 -types-pkg-resources~=0.1.3 \ No newline at end of file +types-pkg-resources~=0.1.3