From f99d2244d5b2973249bfa059cba129036152bd98 Mon Sep 17 00:00:00 2001 From: ch-sa Date: Mon, 22 Nov 2021 23:46:13 +0100 Subject: [PATCH] Use logging - setup logging - replace prints with logs - use pathlib - use shutil - bump version --- labelCloud/__init__.py | 2 +- labelCloud/__main__.py | 7 +- labelCloud/control/alignmode.py | 7 +- labelCloud/control/bbox_controller.py | 9 ++- labelCloud/control/controller.py | 7 +- labelCloud/control/drawing_manager.py | 5 +- labelCloud/control/label_manager.py | 13 ++-- labelCloud/control/pcd_manager.py | 38 ++++++---- labelCloud/label_formats/base.py | 15 ++-- labelCloud/label_formats/centroid_format.py | 7 +- labelCloud/label_formats/kitti_format.py | 5 +- labelCloud/label_formats/vertices_format.py | 7 +- labelCloud/labeling_strategies/picking.py | 4 +- labelCloud/labeling_strategies/spanning.py | 5 +- labelCloud/model/bbox.py | 9 ++- labelCloud/model/point_cloud.py | 11 ++- labelCloud/tests/integration/conftest.py | 3 +- labelCloud/tests/integration/test_gui.py | 3 +- labelCloud/tests/unit/conftest.py | 3 +- labelCloud/utils/__init__.py | 1 + labelCloud/utils/logger.py | 83 +++++++++++++++++++++ labelCloud/utils/math3d.py | 9 ++- labelCloud/view/gui.py | 24 +++--- labelCloud/view/settings_dialog.py | 5 +- labelCloud/view/viewer.py | 5 +- 25 files changed, 204 insertions(+), 83 deletions(-) create mode 100644 labelCloud/utils/logger.py diff --git a/labelCloud/__init__.py b/labelCloud/__init__.py index 54149df..49e0fc1 100644 --- a/labelCloud/__init__.py +++ b/labelCloud/__init__.py @@ -1 +1 @@ -__version__ = "0.6.10" +__version__ = "0.7.0" diff --git a/labelCloud/__main__.py b/labelCloud/__main__.py index 40f16e6..8b10b80 100644 --- a/labelCloud/__main__.py +++ b/labelCloud/__main__.py @@ -1,4 +1,5 @@ import argparse +import logging from labelCloud import __version__ @@ -32,7 +33,7 @@ def setup_example_project() -> None: from labelCloud.control.config_manager import config - print( + logging.info( "Starting labelCloud in example mode.\n" "Setting up project with example point cloud ,label and default config." ) @@ -61,7 +62,7 @@ def setup_example_project() -> None: ), str(label_folder.joinpath("exemplary.json")), ) - print( + logging.info( f"Setup example project in {cwdir}:" "\n - config.ini" "\n - pointclouds/exemplary.ply" @@ -95,7 +96,7 @@ def start_gui(): height = (desktop.height() - view.height()) / 2 view.move(width, height) - print("Showing GUI...") + logging.info("Showing GUI...") sys.exit(app.exec_()) diff --git a/labelCloud/control/alignmode.py b/labelCloud/control/alignmode.py index f211634..377fcf9 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, Union import numpy as np @@ -50,7 +51,7 @@ def change_activation(self, force=None) -> None: self.view.activate_draw_modes( not self.is_active ) # Prevent bbox drawing while aligning - print(f"Alignmode was changed to {self.is_active}!") + logging.info(f"Alignmode was changed to {self.is_active}!") def reset(self, points_only: bool = False) -> None: self.plane1, self.plane2, self.plane3 = (None, None, None) @@ -70,7 +71,7 @@ def register_point(self, new_point) -> None: self.plane3 = new_point self.calculate_angles() else: - print("Cannot register point.") + logging.warning("Cannot register point.") def register_tmp_point(self, new_tmp_point) -> None: if self.plane1 and (not self.plane2): @@ -121,7 +122,7 @@ def calculate_angles(self) -> None: rotation_axis = np.cross(pn_normalized, z_axis) / np.linalg.norm( np.cross(pn_normalized, z_axis) ) - print( + logging.info( f"Alignment rotation: {round(rotation_angle, 2)} " f"around {np.round(rotation_axis, 2)}" ) diff --git a/labelCloud/control/bbox_controller.py b/labelCloud/control/bbox_controller.py index 1e0a7a6..38e16a3 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 typing import TYPE_CHECKING, List, Optional import numpy as np @@ -26,7 +27,7 @@ def wrapper(*args, **kwargs): if args[0].has_active_bbox(): return func(*args, **kwargs) else: - print("There is currently no active bounding box to manipulate.") + logging.warning("There is currently no active bounding box to manipulate.") return wrapper @@ -40,7 +41,9 @@ def wrapper(*args, **kwargs): if not config.getboolean("USER_INTERFACE", "z_rotation_only"): return func(*args, **kwargs) else: - print("Rotations around the x- or y-axis are not supported in this mode.") + logging.warning( + "Rotations around the x- or y-axis are not supported in this mode." + ) return wrapper @@ -282,7 +285,7 @@ def select_bbox_by_ray(self, x: int, y: int) -> None: ) if intersected_bbox_id is not None: self.set_active_bbox(intersected_bbox_id) - print("Selected bounding box %s." % intersected_bbox_id) + logging.info("Selected bounding box %s." % intersected_bbox_id) # HELPER diff --git a/labelCloud/control/controller.py b/labelCloud/control/controller.py index 8751610..09e37e6 100644 --- a/labelCloud/control/controller.py +++ b/labelCloud/control/controller.py @@ -1,3 +1,4 @@ +import logging from typing import Union from PyQt5 import QtCore, QtGui @@ -219,7 +220,7 @@ def key_press_event(self, a0: QtGui.QKeyEvent) -> None: # Reset point cloud pose to intial rotation and translation elif (a0.key() == QtCore.Qt.Key_R) or (a0.key() == QtCore.Qt.Key_Home): self.pcd_manager.reset_transformations() - print("Reseted position to default.") + logging.info("Reseted position to default.") elif a0.key() == QtCore.Qt.Key_Delete: # Delete active bbox self.bbox_controller.delete_current_bbox() @@ -231,10 +232,10 @@ def key_press_event(self, a0: QtGui.QKeyEvent) -> None: elif a0.key() == QtCore.Qt.Key_Escape: if self.drawing_mode.is_active(): self.drawing_mode.reset() - print("Resetted drawn points!") + logging.info("Resetted drawn points!") elif self.align_mode.is_active: self.align_mode.reset() - print("Resetted selected points!") + logging.info("Resetted selected points!") # BBOX MANIPULATION elif (a0.key() == QtCore.Qt.Key_Y) or (a0.key() == QtCore.Qt.Key_Comma): diff --git a/labelCloud/control/drawing_manager.py b/labelCloud/control/drawing_manager.py index f9a7aba..da5b8a2 100644 --- a/labelCloud/control/drawing_manager.py +++ b/labelCloud/control/drawing_manager.py @@ -1,3 +1,4 @@ +import logging from typing import TYPE_CHECKING, Union from ..labeling_strategies import BaseLabelingStrategy @@ -27,11 +28,11 @@ def has_preview(self) -> bool: def set_drawing_strategy(self, strategy: BaseLabelingStrategy) -> None: if self.is_active() and self.drawing_strategy == strategy: self.reset() - print("Deactivated drawing!") + logging.info("Deactivated drawing!") else: if self.is_active(): self.reset() - print("Resetted previous active drawing mode!") + logging.info("Resetted previous active drawing mode!") self.drawing_strategy = strategy diff --git a/labelCloud/control/label_manager.py b/labelCloud/control/label_manager.py index 4fb72f0..be10eea 100644 --- a/labelCloud/control/label_manager.py +++ b/labelCloud/control/label_manager.py @@ -1,3 +1,4 @@ +import logging from pathlib import Path from typing import List @@ -25,7 +26,7 @@ def get_label_strategy(export_format: str, label_folder: Path) -> "BaseLabelForm transformed=False, ) elif export_format != "centroid_abs": - print( + logging.warning( f"Unknown export strategy '{export_format}'. Proceeding with default (centroid_abs)!" ) return CentroidFormat( @@ -58,14 +59,16 @@ def import_labels(self, pcd_path: Path) -> List[BBox]: try: return self.label_strategy.import_labels(pcd_path) except KeyError as key_error: - print("Found a key error with %s in the dictionary." % key_error) - print( + logging.warning("Found a key error with %s in the dictionary." % key_error) + logging.warning( "Could not import labels, please check the consistency of the label format." ) return [] except AttributeError as attribute_error: - print("Attribute Error: %s. Expected a dictionary." % attribute_error) - print( + logging.warning( + "Attribute Error: %s. Expected a dictionary." % attribute_error + ) + logging.warning( "Could not import labels, please check the consistency of the label format." ) return [] diff --git a/labelCloud/control/pcd_manager.py b/labelCloud/control/pcd_manager.py index 4322979..e65008f 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 dataclasses import dataclass from pathlib import Path from shutil import copyfile @@ -11,6 +12,7 @@ import open3d as o3d from ..model import BBox, PointCloud +from ..utils.logger import end_section, green, print_column, start_section from .config_manager import config from .label_manager import LabelManager @@ -77,7 +79,9 @@ def read_pointcloud_folder(self) -> None: if file.suffix in PointCloudManger.PCD_EXTENSIONS: self.pcds.append(file) else: - print(f"Point cloud path {self.pcd_folder} is not a valid directory.") + logging.warning( + f"Point cloud path {self.pcd_folder} is not a valid directory." + ) if self.pcds: self.view.update_status( @@ -106,16 +110,16 @@ def pcds_left(self) -> bool: return self.current_id + 1 < len(self.pcds) def get_next_pcd(self) -> None: - print("Loading next point cloud...") + logging.info("Loading next point cloud...") if self.pcds_left(): self.current_id += 1 self.pointcloud = self.load_pointcloud(self.pcd_path) self.update_pcd_infos() else: - print("No point clouds left!") + logging.warning("No point clouds left!") def get_prev_pcd(self) -> None: - print("Loading previous point cloud...") + logging.info("Loading previous point cloud...") if self.current_id > 0: self.current_id -= 1 self.pointcloud = self.load_pointcloud(self.pcd_path) @@ -125,7 +129,7 @@ def get_prev_pcd(self) -> None: def get_labels_from_file(self) -> List[BBox]: bboxes = self.label_manager.import_labels(self.pcd_path) - print("Loaded %s bboxes!" % len(bboxes)) + logging.info(green("Loaded %s bboxes!" % len(bboxes))) return bboxes # SETTER @@ -142,7 +146,7 @@ def save_labels_into_file(self, bboxes: List[BBox]) -> None: self.view.update_label_completer(self.collected_object_classes) self.view.update_default_object_class_menu(self.collected_object_classes) else: - print("No point clouds to save labels for!") + logging.warning("No point clouds to save labels for!") def save_current_perspective(self, active: bool = True) -> None: if active and self.pointcloud: @@ -150,14 +154,14 @@ def save_current_perspective(self, active: bool = True) -> None: zoom=self.pointcloud.trans_z, rotation=tuple(self.pointcloud.get_rotations()), ) - print(f"Saved current perspective ({self.saved_perspective}).") + logging.info(f"Saved current perspective ({self.saved_perspective}).") else: self.saved_perspective = None - print("Reset saved perspective.") + logging.info("Reset saved perspective.") # MANIPULATOR - def load_pointcloud(self, path_to_pointcloud: Path) -> PointCloud: - print("=" * 20, "Loading", path_to_pointcloud.name, "=" * 20) + def load_pointcloud(self, path_to_pointcloud: str) -> PointCloud: + start_section(f"Loading {path_to_pointcloud.name}") if config.getboolean("USER_INTERFACE", "keep_perspective"): self.save_current_perspective() @@ -181,13 +185,13 @@ def load_pointcloud(self, path_to_pointcloud: Path) -> PointCloud: tmp_pcd.colorless = len(tmp_pcd.colors) == 0 - print("Number of Points: %s" % len(tmp_pcd.points)) + print_column(["Number of Points:", f"{len(tmp_pcd.points):n}"]) # Calculate and set initial translation to view full pointcloud tmp_pcd.center = self.current_o3d_pcd.get_center() tmp_pcd.set_mins_maxs() if PointCloudManger.COLORIZE and tmp_pcd.colorless: - print("Generating colors for colorless point cloud!") + logging.info("Generating colors for colorless point cloud!") min_height, max_height = tmp_pcd.get_min_max_height() tmp_pcd.colors = color_pointcloud(tmp_pcd.points, min_height, max_height) tmp_pcd.colorless = False @@ -211,12 +215,14 @@ def load_pointcloud(self, path_to_pointcloud: Path) -> PointCloud: if self.pointcloud is not None: # Skip first pcd to intialize OpenGL first tmp_pcd.write_vbo() - print("Successfully loaded point cloud from %s!" % path_to_pointcloud) + logging.info( + green(f"Successfully loaded point cloud from {path_to_pointcloud}!") + ) if tmp_pcd.colorless: - print( + logging.warning( "Did not find colors for the loaded point cloud, drawing in colorless mode!" ) - print("=" * 65) + end_section() return tmp_pcd def rotate_around_x(self, dangle) -> None: @@ -280,7 +286,7 @@ def rotate_pointcloud( pcd_maxs = np.amax(self.current_o3d_pcd.points, axis=0) if abs(pcd_mins[2]) > pcd_maxs[2]: - print("Point cloud is upside down, rotating ...") + logging.warning("Point cloud is upside down, rotating ...") self.current_o3d_pcd.rotate( o3d.geometry.get_rotation_matrix_from_xyz([np.pi, 0, 0]), center=(0, 0, 0), diff --git a/labelCloud/label_formats/base.py b/labelCloud/label_formats/base.py index cb90a0f..30a1bd0 100644 --- a/labelCloud/label_formats/base.py +++ b/labelCloud/label_formats/base.py @@ -1,5 +1,6 @@ import json from abc import ABC, abstractmethod +import logging from pathlib import Path from typing import List, Optional, Union @@ -15,22 +16,24 @@ def __init__( self, label_folder: Path, export_precision: int, relative_rotation: bool = False ) -> None: self.label_folder = label_folder - print("Set export strategy to %s." % self.__class__.__name__) + logging.info("Set export strategy to %s." % self.__class__.__name__) self.export_precision = export_precision self.relative_rotation = relative_rotation self.file_ending = ".json" if relative_rotation: - print( + logging.info( "Saving rotations relatively to positve x-axis in radians (-pi..+pi)." ) elif self.__class__.__name__ == "VerticesFormat": - print("Saving rotations implicitly in the vertices coordinates.") + logging.info("Saving rotations implicitly in the vertices coordinates.") else: - print("Saving rotations absolutely to positve x-axis in degrees (0..360°).") + logging.info( + "Saving rotations absolutely to positve x-axis in degrees (0..360°)." + ) def update_label_folder(self, new_label_folder: Path) -> None: self.label_folder = new_label_folder - print(f"Updated label folder to {new_label_folder}.") + logging.info(f"Updated label folder to {new_label_folder}.") def round_dec(self, x, decimal_places: Optional[int] = None) -> List[float]: if not decimal_places: @@ -41,7 +44,7 @@ def save_label_to_file(self, pcd_path: Path, data: Union[dict, str]) -> Path: label_path = self.label_folder.joinpath(pcd_path.stem + self.FILE_ENDING) if label_path.is_file(): - print("File %s already exists, replacing file ..." % label_path) + logging.info("File %s already exists, replacing file ..." % label_path) if label_path.suffix == ".json": with open(label_path, "w") as write_file: json.dump(data, write_file, indent="\t") diff --git a/labelCloud/label_formats/centroid_format.py b/labelCloud/label_formats/centroid_format.py index e6f5fcc..c5621e7 100644 --- a/labelCloud/label_formats/centroid_format.py +++ b/labelCloud/label_formats/centroid_format.py @@ -1,4 +1,5 @@ import json +import logging from pathlib import Path from typing import List @@ -25,7 +26,9 @@ def import_labels(self, pcd_path: Path) -> List[BBox]: bbox.set_rotations(*rotations) bbox.set_classname(label["name"]) labels.append(bbox) - print("Imported %s labels from %s." % (len(data["objects"]), label_path)) + logging.info( + "Imported %s labels from %s." % (len(data["objects"]), label_path) + ) return labels def export_labels(self, bboxes: List[BBox], pcd_path: Path) -> None: @@ -62,7 +65,7 @@ def export_labels(self, bboxes: List[BBox], pcd_path: Path) -> None: # Save to JSON label_path = self.save_label_to_file(pcd_path, data) - print( + logging.info( f"Exported {len(bboxes)} labels to {label_path} " f"in {self.__class__.__name__} formatting!" ) diff --git a/labelCloud/label_formats/kitti_format.py b/labelCloud/label_formats/kitti_format.py index b520d40..e9765dc 100644 --- a/labelCloud/label_formats/kitti_format.py +++ b/labelCloud/label_formats/kitti_format.py @@ -1,3 +1,4 @@ +import logging import math from pathlib import Path from typing import List @@ -44,7 +45,7 @@ def import_labels(self, pcd_path: Path) -> List[BBox]: bbox.set_rotations(0, 0, rel2abs_rotation(float(line_elements[14]))) bbox.set_classname(line_elements[0]) labels.append(bbox) - print("Imported %s labels from %s." % (len(label_lines), label_path)) + logging.info("Imported %s labels from %s." % (len(label_lines), label_path)) return labels def export_labels(self, bboxes: List[BBox], pcd_path: Path) -> None: @@ -78,7 +79,7 @@ def export_labels(self, bboxes: List[BBox], pcd_path: Path) -> None: # Save to TXT path_to_file = self.save_label_to_file(pcd_path, data) - print( + logging.info( f"Exported {len(bboxes)} labels to {path_to_file} " f"in {self.__class__.__name__} formatting!" ) diff --git a/labelCloud/label_formats/vertices_format.py b/labelCloud/label_formats/vertices_format.py index 067f387..253f16e 100644 --- a/labelCloud/label_formats/vertices_format.py +++ b/labelCloud/label_formats/vertices_format.py @@ -1,4 +1,5 @@ import json +import logging from pathlib import Path from typing import List @@ -40,7 +41,9 @@ def import_labels(self, pcd_path: Path) -> List[BBox]: bbox.set_rotations(*rotations) bbox.set_classname(label["name"]) labels.append(bbox) - print("Imported %s labels from %s." % (len(data["objects"]), label_path)) + logging.info( + "Imported %s labels from %s." % (len(data["objects"]), label_path) + ) return labels def export_labels(self, bboxes: List[BBox], pcd_path: Path) -> None: @@ -62,7 +65,7 @@ def export_labels(self, bboxes: List[BBox], pcd_path: Path) -> None: # Save to JSON label_path = self.save_label_to_file(pcd_path, data) - print( + logging.info( f"Exported {len(bboxes)} labels to {label_path} " f"in {self.__class__.__name__} formatting!" ) diff --git a/labelCloud/labeling_strategies/picking.py b/labelCloud/labeling_strategies/picking.py index fbad94b..2ded4a3 100644 --- a/labelCloud/labeling_strategies/picking.py +++ b/labelCloud/labeling_strategies/picking.py @@ -1,3 +1,4 @@ +import logging from typing import TYPE_CHECKING, List import numpy as np @@ -17,7 +18,7 @@ class PickingStrategy(BaseLabelingStrategy): def __init__(self, view: "GUI") -> None: super().__init__(view) - print("Enabled drawing mode.") + logging.info("Enabled drawing mode.") self.view.update_status( "Please pick the location for the bounding box front center.", mode="drawing", @@ -27,7 +28,6 @@ def __init__(self, view: "GUI") -> None: 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: diff --git a/labelCloud/labeling_strategies/spanning.py b/labelCloud/labeling_strategies/spanning.py index 940da8b..bbffeb8 100644 --- a/labelCloud/labeling_strategies/spanning.py +++ b/labelCloud/labeling_strategies/spanning.py @@ -1,3 +1,4 @@ +import logging from typing import TYPE_CHECKING, List import numpy as np @@ -21,7 +22,7 @@ class SpanningStrategy(BaseLabelingStrategy): def __init__(self, view: "GUI") -> None: super().__init__(view) - print("Enabled spanning mode.") + logging.info("Enabled spanning mode.") self.view.update_status( "Begin by selecting a vertex of the bounding box.", mode="drawing" ) @@ -67,7 +68,7 @@ def register_point(self, new_point: List[float]) -> None: elif not self.point_4: self.point_4 = new_point else: - print("Cannot register point.") + logging.warning("Cannot register point.") self.points_registered += 1 def register_tmp_point(self, new_tmp_point: List[float]) -> None: diff --git a/labelCloud/model/bbox.py b/labelCloud/model/bbox.py index 8a78b21..9c06ed8 100644 --- a/labelCloud/model/bbox.py +++ b/labelCloud/model/bbox.py @@ -1,3 +1,4 @@ +import logging from typing import List, Tuple import numpy as np @@ -82,19 +83,19 @@ def set_length(self, length) -> None: if length > 0: self.length = length else: - print("New length is too small.") + logging.warning("New length is too small.") def set_width(self, width) -> None: if width > 0: self.width = width else: - print("New width is too small.") + logging.warning("New width is too small.") def set_height(self, height) -> None: if height > 0: self.height = height else: - print("New height is too small.") + logging.warning("New height is too small.") def set_dimensions(self, length, width, height) -> None: if (length > 0) and (width > 0) and (height > 0): @@ -102,7 +103,7 @@ def set_dimensions(self, length, width, height) -> None: self.width = width self.height = height else: - print("New dimensions are too small.") + logging.warning("New dimensions are too small.") def set_x_rotation(self, angle) -> None: self.x_rotation = angle % 360 diff --git a/labelCloud/model/point_cloud.py b/labelCloud/model/point_cloud.py index fb8f12e..91c1d3c 100644 --- a/labelCloud/model/point_cloud.py +++ b/labelCloud/model/point_cloud.py @@ -6,6 +6,7 @@ import OpenGL.GL as GL from ..control.config_manager import config +from ..utils.logger import print_column # Get size of float (4 bytes) for VBOs SIZE_OF_FLOAT = ctypes.sizeof(ctypes.c_float) @@ -159,7 +160,9 @@ def reset_translation(self) -> None: self.trans_x, self.trans_y, self.trans_z = self.init_translation 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)) - print("Initial Translation:\t%s" % np.round(self.init_translation, 2)) + print_column(["Point Cloud Center:", np.round(self.center, 2)]) + print_column(["Point Cloud Minimums:", np.round(self.pcd_mins, 2)]) + print_column(["Point Cloud Maximums:", np.round(self.pcd_maxs, 2)]) + print_column( + ["Initial Translation:", np.round(self.init_translation, 2)], last=True + ) diff --git a/labelCloud/tests/integration/conftest.py b/labelCloud/tests/integration/conftest.py index 7603e9e..fbda389 100644 --- a/labelCloud/tests/integration/conftest.py +++ b/labelCloud/tests/integration/conftest.py @@ -1,3 +1,4 @@ +import logging import os import pytest @@ -8,7 +9,7 @@ def pytest_configure(config): os.chdir("../labelCloud") - print(f"Set working directory to {os.getcwd()}.") + logging.info(f"Set working directory to {os.getcwd()}.") @pytest.fixture diff --git a/labelCloud/tests/integration/test_gui.py b/labelCloud/tests/integration/test_gui.py index 8c6fe97..757ac05 100644 --- a/labelCloud/tests/integration/test_gui.py +++ b/labelCloud/tests/integration/test_gui.py @@ -1,3 +1,4 @@ +import logging import os from labelCloud.control.config_manager import config @@ -41,7 +42,7 @@ def test_bbox_control_with_buttons(qtbot, startup_pyqt, bbox): qtbot.mouseClick(view.button_left, QtCore.Qt.LeftButton, delay=0) qtbot.mouseClick(view.button_down, QtCore.Qt.LeftButton, delay=0) qtbot.mouseClick(view.button_forward, QtCore.Qt.LeftButton) - print("BBOX: %s" % [str(c) for c in bbox.get_center()]) + logging.info("BBOX: %s" % [str(c) for c in bbox.get_center()]) assert bbox.center == (0.00, 0.00, 0.00) # Scaling diff --git a/labelCloud/tests/unit/conftest.py b/labelCloud/tests/unit/conftest.py index 388f537..9ed7e0a 100644 --- a/labelCloud/tests/unit/conftest.py +++ b/labelCloud/tests/unit/conftest.py @@ -1,3 +1,4 @@ +import logging import os from pathlib import Path @@ -6,7 +7,7 @@ def pytest_configure(config): os.chdir("../labelCloud") - print(f"Set working directory to {os.getcwd()}.") + logging.info(f"Set working directory to {os.getcwd()}.") @pytest.fixture diff --git a/labelCloud/utils/__init__.py b/labelCloud/utils/__init__.py index e69de29..906398a 100644 --- a/labelCloud/utils/__init__.py +++ b/labelCloud/utils/__init__.py @@ -0,0 +1 @@ +from . import logger diff --git a/labelCloud/utils/logger.py b/labelCloud/utils/logger.py new file mode 100644 index 0000000..00e6168 --- /dev/null +++ b/labelCloud/utils/logger.py @@ -0,0 +1,83 @@ +import logging +import shutil +from enum import Enum +from typing import List + +# ---------------------------------- CONFIG ---------------------------------- # + +# Create handlers +c_handler = logging.StreamHandler() +f_handler = logging.FileHandler(".labelCloud.log", mode="a") +c_handler.setLevel(logging.INFO) # TODO: Automatic coloring +f_handler.setLevel(logging.DEBUG) # TODO: Filter colors + +# Create formatters and add it to handlers +f_handler.setFormatter( + logging.Formatter("%(asctime)s - %(levelname)-8s: %(message)s [%(name)s]") +) + + +logging.basicConfig( + level=logging.INFO, + format="%(message)s", + handlers=[c_handler, f_handler], +) + +# ---------------------------------- HELPERS --------------------------------- # + +TERM_SIZE = shutil.get_terminal_size(fallback=(120, 50)) + + +def start_section(text: str): + left_pad = (TERM_SIZE.columns - len(text)) // 2 - 1 + right_pad = TERM_SIZE.columns - len(text) - left_pad - 2 + logging.info(f"{'=' * left_pad} {text} {'=' * right_pad}") + pass + + +def end_section(): + logging.info("=" * TERM_SIZE.columns) + pass + + +rows = [] + + +def print_column(column_values: List[str], last: bool = False): + global rows + rows.append(column_values) + + if last: + col_width = max(len(str(word)) for row in rows for word in row) + 2 # padding + for row in rows: + logging.info("".join(str(word).ljust(col_width) for word in row)) + rows = [] + + +class Format(Enum): + RESET = "\033[0;0m" + RED = "\033[1;31m" + GREEN = "\033[0;32m" + YELLOW = "\033[33m" + BLUE = "\033[1;34m" + CYAN = "\033[1;36m" + BOLD = "\033[;1m" + REVERSE = "\033[;7m" + HEADER = "\033[95m" + OKBLUE = "\033[94m" + OKCYAN = "\033[96m" + OKGREEN = "\033[92m" + WARNING = "\033[93m" + FAIL = "\033[91m" + ENDC = "\033[0m" + UNDERLINE = "\033[4m" + + +def format(text: str, color: Format): + return f"{color.value}{text}{Format.ENDC.value}" + + +red = lambda text: format(text, Format.RED) +green = lambda text: format(text, Format.OKGREEN) +yellow = lambda text: format(text, Format.YELLOW) +bold = lambda text: format(text, Format.BOLD) diff --git a/labelCloud/utils/math3d.py b/labelCloud/utils/math3d.py index e7131cf..2ef6759 100644 --- a/labelCloud/utils/math3d.py +++ b/labelCloud/utils/math3d.py @@ -1,3 +1,4 @@ +import logging import math from typing import List, Optional, Tuple @@ -117,7 +118,7 @@ def vertices2rotations( # Calculate y_rotation if vertices[3][2] != vertices[0][2]: - print("Bounding box is y-rotated!") + logging.info("Bounding box is y-rotated!") x_vec_rot = rotate_around_z( x_vec, -z_rotation, degrees=True ) # apply z-rotation @@ -125,16 +126,16 @@ def vertices2rotations( # Calculate x_rotation if vertices[0][2] != vertices[1][2]: - print("Bounding box is x-rotated!") + logging.info("Bounding box is x-rotated!") y_vec = np.subtract(vertices_trans[1], vertices_trans[0]) # width vector y_vec_rot = rotate_around_z( y_vec, -z_rotation, degrees=True ) # apply z- & y-rotation y_vec_rot = rotate_around_y(y_vec_rot, -y_rotation, degrees=True) x_rotation = radians_to_degrees(np.arctan2(y_vec_rot[2], y_vec_rot[1])) % 360 - print("X-Rotation: %s" % x_rotation) + logging.info("x-Rotation: %s" % x_rotation) - print( + logging.info( "Loaded bounding box has rotation (x, y, z): %s, %s, %s" % (x_rotation, y_rotation, z_rotation) ) diff --git a/labelCloud/view/gui.py b/labelCloud/view/gui.py index de64b9d..a9c3992 100644 --- a/labelCloud/view/gui.py +++ b/labelCloud/view/gui.py @@ -1,3 +1,4 @@ +import logging import re from pathlib import Path from typing import TYPE_CHECKING, List, Set @@ -36,7 +37,11 @@ def string_is_float(string: str, recect_negative: bool = False) -> bool: def set_floor_visibility(state: bool) -> None: - print("SHOW_FLOOR: %s" % state) + logging.info( + "%s floor grid (SHOW_FLOOR: %s).", + "Activated" if state else "Deactivated", + state, + ) config.set("USER_INTERFACE", "show_floor", str(state)) @@ -410,7 +415,7 @@ def eventFilter(self, event_object, event) -> bool: return False def closeEvent(self, a0: QtGui.QCloseEvent) -> None: - print("Closing window after saving ...") + logging.info("Closing window after saving ...") self.controller.save() self.timer.stop() a0.accept() @@ -550,11 +555,6 @@ def update_bbox_parameter(self, parameter: str) -> None: self.controller.bbox_controller.update_rotation(parameter, float(str_value)) return True - 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) -> None: self.button_activate_picking.setEnabled(state) @@ -591,25 +591,25 @@ def change_pointcloud_folder(self) -> None: directory=config.get("FILE", "pointcloud_folder"), ) if not path_to_folder or path_to_folder.isspace(): - print("Please specify a valid folder path.") + logging.warning("Please specify a valid folder path.") else: self.controller.pcd_manager.pcd_folder = path_to_folder self.controller.pcd_manager.read_pointcloud_folder() self.controller.pcd_manager.get_next_pcd() - print("Changed point cloud folder to %s!" % path_to_folder) + logging.info("Changed point cloud folder to %s!" % path_to_folder) def change_label_folder(self) -> None: path_to_folder = QFileDialog.getExistingDirectory( self, "Change Label Folder", directory=config.get("FILE", "label_folder") ) if not path_to_folder or path_to_folder.isspace(): - print("Please specify a valid folder path.") + logging.warning("Please specify a valid folder path.") else: self.controller.pcd_manager.label_manager.label_folder = path_to_folder self.controller.pcd_manager.label_manager.label_strategy.update_label_folder( path_to_folder ) - print("Changed label folder to %s!" % path_to_folder) + logging.info("Changed label folder to %s!" % path_to_folder) def update_default_object_class_menu(self, new_classes: Set[str] = None) -> None: object_classes = set(config.getlist("LABEL", "object_classes")) @@ -629,4 +629,4 @@ def update_default_object_class_menu(self, new_classes: Set[str] = None) -> None 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()}.") + logging.info("Changed default object class to %s.", action.text()) diff --git a/labelCloud/view/settings_dialog.py b/labelCloud/view/settings_dialog.py index ae5e90e..9e7857e 100644 --- a/labelCloud/view/settings_dialog.py +++ b/labelCloud/view/settings_dialog.py @@ -1,3 +1,4 @@ +import logging import pkg_resources from PyQt5 import uic from PyQt5.QtWidgets import QDialog @@ -173,11 +174,11 @@ def save(self) -> None: strategy=config["LABEL"]["label_format"], path_to_label_folder=config["FILE"]["label_folder"], ) - print("Saved and activated new configuration!") + logging.info("Saved and activated new configuration!") def reset(self) -> None: config_manager.reset_to_default() self.fill_with_current_settings() def chancel(self) -> None: - print("Settings dialog was chanceled!") + logging.info("Settings dialog was chanceled!") diff --git a/labelCloud/view/viewer.py b/labelCloud/view/viewer.py index dcc74c3..9a508e9 100644 --- a/labelCloud/view/viewer.py +++ b/labelCloud/view/viewer.py @@ -1,3 +1,4 @@ +import logging from typing import Tuple, Union import numpy as np @@ -61,13 +62,13 @@ def initializeGL(self) -> None: GL.glEnable(GL.GL_DEPTH_TEST) # for visualization of depth GL.glEnable(GL.GL_BLEND) # enable transparency GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA) - print("Intialized widget.") + logging.info("Intialized widget.") # Must be written again, due to buffer clearing self.pcd_manager.pointcloud.write_vbo() def resizeGL(self, width, height) -> None: - print("Resized widget.") + logging.info("Resized widget.") GL.glViewport(0, 0, width, height) GL.glMatrixMode(GL.GL_PROJECTION) GL.glLoadIdentity()