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

Dev #129

Merged
merged 30 commits into from
Jan 27, 2025
Merged

Dev #129

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
61bdf27
Implement update_tile_graphics method in QtBase.py
Jordan-Pierce Jan 23, 2025
c74a57c
Merge pull request #116 from Jordan-Pierce/implement-tile-graphics
Jordan-Pierce Jan 23, 2025
836a558
Tile Inference UI, working
Jordan-Pierce Jan 24, 2025
5649b6c
Tile Inference display with hotkey, toggle
Jordan-Pierce Jan 24, 2025
91a461c
Move annotation size spin box to status bar
Jordan-Pierce Jan 24, 2025
d6023b4
Merge pull request #120 from Jordan-Pierce/move-annotation-size-spin-box
Jordan-Pierce Jan 24, 2025
34f56a7
showing / grey out patch annotation size in status bar; select tool v…
Jordan-Pierce Jan 24, 2025
70dcc77
Add accordion dropdowns to right layout
Jordan-Pierce Jan 24, 2025
ebfadef
Merge pull request #121 from Jordan-Pierce/add-accordion-dropdowns
Jordan-Pierce Jan 24, 2025
27e770f
Revert "Add accordion dropdowns to right layout"
Jordan-Pierce Jan 24, 2025
0f6137c
Merge pull request #122 from Jordan-Pierce/revert-121-add-accordion-d…
Jordan-Pierce Jan 24, 2025
d811fa9
Add multi-select and delete functionality to ImageWindow
Jordan-Pierce Jan 24, 2025
1e350a5
Merge pull request #123 from Jordan-Pierce/add-multi-select
Jordan-Pierce Jan 24, 2025
280eda4
Enhance checkboxes and optimize deletion
Jordan-Pierce Jan 24, 2025
5f7676f
Merge pull request #124 from Jordan-Pierce/enhance-checkboxes
Jordan-Pierce Jan 24, 2025
7b92d9d
Revert "Enhance checkboxes and optimize deletion"
Jordan-Pierce Jan 24, 2025
c273849
Merge pull request #125 from Jordan-Pierce/revert-124-enhance-checkboxes
Jordan-Pierce Jan 24, 2025
4004050
Select / delete multiple images
Jordan-Pierce Jan 24, 2025
8e1d047
Add select all and deselect all buttons
Jordan-Pierce Jan 24, 2025
69825a3
Merge pull request #126 from Jordan-Pierce/add-buttons
Jordan-Pierce Jan 24, 2025
9f89ebb
edits to select all
Jordan-Pierce Jan 27, 2025
eaa6aff
edits to delete image annotations, working
Jordan-Pierce Jan 27, 2025
1c84bc4
Bump version: 0.0.25 → 0.0.26
Jordan-Pierce Jan 27, 2025
2808f0d
Merge branch 'main' into dev
Jordan-Pierce Jan 27, 2025
0e6e810
Add export functionality for TagLab labels and remove unused inferenc…
Jordan-Pierce Jan 27, 2025
b37ffba
v0.0.26
Jordan-Pierce Jan 27, 2025
583018f
v0.0.26
Jordan-Pierce Jan 27, 2025
fcc784b
update tiledataset layout
Jordan-Pierce Jan 27, 2025
b6a1d1f
Add tool images to README for enhanced user guidance
Jordan-Pierce Jan 27, 2025
d8178af
Enhance README with tool images and descriptions for better user guid…
Jordan-Pierce Jan 27, 2025
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
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,63 @@ The `toolbox` also uses the following to create rectangle and polygon annotation

## Tools

<div align="center">
<table>
<tr>
<td align="center">
<img src="https://raw.githubusercontent.com/Jordan-Pierce/CoralNet-Toolbox/refs/heads/main/figures/tools/Patches.gif" height="200"/>
<br>
<em>Patch Annotation Tool</em>
</td>
<td align="center">
<img src="https://raw.githubusercontent.com/Jordan-Pierce/CoralNet-Toolbox/refs/heads/main/figures/tools/Rectangles.gif" height="200"/>
<br>
<em>Rectangle Annotation Tool</em>
</td>
<td align="center">
<img src="https://raw.githubusercontent.com/Jordan-Pierce/CoralNet-Toolbox/refs/heads/main/figures/tools/Polygons.gif" height="200"/>
<br>
<em>Polygon Annotation Tool</em>
</td>
</tr>
<tr>
<td align="center">
<img src="https://raw.githubusercontent.com/Jordan-Pierce/CoralNet-Toolbox/refs/heads/main/figures/tools/Classification.gif" height="200"/>
<br>
<em>Patch-based Image Classification</em>
</td>
<td align="center">
<img src="https://raw.githubusercontent.com/Jordan-Pierce/CoralNet-Toolbox/refs/heads/main/figures/tools/Object_Detection.gif" height="200"/>
<br>
<em>Object Detection</em>
</td>
<td align="center">
<img src="https://raw.githubusercontent.com/Jordan-Pierce/CoralNet-Toolbox/refs/heads/main/figures/tools/Instance_Segmentation.gif" height="200"/>
<br>
<em>Instance Segmentation</em>
</td>
</tr>
<tr>
<td align="center">
<img src="https://raw.githubusercontent.com/Jordan-Pierce/CoralNet-Toolbox/refs/heads/main/figures/tools/Segment_Anything.gif" height="200"/>
<br>
<em>Segment Anything Model (SAM)</em>
</td>
<td align="center">
<img src="https://raw.githubusercontent.com/Jordan-Pierce/CoralNet-Toolbox/refs/heads/main/figures/tools/Classifying_Polygons.gif" height="200"/>
<br>
<em>Polygon Classification</em>
</td>
<td align="center">
<img src="https://raw.githubusercontent.com/Jordan-Pierce/CoralNet-Toolbox/refs/heads/main/figures/tools/Classifying_Orthomosaics.gif" height="200"/>
<br>
<em>Orthomosaic Patch-based Image Classification</em>
</td>
</tr>
</table>
</div>


Enhance your CoralNet experience with these tools:
- ✏️ Annotate: Create annotations freely
- 👁️ Visualize: See CoralNet and CPCe annotations superimposed on images
Expand Down
10 changes: 10 additions & 0 deletions coralnet_toolbox/Annotations/QtPolygonAnnotation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import warnings

import cv2
import math
import numpy as np

Expand Down Expand Up @@ -202,6 +203,15 @@ def create_cropped_image(self, rasterio_src):

# Read the data from rasterio
data = rasterio_src.read(window=window)

# Create mask from polygon
polygon_points = np.array([(p.x() - min_x, p.y() - min_y) for p in self.points])
mask = np.zeros((window.height, window.width), dtype=np.uint8)
cv2.fillPoly(mask, [polygon_points.astype(np.int32)], 1)

# Apply mask to each channel
for i in range(data.shape[0]):
data[i] = data[i] * mask

# Ensure the data is in the correct format for QImage
data = self._prepare_data_for_qimage(data)
Expand Down
25 changes: 13 additions & 12 deletions coralnet_toolbox/IO/QtExportTagLabAnnotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,8 @@ def export_annotations(self):
taglab_data = {
"filename": file_path,
"working_area": None,
"dictionary_name": "scripps",
"dictionary_description": "These color codes are the ones typically used by the "
"Scripps Institution of Oceanography (UCSD)",
"dictionary_name": "custom_dictionary",
"dictionary_description": "These annotations were exported from CoralNet-Toolbox.",
"labels": {},
"images": []
}
Expand Down Expand Up @@ -127,18 +126,20 @@ def export_annotations(self):
if isinstance(annotation, PolygonAnnotation):
# Calculate bounding box, centroid, area, perimeter, and contour
points = annotation.points
min_x = min(point.x() for point in points)
min_y = min(point.y() for point in points)
max_x = max(point.x() for point in points)
max_y = max(point.y() for point in points)
centroid_x = sum(point.x() for point in points) / len(points)
centroid_y = sum(point.y() for point in points) / len(points)
area = annotation.calculate_area()
perimeter = annotation.calculate_perimeter()
min_x = int(min(point.x() for point in points))
min_y = int(min(point.y() for point in points))
max_x = int(max(point.x() for point in points))
max_y = int(max(point.y() for point in points))
width = max_x - min_x
height = max_y - min_y
centroid_x = float(f"{sum(point.x() for point in points) / len(points):.1f}")
centroid_y = float(f"{sum(point.y() for point in points) / len(points):.1f}")
area = float(f"{annotation.calculate_area():.1f}")
perimeter = float(f"{annotation.calculate_perimeter():.1f}")
contour = self.taglabToPoints(np.array([[point.x(), point.y()] for point in points]))

annotation_dict = {
"bbox": [min_x, min_y, max_x, max_y],
"bbox": [min_y, min_x, width, height],
"centroid": [centroid_x, centroid_y],
"area": area,
"perimeter": perimeter,
Expand Down
75 changes: 75 additions & 0 deletions coralnet_toolbox/IO/QtExportTagLabLabels.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)

import json

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QFileDialog, QMessageBox, QApplication

from coralnet_toolbox.QtProgressBar import ProgressBar


# ----------------------------------------------------------------------------------------------------------------------
# Classes
# ----------------------------------------------------------------------------------------------------------------------


class ExportTagLabLabels:
def __init__(self, main_window):
self.main_window = main_window
self.label_window = main_window.label_window

def export_taglab_labels(self):
self.main_window.untoggle_all_tools()

options = QFileDialog.Options()
file_path, _ = QFileDialog.getSaveFileName(self.label_window,
"Export TagLab Labels",
"",
"JSON Files (*.json);;All Files (*)",
options=options)
if file_path:
# Make cursor busy
QApplication.setOverrideCursor(Qt.WaitCursor)
total_labels = len(self.label_window.labels)
progress_bar = ProgressBar(self.label_window, "Exporting TagLab Labels")
progress_bar.show()
progress_bar.start_progress(total_labels)

try:
# Convert labels to list format
labels_list = []
for label in self.label_window.labels:
label_entry = {
'id': f"{label.short_label_code}",
'name': f"{label.short_label_code}",
'description': None,
'fill': label.color.getRgb()[:3],
'border': [200, 200, 200],
'visible': True
}
labels_list.append(label_entry)
progress_bar.update_progress()

taglab_data = {
'Name': 'custom_dictionary',
'Description': 'This label dictionary was exported from CoralNet-Toolbox.',
'Labels': labels_list
}

with open(file_path, 'w') as file:
json.dump(taglab_data, file, indent=4)

QMessageBox.information(self.label_window,
"Labels Exported",
"TagLab labels have been successfully exported.")

except Exception as e:
QMessageBox.warning(self.label_window,
"Error Exporting Labels",
f"An error occurred while exporting TagLab labels: {str(e)}")

finally:
QApplication.restoreOverrideCursor()
progress_bar.stop_progress()
progress_bar.close()
2 changes: 2 additions & 0 deletions coralnet_toolbox/IO/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .QtImportViscoreAnnotations import ImportViscoreAnnotations
from .QtImportTagLabAnnotations import ImportTagLabAnnotations
from .QtExportLabels import ExportLabels
from .QtExportTagLabLabels import ExportTagLabLabels
from .QtExportAnnotations import ExportAnnotations
from .QtExportCoralNetAnnotations import ExportCoralNetAnnotations
from .QtExportViscoreAnnotations import ExportViscoreAnnotations
Expand All @@ -20,6 +21,7 @@
'ImportViscoreAnnotations',
'ImportTagLabAnnotations',
'ExportLabels',
'ExportTagLabLabels',
'ExportAnnotations',
'ExportCoralNetAnnotations',
'ExportViscoreAnnotations',
Expand Down
24 changes: 20 additions & 4 deletions coralnet_toolbox/QtAnnotationWindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -634,20 +634,36 @@ def delete_label_annotations(self, label):
del self.annotations_dict[annotation.id]

def delete_image_annotations(self, image_path):
annotations = self.get_image_annotations(image_path)
self.delete_annotations(annotations)
"""Efficiently delete all annotations for a given image path"""
# Get IDs of annotations to delete
annotation_ids = [
annotation_id for annotation_id, annotation in self.annotations_dict.items()
if annotation.image_path == image_path
]

# Delete graphics items and annotations in batch
for annotation_id in annotation_ids:
annotation = self.annotations_dict[annotation_id]
annotation.delete() # Remove graphics item
del self.annotations_dict[annotation_id] # Remove from dictionary
self.annotationDeleted.emit(annotation_id) # Emit signal

# Clear confidence window if needed
if self.current_image_path == image_path:
self.main_window.confidence_window.clear_display()

def delete_image(self, image_path):
# Delete all annotations associated with image path
self.delete_annotations(self.get_image_annotations(image_path))
# Delete the image
if self.current_image_path == image_path:
self.scene.clear()
self.main_window.confidence_window.clear_display()
self.current_image_path = None
self.image_pixmap = None
self.rasterio_image = None
self.active_image = False # Reset image_set flag

self.active_image = False
def clear_scene(self):
# Clean up
self.unselect_annotations()
Expand Down
18 changes: 17 additions & 1 deletion coralnet_toolbox/QtEventFilter.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ def __init__(self, main_window):
self.segment_deploy_model_dialog = main_window.segment_deploy_model_dialog
self.sam_deploy_generator_dialog = main_window.sam_deploy_generator_dialog
self.auto_distill_deploy_model_dialog = main_window.auto_distill_deploy_model_dialog

self.tile_inference_dialog = main_window.tile_inference_dialog

def eventFilter(self, obj, event):
if event.type() == QEvent.KeyPress:
Expand All @@ -33,7 +35,13 @@ def eventFilter(self, obj, event):
if event.key() in [Qt.Key_W, Qt.Key_A, Qt.Key_S, Qt.Key_D]:
self.label_window.handle_wasd_key(event.key())
return True


# Handle G key for displaying existing tile inference parameters
if event.key() == Qt.Key_G:
if self.main_window.tile_inference_tool_action.isChecked():
self.tile_inference_dialog.update_tile_graphics()
return True

# Handle hotkey for image classification prediction
if event.key() == Qt.Key_1:
self.classify_deploy_model_dialog.predict()
Expand Down Expand Up @@ -89,6 +97,14 @@ def eventFilter(self, obj, event):
if event.key() == Qt.Key_Escape:
self.show_exit_confirmation_dialog()
return True

# Handle key release events
elif event.type() == QEvent.KeyRelease:

# Handle G key for removing existing tile inference parameter
if event.key() == Qt.Key_G:
self.tile_inference_dialog.clear_tile_graphics()
return True

# Return False for other events to allow them to be processed by the target object
return False
Expand Down
Loading
Loading