Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add modules to improve structure #38

Merged
merged 1 commit into from
Nov 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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