feat: Add batch processing for multiple folders in GUI#22
feat: Add batch processing for multiple folders in GUI#22Alpaca233 merged 7 commits intoCephla-Lab:mainfrom
Conversation
Drop multiple folders/files onto the drop area to process them sequentially with shared parameters. Each item is validated on drop (metadata loaded via TileFusion), with invalid items warned and skipped. Progress bar shows determinate progress (N/total) and log messages are prefixed with [1/5 folder_name]. Preview, calculate-flatfield, reg z/t controls, and Napari are grayed out in batch mode. Per-item failures are logged and processing continues with remaining items. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract _run_fusion_pipeline() used by both FusionWorker and BatchFusionWorker, eliminating ~100 lines of duplicated pipeline logic. This also fixes batch mode silently dropping registration z/t/channel parameters. - Make DropArea.file_path a property derived from file_paths list. - Make StitcherGUI.is_batch_mode a property derived from batch_paths. - Remove incorrect flatfield status reset in on_files_dropped (batch mode preserves any previously loaded flatfield for shared use). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CRITICAL: Remove assignment to read-only file_path property in setFiles() which caused AttributeError on every batch drop. - Wrap BatchFusionWorker.run() in top-level try/except that emits error signal; connect it in _run_batch so UI doesn't freeze on unexpected thread death. - Add MemoryError early exit in batch loop instead of continuing futile processing. - Re-enable preview, calc-flatfield, and reg z/t controls in _on_batch_finished so UI isn't stuck after batch completes. - Use context manager for TileFusion validation in on_files_dropped to prevent file handle leaks. - Fix misleading signal comments (folder_name -> item_name, validated -> type-checked). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds batch-processing support to the stitcher GUI, allowing users to drop multiple datasets and process them sequentially with shared settings while providing per-item logging and determinate progress.
Changes:
- Refactors the fusion/stitching pipeline into a shared
_run_fusion_pipeline()used by both single and batch workers. - Adds
BatchFusionWorkerand multi-drop support inDropArea, including per-item progress/log prefixing. - Introduces batch-mode UI state (
batch_paths) and disables certain controls while in batch mode.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
gui/app.py
Outdated
| self.run_button.setEnabled(True) | ||
| self.preview_button.setEnabled(True) | ||
| self.calc_flatfield_button.setEnabled(True) | ||
| self.reg_zt_widget.setEnabled(True) |
There was a problem hiding this comment.
_on_batch_finished re-enables preview_button, calc_flatfield_button, and reg_zt_widget unconditionally, but batch_paths is not cleared, so the app is still in batch mode. This makes batch-only disabled controls usable again (e.g., Preview will run on drop_area.file_path, i.e., the first item) and contradicts the intended batch-mode UI state. Consider either clearing self.batch_paths to exit batch mode when the batch completes, or call _update_batch_mode_ui() here and only enable controls when not in batch mode (and when an input is selected).
| self.run_button.setEnabled(True) | |
| self.preview_button.setEnabled(True) | |
| self.calc_flatfield_button.setEnabled(True) | |
| self.reg_zt_widget.setEnabled(True) | |
| # Batch processing is complete, so leave batch mode before restoring | |
| # controls that are only valid for single-input operations. | |
| self.batch_paths = [] | |
| has_input = bool(getattr(self.drop_area, "file_path", None)) | |
| self.run_button.setEnabled(True) | |
| self.preview_button.setEnabled(has_input) | |
| self.calc_flatfield_button.setEnabled(has_input) | |
| self.reg_zt_widget.setEnabled(has_input) |
| if not valid_paths: | ||
| self.log("No valid datasets found.") | ||
| self.run_button.setEnabled(False) | ||
| return | ||
|
|
||
| # Update DropArea display with validation results | ||
| self.drop_area.setFiles(valid_paths, invalid_names) | ||
| self.batch_paths = valid_paths | ||
| self._update_batch_mode_ui() | ||
| self.run_button.setEnabled(True) | ||
|
|
||
| self.dataset_n_z = 1 | ||
| self.dataset_n_t = 1 | ||
| self.dataset_n_channels = 1 | ||
| self.dataset_channel_names = [] | ||
|
|
There was a problem hiding this comment.
In on_files_dropped, if validation leaves exactly one valid_paths entry (e.g., user drops multiple items but only one is valid), the code still goes through the batch-mode path: setFiles(...), resets dataset dims to 1, and sets self.batch_paths = valid_paths while is_batch_mode will be false. This skips the single-item initialization done in on_file_dropped (dimension detection, flatfield auto-load, etc.) and can lead to inconsistent UI/behavior. Consider treating len(valid_paths) == 1 as a normal single selection (call self.drop_area.setFile(...) + on_file_dropped(...) or share the initialization logic).
| valid_paths = [] | ||
| invalid_names = [] | ||
| for url in urls: | ||
| file_path = url.toLocalFile() | ||
| if self._is_valid_path(file_path): | ||
| valid_paths.append(file_path) | ||
| else: | ||
| self.setStyleSheet(self._default_style) | ||
| else: | ||
| invalid_names.append(Path(file_path).name) | ||
|
|
||
| if not valid_paths: | ||
| self.setStyleSheet(self._default_style) | ||
| return | ||
|
|
||
| if len(valid_paths) == 1: | ||
| self.setFile(valid_paths[0]) | ||
| self.fileDropped.emit(valid_paths[0]) | ||
| else: | ||
| self.setFiles(valid_paths, invalid_names) | ||
| self.filesDropped.emit(valid_paths) |
There was a problem hiding this comment.
DropArea.dropEvent collects invalid_names (non-folder/non-TIFF) and shows them in setFiles(...), but filesDropped only emits valid_paths. StitcherGUI.on_files_dropped then re-renders the DropArea with its own invalid_names from TileFusion validation, which can overwrite and lose the original skipped names from the drop (and the log says Validating {len(paths)} where paths no longer includes those skipped items). If you want the UI/log to reflect all dropped invalid items, consider emitting both valid and invalid lists (or re-validating from the original urls list in the GUI) so skipped items aren't silently dropped from the later summary.
| total = len(self.batch_paths) | ||
| self.progress_bar.setRange(0, total) | ||
| self.progress_bar.setValue(0) | ||
| self.log(f"Starting batch processing: {total} items\n") |
There was a problem hiding this comment.
Batch mode sets the progress bar to a determinate range (setRange(0, total)), but the default QProgressBar text format is percentage; the PR description says it should show N/total. Consider setting a value-based format for batch runs (e.g., %v/%m) and ensuring text visibility/layout supports it (the bar is currently only 6px tall, which may clip the text).
gui/app.py
Outdated
| def _update_batch_mode_ui(self): | ||
| """Update UI to reflect batch vs single mode.""" | ||
| batch = self.is_batch_mode | ||
| self.preview_button.setEnabled(not batch) | ||
| self.calc_flatfield_button.setEnabled(not batch and self.drop_area.file_path is not None) | ||
| self.reg_zt_widget.setEnabled(not batch) | ||
| self.napari_button.setEnabled(False) |
There was a problem hiding this comment.
In _update_batch_mode_ui, batch mode disables reg_zt_widget but doesn't provide an explanatory tooltip the way Preview/Flatfield/Napari do. Since the PR description mentions tooltips for grayed-out registration z/t/channel controls, consider adding a tooltip to reg_zt_widget (or its child controls) when batch is true, and clearing it when leaving batch mode.
| def run(self): | ||
| try: | ||
| self._run_batch() | ||
| except Exception as e: | ||
| import traceback | ||
|
|
||
| self.error.emit(f"Error: {str(e)}\n{traceback.format_exc()}") | ||
| self.error.emit(f"Batch processing failed: {e}\n{traceback.format_exc()}") | ||
|
|
There was a problem hiding this comment.
BatchFusionWorker.run catches exceptions and emits error, but it does not emit any terminal signal (finished/item_finished) in that path. If an unexpected exception occurs before _run_batch completes, the GUI cleanup in _on_batch_finished will never run, and the progress bar may remain in determinate mode (and the app may stay in a partially-disabled state). Consider emitting finished (with failed counts) or a dedicated aborted signal from the exception handler so the UI can reliably reset batch-mode state.
- Clear batch_paths in _on_batch_finished and call _update_batch_mode_ui() to properly exit batch mode and restore controls to correct state. - Emit finished signal from BatchFusionWorker error handler so UI always resets (progress bar, disabled buttons) even on unexpected thread failure. - Redirect single-valid-item from multi-drop to on_file_dropped so dimension detection and flatfield auto-load still work. - Add tooltip for reg_zt_widget in batch mode for consistency with other disabled controls. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove unconditional napari_button.setEnabled(False) from _update_batch_mode_ui so button stays available in batch mode. - Re-enable napari button in _on_batch_finished. - When no output_path is set, open an empty Napari viewer instead of silently returning. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
TileFusion; invalid items are warned in the UI and skippedN/totalin batch mode, log messages prefixed with[1/5 folder_name]to track which item is processingTest plan
[N/total name]prefixes🤖 Generated with Claude Code