lazylabel-gui 1.3.2__tar.gz → 1.3.3__tar.gz
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.
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/PKG-INFO +1 -1
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/pyproject.toml +1 -1
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/main_window.py +207 -94
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/utils/fast_file_manager.py +162 -7
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel_gui.egg-info/PKG-INFO +1 -1
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/LICENSE +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/README.md +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/setup.cfg +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/__init__.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/__main__.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/config/__init__.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/config/hotkeys.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/config/paths.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/config/settings.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/core/__init__.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/core/file_manager.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/core/model_manager.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/core/segment_manager.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/main.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/models/__init__.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/models/sam2_model.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/models/sam_model.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/__init__.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/control_panel.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/editable_vertex.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/hotkey_dialog.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/hoverable_pixelmap_item.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/hoverable_polygon_item.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/modes/__init__.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/modes/base_mode.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/modes/multi_view_mode.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/modes/single_view_mode.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/numeric_table_widget_item.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/photo_viewer.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/reorderable_class_table.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/right_panel.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/widgets/__init__.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/widgets/adjustments_widget.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/widgets/border_crop_widget.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/widgets/channel_threshold_widget.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/widgets/fft_threshold_widget.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/widgets/fragment_threshold_widget.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/widgets/model_selection_widget.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/widgets/settings_widget.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/widgets/status_bar.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/utils/__init__.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/utils/custom_file_system_model.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/utils/logger.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/utils/utils.py +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel_gui.egg-info/SOURCES.txt +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel_gui.egg-info/dependency_links.txt +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel_gui.egg-info/entry_points.txt +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel_gui.egg-info/requires.txt +0 -0
- {lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel_gui.egg-info/top_level.txt +0 -0
@@ -1788,31 +1788,12 @@ class MainWindow(QMainWindow):
|
|
1788
1788
|
):
|
1789
1789
|
self._save_multi_view_output()
|
1790
1790
|
|
1791
|
-
# Get
|
1792
|
-
|
1793
|
-
|
1794
|
-
current_idx = -1
|
1791
|
+
# Get surrounding files in current sorted/filtered order
|
1792
|
+
file_manager = self.right_panel.file_manager
|
1793
|
+
surrounding_files = file_manager.getSurroundingFiles(Path(path), num_viewers)
|
1795
1794
|
|
1796
|
-
#
|
1797
|
-
for
|
1798
|
-
file_info = file_manager_model.getFileInfo(i)
|
1799
|
-
if file_info:
|
1800
|
-
all_files.append(str(file_info.path))
|
1801
|
-
if str(file_info.path) == path:
|
1802
|
-
current_idx = i
|
1803
|
-
|
1804
|
-
if current_idx == -1:
|
1805
|
-
# File not found in current list
|
1806
|
-
return
|
1807
|
-
|
1808
|
-
# Load consecutive images
|
1809
|
-
images_to_load = []
|
1810
|
-
for i in range(num_viewers):
|
1811
|
-
idx = current_idx + i
|
1812
|
-
if idx < len(all_files):
|
1813
|
-
images_to_load.append(all_files[idx])
|
1814
|
-
else:
|
1815
|
-
images_to_load.append(None)
|
1795
|
+
# Convert to strings for loading
|
1796
|
+
images_to_load = [str(p) if p else None for p in surrounding_files]
|
1816
1797
|
|
1817
1798
|
# Load the images
|
1818
1799
|
self._load_multi_view_images(images_to_load)
|
@@ -3320,6 +3301,18 @@ class MainWindow(QMainWindow):
|
|
3320
3301
|
# Restore original segments
|
3321
3302
|
self.segment_manager.segments = original_segments
|
3322
3303
|
|
3304
|
+
# Save class aliases if enabled
|
3305
|
+
if settings.get("save_class_aliases", False) and viewer_segments:
|
3306
|
+
# Temporarily set segments for this viewer to save correct aliases
|
3307
|
+
original_segments = self.segment_manager.segments
|
3308
|
+
self.segment_manager.segments = viewer_segments
|
3309
|
+
|
3310
|
+
aliases_path = self.file_manager.save_class_aliases(image_path)
|
3311
|
+
saved_files.append(os.path.basename(aliases_path))
|
3312
|
+
|
3313
|
+
# Restore original segments
|
3314
|
+
self.segment_manager.segments = original_segments
|
3315
|
+
|
3323
3316
|
# If no segments for this viewer, delete associated files
|
3324
3317
|
if not viewer_segments:
|
3325
3318
|
base, _ = os.path.splitext(image_path)
|
@@ -3334,7 +3327,7 @@ class MainWindow(QMainWindow):
|
|
3334
3327
|
if hasattr(self, "right_panel") and hasattr(
|
3335
3328
|
self.right_panel, "file_manager"
|
3336
3329
|
):
|
3337
|
-
self.right_panel.file_manager.
|
3330
|
+
self.right_panel.file_manager.updateFileStatus(
|
3338
3331
|
Path(image_path)
|
3339
3332
|
)
|
3340
3333
|
except Exception as e:
|
@@ -3353,13 +3346,13 @@ class MainWindow(QMainWindow):
|
|
3353
3346
|
|
3354
3347
|
# Update FastFileManager to show NPZ checkmarks for multi-view
|
3355
3348
|
if hasattr(self, "multi_view_images") and self.multi_view_images:
|
3356
|
-
|
3357
|
-
|
3358
|
-
|
3359
|
-
|
3360
|
-
|
3361
|
-
|
3362
|
-
self.right_panel.file_manager.
|
3349
|
+
if hasattr(self, "right_panel") and hasattr(
|
3350
|
+
self.right_panel, "file_manager"
|
3351
|
+
):
|
3352
|
+
# Batch update for better performance
|
3353
|
+
valid_paths = [Path(img) for img in self.multi_view_images if img]
|
3354
|
+
if valid_paths:
|
3355
|
+
self.right_panel.file_manager.batchUpdateFileStatus(valid_paths)
|
3363
3356
|
# Clear the tracking list for next save
|
3364
3357
|
self._saved_file_paths = []
|
3365
3358
|
|
@@ -3395,7 +3388,7 @@ class MainWindow(QMainWindow):
|
|
3395
3388
|
if hasattr(self, "right_panel") and hasattr(
|
3396
3389
|
self.right_panel, "file_manager"
|
3397
3390
|
):
|
3398
|
-
self.right_panel.file_manager.
|
3391
|
+
self.right_panel.file_manager.updateFileStatus(
|
3399
3392
|
Path(self.current_image_path)
|
3400
3393
|
)
|
3401
3394
|
except Exception as e:
|
@@ -3445,6 +3438,8 @@ class MainWindow(QMainWindow):
|
|
3445
3438
|
else:
|
3446
3439
|
logger.warning("No classes defined for saving")
|
3447
3440
|
self._show_warning_notification("No classes defined for saving.")
|
3441
|
+
# Save TXT file if enabled
|
3442
|
+
txt_path = None
|
3448
3443
|
if settings.get("save_txt", True):
|
3449
3444
|
h, w = (
|
3450
3445
|
self.viewer._pixmap_item.pixmap().height(),
|
@@ -3458,21 +3453,38 @@ class MainWindow(QMainWindow):
|
|
3458
3453
|
else:
|
3459
3454
|
class_labels = [str(cid) for cid in class_order]
|
3460
3455
|
if class_order:
|
3461
|
-
self.file_manager.save_yolo_txt(
|
3456
|
+
txt_path = self.file_manager.save_yolo_txt(
|
3462
3457
|
self.current_image_path,
|
3463
3458
|
(h, w),
|
3464
3459
|
class_order,
|
3465
3460
|
class_labels,
|
3466
3461
|
self.current_crop_coords,
|
3467
3462
|
)
|
3468
|
-
|
3463
|
+
if txt_path:
|
3464
|
+
logger.debug(f"TXT save completed: {txt_path}")
|
3465
|
+
self._show_success_notification(
|
3466
|
+
f"Saved: {os.path.basename(txt_path)}"
|
3467
|
+
)
|
3468
|
+
|
3469
|
+
# Save class aliases if enabled
|
3470
|
+
if settings.get("save_class_aliases", False):
|
3471
|
+
aliases_path = self.file_manager.save_class_aliases(
|
3472
|
+
self.current_image_path
|
3473
|
+
)
|
3474
|
+
if aliases_path:
|
3475
|
+
logger.debug(f"Class aliases saved: {aliases_path}")
|
3476
|
+
self._show_success_notification(
|
3477
|
+
f"Saved: {os.path.basename(aliases_path)}"
|
3478
|
+
)
|
3479
|
+
|
3480
|
+
# Update FastFileManager to show NPZ/TXT checkmarks
|
3469
3481
|
if (
|
3470
|
-
npz_path
|
3482
|
+
(npz_path or txt_path)
|
3471
3483
|
and hasattr(self, "right_panel")
|
3472
3484
|
and hasattr(self.right_panel, "file_manager")
|
3473
3485
|
):
|
3474
|
-
# Update the
|
3475
|
-
self.right_panel.file_manager.
|
3486
|
+
# Update the file status in the FastFileManager
|
3487
|
+
self.right_panel.file_manager.updateFileStatus(
|
3476
3488
|
Path(self.current_image_path)
|
3477
3489
|
)
|
3478
3490
|
except Exception as e:
|
@@ -7236,24 +7248,73 @@ class MainWindow(QMainWindow):
|
|
7236
7248
|
self._load_multi_view_images(image_paths)
|
7237
7249
|
|
7238
7250
|
def _load_next_multi_batch(self):
|
7239
|
-
"""Load the next batch of images using cached list."""
|
7240
|
-
if
|
7241
|
-
|
7242
|
-
|
7243
|
-
|
7244
|
-
|
7245
|
-
"auto_save", True
|
7251
|
+
"""Load the next batch of images using fast file manager or cached list."""
|
7252
|
+
# Auto-save if enabled and we have current images
|
7253
|
+
if (
|
7254
|
+
hasattr(self, "multi_view_images")
|
7255
|
+
and self.multi_view_images
|
7256
|
+
and self.multi_view_images[0]
|
7257
|
+
and self.control_panel.get_settings().get("auto_save", True)
|
7246
7258
|
):
|
7247
7259
|
self._save_multi_view_output()
|
7248
7260
|
|
7249
|
-
#
|
7261
|
+
# Get current image path - look for any valid image in multi-view state
|
7262
|
+
current_path = None
|
7263
|
+
if hasattr(self, "multi_view_images") and self.multi_view_images:
|
7264
|
+
# Find the first valid image path in the current multi-view state
|
7265
|
+
for img_path in self.multi_view_images:
|
7266
|
+
if img_path:
|
7267
|
+
current_path = img_path
|
7268
|
+
break
|
7269
|
+
|
7270
|
+
# Fallback to current_image_path if no valid multi-view images found
|
7271
|
+
if (
|
7272
|
+
not current_path
|
7273
|
+
and hasattr(self, "current_image_path")
|
7274
|
+
and self.current_image_path
|
7275
|
+
):
|
7276
|
+
current_path = self.current_image_path
|
7277
|
+
|
7278
|
+
# If no valid path found, can't navigate forward
|
7279
|
+
if not current_path:
|
7280
|
+
return
|
7281
|
+
|
7282
|
+
# Get the number of viewers for multi-view mode
|
7283
|
+
config = self._get_multi_view_config()
|
7284
|
+
num_viewers = config["num_viewers"]
|
7285
|
+
|
7286
|
+
# Try to use fast file manager first (respects sorting/filtering)
|
7287
|
+
try:
|
7288
|
+
if (
|
7289
|
+
hasattr(self, "right_panel")
|
7290
|
+
and hasattr(self.right_panel, "file_manager")
|
7291
|
+
and hasattr(self.right_panel.file_manager, "getSurroundingFiles")
|
7292
|
+
):
|
7293
|
+
file_manager = self.right_panel.file_manager
|
7294
|
+
surrounding_files = file_manager.getSurroundingFiles(
|
7295
|
+
Path(current_path), num_viewers * 2
|
7296
|
+
)
|
7297
|
+
|
7298
|
+
if len(surrounding_files) > num_viewers:
|
7299
|
+
# Skip ahead by num_viewers to get the next batch
|
7300
|
+
next_batch = surrounding_files[num_viewers : num_viewers * 2]
|
7301
|
+
|
7302
|
+
# Convert to strings and load
|
7303
|
+
images_to_load = [str(p) if p else None for p in next_batch]
|
7304
|
+
self._load_multi_view_images(images_to_load)
|
7305
|
+
|
7306
|
+
# Update file manager selection to first image of new batch
|
7307
|
+
if next_batch and next_batch[0]:
|
7308
|
+
self.right_panel.select_file(next_batch[0])
|
7309
|
+
return
|
7310
|
+
except Exception:
|
7311
|
+
pass # Fall back to cached list approach
|
7312
|
+
|
7313
|
+
# Fall back to cached list approach (for backward compatibility / tests)
|
7250
7314
|
if not self.cached_image_paths:
|
7251
7315
|
self._load_next_multi_batch_fallback()
|
7252
7316
|
return
|
7253
7317
|
|
7254
|
-
# Get current image path
|
7255
|
-
current_path = self.file_model.filePath(self.current_file_index)
|
7256
|
-
|
7257
7318
|
# Find current position in cached list
|
7258
7319
|
try:
|
7259
7320
|
current_index = self.cached_image_paths.index(current_path)
|
@@ -7262,13 +7323,14 @@ class MainWindow(QMainWindow):
|
|
7262
7323
|
self._load_next_multi_batch_fallback()
|
7263
7324
|
return
|
7264
7325
|
|
7265
|
-
# Get the number of viewers for multi-view mode
|
7266
|
-
config = self._get_multi_view_config()
|
7267
|
-
num_viewers = config["num_viewers"]
|
7268
|
-
|
7269
7326
|
# Skip num_viewers positions ahead in cached list
|
7270
7327
|
target_index = current_index + num_viewers
|
7271
7328
|
|
7329
|
+
# Check if we can navigate forward (at least one valid image at target position)
|
7330
|
+
if target_index >= len(self.cached_image_paths):
|
7331
|
+
return # Can't navigate forward - at or past the end
|
7332
|
+
|
7333
|
+
# Check if we have at least one valid image at the target position
|
7272
7334
|
if target_index < len(self.cached_image_paths):
|
7273
7335
|
# Collect consecutive images
|
7274
7336
|
image_paths = []
|
@@ -7278,39 +7340,88 @@ class MainWindow(QMainWindow):
|
|
7278
7340
|
else:
|
7279
7341
|
image_paths.append(None)
|
7280
7342
|
|
7281
|
-
#
|
7282
|
-
|
7283
|
-
|
7284
|
-
|
7285
|
-
|
7286
|
-
|
7287
|
-
|
7288
|
-
|
7289
|
-
|
7290
|
-
|
7291
|
-
self.
|
7292
|
-
self.
|
7293
|
-
|
7343
|
+
# Only proceed if we have at least one valid image (prevent all-None batches)
|
7344
|
+
if any(path is not None for path in image_paths):
|
7345
|
+
# Load all images
|
7346
|
+
self._load_multi_view_images(image_paths)
|
7347
|
+
|
7348
|
+
# Update current file index to the first image of the new batch
|
7349
|
+
# Find the file model index for this path
|
7350
|
+
if image_paths and image_paths[0]:
|
7351
|
+
parent_index = self.current_file_index.parent()
|
7352
|
+
for row in range(self.file_model.rowCount(parent_index)):
|
7353
|
+
index = self.file_model.index(row, 0, parent_index)
|
7354
|
+
if self.file_model.filePath(index) == image_paths[0]:
|
7355
|
+
self.current_file_index = index
|
7356
|
+
self.right_panel.file_tree.setCurrentIndex(index)
|
7357
|
+
break
|
7358
|
+
# If all would be None, don't navigate (stay at current position)
|
7294
7359
|
|
7295
7360
|
def _load_previous_multi_batch(self):
|
7296
|
-
"""Load the previous batch of images using cached list."""
|
7297
|
-
if
|
7298
|
-
|
7299
|
-
|
7300
|
-
|
7301
|
-
|
7302
|
-
"auto_save", True
|
7361
|
+
"""Load the previous batch of images using fast file manager or cached list."""
|
7362
|
+
# Auto-save if enabled and we have current images
|
7363
|
+
if (
|
7364
|
+
hasattr(self, "multi_view_images")
|
7365
|
+
and self.multi_view_images
|
7366
|
+
and self.multi_view_images[0]
|
7367
|
+
and self.control_panel.get_settings().get("auto_save", True)
|
7303
7368
|
):
|
7304
7369
|
self._save_multi_view_output()
|
7305
7370
|
|
7306
|
-
#
|
7371
|
+
# Get current image path - look for any valid image in multi-view state
|
7372
|
+
current_path = None
|
7373
|
+
if hasattr(self, "multi_view_images") and self.multi_view_images:
|
7374
|
+
# Find the first valid image path in the current multi-view state
|
7375
|
+
for img_path in self.multi_view_images:
|
7376
|
+
if img_path:
|
7377
|
+
current_path = img_path
|
7378
|
+
break
|
7379
|
+
|
7380
|
+
# Fallback to current_image_path if no valid multi-view images found
|
7381
|
+
if (
|
7382
|
+
not current_path
|
7383
|
+
and hasattr(self, "current_image_path")
|
7384
|
+
and self.current_image_path
|
7385
|
+
):
|
7386
|
+
current_path = self.current_image_path
|
7387
|
+
|
7388
|
+
# If no valid path found, can't navigate backward
|
7389
|
+
if not current_path:
|
7390
|
+
return
|
7391
|
+
|
7392
|
+
# Get the number of viewers for multi-view mode
|
7393
|
+
config = self._get_multi_view_config()
|
7394
|
+
num_viewers = config["num_viewers"]
|
7395
|
+
|
7396
|
+
# Try to use fast file manager first (respects sorting/filtering)
|
7397
|
+
try:
|
7398
|
+
if (
|
7399
|
+
hasattr(self, "right_panel")
|
7400
|
+
and hasattr(self.right_panel, "file_manager")
|
7401
|
+
and hasattr(self.right_panel.file_manager, "getPreviousFiles")
|
7402
|
+
):
|
7403
|
+
file_manager = self.right_panel.file_manager
|
7404
|
+
previous_batch = file_manager.getPreviousFiles(
|
7405
|
+
Path(current_path), num_viewers
|
7406
|
+
)
|
7407
|
+
|
7408
|
+
if previous_batch and any(previous_batch):
|
7409
|
+
# Convert to strings and load
|
7410
|
+
images_to_load = [str(p) if p else None for p in previous_batch]
|
7411
|
+
self._load_multi_view_images(images_to_load)
|
7412
|
+
|
7413
|
+
# Update file manager selection to first image of new batch
|
7414
|
+
if previous_batch and previous_batch[0]:
|
7415
|
+
self.right_panel.select_file(previous_batch[0])
|
7416
|
+
return
|
7417
|
+
except Exception:
|
7418
|
+
pass # Fall back to cached list approach
|
7419
|
+
|
7420
|
+
# Fall back to cached list approach (for backward compatibility / tests)
|
7307
7421
|
if not self.cached_image_paths:
|
7308
7422
|
self._load_previous_multi_batch_fallback()
|
7309
7423
|
return
|
7310
7424
|
|
7311
|
-
# Get current image path
|
7312
|
-
current_path = self.file_model.filePath(self.current_file_index)
|
7313
|
-
|
7314
7425
|
# Find current position in cached list
|
7315
7426
|
try:
|
7316
7427
|
current_index = self.cached_image_paths.index(current_path)
|
@@ -7319,10 +7430,6 @@ class MainWindow(QMainWindow):
|
|
7319
7430
|
self._load_previous_multi_batch_fallback()
|
7320
7431
|
return
|
7321
7432
|
|
7322
|
-
# Get the number of viewers for multi-view mode
|
7323
|
-
config = self._get_multi_view_config()
|
7324
|
-
num_viewers = config["num_viewers"]
|
7325
|
-
|
7326
7433
|
# Skip num_viewers positions back in cached list
|
7327
7434
|
target_index = current_index - num_viewers
|
7328
7435
|
if target_index < 0:
|
@@ -7331,24 +7438,30 @@ class MainWindow(QMainWindow):
|
|
7331
7438
|
# Collect consecutive images
|
7332
7439
|
image_paths = []
|
7333
7440
|
for i in range(num_viewers):
|
7334
|
-
if
|
7441
|
+
if (
|
7442
|
+
target_index + i < len(self.cached_image_paths)
|
7443
|
+
and target_index + i >= 0
|
7444
|
+
):
|
7335
7445
|
image_paths.append(self.cached_image_paths[target_index + i])
|
7336
7446
|
else:
|
7337
7447
|
image_paths.append(None)
|
7338
7448
|
|
7339
|
-
#
|
7340
|
-
|
7449
|
+
# Only proceed if we have at least one valid image (prevent all-None batches)
|
7450
|
+
if any(path is not None for path in image_paths):
|
7451
|
+
# Load all images
|
7452
|
+
self._load_multi_view_images(image_paths)
|
7341
7453
|
|
7342
|
-
|
7343
|
-
|
7344
|
-
|
7345
|
-
|
7346
|
-
|
7347
|
-
|
7348
|
-
|
7349
|
-
|
7350
|
-
|
7351
|
-
|
7454
|
+
# Update current file index to the first image of the new batch
|
7455
|
+
# Find the file model index for this path
|
7456
|
+
if image_paths and image_paths[0]:
|
7457
|
+
parent_index = self.current_file_index.parent()
|
7458
|
+
for row in range(self.file_model.rowCount(parent_index)):
|
7459
|
+
index = self.file_model.index(row, 0, parent_index)
|
7460
|
+
if self.file_model.filePath(index) == image_paths[0]:
|
7461
|
+
self.current_file_index = index
|
7462
|
+
self.right_panel.file_tree.setCurrentIndex(index)
|
7463
|
+
break
|
7464
|
+
# If all would be None, don't navigate (stay at current position)
|
7352
7465
|
|
7353
7466
|
def _load_next_multi_batch_fallback(self):
|
7354
7467
|
"""Fallback navigation using file model when cached list isn't available."""
|
@@ -136,6 +136,7 @@ class FastFileModel(QAbstractTableModel):
|
|
136
136
|
def __init__(self):
|
137
137
|
super().__init__()
|
138
138
|
self._files: list[FileInfo] = []
|
139
|
+
self._path_to_index: dict[str, int] = {} # For O(1) lookups
|
139
140
|
self._scanner: FileScanner | None = None
|
140
141
|
|
141
142
|
def rowCount(self, parent=QModelIndex()):
|
@@ -218,6 +219,7 @@ class FastFileModel(QAbstractTableModel):
|
|
218
219
|
# Clear current files
|
219
220
|
self.beginResetModel()
|
220
221
|
self._files.clear()
|
222
|
+
self._path_to_index.clear()
|
221
223
|
self.endResetModel()
|
222
224
|
|
223
225
|
# Start new scan
|
@@ -231,7 +233,13 @@ class FastFileModel(QAbstractTableModel):
|
|
231
233
|
start_row = len(self._files)
|
232
234
|
end_row = start_row + len(files) - 1
|
233
235
|
self.beginInsertRows(QModelIndex(), start_row, end_row)
|
234
|
-
|
236
|
+
|
237
|
+
# Add files and update path-to-index mapping
|
238
|
+
for i, file_info in enumerate(files):
|
239
|
+
idx = start_row + i
|
240
|
+
self._files.append(file_info)
|
241
|
+
self._path_to_index[str(file_info.path)] = idx
|
242
|
+
|
235
243
|
self.endInsertRows()
|
236
244
|
|
237
245
|
def _on_scan_complete(self, total: int):
|
@@ -262,12 +270,80 @@ class FastFileModel(QAbstractTableModel):
|
|
262
270
|
self.dataChanged.emit(index, index)
|
263
271
|
break
|
264
272
|
|
273
|
+
def updateFileStatus(self, image_path: Path):
|
274
|
+
"""Update both NPZ and TXT status for a specific image file"""
|
275
|
+
image_path_str = str(image_path)
|
276
|
+
npz_path = image_path.with_suffix(".npz")
|
277
|
+
txt_path = image_path.with_suffix(".txt")
|
278
|
+
has_npz = npz_path.exists()
|
279
|
+
has_txt = txt_path.exists()
|
280
|
+
|
281
|
+
# O(1) lookup using path-to-index mapping
|
282
|
+
if image_path_str not in self._path_to_index:
|
283
|
+
return # File not in current view
|
284
|
+
|
285
|
+
i = self._path_to_index[image_path_str]
|
286
|
+
file_info = self._files[i]
|
287
|
+
|
288
|
+
# Update status and emit changes only if needed
|
289
|
+
old_has_npz = file_info.has_npz
|
290
|
+
old_has_txt = file_info.has_txt
|
291
|
+
file_info.has_npz = has_npz
|
292
|
+
file_info.has_txt = has_txt
|
293
|
+
|
294
|
+
# Emit dataChanged for NPZ column if status changed
|
295
|
+
if old_has_npz != has_npz:
|
296
|
+
index = self.index(i, 3) # NPZ column
|
297
|
+
self.dataChanged.emit(index, index)
|
298
|
+
|
299
|
+
# Emit dataChanged for TXT column if status changed
|
300
|
+
if old_has_txt != has_txt:
|
301
|
+
index = self.index(i, 4) # TXT column
|
302
|
+
self.dataChanged.emit(index, index)
|
303
|
+
|
265
304
|
def getFileIndex(self, path: Path) -> int:
|
266
305
|
"""Get index of file by path"""
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
306
|
+
return self._path_to_index.get(str(path), -1)
|
307
|
+
|
308
|
+
def batchUpdateFileStatus(self, image_paths: list[Path]):
|
309
|
+
"""Batch update file status for multiple files"""
|
310
|
+
if not image_paths:
|
311
|
+
return
|
312
|
+
|
313
|
+
changed_indices = []
|
314
|
+
|
315
|
+
for image_path in image_paths:
|
316
|
+
image_path_str = str(image_path)
|
317
|
+
|
318
|
+
# O(1) lookup using path-to-index mapping
|
319
|
+
if image_path_str not in self._path_to_index:
|
320
|
+
continue # File not in current view
|
321
|
+
|
322
|
+
i = self._path_to_index[image_path_str]
|
323
|
+
file_info = self._files[i]
|
324
|
+
|
325
|
+
# Check file existence
|
326
|
+
npz_path = image_path.with_suffix(".npz")
|
327
|
+
txt_path = image_path.with_suffix(".txt")
|
328
|
+
has_npz = npz_path.exists()
|
329
|
+
has_txt = txt_path.exists()
|
330
|
+
|
331
|
+
# Update status and track changes
|
332
|
+
old_has_npz = file_info.has_npz
|
333
|
+
old_has_txt = file_info.has_txt
|
334
|
+
file_info.has_npz = has_npz
|
335
|
+
file_info.has_txt = has_txt
|
336
|
+
|
337
|
+
# Track changed indices for batch emission
|
338
|
+
if old_has_npz != has_npz:
|
339
|
+
changed_indices.append((i, 3)) # NPZ column
|
340
|
+
if old_has_txt != has_txt:
|
341
|
+
changed_indices.append((i, 4)) # TXT column
|
342
|
+
|
343
|
+
# Batch emit dataChanged signals
|
344
|
+
for i, col in changed_indices:
|
345
|
+
index = self.index(i, col)
|
346
|
+
self.dataChanged.emit(index, index)
|
271
347
|
|
272
348
|
|
273
349
|
class FileSortProxyModel(QSortFilterProxyModel):
|
@@ -551,9 +627,88 @@ class FastFileManager(QWidget):
|
|
551
627
|
"""Update NPZ status for a specific image file"""
|
552
628
|
self._model.updateNpzStatus(image_path)
|
553
629
|
|
630
|
+
def updateFileStatus(self, image_path: Path):
|
631
|
+
"""Update both NPZ and TXT status for a specific image file"""
|
632
|
+
self._model.updateFileStatus(image_path)
|
633
|
+
|
554
634
|
def refreshFile(self, image_path: Path):
|
555
|
-
"""Refresh status for a specific file
|
556
|
-
self.
|
635
|
+
"""Refresh status for a specific file"""
|
636
|
+
self.updateFileStatus(image_path)
|
637
|
+
|
638
|
+
def batchUpdateFileStatus(self, image_paths: list[Path]):
|
639
|
+
"""Batch update file status for multiple files"""
|
640
|
+
self._model.batchUpdateFileStatus(image_paths)
|
641
|
+
|
642
|
+
def getSurroundingFiles(self, current_path: Path, count: int) -> list[Path]:
|
643
|
+
"""Get files in current sorted/filtered order surrounding the given path"""
|
644
|
+
files = []
|
645
|
+
|
646
|
+
# Find current file in proxy model order
|
647
|
+
current_index = -1
|
648
|
+
for row in range(self._proxy_model.rowCount()):
|
649
|
+
proxy_index = self._proxy_model.index(row, 0)
|
650
|
+
source_index = self._proxy_model.mapToSource(proxy_index)
|
651
|
+
file_info = self._model.getFileInfo(source_index.row())
|
652
|
+
if file_info and file_info.path == current_path:
|
653
|
+
current_index = row
|
654
|
+
break
|
655
|
+
|
656
|
+
if current_index == -1:
|
657
|
+
return [] # File not found in current view
|
658
|
+
|
659
|
+
# Get surrounding files in proxy order
|
660
|
+
for i in range(count):
|
661
|
+
row = current_index + i
|
662
|
+
if row < self._proxy_model.rowCount():
|
663
|
+
proxy_index = self._proxy_model.index(row, 0)
|
664
|
+
source_index = self._proxy_model.mapToSource(proxy_index)
|
665
|
+
file_info = self._model.getFileInfo(source_index.row())
|
666
|
+
if file_info:
|
667
|
+
files.append(file_info.path)
|
668
|
+
else:
|
669
|
+
files.append(None)
|
670
|
+
else:
|
671
|
+
files.append(None)
|
672
|
+
|
673
|
+
return files
|
674
|
+
|
675
|
+
def getPreviousFiles(self, current_path: Path, count: int) -> list[Path]:
|
676
|
+
"""Get previous files in current sorted/filtered order before the given path"""
|
677
|
+
files = []
|
678
|
+
|
679
|
+
# Find current file in proxy model order
|
680
|
+
current_index = -1
|
681
|
+
for row in range(self._proxy_model.rowCount()):
|
682
|
+
proxy_index = self._proxy_model.index(row, 0)
|
683
|
+
source_index = self._proxy_model.mapToSource(proxy_index)
|
684
|
+
file_info = self._model.getFileInfo(source_index.row())
|
685
|
+
if file_info and file_info.path == current_path:
|
686
|
+
current_index = row
|
687
|
+
break
|
688
|
+
|
689
|
+
if current_index == -1:
|
690
|
+
return [] # File not found in current view
|
691
|
+
|
692
|
+
# Get previous files going backward from current position
|
693
|
+
start_row = current_index - count
|
694
|
+
if start_row < 0:
|
695
|
+
start_row = 0
|
696
|
+
|
697
|
+
# Get consecutive files starting from start_row
|
698
|
+
for i in range(count):
|
699
|
+
row = start_row + i
|
700
|
+
if row < current_index and row >= 0:
|
701
|
+
proxy_index = self._proxy_model.index(row, 0)
|
702
|
+
source_index = self._proxy_model.mapToSource(proxy_index)
|
703
|
+
file_info = self._model.getFileInfo(source_index.row())
|
704
|
+
if file_info:
|
705
|
+
files.append(file_info.path)
|
706
|
+
else:
|
707
|
+
files.append(None)
|
708
|
+
else:
|
709
|
+
files.append(None)
|
710
|
+
|
711
|
+
return files
|
557
712
|
|
558
713
|
def _on_item_clicked(self, index: QModelIndex):
|
559
714
|
"""Handle item click"""
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/widgets/channel_threshold_widget.py
RENAMED
File without changes
|
{lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/widgets/fft_threshold_widget.py
RENAMED
File without changes
|
{lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/widgets/fragment_threshold_widget.py
RENAMED
File without changes
|
{lazylabel_gui-1.3.2 → lazylabel_gui-1.3.3}/src/lazylabel/ui/widgets/model_selection_widget.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|