Skip to content

Commit

Permalink
Merge pull request #38 from ch-sa/ch-sa/refactoring
Browse files Browse the repository at this point in the history
Add modules to improve structure
  • Loading branch information
ch-sa authored Nov 21, 2021
2 parents 0469dae + a9a15c5 commit ad35f02
Show file tree
Hide file tree
Showing 16 changed files with 436 additions and 372 deletions.
10 changes: 2 additions & 8 deletions labelCloud/control/bbox_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,14 @@ def __init__(self) -> None:

# GETTERS
def has_active_bbox(self) -> bool:
return 0 <= self.active_bbox_id < self.get_no_of_bboxes()
return 0 <= self.active_bbox_id < len(self.bboxes)

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) -> List[BBox]:
return self.bboxes

def get_no_of_bboxes(self) -> int:
return len(self.bboxes)

@has_active_bbox_decorator
def get_classname(self) -> str:
return self.get_active_bbox().get_classname()
Expand All @@ -96,7 +90,7 @@ 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:
self.set_active_bbox(self.get_no_of_bboxes() - 1)
self.set_active_bbox(len(self.bboxes) - 1)
else:
self.update_label_list()

Expand Down
2 changes: 1 addition & 1 deletion labelCloud/control/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def prev_pcd(self) -> None:
# CONTROL METHODS
def save(self) -> None:
"""Saves all bounding boxes in the label file."""
self.pcd_manager.save_labels_into_file(self.bbox_controller.get_bboxes())
self.pcd_manager.save_labels_into_file(self.bbox_controller.bboxes)

def reset(self) -> None:
"""Resets the controllers and bounding boxes from the current screen."""
Expand Down
9 changes: 5 additions & 4 deletions labelCloud/control/drawing_manager.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from typing import TYPE_CHECKING, Union

from labeling_strategies import BaseLabelingStrategy

from .bbox_controller import BoundingBoxController
from .drawing_strategies import IDrawingStrategy

if TYPE_CHECKING:
from view.gui import GUI
Expand All @@ -11,20 +12,20 @@ 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
self.drawing_strategy: Union[BaseLabelingStrategy, None] = None

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)
return isinstance(self.drawing_strategy, BaseLabelingStrategy)

def has_preview(self) -> bool:
if self.is_active():
return self.drawing_strategy.__class__.PREVIEW

def set_drawing_strategy(self, strategy: IDrawingStrategy) -> None:
def set_drawing_strategy(self, strategy: BaseLabelingStrategy) -> None:
if self.is_active() and self.drawing_strategy == strategy:
self.reset()
print("Deactivated drawing!")
Expand Down
265 changes: 12 additions & 253 deletions labelCloud/control/label_manager.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
import json
import ntpath
import os
from abc import ABC, ABCMeta, abstractmethod
from typing import List

import numpy as np
from label_formats import BaseLabelFormat, CentroidFormat, KittiFormat, VerticesFormat
from model.bbox import BBox
from utils import math3d

from .config_manager import config


def get_label_strategy(export_format: str, label_folder: str) -> "IFormattingInterface":
def get_label_strategy(export_format: str, label_folder: str) -> "BaseLabelFormat":
if export_format == "vertices":
return VerticesFormat(label_folder)
return VerticesFormat(label_folder, LabelManager.EXPORT_PRECISION)
elif export_format == "centroid_rel":
return CentroidFormat(label_folder, relative_rotation=True)
return CentroidFormat(
label_folder, LabelManager.EXPORT_PRECISION, relative_rotation=True
)
elif export_format == "kitti":
return KittiFormat(label_folder, relative_rotation=True)
return KittiFormat(
label_folder, LabelManager.EXPORT_PRECISION, relative_rotation=True
)
elif export_format != "centroid_abs":
print(
f"Unknown export strategy '{export_format}'. Proceeding with default (centroid_abs)!"
)
return CentroidFormat(label_folder, relative_rotation=False)
return CentroidFormat(
label_folder, LabelManager.EXPORT_PRECISION, relative_rotation=False
)


class LabelManager(object):
Expand Down Expand Up @@ -64,247 +67,3 @@ 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)


#
# FORMAT HELPERS
#


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":
with open(path_to_file, "w") as write_file:
json.dump(data, write_file, indent="\t")
else:
with open(path_to_file, "w") as write_file:
write_file.write(data)


def round_dec(x, decimal_places: int = LabelManager.EXPORT_PRECISION) -> List[float]:
return np.round(x, decimal_places).tolist()


def abs2rel_rotation(abs_rotation: float) -> float:
"""Convert absolute rotation 0..360° into -pi..+pi from x-Axis.
:param abs_rotation: Counterclockwise rotation from x-axis around z-axis
:return: Relative rotation from x-axis around z-axis
"""
rel_rotation = np.deg2rad(abs_rotation)
if rel_rotation > np.pi:
rel_rotation = rel_rotation - 2 * np.pi
return rel_rotation


def rel2abs_rotation(rel_rotation: float) -> float:
"""Convert relative rotation from -pi..+pi into 0..360° from x-Axis.
:param rel_rotation: Rotation from x-axis around z-axis
:return: Counterclockwise rotation from x-axis around z-axis
"""
abs_rotation = np.rad2deg(rel_rotation)
if abs_rotation < 0:
abs_rotation = abs_rotation + 360
return abs_rotation


class IFormattingInterface(object):
__metaclass__ = ABCMeta

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
if relative_rotation:
print(
"Saving rotations relatively to positve x-axis in radians (-pi..+pi)."
)
elif self.__class__.__name__ == "VerticesFormat":
print("Saving rotations implicitly in the vertices coordinates.")
else:
print("Saving rotations absolutely to positve x-axis in degrees (0..360°).")

@abstractmethod
def import_labels(self, pcd_name_stripped) -> List[BBox]:
raise NotImplementedError

@abstractmethod
def export_labels(self, bboxes, pcd_name, pcd_folder, pcd_path) -> None:
raise NotImplementedError

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) -> List[BBox]:
labels = []
path_to_label = os.path.join(self.label_folder, pcd_name_stripped + ".json")

if os.path.isfile(path_to_label):
with open(path_to_label, "r") as read_file:
data = json.load(read_file)

for label in data["objects"]:
vertices = label["vertices"]

# Calculate centroid
centroid = np.add(
np.subtract(vertices[4], vertices[2]) / 2, vertices[2]
)

# Calculate dimensions
length = math3d.vector_length(np.subtract(vertices[0], vertices[3]))
width = math3d.vector_length(np.subtract(vertices[0], vertices[1]))
height = math3d.vector_length(np.subtract(vertices[0], vertices[4]))

# Calculate rotations
rotations = math3d.vertices2rotations(vertices, centroid)

bbox = BBox(*centroid, length, width, height)
bbox.set_rotations(*rotations)
bbox.set_classname(label["name"])
labels.append(bbox)
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) -> None:
data = dict()
# Header
data["folder"] = pcd_folder
data["filename"] = pcd_name
data["path"] = pcd_path

# Labels
data["objects"] = []
for bbox in bboxes:
label = dict()
label["name"] = bbox.get_classname()
label["vertices"] = round_dec(
bbox.get_vertices().tolist()
) # ToDo: Add option for axis-aligned vertices
data["objects"].append(label)

path_to_json = os.path.join(
self.label_folder, os.path.splitext(pcd_name)[0] + ".json"
)
save_to_label_file(path_to_json, data)
print(
"Exported %s labels to %s in %s formatting!"
% (len(bboxes), path_to_json, self.__class__.__name__)
)


class CentroidFormat(IFormattingInterface, ABC):
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):
with open(path_to_label, "r") as read_file:
data = json.load(read_file)

for label in data["objects"]:
bbox = BBox(*label["centroid"].values(), *label["dimensions"].values())
rotations = label["rotations"].values()
if self.relative_rotation:
rotations = map(rel2abs_rotation, rotations)
bbox.set_rotations(*rotations)
bbox.set_classname(label["name"])
labels.append(bbox)
print("Imported %s labels from %s." % (len(data["objects"]), path_to_label))
return labels

def export_labels(
self, bboxes: List[BBox], pcd_name: str, pcd_folder: str, pcd_path: str
) -> None:
data = dict()
# Header
data["folder"] = pcd_folder
data["filename"] = pcd_name
data["path"] = pcd_path

# Labels
data["objects"] = []
for bbox in bboxes:
label = dict()
label["name"] = bbox.get_classname()
label["centroid"] = {
str(axis): round_dec(val)
for axis, val in zip(["x", "y", "z"], bbox.get_center())
}
label["dimensions"] = {
str(dim): round_dec(val)
for dim, val in zip(
["length", "width", "height"], bbox.get_dimensions()
)
}
conv_rotations = bbox.get_rotations()
if self.relative_rotation:
conv_rotations = map(abs2rel_rotation, conv_rotations)

label["rotations"] = {
str(axis): round_dec(angle)
for axis, angle in zip(["x", "y", "z"], conv_rotations)
}
data["objects"].append(label)

# Save to JSON
path_to_json = os.path.join(
self.label_folder, os.path.splitext(pcd_name)[0] + ".json"
)
save_to_label_file(path_to_json, data)
print(
"Exported %s labels to %s in %s formatting!"
% (len(bboxes), path_to_json, self.__class__.__name__)
)


class KittiFormat(IFormattingInterface, ABC):
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):
with open(path_to_label, "r") as read_file:
label_lines = read_file.readlines()

for line in label_lines:
line_elements = line.split()
centroid = [float(v) for v in line_elements[11:14]]
dimensions = [float(v) for v in line_elements[8:11]]
bbox = BBox(*centroid, *dimensions)
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), path_to_label))
return labels

def export_labels(
self, bboxes: List[BBox], pcd_name: str, pcd_folder: str, pcd_path: str
) -> None:
data = str()

# Labels
for bbox in bboxes:
obj_type = bbox.get_classname()
location = " ".join([str(round_dec(v)) for v in bbox.get_center()])
dimensions = " ".join([str(round_dec(v)) for v in bbox.get_dimensions()])
rotation_y = round_dec(abs2rel_rotation(bbox.get_z_rotation()))

data += (
" ".join(
[obj_type, "0 0 0 0 0 0 0", dimensions, location, str(rotation_y)]
)
+ "\n"
)

# Save to TXT
path_to_txt = os.path.join(
self.label_folder, os.path.splitext(pcd_name)[0] + ".txt"
)
save_to_label_file(path_to_txt, data)
print(
"Exported %s labels to %s in %s formatting!"
% (len(bboxes), path_to_txt, self.__class__.__name__)
)
8 changes: 8 additions & 0 deletions labelCloud/label_formats/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from .base import (
BaseLabelFormat,
abs2rel_rotation,
rel2abs_rotation,
)
from .centroid_format import CentroidFormat
from .kitti_format import KittiFormat
from .vertices_format import VerticesFormat
Loading

0 comments on commit ad35f02

Please sign in to comment.