Skip to content

Commit

Permalink
Use dropdown list instead of editbox for box classes (#103)
Browse files Browse the repository at this point in the history
* Use dropdown list instead of editbox for box classes

* not ignore labels/schema

* mypy and black

* change to_open3d_point_cloud, to_point_cloud to staticmethod-s

* remove print

* change stylesheet for QComboVox#current_class_dropdown
  • Loading branch information
chingyulin authored and ch-sa committed Oct 15, 2022
1 parent 0d9f83f commit 526dd65
Show file tree
Hide file tree
Showing 17 changed files with 110 additions and 126 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
!labelCloud/resources/examples/exemplary.ply
!labelCloud/resources/examples/exemplary.json
!labelCloud/resources/labelCloud_icon.pcd
!labels/segmentation/schema/label_definition.json
!labels/schema/label_definition.json
!labels/segmentation/exemplary.bin

# ---------------------------------------------------------------------------- #
Expand Down
4 changes: 1 addition & 3 deletions config.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[MODE]
segmentation = true
segmentation = false

[FILE]
; source of point clouds
Expand Down Expand Up @@ -28,8 +28,6 @@ label_color_mix_ratio = 0.3
[LABEL]
; format for exporting labels. choose from (vertices, centroid_rel, centroid_abs, kitti)
label_format = centroid_abs
; list of object classes for autocompletion in the text field
object_classes = cart, box
; default object class for new bounding boxes
std_object_class = cart
; number of decimal places for exporting the bounding box parameter.
Expand Down
9 changes: 7 additions & 2 deletions labelCloud/control/bbox_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ def add_bbox(self, bbox: BBox) -> None:
if isinstance(bbox, BBox):
self.bboxes.append(bbox)
self.set_active_bbox(self.bboxes.index(bbox))
self.view.current_class_dropdown.setCurrentText(
self.get_active_bbox().classname # type: ignore
)
self.view.status_manager.update_status(
"Bounding Box added, it can now be corrected.", Mode.CORRECTION
)
Expand Down Expand Up @@ -306,9 +309,11 @@ def update_z_dial(self) -> None:

def update_curr_class(self) -> None:
if self.has_active_bbox():
self.view.update_curr_class_edit()
self.view.current_class_dropdown.setCurrentText(
self.get_active_bbox().classname # type: ignore
)
else:
self.view.update_curr_class_edit(force="")
self.view.controller.pcd_manager.populate_class_dropdown()

def update_label_list(self) -> None:
"""Updates the list of drawn labels and highlights the active label.
Expand Down
25 changes: 18 additions & 7 deletions labelCloud/control/pcd_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@
from shutil import copyfile
from typing import TYPE_CHECKING, List, Optional, Set, Tuple

import pkg_resources

import numpy as np
import open3d as o3d
import pkg_resources

from ..definitions.types import Point3D
from ..io.pointclouds import BasePointCloudHandler, Open3DHandler
Expand Down Expand Up @@ -41,7 +40,9 @@ def __init__(self) -> None:

# Point cloud control
self.pointcloud: Optional[PointCloud] = None
self.collected_object_classes: Set[str] = set()
self.collected_object_classes: Set[
str
] = set() # TODO: this should integrate with the new label definition setup.
self.saved_perspective: Optional[Perspective] = None

@property
Expand Down Expand Up @@ -134,6 +135,13 @@ def get_prev_pcd(self) -> None:
else:
raise Exception("No point cloud left for loading!")

def populate_class_dropdown(self) -> None:
# Add point label list
self.view.current_class_dropdown.clear()
assert self.pointcloud is not None
for key in self.pointcloud.label_definition:
self.view.current_class_dropdown.addItem(key)

def get_labels_from_file(self) -> List[BBox]:
bboxes = self.label_manager.import_labels(self.pcd_path)
logging.info(green("Loaded %s bboxes!" % len(bboxes)))
Expand All @@ -150,8 +158,6 @@ def save_labels_into_file(self, bboxes: List[BBox]) -> None:
self.collected_object_classes.update(
{bbox.get_classname() for bbox in bboxes}
)
self.view.update_label_completer(self.collected_object_classes)
self.view.update_default_object_class_menu(self.collected_object_classes)
else:
logging.warning("No point clouds to save labels for!")

Expand Down Expand Up @@ -225,7 +231,7 @@ def rotate_pointcloud(
rotation_matrix = o3d.geometry.get_rotation_matrix_from_axis_angle(
np.multiply(axis, angle)
)
o3d_pointcloud = Open3DHandler().to_open3d_point_cloud(self.pointcloud)
o3d_pointcloud = Open3DHandler.to_open3d_point_cloud(self.pointcloud)
o3d_pointcloud.rotate(rotation_matrix, center=tuple(rotation_point))
o3d_pointcloud.translate([0, 0, -rotation_point[2]])
logging.info("Rotating point cloud...")
Expand All @@ -241,8 +247,13 @@ def rotate_pointcloud(
center=(0, 0, 0),
)

points, colors = Open3DHandler.to_point_cloud(o3d_pointcloud)
self.pointcloud = PointCloud(
self.pcd_path, *Open3DHandler().to_point_cloud(o3d_pointcloud)
self.pcd_path,
points,
self.pointcloud.label_definition,
colors,
self.pointcloud.labels,
)
self.pointcloud.to_file()

Expand Down
10 changes: 10 additions & 0 deletions labelCloud/io/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import json
from pathlib import Path
from typing import Dict


def read_label_definition(label_definition_path: Path) -> Dict[str, int]:
with open(label_definition_path, "r") as f:
label_definition: Dict[str, int] = json.loads(f.read())
assert len(label_definition) > 0
return label_definition
9 changes: 4 additions & 5 deletions labelCloud/io/pointclouds/open3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

import numpy as np
import numpy.typing as npt

import open3d as o3d

from . import BasePointCloudHandler
Expand All @@ -18,17 +17,17 @@ class Open3DHandler(BasePointCloudHandler):
def __init__(self) -> None:
super().__init__()

@staticmethod
def to_point_cloud(
self, pointcloud: o3d.geometry.PointCloud
pointcloud: o3d.geometry.PointCloud,
) -> Tuple[npt.NDArray, Optional[npt.NDArray]]:
return (
np.asarray(pointcloud.points).astype("float32"),
np.asarray(pointcloud.colors).astype("float32"),
)

def to_open3d_point_cloud(
self, pointcloud: "PointCloud"
) -> o3d.geometry.PointCloud:
@staticmethod
def to_open3d_point_cloud(pointcloud: "PointCloud") -> o3d.geometry.PointCloud:
o3d_pointcloud = o3d.geometry.PointCloud(
o3d.utility.Vector3dVector(pointcloud.points)
)
Expand Down
21 changes: 9 additions & 12 deletions labelCloud/io/segmentations/base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import json
from abc import abstractmethod
from pathlib import Path
from typing import Dict, Tuple, Type
from typing import Dict, Set, Type

import numpy as np
import numpy.typing as npt
Expand All @@ -10,23 +9,18 @@


class BaseSegmentationHandler(object, metaclass=SingletonABCMeta):
EXTENSIONS = set() # should be set in subclasses
EXTENSIONS: Set[str] = set() # should be set in subclasses

def __init__(self, label_definition_path: Path) -> None:
self.read_label_definition(label_definition_path)

def read_label_definition(self, label_definition_path: Path) -> None:
with open(label_definition_path, "r") as f:
self.label_definition: Dict[str, int] = json.loads(f.read())
assert len(self.label_definition) > 0
def __init__(self, label_definition: Dict[str, int]) -> None:
self.label_definition = label_definition

@property
def default_label(self) -> int:
return min(list(self.label_definition.values()))

def read_or_create_labels(
self, label_path: Path, num_points: int
) -> Tuple[Dict[str, int], npt.NDArray[np.int8]]:
) -> npt.NDArray[np.int8]:
"""Read labels per point and its schema"""
if label_path.exists():
labels = self._read_labels(label_path)
Expand All @@ -36,7 +30,7 @@ def read_or_create_labels(
)
else:
labels = self._create_labels(num_points)
return self.label_definition, labels
return labels

def overwrite_labels(self, label_path: Path, labels: npt.NDArray[np.int8]) -> None:
return self._write_labels(label_path, labels)
Expand All @@ -58,3 +52,6 @@ def get_handler(cls, file_extension: str) -> Type["BaseSegmentationHandler"]:
for subclass in cls.__subclasses__():
if file_extension in subclass.EXTENSIONS:
return subclass
raise NotImplementedError(
f"{file_extension} is not supported for segmentation labels."
)
2 changes: 1 addition & 1 deletion labelCloud/io/segmentations/numpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class NumpySegmentationHandler(BaseSegmentationHandler):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)

def _create_labels(self, num_points: int) -> npt.NDArray[np.int8]:
def _create_labels(self, num_points: int, *args, **kwargs) -> npt.NDArray[np.int8]:
return np.ones(shape=(num_points,), dtype=np.int8) * self.default_label

def _read_labels(self, label_path: Path) -> npt.NDArray[np.int8]:
Expand Down
42 changes: 20 additions & 22 deletions labelCloud/model/point_cloud.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import ctypes
import logging
from pathlib import Path
from typing import Dict, Optional, Tuple

import pkg_resources
from typing import Dict, Optional, Tuple, cast

import numpy as np
import numpy.typing as npt
import OpenGL.GL as GL

from ..control.config_manager import config
from ..definitions.types import Point3D, Rotations3D, Translation3D
from ..io import read_label_definition
from ..io.pointclouds import BasePointCloudHandler
from ..io.segmentations import BaseSegmentationHandler
from ..utils.color import colorize_points_with_height, get_distinct_colors
Expand Down Expand Up @@ -43,10 +42,10 @@ class PointCloud(object):
def __init__(
self,
path: Path,
points: np.ndarray,
points: npt.NDArray[np.float32],
label_definition: Dict[str, int],
colors: Optional[np.ndarray] = None,
segmentation_labels: Optional[npt.NDArray[np.int8]] = None,
label_definition: Optional[Dict[str, int]] = None,
init_translation: Optional[Tuple[float, float, float]] = None,
init_rotation: Optional[Tuple[float, float, float]] = None,
write_buffer: bool = True,
Expand All @@ -55,11 +54,11 @@ def __init__(
self.path = path
self.points = points
self.colors = colors if type(colors) == np.ndarray and len(colors) > 0 else None
self.label_definition = label_definition

self.labels = self.label_definition = self.label_color_map = None
self.labels = self.label_color_map = None
if self.SEGMENTATION:
self.labels = segmentation_labels
self.label_definition = label_definition
self.label_color_map = get_distinct_colors(len(label_definition))
self.mix_ratio = config.getfloat("POINTCLOUD", "label_color_mix_ratio")

Expand Down Expand Up @@ -95,7 +94,6 @@ def __init__(
logging.info(
"Generated colors for colorless point cloud based on `colorless_color`."
)

if write_buffer:
self.create_buffers()

Expand All @@ -109,6 +107,7 @@ def point_size(self) -> float:

def create_buffers(self) -> None:
"""Create 3 different buffers holding points, colors and label colors information"""
self.colors = cast(npt.NDArray[np.float32], self.colors)
(
self.position_vbo,
self.color_vbo,
Expand All @@ -126,9 +125,10 @@ def create_buffers(self) -> None:
@property
def label_colors(self) -> npt.NDArray[np.float32]:
"""blend the points with label color map"""
self.colors = cast(npt.NDArray[np.float32], self.colors)
if self.labels is not None:
label_one_hot = np.eye(len(self.label_definition))[self.labels]
colors = np.dot(label_one_hot, self.label_color_map).astype(np.float32)
colors = np.dot(label_one_hot, self.label_color_map).astype(np.float32) # type: ignore
return colors * self.mix_ratio + self.colors * (1 - self.mix_ratio)
else:
return self.colors
Expand All @@ -149,30 +149,30 @@ def from_file(
path.suffix
).read_point_cloud(path=path)

labels = label_def = None
label_definition = read_label_definition(
config.getpath("FILE", "label_folder")
/ Path(f"schema/label_definition.json")
)
labels = None
if cls.SEGMENTATION:

label_path = config.getpath("FILE", "label_folder") / Path(
f"segmentation/{path.stem}.bin"
)
label_defintion_path = config.getpath("FILE", "label_folder") / Path(
f"segmentation/schema/label_definition.json"
)

logging.info(f"Loading segmentation labels from {label_path}.")
seg_handler = BaseSegmentationHandler.get_handler(label_path.suffix)(
label_definition_path=label_defintion_path
label_definition=label_definition
)
label_def, labels = seg_handler.read_or_create_labels(
labels = seg_handler.read_or_create_labels(
label_path=label_path, num_points=points.shape[0]
)

return cls(
path,
points,
label_definition,
colors,
labels,
label_def,
init_translation,
init_rotation,
write_buffer,
Expand All @@ -194,13 +194,11 @@ def color_with_label(self) -> bool:
return config.getboolean("POINTCLOUD", "color_with_label")

@property
def int2label(self) -> Optional[Dict[int, str]]:
if self.label_definition is not None:
return {ind: label for label, ind in self.label_definition.items()}
return None
def int2label(self) -> Dict[int, str]:
return {ind: label for label, ind in self.label_definition.items()}

@property
def label_counts(self) -> Optional[Dict[int, int]]:
def label_counts(self) -> Optional[Dict[str, int]]:
if self.labels is not None and self.label_definition:

counter = {k: 0 for k in self.label_definition}
Expand Down
2 changes: 0 additions & 2 deletions labelCloud/resources/default_config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ std_zoom = 0.0025
[LABEL]
; format for exporting labels. choose from (vertices, centroid_rel, centroid_abs, kitti)
label_format = centroid_abs
; list of object classes for autocompletion in the text field
object_classes = cart, box
; default object class for new bounding boxes
std_object_class = cart
; number of decimal places for exporting the bounding box parameter.
Expand Down
22 changes: 11 additions & 11 deletions labelCloud/resources/interfaces/interface.ui
Original file line number Diff line number Diff line change
Expand Up @@ -893,16 +893,16 @@
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edit_current_class">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="current_class_dropdown">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="current_bbox_title">
<property name="sizePolicy">
Expand Down Expand Up @@ -1685,7 +1685,7 @@
<tabstop>button_pick_bbox</tabstop>
<tabstop>button_span_bbox</tabstop>
<tabstop>button_save_label</tabstop>
<tabstop>edit_current_class</tabstop>
<tabstop>current_class_dropdown</tabstop>
<tabstop>edit_pos_x</tabstop>
<tabstop>edit_pos_y</tabstop>
<tabstop>edit_pos_z</tabstop>
Expand Down
Loading

0 comments on commit 526dd65

Please sign in to comment.