coralnet-toolbox 0.0.70__py2.py3-none-any.whl → 0.0.72__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.
@@ -57,9 +57,14 @@ class RectangleAnnotation(Annotation):
57
57
 
58
58
  def set_cropped_bbox(self):
59
59
  """Set the cropped bounding box for the annotation."""
60
+ # Store the raw coordinates, keeping the top_left and bottom_right as they are
61
+ # to maintain the user's dragging experience
60
62
  self.cropped_bbox = (self.top_left.x(), self.top_left.y(), self.bottom_right.x(), self.bottom_right.y())
61
- self.annotation_size = int(max(self.bottom_right.x() - self.top_left.x(),
62
- self.bottom_right.y() - self.top_left.y()))
63
+
64
+ # Calculate annotation_size using absolute differences to handle inverted coordinates
65
+ width = abs(self.bottom_right.x() - self.top_left.x())
66
+ height = abs(self.bottom_right.y() - self.top_left.y())
67
+ self.annotation_size = int(max(width, height))
63
68
 
64
69
  def contains_point(self, point: QPointF) -> bool:
65
70
  """Check if the given point is within the rectangle."""
@@ -172,6 +177,10 @@ class RectangleAnnotation(Annotation):
172
177
  self.set_cropped_bbox()
173
178
  # Get the bounding box of the rectangle
174
179
  min_x, min_y, max_x, max_y = self.cropped_bbox
180
+
181
+ # Ensure min/max values are correctly ordered to avoid negative width/height
182
+ min_x, max_x = min(min_x, max_x), max(min_x, max_x)
183
+ min_y, max_y = min(min_y, max_y), max(min_y, max_y)
175
184
 
176
185
  # Calculate the window for rasterio
177
186
  window = Window(
@@ -275,10 +284,30 @@ class RectangleAnnotation(Annotation):
275
284
  elif handle == "bottom_right":
276
285
  self.bottom_right = new_pos
277
286
 
287
+ # Normalize coordinates to ensure top_left has smaller values than bottom_right
288
+ self.normalize_coordinates()
289
+
278
290
  self.set_precision(self.top_left, self.bottom_right)
279
291
  self.set_centroid()
280
292
  self.update_graphics_item()
281
293
  self.annotationUpdated.emit(self)
294
+
295
+ def normalize_coordinates(self):
296
+ """Ensure that top_left has smaller coordinates than bottom_right."""
297
+ # Create temporary points to store the normalized coordinates
298
+ x_min = min(self.top_left.x(), self.bottom_right.x())
299
+ y_min = min(self.top_left.y(), self.bottom_right.y())
300
+ x_max = max(self.top_left.x(), self.bottom_right.x())
301
+ y_max = max(self.top_left.y(), self.bottom_right.y())
302
+
303
+ # Update the points
304
+ self.top_left.setX(x_min)
305
+ self.top_left.setY(y_min)
306
+ self.bottom_right.setX(x_max)
307
+ self.bottom_right.setY(y_max)
308
+
309
+ # Update centroid after normalization
310
+ self.set_centroid()
282
311
 
283
312
  @classmethod
284
313
  def combine(cls, annotations: list):
@@ -94,6 +94,7 @@ class AnnotationImageWidget(QWidget):
94
94
  self.widget_height = widget_height
95
95
  self.aspect_ratio = 1.0
96
96
  self.pixmap = None
97
+ self.is_loaded = False # Flag for lazy loading
97
98
 
98
99
  self.animation_offset = 0
99
100
  self.animation_timer = QTimer(self)
@@ -101,7 +102,9 @@ class AnnotationImageWidget(QWidget):
101
102
  self.animation_timer.setInterval(75)
102
103
 
103
104
  self.setup_ui()
104
- self.load_and_set_image()
105
+ self.recalculate_aspect_ratio() # Calculate aspect ratio from geometry
106
+ self.update_height(self.widget_height) # Set initial size
107
+ self.update_tooltip()
105
108
 
106
109
  def setup_ui(self):
107
110
  """Set up the basic UI with a label for the image."""
@@ -117,43 +120,72 @@ class AnnotationImageWidget(QWidget):
117
120
  """Updates the tooltip by fetching the latest text from the data item."""
118
121
  self.setToolTip(self.data_item.get_tooltip_text())
119
122
 
120
- def load_and_set_image(self):
121
- """Load image, calculate its aspect ratio, and set the widget's initial size."""
123
+ def recalculate_aspect_ratio(self):
124
+ """Calculate aspect ratio from annotation geometry without loading image."""
125
+ try:
126
+ if hasattr(self.annotation, 'rect'): # RectangleAnnotation
127
+ rect = self.annotation.rect
128
+ if rect.height() > 0:
129
+ self.aspect_ratio = rect.width() / rect.height()
130
+ elif hasattr(self.annotation, 'size'): # PatchAnnotation
131
+ self.aspect_ratio = 1.0
132
+ elif hasattr(self.annotation, 'polygon'): # PolygonAnnotation
133
+ rect = self.annotation.polygon.boundingRect()
134
+ if rect.height() > 0:
135
+ self.aspect_ratio = rect.width() / rect.height()
136
+ else:
137
+ # Fallback for other types or if geometry is not available
138
+ self.aspect_ratio = 1.0
139
+ except Exception as e:
140
+ print(f"Could not determine aspect ratio for {self.annotation.id}: {e}")
141
+ self.aspect_ratio = 1.0
142
+
143
+ def load_image(self):
144
+ """Loads the image pixmap if it hasn't been loaded yet."""
145
+ if self.is_loaded:
146
+ return
147
+
122
148
  try:
123
149
  cropped_pixmap = self.annotation.get_cropped_image_graphic()
124
150
  if cropped_pixmap and not cropped_pixmap.isNull():
125
151
  self.pixmap = cropped_pixmap
126
- if self.pixmap.height() > 0:
127
- self.aspect_ratio = self.pixmap.width() / self.pixmap.height()
128
- else:
129
- self.aspect_ratio = 1.0
152
+ self.is_loaded = True
153
+ self._display_pixmap()
130
154
  else:
131
155
  self.image_label.setText("No Image\nAvailable")
132
156
  self.pixmap = None
133
- self.aspect_ratio = 1.0
134
157
  except Exception as e:
135
158
  print(f"Error loading annotation image: {e}")
136
159
  self.image_label.setText("Error\nLoading Image")
137
160
  self.pixmap = None
138
- self.aspect_ratio = 1.0
139
-
140
- self.update_height(self.widget_height)
141
- # Set the initial tooltip
142
- self.update_tooltip()
143
161
 
144
- def update_height(self, new_height):
145
- """Updates the widget's height and rescales its width and content accordingly."""
146
- self.widget_height = new_height
147
- new_width = int(self.widget_height * self.aspect_ratio)
148
- self.setFixedSize(new_width, new_height)
162
+ def unload_image(self):
163
+ """Unloads the pixmap to free memory."""
164
+ if not self.is_loaded:
165
+ return
166
+ self.pixmap = None
167
+ self.image_label.clear()
168
+ self.is_loaded = False
169
+
170
+ def _display_pixmap(self):
171
+ """Scales and displays the currently loaded pixmap."""
149
172
  if self.pixmap:
173
+ new_width = int(self.widget_height * self.aspect_ratio)
150
174
  scaled_pixmap = self.pixmap.scaled(
151
175
  new_width - 8,
152
- new_height - 8,
176
+ self.widget_height - 8,
153
177
  Qt.KeepAspectRatio,
154
178
  Qt.SmoothTransformation
155
179
  )
156
180
  self.image_label.setPixmap(scaled_pixmap)
181
+
182
+ def update_height(self, new_height):
183
+ """Updates the widget's height and rescales its width and content accordingly."""
184
+ self.widget_height = new_height
185
+ new_width = int(self.widget_height * self.aspect_ratio)
186
+ self.setFixedSize(new_width, new_height)
187
+ if self.pixmap:
188
+ self._display_pixmap()
157
189
  self.update()
158
190
 
159
191
  def update_selection_visuals(self):
@@ -336,4 +368,4 @@ class AnnotationDataItem:
336
368
  return list(self.annotation.user_confidence.values())[0]
337
369
  elif hasattr(self.annotation, 'machine_confidence') and self.annotation.machine_confidence:
338
370
  return list(self.annotation.machine_confidence.values())[0]
339
- return 0.0
371
+ return 0.0