Skip to content

Commit

Permalink
Merge pull request #104 from Jordan-Pierce/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
Jordan-Pierce authored Jan 7, 2025
2 parents c80cabb + 546cc6e commit bae2538
Show file tree
Hide file tree
Showing 18 changed files with 1,807 additions and 107 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ Enhance your CoralNet experience with these tools:
- ⚙️ Batch Inference: Perform predictions on multiple images, automatically
- ↔️ I/O: Import and Export annotations from / to CoralNet, Viscore, and TagLab
- 📸 YOLO: Import and Export YOLO datasets for machine learning
- 🧱 Tiling: Tile existing Detection / Segmentation datasets using `yolo-tiling`

### TODO
- 🔍 API: Get predictions from any CoralNet source model
Expand Down
235 changes: 134 additions & 101 deletions coralnet_toolbox/IO/QtImportTagLabAnnotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import os
import json
import uuid
import traceback

import numpy as np
from PyQt5.QtCore import Qt, QPointF
Expand All @@ -29,6 +30,7 @@ def __init__(self, main_window):
self.annotation_window = main_window.annotation_window

def taglabToPoints(self, c):
"""Convert a TagLab contour string to a list of points."""
d = (c * 10).astype(int)
d = np.diff(d, axis=0, prepend=[[0, 0]])
d = np.reshape(d, -1)
Expand All @@ -37,6 +39,7 @@ def taglabToPoints(self, c):
return d

def taglabToContour(self, p):
"""Convert a TagLab contour string to a list of readable points."""
if type(p) is str:
p = map(int, p.split(' '))
c = np.fromiter(p, dtype=int)
Expand All @@ -55,8 +58,20 @@ def parse_contour(self, contour_str):
"""Parse the contour string into a list of QPointF objects."""
points = self.taglabToContour(contour_str)
return [QPointF(x, y) for x, y in points]

def standardize_data(self, image_data):
"""Standardize the data format for TagLab annotations."""
# Deals with the fact that TagLab JSON files can have different structures
# before and after point annotations were introduced in version v2024.10.29
for image in image_data:
# Older versions do not have regions and points, annotations is a list not a dict
if isinstance(image['annotations'], list):
image['annotations'] = {'regions': image['annotations'], 'points': []}

return image_data

def import_annotations(self):
"""Import annotations from TagLab JSON files."""
self.main_window.untoggle_all_tools()

if not self.annotation_window.active_image:
Expand Down Expand Up @@ -98,18 +113,21 @@ def import_annotations(self):
merged_data["labels"].update(data["labels"])
merged_data["images"].extend(data["images"])

required_keys = ['labels', 'images']
if not all(key in merged_data for key in required_keys):
if not all(key in merged_data for key in ['labels', 'images']):
QMessageBox.warning(self.annotation_window,
"Invalid JSON Format",
"The selected JSON files do not match the expected TagLab format.")
return

# Standardize the data (deals with different TagLab JSON structures)
merged_data["images"] = self.standardize_data(merged_data["images"])

# Map image names to image paths
image_path_map = {os.path.basename(path): path for path in self.image_window.image_paths}

total_annotations = sum(len(image_data['annotations']['regions']) + len(image_data['annotations']['points'])
for image_data in merged_data['images'])
num_regions = sum(len(image_data['annotations']['regions']) for image_data in merged_data['images'])
num_points = sum(len(image_data['annotations']['points']) for image_data in merged_data['images'])
total_annotations = num_regions + num_points

progress_bar = ProgressBar(self.annotation_window, title="Importing TagLab Annotations")
progress_bar.show()
Expand All @@ -118,122 +136,137 @@ def import_annotations(self):
QApplication.setOverrideCursor(Qt.WaitCursor)

for image_data in merged_data['images']:
# Get the basename from the TagLab project file (not path)
image_basename = os.path.basename(image_data['channels'][0]['filename'])
# Check to see if there is a matching image (using basename) in the current project
if image_basename not in image_path_map:
continue # Skip this image

# Get the full path to the image
image_full_path = image_path_map[image_basename]

if not image_full_path:
QMessageBox.warning(self.annotation_window,
"Image Not Found",
f"The image '{image_basename}' "
f"from the TagLab project was not found in this project.")
continue

# Loop through all the polygon annotations for this image
for annotation in list(image_data['annotations']['regions']):
# Get the information for the label for this annotation
label_id = annotation['class name']
label_info = merged_data['labels'][label_id]
short_label_code = label_info['name']
long_label_code = label_info['name']
color = QColor(*label_info['fill'])

# Unpack the annotation data
bbox = annotation['bbox']
centroid = annotation['centroid']
area = annotation['area']
perimeter = annotation['perimeter']
contour = annotation['contour']
inner_contours = annotation['inner contours']
class_name = annotation['class name']
instance_name = annotation['instance name']
blob_name = annotation['blob name']
idx = annotation['id']
note = annotation['note']
data = annotation['data']

# Convert contour string to points
points = self.parse_contour(annotation['contour'])

existing_label = self.label_window.get_label_by_codes(short_label_code, long_label_code)

if existing_label:
label_id = existing_label.id
else:
label_id = str(uuid.uuid4())
self.label_window.add_label_if_not_exists(short_label_code, long_label_code, color, label_id)

polygon_annotation = PolygonAnnotation(
points=points,
short_label_code=short_label_code,
long_label_code=long_label_code,
color=color,
image_path=image_full_path,
label_id=label_id
)

# Add annotation to the dict
self.annotation_window.annotations_dict[polygon_annotation.id] = polygon_annotation

# Update the progress bar
progress_bar.update_progress()
try:
# Get the information for the label for this annotation
label_id = annotation['class name']
label_info = merged_data['labels'][label_id]
short_label_code = label_info['name'].strip()
long_label_code = label_info['name'].strip()
color = QColor(*label_info['fill'])

# Unpack the annotation data
bbox = annotation['bbox']
centroid = annotation['centroid']
area = annotation['area']
perimeter = annotation['perimeter']
contour = annotation['contour']
inner_contours = annotation['inner contours']
class_name = annotation['class name']
instance_name = annotation['instance name']
blob_name = annotation['blob name']
idx = annotation['id']
note = annotation['note']
data = annotation['data']

# Convert contour string to points
points = self.parse_contour(annotation['contour'])

existing_label = self.label_window.get_label_by_codes(short_label_code, long_label_code)

if existing_label:
label_id = existing_label.id
else:
label_id = str(uuid.uuid4())
self.label_window.add_label_if_not_exists(short_label_code,
long_label_code,
color,
label_id)
# Create the polygon annotation
polygon_annotation = PolygonAnnotation(
points=points,
short_label_code=short_label_code,
long_label_code=long_label_code,
color=color,
image_path=image_full_path,
label_id=label_id
)
# Add annotation to the dict
self.annotation_window.annotations_dict[polygon_annotation.id] = polygon_annotation

except Exception as e:
print(f"Error importing annotation: {str(e)}\n{traceback.print_exc()}")
finally:
# Update the progress bar
progress_bar.update_progress()

# Loop through all the point annotations for this image
for annotation in list(image_data['annotations']['points']):
# Get the information for the label for this annotation
label_id = annotation['Class'] # Inconsistent
label_info = merged_data['labels'][label_id]
short_label_code = label_info['name']
long_label_code = label_info['name']
color = QColor(*label_info['fill'])

# Unpack the annotation data
class_name = annotation['Class']
x = annotation['X']
y = annotation['Y']
idx = annotation['Id']
note = annotation['Note']
data = annotation['Data']

existing_label = self.label_window.get_label_by_codes(short_label_code, long_label_code)

if existing_label:
label_id = existing_label.id
else:
label_id = str(uuid.uuid4())
self.label_window.add_label_if_not_exists(short_label_code, long_label_code, color, label_id)

patch_annotation = PatchAnnotation(
center_xy=QPointF(x, y),
annotation_size=annotation_size,
short_label_code=short_label_code,
long_label_code=long_label_code,
color=color,
image_path=image_full_path,
label_id=label_id
)

# Add annotation to the dict
self.annotation_window.annotations_dict[patch_annotation.id] = patch_annotation
# Update the progress bar
progress_bar.update_progress()
try:
# Get the information for the label for this annotation
label_id = annotation['Class'] # Inconsistent
label_info = merged_data['labels'][label_id]
short_label_code = label_info['name'].strip()
long_label_code = label_info['name'].strip()
color = QColor(*label_info['fill'])

# Unpack the annotation data
class_name = annotation['Class']
x = annotation['X']
y = annotation['Y']
idx = annotation['Id']
note = annotation['Note']
data = annotation['Data']

existing_label = self.label_window.get_label_by_codes(short_label_code, long_label_code)

if existing_label:
label_id = existing_label.id
else:
label_id = str(uuid.uuid4())
self.label_window.add_label_if_not_exists(short_label_code,
long_label_code,
color,
label_id)
# Create the patch annotation
patch_annotation = PatchAnnotation(
center_xy=QPointF(x, y),
annotation_size=annotation_size,
short_label_code=short_label_code,
long_label_code=long_label_code,
color=color,
image_path=image_full_path,
label_id=label_id
)
# Add annotation to the dict
self.annotation_window.annotations_dict[patch_annotation.id] = patch_annotation

except Exception as e:
print(f"Error importing annotation: {str(e)}\n{traceback.print_exc()}")
finally:
# Update the progress bar
progress_bar.update_progress()

# Update the image window's image dict
self.image_window.update_image_annotations(image_full_path)

# Load the annotations for current image
self.annotation_window.load_annotations()

# Stop the progress bar
progress_bar.stop_progress()
progress_bar.close()

QMessageBox.information(self.annotation_window,
"Annotations Imported",
"Annotations have been successfully imported.")

except Exception as e:
print(f"Error importing annotation: {str(e)}\n{traceback.print_exc()}")

QMessageBox.warning(self.annotation_window,
"Error Importing Annotations",
f"An error occurred while importing annotations: {str(e)}")

QApplication.restoreOverrideCursor()
f"An error occurred while importing annotations:\n\n{str(e)}\
\n\nPlease check the console for more details.")

finally:
# Stop the progress bar
progress_bar.stop_progress()
progress_bar.close()
QApplication.restoreOverrideCursor()
Binary file modified coralnet_toolbox/Icons/parameters.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added coralnet_toolbox/Icons/settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
Loading

0 comments on commit bae2538

Please sign in to comment.