coralnet-toolbox 0.0.73__py2.py3-none-any.whl → 0.0.75__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coralnet_toolbox/Annotations/QtAnnotation.py +28 -69
- coralnet_toolbox/Annotations/QtMaskAnnotation.py +408 -0
- coralnet_toolbox/Annotations/QtMultiPolygonAnnotation.py +72 -56
- coralnet_toolbox/Annotations/QtPatchAnnotation.py +165 -216
- coralnet_toolbox/Annotations/QtPolygonAnnotation.py +497 -353
- coralnet_toolbox/Annotations/QtRectangleAnnotation.py +126 -116
- coralnet_toolbox/CoralNet/QtDownload.py +2 -1
- coralnet_toolbox/Explorer/QtDataItem.py +52 -22
- coralnet_toolbox/Explorer/QtExplorer.py +293 -1614
- coralnet_toolbox/Explorer/QtSettingsWidgets.py +203 -85
- coralnet_toolbox/Explorer/QtViewers.py +1568 -0
- coralnet_toolbox/Explorer/transformer_models.py +59 -0
- coralnet_toolbox/Explorer/yolo_models.py +112 -0
- coralnet_toolbox/IO/QtExportTagLabAnnotations.py +30 -10
- coralnet_toolbox/IO/QtImportTagLabAnnotations.py +21 -15
- coralnet_toolbox/IO/QtOpenProject.py +46 -78
- coralnet_toolbox/IO/QtSaveProject.py +18 -43
- coralnet_toolbox/MachineLearning/ExportDataset/QtBase.py +1 -1
- coralnet_toolbox/MachineLearning/ImportDataset/QtBase.py +253 -141
- coralnet_toolbox/MachineLearning/VideoInference/QtBase.py +0 -4
- coralnet_toolbox/MachineLearning/VideoInference/YOLO3D/run.py +102 -16
- coralnet_toolbox/QtAnnotationWindow.py +16 -10
- coralnet_toolbox/QtEventFilter.py +11 -0
- coralnet_toolbox/QtImageWindow.py +120 -75
- coralnet_toolbox/QtLabelWindow.py +13 -1
- coralnet_toolbox/QtMainWindow.py +5 -27
- coralnet_toolbox/QtProgressBar.py +52 -27
- coralnet_toolbox/Rasters/RasterTableModel.py +28 -8
- coralnet_toolbox/SAM/QtDeployGenerator.py +1 -4
- coralnet_toolbox/SAM/QtDeployPredictor.py +11 -3
- coralnet_toolbox/SeeAnything/QtDeployGenerator.py +805 -162
- coralnet_toolbox/SeeAnything/QtDeployPredictor.py +130 -151
- coralnet_toolbox/Tools/QtCutSubTool.py +18 -2
- coralnet_toolbox/Tools/QtPolygonTool.py +42 -3
- coralnet_toolbox/Tools/QtRectangleTool.py +30 -0
- coralnet_toolbox/Tools/QtResizeSubTool.py +19 -2
- coralnet_toolbox/Tools/QtSAMTool.py +72 -50
- coralnet_toolbox/Tools/QtSeeAnythingTool.py +8 -5
- coralnet_toolbox/Tools/QtSelectTool.py +27 -3
- coralnet_toolbox/Tools/QtSubtractSubTool.py +66 -0
- coralnet_toolbox/Tools/__init__.py +2 -0
- coralnet_toolbox/__init__.py +1 -1
- coralnet_toolbox/utilities.py +158 -47
- coralnet_toolbox-0.0.75.dist-info/METADATA +378 -0
- {coralnet_toolbox-0.0.73.dist-info → coralnet_toolbox-0.0.75.dist-info}/RECORD +49 -44
- coralnet_toolbox-0.0.73.dist-info/METADATA +0 -341
- {coralnet_toolbox-0.0.73.dist-info → coralnet_toolbox-0.0.75.dist-info}/WHEEL +0 -0
- {coralnet_toolbox-0.0.73.dist-info → coralnet_toolbox-0.0.75.dist-info}/entry_points.txt +0 -0
- {coralnet_toolbox-0.0.73.dist-info → coralnet_toolbox-0.0.75.dist-info}/licenses/LICENSE.txt +0 -0
- {coralnet_toolbox-0.0.73.dist-info → coralnet_toolbox-0.0.75.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
"""
|
2
|
+
Transformer models configuration for the Explorer tool.
|
3
|
+
|
4
|
+
This module contains the transformer models dictionary used in the Explorer tool.
|
5
|
+
It's extracted into a separate module to allow easy importing in tests without
|
6
|
+
Qt dependencies.
|
7
|
+
"""
|
8
|
+
|
9
|
+
TRANSFORMER_MODELS = {
|
10
|
+
'DINOv2 (Small)': 'facebook/dinov2-small',
|
11
|
+
'DINOv2 (Base)': 'facebook/dinov2-base',
|
12
|
+
'DINOv2 (Large)': 'facebook/dinov2-large',
|
13
|
+
'DINOv3 ConvNext (Tiny)': 'facebook/dinov3-convnext-tiny-pretrain-lvd1689m',
|
14
|
+
'ResNet-50': 'microsoft/resnet-50',
|
15
|
+
'ResNet-101': 'microsoft/resnet-101',
|
16
|
+
'Swin Transformer (Tiny)': 'microsoft/swin-tiny-patch4-window7-224',
|
17
|
+
'Swin Transformer (Base)': 'microsoft/swin-base-patch4-window7-224',
|
18
|
+
'ViT (Base)': 'google/vit-base-patch16-224',
|
19
|
+
'ViT (Large)': 'google/vit-large-patch16-224',
|
20
|
+
}
|
21
|
+
|
22
|
+
|
23
|
+
def is_transformer_model(model_name):
|
24
|
+
"""
|
25
|
+
Determine if a model name refers to a transformer model.
|
26
|
+
|
27
|
+
This function checks if the model name indicates a HuggingFace transformer model
|
28
|
+
that should be handled by the transformer feature extraction pipeline.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
model_name (str): The model name to check
|
32
|
+
|
33
|
+
Returns:
|
34
|
+
bool: True if this is a transformer model, False otherwise
|
35
|
+
"""
|
36
|
+
if not model_name or not isinstance(model_name, str):
|
37
|
+
return False
|
38
|
+
|
39
|
+
# Check if it's one of our known transformer model IDs
|
40
|
+
if model_name in TRANSFORMER_MODELS.values():
|
41
|
+
return True
|
42
|
+
|
43
|
+
# Check for common HuggingFace model naming patterns
|
44
|
+
# Models from HuggingFace typically contain '/' in their names
|
45
|
+
if "/" in model_name:
|
46
|
+
# Check for known organization prefixes
|
47
|
+
known_prefixes = ("facebook/",
|
48
|
+
"microsoft/",
|
49
|
+
"google/",
|
50
|
+
"openai/",
|
51
|
+
"imageomics/")
|
52
|
+
|
53
|
+
if model_name.startswith(known_prefixes):
|
54
|
+
return True
|
55
|
+
|
56
|
+
# Any model with '/' is likely a HuggingFace model
|
57
|
+
return True
|
58
|
+
|
59
|
+
return False
|
@@ -0,0 +1,112 @@
|
|
1
|
+
"""
|
2
|
+
YOLO models configuration for the Explorer tool.
|
3
|
+
|
4
|
+
This module contains the YOLO models dictionary used in the Explorer tool.
|
5
|
+
It's extracted into a separate module to allow easy importing in tests without
|
6
|
+
Qt dependencies.
|
7
|
+
"""
|
8
|
+
|
9
|
+
from coralnet_toolbox.MachineLearning.Community.cfg import get_available_configs
|
10
|
+
|
11
|
+
# Dictionary mapping display names to model file names (classification models only)
|
12
|
+
YOLO_MODELS = {
|
13
|
+
# YOLOv8 classification models
|
14
|
+
'YOLOv8 (Nano)': 'yolov8n-cls.pt',
|
15
|
+
'YOLOv8 (Small)': 'yolov8s-cls.pt',
|
16
|
+
'YOLOv8 (Medium)': 'yolov8m-cls.pt',
|
17
|
+
'YOLOv8 (Large)': 'yolov8l-cls.pt',
|
18
|
+
'YOLOv8 (X-Large)': 'yolov8x-cls.pt',
|
19
|
+
|
20
|
+
# YOLOv11 classification models
|
21
|
+
'YOLOv11 (Nano)': 'yolov11n-cls.pt',
|
22
|
+
'YOLOv11 (Small)': 'yolov11s-cls.pt',
|
23
|
+
'YOLOv11 (Medium)': 'yolov11m-cls.pt',
|
24
|
+
'YOLOv11 (Large)': 'yolov11l-cls.pt',
|
25
|
+
'YOLOv11 (X-Large)': 'yolov11x-cls.pt',
|
26
|
+
|
27
|
+
# YOLOv12 classification models
|
28
|
+
'YOLOv12 (Nano)': 'yolov12n-cls.pt',
|
29
|
+
'YOLOv12 (Small)': 'yolov12s-cls.pt',
|
30
|
+
'YOLOv12 (Medium)': 'yolov12m-cls.pt',
|
31
|
+
'YOLOv12 (Large)': 'yolov12l-cls.pt',
|
32
|
+
'YOLOv12 (X-Large)': 'yolov12x-cls.pt',
|
33
|
+
}
|
34
|
+
|
35
|
+
|
36
|
+
def get_community_models(task='classify'):
|
37
|
+
"""
|
38
|
+
Get available community models for a specific task.
|
39
|
+
|
40
|
+
Args:
|
41
|
+
task (str): The task type, default is 'classify'
|
42
|
+
|
43
|
+
Returns:
|
44
|
+
dict: Dictionary of community models
|
45
|
+
"""
|
46
|
+
return get_available_configs(task=task)
|
47
|
+
|
48
|
+
|
49
|
+
def is_yolo_model(model_name):
|
50
|
+
"""
|
51
|
+
Determine if a model name refers to a YOLO model.
|
52
|
+
|
53
|
+
This function checks if the model name indicates a YOLO model
|
54
|
+
that should be handled by the YOLO feature extraction pipeline.
|
55
|
+
|
56
|
+
Args:
|
57
|
+
model_name (str): The model name to check
|
58
|
+
|
59
|
+
Returns:
|
60
|
+
bool: True if this is a YOLO model, False otherwise
|
61
|
+
"""
|
62
|
+
if not model_name or not isinstance(model_name, str):
|
63
|
+
return False
|
64
|
+
|
65
|
+
# Check if it's one of our known YOLO model IDs
|
66
|
+
if model_name in YOLO_MODELS.values():
|
67
|
+
return True
|
68
|
+
|
69
|
+
# Check if it's a community model (check both keys and values)
|
70
|
+
community_models = get_community_models()
|
71
|
+
if community_models:
|
72
|
+
if model_name in community_models or model_name in community_models.values():
|
73
|
+
return True
|
74
|
+
|
75
|
+
# Check for .pt file extension (any PyTorch model file)
|
76
|
+
if model_name.lower().endswith('.pt'):
|
77
|
+
return True
|
78
|
+
|
79
|
+
return False
|
80
|
+
|
81
|
+
|
82
|
+
def get_yolo_model_task(model_name):
|
83
|
+
"""
|
84
|
+
Determine the task type of a YOLO model based on its name.
|
85
|
+
|
86
|
+
Args:
|
87
|
+
model_name (str): The model name or path
|
88
|
+
|
89
|
+
Returns:
|
90
|
+
str: One of 'classify', 'detect', 'segment', or 'unknown'
|
91
|
+
"""
|
92
|
+
if not model_name or not isinstance(model_name, str):
|
93
|
+
return 'unknown'
|
94
|
+
|
95
|
+
# Check if it's a community model - all community models are classify
|
96
|
+
community_models = get_community_models()
|
97
|
+
if community_models:
|
98
|
+
if model_name in community_models or model_name in community_models.values():
|
99
|
+
return 'classify'
|
100
|
+
|
101
|
+
# Extract just the filename if a full path is provided
|
102
|
+
filename = model_name.split('/')[-1].split('\\')[-1].lower()
|
103
|
+
|
104
|
+
if '-cls' in filename:
|
105
|
+
return 'classify'
|
106
|
+
elif '-seg' in filename:
|
107
|
+
return 'segment'
|
108
|
+
elif filename.endswith('.pt'):
|
109
|
+
# Default YOLO models without specific suffixes are detection models
|
110
|
+
return 'detect'
|
111
|
+
|
112
|
+
return 'unknown'
|
@@ -197,30 +197,50 @@ class ExportTagLabAnnotations:
|
|
197
197
|
"""
|
198
198
|
# Calculate bounding box, centroid, area, perimeter, and contour
|
199
199
|
points = annotation.points
|
200
|
+
# Convert points to TagLab contour format
|
201
|
+
contour = self.taglabToPoints(np.array([[point.x(), point.y()] for point in points]))
|
202
|
+
inner_contours = []
|
203
|
+
if hasattr(annotation, 'holes') and annotation.holes:
|
204
|
+
# Convert holes to TagLab format
|
205
|
+
for hole in annotation.holes:
|
206
|
+
inner_contours.append(self.taglabToPoints(np.array([[point.x(), point.y()] for point in hole])))
|
207
|
+
|
208
|
+
# Calculate bounding box
|
200
209
|
min_x = int(min(point.x() for point in points))
|
201
210
|
min_y = int(min(point.y() for point in points))
|
202
211
|
max_x = int(max(point.x() for point in points))
|
203
|
-
max_y = int(max(point.y() for point in points))
|
212
|
+
max_y = int(max(point.y() for point in points))
|
204
213
|
width = max_x - min_x
|
205
214
|
height = max_y - min_y
|
215
|
+
bbox = [min_x, min_y, width, height]
|
216
|
+
|
217
|
+
# Calculate centroid
|
206
218
|
centroid_x = float(f"{sum(point.x() for point in points) / len(points):.1f}")
|
207
219
|
centroid_y = float(f"{sum(point.y() for point in points) / len(points):.1f}")
|
220
|
+
centroid = [centroid_x, centroid_y]
|
221
|
+
|
208
222
|
area = float(f"{annotation.get_area():.1f}")
|
209
223
|
perimeter = float(f"{annotation.get_perimeter():.1f}")
|
210
|
-
|
224
|
+
data = annotation.data if hasattr(annotation, 'data') else {}
|
225
|
+
|
226
|
+
# Pop these keys from data if they exist
|
227
|
+
class_name = data.pop('class_name', annotation.label.short_label_code)
|
228
|
+
instance_name = data.pop('instance_name', "coral0")
|
229
|
+
blob_name = data.pop('blob_name', f"c-0-{centroid_x}x-{centroid_y}y")
|
230
|
+
note = data.pop('note', "")
|
211
231
|
|
212
232
|
annotation_dict = {
|
213
|
-
"bbox":
|
214
|
-
"centroid":
|
233
|
+
"bbox": bbox,
|
234
|
+
"centroid": centroid,
|
215
235
|
"area": area,
|
216
236
|
"perimeter": perimeter,
|
217
237
|
"contour": contour,
|
218
|
-
"inner contours":
|
219
|
-
"class name":
|
220
|
-
"instance name":
|
221
|
-
"blob name":
|
222
|
-
"note":
|
223
|
-
"data":
|
238
|
+
"inner contours": inner_contours,
|
239
|
+
"class name": class_name,
|
240
|
+
"instance name": instance_name,
|
241
|
+
"blob name": blob_name,
|
242
|
+
"note": note,
|
243
|
+
"data": data
|
224
244
|
}
|
225
245
|
|
226
246
|
return annotation_dict
|
@@ -161,23 +161,25 @@ class ImportTagLabAnnotations:
|
|
161
161
|
short_label_code = label_info['name'].strip()
|
162
162
|
long_label_code = label_info['name'].strip()
|
163
163
|
color = QColor(*label_info['fill'])
|
164
|
-
|
165
|
-
#
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
164
|
+
|
165
|
+
# Pack all other data into a dict
|
166
|
+
imported_data = {
|
167
|
+
'bbox': annotation.get('bbox'),
|
168
|
+
'centroid': annotation.get('centroid'),
|
169
|
+
'area': annotation.get('area'),
|
170
|
+
'perimeter': annotation.get('perimeter'),
|
171
|
+
'class_name': annotation.get('class name'),
|
172
|
+
'instance_name': annotation.get('instance name'),
|
173
|
+
'blob_name': annotation.get('blob name'),
|
174
|
+
'id': annotation.get('id'),
|
175
|
+
'note': annotation.get('note'),
|
176
|
+
'data': annotation.get('data'),
|
177
|
+
}
|
178
178
|
|
179
179
|
# Convert contour string to points
|
180
180
|
points = self.parse_contour(annotation['contour'])
|
181
|
+
# Convert inner contours to a list of lists of points (holes)
|
182
|
+
holes = [self.parse_contour(inner) for inner in annotation.get('inner contours', [])]
|
181
183
|
|
182
184
|
# Create the label if it doesn't exist
|
183
185
|
label = self.label_window.add_label_if_not_exists(short_label_code,
|
@@ -191,8 +193,12 @@ class ImportTagLabAnnotations:
|
|
191
193
|
long_label_code=long_label_code,
|
192
194
|
color=color,
|
193
195
|
image_path=image_full_path,
|
194
|
-
label_id=label_id
|
196
|
+
label_id=label_id,
|
197
|
+
holes=holes,
|
195
198
|
)
|
199
|
+
# Add additional data to the annotation
|
200
|
+
polygon_annotation.data = imported_data
|
201
|
+
|
196
202
|
# Add annotation to the dict
|
197
203
|
self.annotation_window.add_annotation_to_dict(polygon_annotation)
|
198
204
|
|
@@ -12,16 +12,12 @@ from PyQt5.QtWidgets import (QDialog, QFileDialog, QVBoxLayout, QPushButton, QLa
|
|
12
12
|
QLineEdit)
|
13
13
|
|
14
14
|
from coralnet_toolbox.QtLabelWindow import Label
|
15
|
-
|
16
15
|
from coralnet_toolbox.QtWorkArea import WorkArea
|
17
|
-
|
18
16
|
from coralnet_toolbox.Annotations.QtPatchAnnotation import PatchAnnotation
|
19
17
|
from coralnet_toolbox.Annotations.QtPolygonAnnotation import PolygonAnnotation
|
20
18
|
from coralnet_toolbox.Annotations.QtRectangleAnnotation import RectangleAnnotation
|
21
19
|
from coralnet_toolbox.Annotations.QtMultiPolygonAnnotation import MultiPolygonAnnotation
|
22
|
-
|
23
20
|
from coralnet_toolbox.Common.QtUpdateImagePaths import UpdateImagePaths
|
24
|
-
|
25
21
|
from coralnet_toolbox.QtProgressBar import ProgressBar
|
26
22
|
|
27
23
|
|
@@ -143,19 +139,18 @@ class OpenProject(QDialog):
|
|
143
139
|
with open(file_path, 'r') as file:
|
144
140
|
project_data = json.load(file)
|
145
141
|
|
142
|
+
# Handle both new and old project formats for images and work areas
|
143
|
+
images_data = project_data.get('images', project_data.get('image_paths'))
|
144
|
+
legacy_workareas = project_data.get('workareas') # For backward compatibility
|
145
|
+
|
146
146
|
# Update main window with loaded project data
|
147
|
-
self.import_images(
|
148
|
-
self.import_workareas(project_data.get('workareas'))
|
147
|
+
self.import_images(images_data, legacy_workareas)
|
149
148
|
self.import_labels(project_data.get('labels'))
|
150
149
|
self.import_annotations(project_data.get('annotations'))
|
151
150
|
|
152
151
|
# Update current project path
|
153
152
|
self.current_project_path = file_path
|
154
153
|
|
155
|
-
QMessageBox.information(self.annotation_window,
|
156
|
-
"Project Loaded",
|
157
|
-
"Project has been successfully loaded.")
|
158
|
-
|
159
154
|
except Exception as e:
|
160
155
|
QMessageBox.warning(self.annotation_window,
|
161
156
|
"Error Loading Project",
|
@@ -168,10 +163,15 @@ class OpenProject(QDialog):
|
|
168
163
|
# Exit
|
169
164
|
self.accept()
|
170
165
|
|
171
|
-
def import_images(self,
|
172
|
-
"""Import images from the given
|
173
|
-
if not
|
166
|
+
def import_images(self, images_data, legacy_workareas=None):
|
167
|
+
"""Import images, states, and work areas from the given data."""
|
168
|
+
if not images_data:
|
174
169
|
return
|
170
|
+
|
171
|
+
# Determine if the format is old (list of strings) or new (list of dicts)
|
172
|
+
is_new_format = isinstance(images_data[0], dict)
|
173
|
+
|
174
|
+
image_paths = [img['path'] for img in images_data] if is_new_format else images_data
|
175
175
|
|
176
176
|
if not all([os.path.exists(path) for path in image_paths]):
|
177
177
|
image_paths, self.updated_paths = UpdateImagePaths.update_paths(image_paths)
|
@@ -183,15 +183,46 @@ class OpenProject(QDialog):
|
|
183
183
|
progress_bar.start_progress(total_images)
|
184
184
|
|
185
185
|
try:
|
186
|
+
# Create a map for quick data lookup if using the new format
|
187
|
+
image_data_map = {img['path']: img for img in images_data} if is_new_format else {}
|
188
|
+
|
186
189
|
# Add images to the image window's raster manager one by one
|
187
190
|
for path in image_paths:
|
188
|
-
# Use the improved add_image method which handles both
|
189
|
-
# adding to raster_manager and updating filtered_paths
|
190
191
|
self.image_window.add_image(path)
|
192
|
+
raster = self.image_window.raster_manager.get_raster(path)
|
193
|
+
if not raster:
|
194
|
+
continue
|
195
|
+
|
196
|
+
# If using the new format, apply saved state and work areas
|
197
|
+
if is_new_format and path in image_data_map:
|
198
|
+
data = image_data_map[path]
|
199
|
+
state = data.get('state', {})
|
200
|
+
work_areas_list = data.get('work_areas', [])
|
201
|
+
|
202
|
+
# Apply raster state
|
203
|
+
raster.checkbox_state = state.get('checkbox_state', False)
|
204
|
+
|
205
|
+
# Import work areas for this image
|
206
|
+
for work_area_data in work_areas_list:
|
207
|
+
try:
|
208
|
+
work_area = WorkArea.from_dict(work_area_data, path)
|
209
|
+
raster.add_work_area(work_area)
|
210
|
+
except Exception as e:
|
211
|
+
print(f"Warning: Could not import work area {work_area_data}: {str(e)}")
|
191
212
|
|
192
213
|
# Update the progress bar
|
193
214
|
progress_bar.update_progress()
|
194
215
|
|
216
|
+
# Handle backward compatibility for old, top-level work areas
|
217
|
+
if legacy_workareas:
|
218
|
+
for image_path, work_areas_list in legacy_workareas.items():
|
219
|
+
current_path = self.updated_paths.get(image_path, image_path)
|
220
|
+
raster = self.image_window.raster_manager.get_raster(current_path)
|
221
|
+
if raster:
|
222
|
+
for work_area_data in work_areas_list:
|
223
|
+
work_area = WorkArea.from_dict(work_area_data, current_path)
|
224
|
+
raster.add_work_area(work_area)
|
225
|
+
|
195
226
|
# Show the last image if any were imported
|
196
227
|
if self.image_window.raster_manager.image_paths:
|
197
228
|
self.image_window.load_image_by_path(self.image_window.raster_manager.image_paths[-1])
|
@@ -204,69 +235,6 @@ class OpenProject(QDialog):
|
|
204
235
|
# Close progress bar
|
205
236
|
progress_bar.stop_progress()
|
206
237
|
progress_bar.close()
|
207
|
-
|
208
|
-
def import_workareas(self, workareas):
|
209
|
-
"""Import work areas for each image."""
|
210
|
-
if not workareas:
|
211
|
-
return
|
212
|
-
|
213
|
-
# Start the progress bar
|
214
|
-
total_images = len(workareas)
|
215
|
-
progress_bar = ProgressBar(self.annotation_window, title="Importing Work Areas")
|
216
|
-
progress_bar.show()
|
217
|
-
progress_bar.start_progress(total_images)
|
218
|
-
|
219
|
-
try:
|
220
|
-
# Loop through each image's work areas
|
221
|
-
for image_path, work_areas_list in workareas.items():
|
222
|
-
|
223
|
-
# Check if the image path was updated (moved)
|
224
|
-
updated_path = False
|
225
|
-
|
226
|
-
if image_path not in self.image_window.raster_manager.image_paths:
|
227
|
-
# Check if the path was updated
|
228
|
-
if image_path in self.updated_paths:
|
229
|
-
image_path = self.updated_paths[image_path]
|
230
|
-
updated_path = True
|
231
|
-
else:
|
232
|
-
print(f"Warning: Image not found for work areas: {image_path}")
|
233
|
-
continue
|
234
|
-
|
235
|
-
# Get the raster for this image
|
236
|
-
raster = self.image_window.raster_manager.get_raster(image_path)
|
237
|
-
if not raster:
|
238
|
-
print(f"Warning: Could not get raster for image: {image_path}")
|
239
|
-
continue
|
240
|
-
|
241
|
-
# Import each work area for this image
|
242
|
-
for work_area_data in work_areas_list:
|
243
|
-
try:
|
244
|
-
# Update image path if it was changed
|
245
|
-
if updated_path:
|
246
|
-
work_area_data['image_path'] = image_path
|
247
|
-
|
248
|
-
# Create WorkArea from dictionary
|
249
|
-
work_area = WorkArea.from_dict(work_area_data, image_path)
|
250
|
-
|
251
|
-
# Add work area to the raster
|
252
|
-
raster.add_work_area(work_area)
|
253
|
-
|
254
|
-
except Exception as e:
|
255
|
-
print(f"Warning: Could not import work area {work_area_data}: {str(e)}")
|
256
|
-
continue
|
257
|
-
|
258
|
-
# Update the progress bar
|
259
|
-
progress_bar.update_progress()
|
260
|
-
|
261
|
-
except Exception as e:
|
262
|
-
QMessageBox.warning(self.annotation_window,
|
263
|
-
"Error Importing Work Areas",
|
264
|
-
f"An error occurred while importing work areas: {str(e)}")
|
265
|
-
|
266
|
-
finally:
|
267
|
-
# Close progress bar
|
268
|
-
progress_bar.stop_progress()
|
269
|
-
progress_bar.close()
|
270
238
|
|
271
239
|
def import_labels(self, labels):
|
272
240
|
"""Import labels from the given list."""
|
@@ -96,10 +96,9 @@ class SaveProject(QDialog):
|
|
96
96
|
|
97
97
|
try:
|
98
98
|
project_data = {
|
99
|
-
'
|
99
|
+
'images': self.get_images(),
|
100
100
|
'labels': self.get_labels(),
|
101
|
-
'annotations': self.get_annotations()
|
102
|
-
'workareas': self.get_workareas()
|
101
|
+
'annotations': self.get_annotations()
|
103
102
|
}
|
104
103
|
|
105
104
|
with open(file_path, 'w') as file:
|
@@ -125,10 +124,10 @@ class SaveProject(QDialog):
|
|
125
124
|
self.accept()
|
126
125
|
|
127
126
|
def get_images(self):
|
128
|
-
"""Get the list of image paths
|
127
|
+
"""Get the list of image objects, including paths, states, and work areas."""
|
129
128
|
# Start the progress bar
|
130
129
|
total_images = len(self.image_window.raster_manager.image_paths)
|
131
|
-
progress_bar = ProgressBar(self.label_window, "Exporting
|
130
|
+
progress_bar = ProgressBar(self.label_window, "Exporting Image Data")
|
132
131
|
progress_bar.show()
|
133
132
|
progress_bar.start_progress(total_images)
|
134
133
|
|
@@ -137,7 +136,19 @@ class SaveProject(QDialog):
|
|
137
136
|
|
138
137
|
# Loop through all of the image paths
|
139
138
|
for image_path in self.image_window.raster_manager.image_paths:
|
140
|
-
|
139
|
+
raster = self.image_window.raster_manager.get_raster(image_path)
|
140
|
+
if raster:
|
141
|
+
# Get work areas for this raster
|
142
|
+
work_areas_list = [wa.to_dict() for wa in raster.get_work_areas()]
|
143
|
+
|
144
|
+
image_data = {
|
145
|
+
'path': image_path,
|
146
|
+
'state': {
|
147
|
+
'checkbox_state': raster.checkbox_state
|
148
|
+
},
|
149
|
+
'work_areas': work_areas_list
|
150
|
+
}
|
151
|
+
export_images.append(image_data)
|
141
152
|
progress_bar.update_progress()
|
142
153
|
|
143
154
|
except Exception as e:
|
@@ -234,42 +245,6 @@ class SaveProject(QDialog):
|
|
234
245
|
|
235
246
|
return export_annotations
|
236
247
|
|
237
|
-
def get_workareas(self):
|
238
|
-
"""Get the work areas to export."""
|
239
|
-
# Start progress bar
|
240
|
-
total_rasters = len(self.image_window.raster_manager.image_paths)
|
241
|
-
progress_bar = ProgressBar(self.annotation_window, title="Exporting Work Areas")
|
242
|
-
progress_bar.show()
|
243
|
-
progress_bar.start_progress(total_rasters)
|
244
|
-
|
245
|
-
try:
|
246
|
-
export_workareas = {}
|
247
|
-
|
248
|
-
# Loop through all rasters to get their work areas
|
249
|
-
for image_path in self.image_window.raster_manager.image_paths:
|
250
|
-
raster = self.image_window.raster_manager.get_raster(image_path)
|
251
|
-
if raster and raster.has_work_areas():
|
252
|
-
work_areas_list = []
|
253
|
-
for work_area in raster.get_work_areas():
|
254
|
-
work_areas_list.append(work_area.to_dict())
|
255
|
-
|
256
|
-
if work_areas_list: # Only add if there are work areas
|
257
|
-
export_workareas[image_path] = work_areas_list
|
258
|
-
|
259
|
-
progress_bar.update_progress()
|
260
|
-
|
261
|
-
except Exception as e:
|
262
|
-
QMessageBox.warning(self.annotation_window,
|
263
|
-
"Error Exporting Work Areas",
|
264
|
-
f"An error occurred while exporting work areas: {str(e)}")
|
265
|
-
|
266
|
-
finally:
|
267
|
-
# Stop the progress bar
|
268
|
-
progress_bar.stop_progress()
|
269
|
-
progress_bar.close()
|
270
|
-
|
271
|
-
return export_workareas
|
272
|
-
|
273
248
|
def get_project_path(self):
|
274
249
|
"""Get the current project path."""
|
275
250
|
return self.current_project_path
|
@@ -290,4 +265,4 @@ class SaveProject(QDialog):
|
|
290
265
|
"""Handle dialog rejection (Cancel or close)"""
|
291
266
|
if self.current_project_path:
|
292
267
|
self.file_path_edit.setText(self.current_project_path)
|
293
|
-
super().reject()
|
268
|
+
super().reject()
|
@@ -42,7 +42,7 @@ class Base(QDialog):
|
|
42
42
|
self.annotation_window = main_window.annotation_window
|
43
43
|
self.image_window = main_window.image_window
|
44
44
|
|
45
|
-
self.resize(
|
45
|
+
self.resize(800, 800)
|
46
46
|
self.setWindowIcon(get_icon("coral.png"))
|
47
47
|
self.setWindowTitle("Export Dataset")
|
48
48
|
|