Skip to content

Commit

Permalink
Added labelme labeler. Detection time is now calculated - for now onl…
Browse files Browse the repository at this point in the history
…y in LabelMe labeler
  • Loading branch information
Artur Siepietowski committed Oct 11, 2021
1 parent 7446501 commit 9dd6566
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 74 deletions.
16 changes: 7 additions & 9 deletions detector/configs/abstract.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
from typing import Type

from detector.detection import PreprocessingParams, EdgeDetectorParams, SegmentDetectorParams, SegmentClustererParams, \
ClusterCleaningParams, IntersectionDetectorParams, RectangleDetectorParams
from detector.utils.pvpd_base_class import PVPDBaseClass


class Config(PVPDBaseClass):
preprocessing_params: Type[PreprocessingParams] = None
edge_detector_params: Type[EdgeDetectorParams] = None
segment_detector_params: Type[SegmentDetectorParams] = None
segment_clusterer_params: Type[SegmentClustererParams] = None
cluster_cleaning_params: Type[ClusterCleaningParams] = None
intersection_detector_params: Type[IntersectionDetectorParams] = None
rectangle_detector_params: Type[RectangleDetectorParams] = None
preprocessing_params: PreprocessingParams = None
edge_detector_params: EdgeDetectorParams = None
segment_detector_params: SegmentDetectorParams = None
segment_clusterer_params: SegmentClustererParams = None
cluster_cleaning_params: ClusterCleaningParams = None
intersection_detector_params: IntersectionDetectorParams = None
rectangle_detector_params: RectangleDetectorParams = None
39 changes: 33 additions & 6 deletions detector/detector.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
from datetime import datetime
from typing import Tuple, Any, Type, List

import numpy as np
Expand Down Expand Up @@ -81,7 +82,8 @@ def detect_segments_functional(edge_image: np.ndarray, params: SegmentDetectorPa
return segment_detector.segments

@staticmethod
def cluster_segments_functional(segments, params: SegmentClustererParams, cleaning_params: ClusterCleaningParams) -> Any:
def cluster_segments_functional(segments, params: SegmentClustererParams,
cleaning_params: ClusterCleaningParams) -> Any:
"""Clusters the segments in :param:`segments` according to the parameters in :param:`param`.
After that aggregate and clean cluster using :param:`cleaning_params`
See Also:
Expand Down Expand Up @@ -122,9 +124,17 @@ def detect_rectangles_functional(intersections, params: RectangleDetectorParams)

@staticmethod
def get_rectangles_labels(rectangles: List[np.ndarray], rectangle_labeler: Type[RectangleLabeler],
preprocessed_image: np.ndarray, label_path: str) -> Any:
thermal_image: np.ndarray, image_path: str,
preprocessed_image: np.ndarray, label_path: str, tags: dict) -> Any:
"""Create label files using labeler based on detected rectangles."""
labeler = rectangle_labeler(rectangles=rectangles, preprocessed_image=preprocessed_image, label_path=label_path)
labeler = rectangle_labeler(
rectangles=rectangles,
thermal_image=thermal_image,
image_path=image_path,
preprocessed_image=preprocessed_image,
label_path=label_path,
tags=tags
)
if label_path:
label_file = labeler.create_label_file()
logging.info(f"Created label file: {label_file}")
Expand Down Expand Up @@ -162,14 +172,15 @@ def process_panel(contours, contour_id, preprocessed, last_scaled_frame_rgb, con
@staticmethod
def main(image_path: str, config: Config, labelers: List[Type[RectangleLabeler]] = None, labels_path: str = None,
silent: bool = True):
img = read_bgr_img(image_path)
start_time = datetime.now()
thermal_image = read_bgr_img(image_path)

# distorted_image = img
# if False:
# undistorted_image = cv2.undistort(src=distorted_image)
# else:
# undistorted_image = distorted_image
preprocessed_image, scaled_image_rgb, mask = Detector.preprocess_image_functional(img,
preprocessed_image, scaled_image_rgb, mask = Detector.preprocess_image_functional(thermal_image,
config.preprocessing_params,
silent)

Expand All @@ -190,8 +201,24 @@ def main(image_path: str, config: Config, labelers: List[Type[RectangleLabeler]]
except Exception as e:
logging.error(f"Failed to process panel for contour_id {contour_id} due to {e}")

end_time = datetime.now()


tags = {
'start_time': start_time,
'end_time': end_time,
'detection_duration': end_time - start_time
}
for labeler in labelers:
Detector.get_rectangles_labels(rectangles, labeler, preprocessed_image, labels_path)
Detector.get_rectangles_labels(
rectangles,
labeler,
thermal_image,
image_path,
preprocessed_image,
labels_path,
tags
)

if not silent:
draw_rectangles(rectangles, scaled_image_rgb, "rectangles")
Expand Down
7 changes: 5 additions & 2 deletions detector/labelers/abstract.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from abc import abstractmethod
from typing import List

import numpy as np

Expand All @@ -9,13 +8,17 @@
class RectangleLabeler(PVPDBaseClass):
file_extension = None

def __init__(self, rectangles: list, preprocessed_image: np.ndarray, label_path: str):
def __init__(self, rectangles: list, thermal_image: np.ndarray, image_path: str, preprocessed_image: np.ndarray,
label_path: str, tags: dict):
self.rectangles = rectangles
self.preprocessed_image = preprocessed_image
self.thermal_image = thermal_image
self.image_path = image_path
self.label_path = label_path
self.class_id = 0
self.labels_collector = []
self.labeled = False
self.tags = tags

@abstractmethod
def label_image(self):
Expand Down
65 changes: 65 additions & 0 deletions detector/labelers/labelme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import base64
import json
import os

from labelme import LabelFile, __version__
from labelme.label_file import LabelFileError

from detector.labelers.abstract import RectangleLabeler
from detector.utils.utils import to_camel


class LabelMeLabeler(RectangleLabeler):
extension = 'json'

def __save(self, filename, shapes, image_path, image_height, image_width, image_data=None, tags=None):
if image_data is not None:
image_data = base64.b64encode(image_data).decode("utf-8")
image_height, image_width = LabelFile._check_image_height_and_width(
image_data, image_height, image_width
)
if tags is None:
tags = {}
data = dict(
version=__version__,
flags=None,
shapes=shapes,
imagePath=image_path,
imageData=image_data,
imageHeight=image_height,
imageWidth=image_width,
)
for key, value in tags.items():
assert key not in data
data[to_camel(key)] = value
try:
with open(filename, "w") as f:
json.dump(data, f, ensure_ascii=False, indent=2, default=str)
except Exception as e:
raise LabelFileError(e)

@staticmethod
def __create_shape(rectangle):
return {
"label": "0",
"points": rectangle.tolist(),
"group_id": None,
"shape_type": "polygon",
"flags": {}
}

def label_image(self):
self.labels_collector = self.rectangles
self.labeled = True
return self.rectangles

def create_label_file(self):
if not self.labeled:
self.label_image()
root, ext = os.path.splitext(self.label_path)
file_name = f'{root}.{self.extension}'
image_data = LabelFile().load_image_file(self.image_path)
shapes = [self.__create_shape(rectangle) for rectangle in self.rectangles]
self.__save(file_name, shapes, f'{root}.JPG', self.preprocessed_image.shape[0],
self.preprocessed_image.shape[1], image_data, self.tags)
return file_name
1 change: 0 additions & 1 deletion detector/labelers/yolo.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ def label_image(self):
return self.labels_collector

def create_label_file(self):

if not self.labeled:
self.label_image()
root, ext = os.path.splitext(self.label_path)
Expand Down
15 changes: 13 additions & 2 deletions detector/utils/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from functools import lru_cache
from pathlib import Path

import cv2
Expand Down Expand Up @@ -91,9 +92,19 @@ def auto_canny(image):
return lower, upper


def to_camel(string):
return ''.join((wd.title() if i else wd) for (i, wd) in enumerate(string.split('_')))


@lru_cache()
def color_maps():
return {color_map_name: value for color_map_name, value in cm.__dict__.items() if
getattr(value, '__module__', "") == 'matplotlib.colors'}


def available_color_maps():
return cm._cmap_registry.keys()
return color_maps().keys()


def get_color_map_by_name(name):
return cm._cmap_registry[name]
return color_maps()[name]
49 changes: 39 additions & 10 deletions detector_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,36 @@
image_types = (THERMAL, RAW)


def process(multithread, **args):
if multithread:
process_multiple(**args)
else:
process_single(**args)


def process_single(file_paths, config, output_dir, labelers, silent):
logging.info(f"Annotation of images started. Result will be saved to '{output_dir}'.")
for index, multi_color_file_paths in enumerate(file_paths):
if multi_color_file_paths is None:
logging.warning("No thermal image provided. Cannot proceed with PV panels detection.")
for subindex, file_path in enumerate(multi_color_file_paths):
output_path = f"{output_dir}/{os.path.basename(file_path)}"
logging.info(f"Annotation of img '{file_path}' started. Result will be saved to '{output_path}'. "
f"Run index {index}.{subindex}.")
Detector.main(os.path.abspath(file_path), config, labelers, output_path, silent)


def save_img_callback(args):
save_img(args[0], args[1])
pass
# save_img(args[0], args[1])


def error_callback(e):
logging.error(f"Annotation of img failed with: {e}")


def process(file_paths, config, output_dir, labelers, silent):
logging.info(f"Annotation of images started. Result will be saved to '{output_dir}'.")
def process_multiple(file_paths, config, output_dir, labelers, silent):
logging.info(f"Multithreading annotation of images started. Result will be saved to '{output_dir}'.")
p = Pool(max(os.cpu_count() - 2, 1))
for index, multi_color_file_paths in enumerate(file_paths):
if multi_color_file_paths is None:
Expand All @@ -46,6 +66,7 @@ def process(file_paths, config, output_dir, labelers, silent):


helps = {
'multithread': "Multithreaded version of detection. Speed up detection using all available processor resources",
'config': "Config Class name. Based on config PV panels are detected from image. "
"User can create his own config and place in detector/configs directory. "
"Class name must be unique",
Expand All @@ -70,7 +91,7 @@ def process(file_paths, config, output_dir, labelers, silent):
def parse_arguments():
init_logger()
parser = argparse.ArgumentParser(prog='PhotoVoltaic Panels Detector', description='')

parser.add_argument('-m', '--multithread', action='store_true', help=helps['multithread'])
parser.add_argument('-t', '--type', choices=image_types, default=THERMAL, help=helps['type'])
extract_group = parser.add_argument_group('extract')
process_group = parser.add_argument_group('process')
Expand Down Expand Up @@ -99,14 +120,22 @@ def parse_arguments():
files = iter(files)

if args.type == RAW:
thermal_files = (ThermalImageExtractor.get_thermal_image_file_path(file, args.color_map, args.thermal_image_output) for file in files)
thermal_files = (
ThermalImageExtractor.get_thermal_image_file_path(
file,
args.color_map,
args.thermal_image_output
) for file in files)
files = thermal_files

process(file_paths=files,
config=Config.get_subclass_by_name(args.config),
silent=not args.show_step_images,
labelers=[RectangleLabeler.get_subclass_by_name(labeler) for labeler in args.labelers],
output_dir=args.output_dir)
process(
multithread=args.multithread,
file_paths=files,
config=Config.get_subclass_by_name(args.config),
silent=not args.show_step_images,
labelers=[RectangleLabeler.get_subclass_by_name(labeler) for labeler in args.labelers],
output_dir=args.output_dir
)
return args


Expand Down
27 changes: 0 additions & 27 deletions experiments/confusion_matrix.py

This file was deleted.

2 changes: 1 addition & 1 deletion experiments/dataset_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import random
from shutil import copyfile

from extractor.extractor import ThermalImageExtractor
from detector.extractor.extractor import ThermalImageExtractor

path = 'data/raw'
# https://en.wikipedia.org/wiki/Reservoir_sampling
Expand Down
15 changes: 0 additions & 15 deletions experiments/remove_not_labelled.py

This file was deleted.

10 changes: 10 additions & 0 deletions experiments/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
imgviz==1.4.0
labelme==4.5.13
pandas==1.3.3
PyQt5==5.15.2
PyQt5-sip==12.9.0
pytz==2021.3
PyYAML==5.4.1
QtPy==1.11.2
Shapely==1.7.1
termcolor==1.1.0
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cycler==0.10.0
joblib==1.0.1
kiwisolver==1.3.2
matplotlib==3.4.3
matplotlib==3.2.2
numpy==1.21.2
opencv-python-headless==4.5.3.56
Pillow==8.3.2
Expand Down

0 comments on commit 9dd6566

Please sign in to comment.