lazylabel-gui 1.3.1__py3-none-any.whl → 1.3.3__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.
- lazylabel/core/file_manager.py +33 -1
- lazylabel/ui/main_window.py +380 -119
- lazylabel/ui/right_panel.py +29 -2
- lazylabel/utils/fast_file_manager.py +831 -0
- {lazylabel_gui-1.3.1.dist-info → lazylabel_gui-1.3.3.dist-info}/METADATA +1 -1
- {lazylabel_gui-1.3.1.dist-info → lazylabel_gui-1.3.3.dist-info}/RECORD +10 -10
- lazylabel/ui/test_hover.py +0 -48
- {lazylabel_gui-1.3.1.dist-info → lazylabel_gui-1.3.3.dist-info}/WHEEL +0 -0
- {lazylabel_gui-1.3.1.dist-info → lazylabel_gui-1.3.3.dist-info}/entry_points.txt +0 -0
- {lazylabel_gui-1.3.1.dist-info → lazylabel_gui-1.3.3.dist-info}/licenses/LICENSE +0 -0
- {lazylabel_gui-1.3.1.dist-info → lazylabel_gui-1.3.3.dist-info}/top_level.txt +0 -0
lazylabel/ui/main_window.py
CHANGED
@@ -1176,6 +1176,8 @@ class MainWindow(QMainWindow):
|
|
1176
1176
|
# Right panel connections
|
1177
1177
|
self.right_panel.open_folder_requested.connect(self._open_folder_dialog)
|
1178
1178
|
self.right_panel.image_selected.connect(self._load_selected_image)
|
1179
|
+
# Connect new path-based signal from FastFileManager
|
1180
|
+
self.right_panel.image_path_selected.connect(self._load_image_from_path)
|
1179
1181
|
self.right_panel.merge_selection_requested.connect(
|
1180
1182
|
self._assign_selected_to_class
|
1181
1183
|
)
|
@@ -1625,6 +1627,13 @@ class MainWindow(QMainWindow):
|
|
1625
1627
|
self._start_background_image_discovery()
|
1626
1628
|
self.viewer.setFocus()
|
1627
1629
|
|
1630
|
+
def _load_image_from_path(self, file_path: Path):
|
1631
|
+
"""Load image from a Path object (used by FastFileManager)."""
|
1632
|
+
if file_path.is_file() and self.file_manager.is_image_file(str(file_path)):
|
1633
|
+
# Convert Path to QModelIndex for compatibility
|
1634
|
+
# This allows existing code to work while using the new file manager
|
1635
|
+
self._load_image_by_path(str(file_path))
|
1636
|
+
|
1628
1637
|
def _load_selected_image(self, index):
|
1629
1638
|
"""Load the selected image. Auto-saves previous work if enabled."""
|
1630
1639
|
|
@@ -1686,6 +1695,112 @@ class MainWindow(QMainWindow):
|
|
1686
1695
|
|
1687
1696
|
self._show_success_notification(f"Loaded: {Path(self.current_image_path).name}")
|
1688
1697
|
|
1698
|
+
def _load_image_by_path(self, path: str):
|
1699
|
+
"""Load image by file path directly (for FastFileManager)."""
|
1700
|
+
# Check if we're in multi-view mode
|
1701
|
+
if hasattr(self, "view_mode") and self.view_mode == "multi":
|
1702
|
+
# For multi-view, we need to handle differently
|
1703
|
+
# Load the selected image and consecutive ones
|
1704
|
+
self._load_multi_view_from_path(path)
|
1705
|
+
return
|
1706
|
+
|
1707
|
+
if path == self.current_image_path: # Only reset if loading a new image
|
1708
|
+
return
|
1709
|
+
|
1710
|
+
# Auto-save if enabled and we have a current image (not the first load)
|
1711
|
+
if self.current_image_path and self.control_panel.get_settings().get(
|
1712
|
+
"auto_save", True
|
1713
|
+
):
|
1714
|
+
self._save_output_to_npz()
|
1715
|
+
|
1716
|
+
self.segment_manager.clear()
|
1717
|
+
# Remove all scene items except the pixmap
|
1718
|
+
items_to_remove = [
|
1719
|
+
item
|
1720
|
+
for item in self.viewer.scene().items()
|
1721
|
+
if item is not self.viewer._pixmap_item
|
1722
|
+
]
|
1723
|
+
for item in items_to_remove:
|
1724
|
+
self.viewer.scene().removeItem(item)
|
1725
|
+
self.current_image_path = path
|
1726
|
+
|
1727
|
+
# Load the image
|
1728
|
+
original_image = cv2.imread(path)
|
1729
|
+
if original_image is None:
|
1730
|
+
logger.error(f"Failed to load image: {path}")
|
1731
|
+
return
|
1732
|
+
|
1733
|
+
# Convert BGR to RGB
|
1734
|
+
if len(original_image.shape) == 3:
|
1735
|
+
original_image = cv2.cvtColor(original_image, cv2.COLOR_BGR2RGB)
|
1736
|
+
self.original_image = original_image
|
1737
|
+
|
1738
|
+
# Convert to QImage and display
|
1739
|
+
height, width = original_image.shape[:2]
|
1740
|
+
bytes_per_line = 3 * width if len(original_image.shape) == 3 else width
|
1741
|
+
|
1742
|
+
if len(original_image.shape) == 3:
|
1743
|
+
q_image = QImage(
|
1744
|
+
original_image.data.tobytes(),
|
1745
|
+
width,
|
1746
|
+
height,
|
1747
|
+
bytes_per_line,
|
1748
|
+
QImage.Format.Format_RGB888,
|
1749
|
+
)
|
1750
|
+
else:
|
1751
|
+
q_image = QImage(
|
1752
|
+
original_image.data.tobytes(),
|
1753
|
+
width,
|
1754
|
+
height,
|
1755
|
+
bytes_per_line,
|
1756
|
+
QImage.Format.Format_Grayscale8,
|
1757
|
+
)
|
1758
|
+
|
1759
|
+
pixmap = QPixmap.fromImage(q_image)
|
1760
|
+
self.viewer.set_photo(pixmap)
|
1761
|
+
|
1762
|
+
# Load existing segments and class aliases
|
1763
|
+
self.file_manager.load_class_aliases(path)
|
1764
|
+
self.file_manager.load_existing_mask(path)
|
1765
|
+
|
1766
|
+
# Update UI lists to reflect loaded segments
|
1767
|
+
self._update_all_lists()
|
1768
|
+
|
1769
|
+
# Update display if we have an update method
|
1770
|
+
if hasattr(self, "_update_display"):
|
1771
|
+
self._update_display()
|
1772
|
+
self._show_success_notification(f"Loaded: {Path(path).name}")
|
1773
|
+
|
1774
|
+
# Update file selection in the file manager
|
1775
|
+
self.right_panel.select_file(Path(path))
|
1776
|
+
|
1777
|
+
def _load_multi_view_from_path(self, path: str):
|
1778
|
+
"""Load multi-view starting from a specific path using FastFileManager."""
|
1779
|
+
config = self._get_multi_view_config()
|
1780
|
+
num_viewers = config["num_viewers"]
|
1781
|
+
|
1782
|
+
# Auto-save if enabled
|
1783
|
+
if (
|
1784
|
+
hasattr(self, "multi_view_images")
|
1785
|
+
and self.multi_view_images
|
1786
|
+
and self.multi_view_images[0]
|
1787
|
+
and self.control_panel.get_settings().get("auto_save", True)
|
1788
|
+
):
|
1789
|
+
self._save_multi_view_output()
|
1790
|
+
|
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)
|
1794
|
+
|
1795
|
+
# Convert to strings for loading
|
1796
|
+
images_to_load = [str(p) if p else None for p in surrounding_files]
|
1797
|
+
|
1798
|
+
# Load the images
|
1799
|
+
self._load_multi_view_images(images_to_load)
|
1800
|
+
|
1801
|
+
# Update file manager selection
|
1802
|
+
self.right_panel.select_file(Path(path))
|
1803
|
+
|
1689
1804
|
def _load_selected_image_multi_view(self, index, path):
|
1690
1805
|
"""Load selected image in multi-view mode starting from the selected file."""
|
1691
1806
|
# Auto-save if enabled and we have current images (not the first load)
|
@@ -2141,17 +2256,9 @@ class MainWindow(QMainWindow):
|
|
2141
2256
|
self._load_next_multi_batch()
|
2142
2257
|
return
|
2143
2258
|
|
2144
|
-
|
2145
|
-
|
2146
|
-
|
2147
|
-
row = self.current_file_index.row()
|
2148
|
-
# Find next valid image file
|
2149
|
-
for next_row in range(row + 1, self.file_model.rowCount(parent)):
|
2150
|
-
next_index = self.file_model.index(next_row, 0, parent)
|
2151
|
-
path = self.file_model.filePath(next_index)
|
2152
|
-
if os.path.isfile(path) and self.file_manager.is_image_file(path):
|
2153
|
-
self._load_selected_image(next_index)
|
2154
|
-
return
|
2259
|
+
# Use new file manager navigation
|
2260
|
+
self.right_panel.navigate_next_image()
|
2261
|
+
return
|
2155
2262
|
|
2156
2263
|
def _load_previous_image(self):
|
2157
2264
|
"""Load previous image in the file list."""
|
@@ -2160,17 +2267,9 @@ class MainWindow(QMainWindow):
|
|
2160
2267
|
self._load_previous_multi_batch()
|
2161
2268
|
return
|
2162
2269
|
|
2163
|
-
|
2164
|
-
|
2165
|
-
|
2166
|
-
row = self.current_file_index.row()
|
2167
|
-
# Find previous valid image file
|
2168
|
-
for prev_row in range(row - 1, -1, -1):
|
2169
|
-
prev_index = self.file_model.index(prev_row, 0, parent)
|
2170
|
-
path = self.file_model.filePath(prev_index)
|
2171
|
-
if os.path.isfile(path) and self.file_manager.is_image_file(path):
|
2172
|
-
self._load_selected_image(prev_index)
|
2173
|
-
return
|
2270
|
+
# Use new file manager navigation
|
2271
|
+
self.right_panel.navigate_previous_image()
|
2272
|
+
return
|
2174
2273
|
|
2175
2274
|
# Segment management methods
|
2176
2275
|
def _assign_selected_to_class(self):
|
@@ -2823,20 +2922,34 @@ class MainWindow(QMainWindow):
|
|
2823
2922
|
|
2824
2923
|
def _handle_enter_press(self):
|
2825
2924
|
"""Handle enter key press."""
|
2925
|
+
logger.debug(f"Enter pressed - mode: {self.mode}, view_mode: {self.view_mode}")
|
2926
|
+
|
2826
2927
|
if self.mode == "polygon":
|
2827
|
-
|
2828
|
-
self.
|
2829
|
-
|
2830
|
-
|
2831
|
-
|
2928
|
+
logger.debug(
|
2929
|
+
f"Polygon mode - polygon_points: {len(self.polygon_points) if hasattr(self, 'polygon_points') and self.polygon_points else 0}"
|
2930
|
+
)
|
2931
|
+
if self.view_mode == "single":
|
2932
|
+
# If there are pending polygon points, finalize them first
|
2933
|
+
if self.polygon_points:
|
2934
|
+
logger.debug("Finalizing pending polygon")
|
2935
|
+
self._finalize_polygon()
|
2936
|
+
# Always save after handling pending work to show checkmarks and notifications
|
2937
|
+
logger.debug("Saving polygon segments to NPZ")
|
2938
|
+
self._save_output_to_npz()
|
2939
|
+
elif self.view_mode == "multi":
|
2832
2940
|
# Complete polygons for all viewers that have points
|
2833
|
-
|
2834
|
-
|
2835
|
-
|
2836
|
-
|
2837
|
-
self
|
2838
|
-
|
2839
|
-
|
2941
|
+
if hasattr(self, "multi_view_polygon_points"):
|
2942
|
+
for i, points in enumerate(self.multi_view_polygon_points):
|
2943
|
+
if points and len(points) >= 3:
|
2944
|
+
# Use the multi-view mode handler for proper pairing logic
|
2945
|
+
if hasattr(self, "multi_view_mode_handler"):
|
2946
|
+
self.multi_view_mode_handler._finalize_multi_view_polygon(
|
2947
|
+
i
|
2948
|
+
)
|
2949
|
+
else:
|
2950
|
+
self._finalize_multi_view_polygon(i)
|
2951
|
+
# Always save after handling pending work to show checkmarks and notifications
|
2952
|
+
self._save_output_to_npz()
|
2840
2953
|
else:
|
2841
2954
|
# First accept any AI segments (same as spacebar), then save
|
2842
2955
|
self._accept_ai_segment()
|
@@ -3188,6 +3301,18 @@ class MainWindow(QMainWindow):
|
|
3188
3301
|
# Restore original segments
|
3189
3302
|
self.segment_manager.segments = original_segments
|
3190
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
|
+
|
3191
3316
|
# If no segments for this viewer, delete associated files
|
3192
3317
|
if not viewer_segments:
|
3193
3318
|
base, _ = os.path.splitext(image_path)
|
@@ -3198,7 +3323,13 @@ class MainWindow(QMainWindow):
|
|
3198
3323
|
try:
|
3199
3324
|
os.remove(file_path)
|
3200
3325
|
deleted_files.append(os.path.basename(file_path))
|
3201
|
-
|
3326
|
+
# Update FastFileManager after file deletion
|
3327
|
+
if hasattr(self, "right_panel") and hasattr(
|
3328
|
+
self.right_panel, "file_manager"
|
3329
|
+
):
|
3330
|
+
self.right_panel.file_manager.updateFileStatus(
|
3331
|
+
Path(image_path)
|
3332
|
+
)
|
3202
3333
|
except Exception as e:
|
3203
3334
|
self._show_error_notification(
|
3204
3335
|
f"Error deleting {file_path}: {e}"
|
@@ -3213,20 +3344,15 @@ class MainWindow(QMainWindow):
|
|
3213
3344
|
f"Error saving {os.path.basename(image_path)}: {str(e)}"
|
3214
3345
|
)
|
3215
3346
|
|
3216
|
-
# Update
|
3217
|
-
if hasattr(self, "
|
3218
|
-
|
3219
|
-
|
3220
|
-
|
3221
|
-
|
3222
|
-
|
3223
|
-
|
3224
|
-
|
3225
|
-
self.file_model.set_highlighted_path(None)
|
3226
|
-
if self.file_model.highlighted_path == p
|
3227
|
-
else None
|
3228
|
-
),
|
3229
|
-
)
|
3347
|
+
# Update FastFileManager to show NPZ checkmarks for multi-view
|
3348
|
+
if hasattr(self, "multi_view_images") and self.multi_view_images:
|
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)
|
3230
3356
|
# Clear the tracking list for next save
|
3231
3357
|
self._saved_file_paths = []
|
3232
3358
|
|
@@ -3258,7 +3384,13 @@ class MainWindow(QMainWindow):
|
|
3258
3384
|
try:
|
3259
3385
|
os.remove(file_path)
|
3260
3386
|
deleted_files.append(file_path)
|
3261
|
-
|
3387
|
+
# Update FastFileManager after file deletion
|
3388
|
+
if hasattr(self, "right_panel") and hasattr(
|
3389
|
+
self.right_panel, "file_manager"
|
3390
|
+
):
|
3391
|
+
self.right_panel.file_manager.updateFileStatus(
|
3392
|
+
Path(self.current_image_path)
|
3393
|
+
)
|
3262
3394
|
except Exception as e:
|
3263
3395
|
self._show_error_notification(
|
3264
3396
|
f"Error deleting {file_path}: {e}"
|
@@ -3276,25 +3408,38 @@ class MainWindow(QMainWindow):
|
|
3276
3408
|
try:
|
3277
3409
|
settings = self.control_panel.get_settings()
|
3278
3410
|
npz_path = None
|
3279
|
-
|
3411
|
+
|
3412
|
+
# Debug logging
|
3413
|
+
logger.debug(f"Starting save process for: {self.current_image_path}")
|
3414
|
+
logger.debug(f"Number of segments: {len(self.segment_manager.segments)}")
|
3415
|
+
|
3280
3416
|
if settings.get("save_npz", True):
|
3281
3417
|
h, w = (
|
3282
3418
|
self.viewer._pixmap_item.pixmap().height(),
|
3283
3419
|
self.viewer._pixmap_item.pixmap().width(),
|
3284
3420
|
)
|
3285
3421
|
class_order = self.segment_manager.get_unique_class_ids()
|
3422
|
+
logger.debug(f"Class order for saving: {class_order}")
|
3423
|
+
|
3286
3424
|
if class_order:
|
3425
|
+
logger.debug(
|
3426
|
+
f"Attempting to save NPZ to: {os.path.splitext(self.current_image_path)[0]}.npz"
|
3427
|
+
)
|
3287
3428
|
npz_path = self.file_manager.save_npz(
|
3288
3429
|
self.current_image_path,
|
3289
3430
|
(h, w),
|
3290
3431
|
class_order,
|
3291
3432
|
self.current_crop_coords,
|
3292
3433
|
)
|
3434
|
+
logger.debug(f"NPZ save completed: {npz_path}")
|
3293
3435
|
self._show_success_notification(
|
3294
3436
|
f"Saved: {os.path.basename(npz_path)}"
|
3295
3437
|
)
|
3296
3438
|
else:
|
3439
|
+
logger.warning("No classes defined for saving")
|
3297
3440
|
self._show_warning_notification("No classes defined for saving.")
|
3441
|
+
# Save TXT file if enabled
|
3442
|
+
txt_path = None
|
3298
3443
|
if settings.get("save_txt", True):
|
3299
3444
|
h, w = (
|
3300
3445
|
self.viewer._pixmap_item.pixmap().height(),
|
@@ -3315,20 +3460,35 @@ class MainWindow(QMainWindow):
|
|
3315
3460
|
class_labels,
|
3316
3461
|
self.current_crop_coords,
|
3317
3462
|
)
|
3318
|
-
|
3319
|
-
|
3320
|
-
|
3321
|
-
|
3322
|
-
|
3323
|
-
|
3324
|
-
|
3325
|
-
|
3326
|
-
|
3327
|
-
|
3328
|
-
|
3329
|
-
|
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)}"
|
3330
3478
|
)
|
3479
|
+
|
3480
|
+
# Update FastFileManager to show NPZ/TXT checkmarks
|
3481
|
+
if (
|
3482
|
+
(npz_path or txt_path)
|
3483
|
+
and hasattr(self, "right_panel")
|
3484
|
+
and hasattr(self.right_panel, "file_manager")
|
3485
|
+
):
|
3486
|
+
# Update the file status in the FastFileManager
|
3487
|
+
self.right_panel.file_manager.updateFileStatus(
|
3488
|
+
Path(self.current_image_path)
|
3489
|
+
)
|
3331
3490
|
except Exception as e:
|
3491
|
+
logger.error(f"Error saving file: {str(e)}", exc_info=True)
|
3332
3492
|
self._show_error_notification(f"Error saving: {str(e)}")
|
3333
3493
|
|
3334
3494
|
def _handle_merge_press(self):
|
@@ -7088,24 +7248,73 @@ class MainWindow(QMainWindow):
|
|
7088
7248
|
self._load_multi_view_images(image_paths)
|
7089
7249
|
|
7090
7250
|
def _load_next_multi_batch(self):
|
7091
|
-
"""Load the next batch of images using cached list."""
|
7092
|
-
if
|
7093
|
-
|
7094
|
-
|
7095
|
-
|
7096
|
-
|
7097
|
-
"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)
|
7098
7258
|
):
|
7099
7259
|
self._save_multi_view_output()
|
7100
7260
|
|
7101
|
-
#
|
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)
|
7102
7314
|
if not self.cached_image_paths:
|
7103
7315
|
self._load_next_multi_batch_fallback()
|
7104
7316
|
return
|
7105
7317
|
|
7106
|
-
# Get current image path
|
7107
|
-
current_path = self.file_model.filePath(self.current_file_index)
|
7108
|
-
|
7109
7318
|
# Find current position in cached list
|
7110
7319
|
try:
|
7111
7320
|
current_index = self.cached_image_paths.index(current_path)
|
@@ -7114,13 +7323,14 @@ class MainWindow(QMainWindow):
|
|
7114
7323
|
self._load_next_multi_batch_fallback()
|
7115
7324
|
return
|
7116
7325
|
|
7117
|
-
# Get the number of viewers for multi-view mode
|
7118
|
-
config = self._get_multi_view_config()
|
7119
|
-
num_viewers = config["num_viewers"]
|
7120
|
-
|
7121
7326
|
# Skip num_viewers positions ahead in cached list
|
7122
7327
|
target_index = current_index + num_viewers
|
7123
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
|
7124
7334
|
if target_index < len(self.cached_image_paths):
|
7125
7335
|
# Collect consecutive images
|
7126
7336
|
image_paths = []
|
@@ -7130,39 +7340,88 @@ class MainWindow(QMainWindow):
|
|
7130
7340
|
else:
|
7131
7341
|
image_paths.append(None)
|
7132
7342
|
|
7133
|
-
#
|
7134
|
-
|
7135
|
-
|
7136
|
-
|
7137
|
-
|
7138
|
-
|
7139
|
-
|
7140
|
-
|
7141
|
-
|
7142
|
-
|
7143
|
-
self.
|
7144
|
-
self.
|
7145
|
-
|
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)
|
7146
7359
|
|
7147
7360
|
def _load_previous_multi_batch(self):
|
7148
|
-
"""Load the previous batch of images using cached list."""
|
7149
|
-
if
|
7150
|
-
|
7151
|
-
|
7152
|
-
|
7153
|
-
|
7154
|
-
"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)
|
7155
7368
|
):
|
7156
7369
|
self._save_multi_view_output()
|
7157
7370
|
|
7158
|
-
#
|
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)
|
7159
7421
|
if not self.cached_image_paths:
|
7160
7422
|
self._load_previous_multi_batch_fallback()
|
7161
7423
|
return
|
7162
7424
|
|
7163
|
-
# Get current image path
|
7164
|
-
current_path = self.file_model.filePath(self.current_file_index)
|
7165
|
-
|
7166
7425
|
# Find current position in cached list
|
7167
7426
|
try:
|
7168
7427
|
current_index = self.cached_image_paths.index(current_path)
|
@@ -7171,10 +7430,6 @@ class MainWindow(QMainWindow):
|
|
7171
7430
|
self._load_previous_multi_batch_fallback()
|
7172
7431
|
return
|
7173
7432
|
|
7174
|
-
# Get the number of viewers for multi-view mode
|
7175
|
-
config = self._get_multi_view_config()
|
7176
|
-
num_viewers = config["num_viewers"]
|
7177
|
-
|
7178
7433
|
# Skip num_viewers positions back in cached list
|
7179
7434
|
target_index = current_index - num_viewers
|
7180
7435
|
if target_index < 0:
|
@@ -7183,24 +7438,30 @@ class MainWindow(QMainWindow):
|
|
7183
7438
|
# Collect consecutive images
|
7184
7439
|
image_paths = []
|
7185
7440
|
for i in range(num_viewers):
|
7186
|
-
if
|
7441
|
+
if (
|
7442
|
+
target_index + i < len(self.cached_image_paths)
|
7443
|
+
and target_index + i >= 0
|
7444
|
+
):
|
7187
7445
|
image_paths.append(self.cached_image_paths[target_index + i])
|
7188
7446
|
else:
|
7189
7447
|
image_paths.append(None)
|
7190
7448
|
|
7191
|
-
#
|
7192
|
-
|
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)
|
7193
7453
|
|
7194
|
-
|
7195
|
-
|
7196
|
-
|
7197
|
-
|
7198
|
-
|
7199
|
-
|
7200
|
-
|
7201
|
-
|
7202
|
-
|
7203
|
-
|
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)
|
7204
7465
|
|
7205
7466
|
def _load_next_multi_batch_fallback(self):
|
7206
7467
|
"""Fallback navigation using file model when cached list isn't available."""
|